Proxy: رهگیری و سفارشی‌سازی رفتار پایه‌ای اشیاء

تا این‌جا، رفتار عملیات‌های پایه‌ای روی شیء—مثل خواندن یک ویژگی یا تنظیم مقدار آن—همیشه به همان شکل پیش‌فرض جاوااسکریپت اجرا شده است. Proxy ابزاری قدرتمند است که امکان می‌دهد این رفتارهای پایه‌ای، پیش از رسیدن به شیء اصلی، رهگیری و سفارشی‌سازی شوند؛ ابزاری که پایه‌ی بسیاری از کتابخانه‌های مدرن مدیریت وضعیت (state management) است.

ساخت یک Proxy ساده

Proxy با گرفتن دو آرگومان ساخته می‌شود: شیء اصلی (target) که رفتار آن قرار است رهگیری شود، و شیء handler که شامل توابعی به نام trap برای هر نوع عملیات است:

const target = { name: "سارا", age: 28 };

const handler = {
  get(obj, property) {
    console.log(`خواندن ویژگی: ${property}`);
    return obj[property];
  },
};

const proxy = new Proxy(target, handler);

console.log(proxy.name);
// خروجی:
// خواندن ویژگی: name
// سارا

در این مثال، get یک trap است که هر بار خواندن یک ویژگی از proxy، پیش از بازگرداندن مقدار واقعی، ابتدا از این تابع عبور می‌کند.

Trap رایج: set برای اعتبارسنجی مقادیر

یکی از کاربردهای عملی Proxy، اعتبارسنجی خودکار مقادیر هنگام تنظیم آن‌هاست، بدون نیاز به نوشتن getter/setter دستی برای هر ویژگی:

اعتبارسنجی خودکار با Proxy
خروجی کنسول اینجا نمایش داده می‌شود...

نکته‌ی مهم این است که trap مربوط به set باید مقدار true برگرداند تا جاوااسکریپت بداند عملیات با موفقیت انجام شده؛ در غیر این صورت، در حالت strict (که ماژول‌ها به‌طور پیش‌فرض دارند) خطا تولید می‌شود.

Trap رایج دیگر: has برای سفارشی‌سازی عملگر in

علاوه بر get و set، Proxy تعداد زیادی trap دیگر نیز پشتیبانی می‌کند؛ از جمله has که رفتار عملگر in را رهگیری می‌کند:

const hiddenFields = ["password", "token"];

const secureObject = new Proxy(
  { username: "ali", password: "1234" },
  {
    has(obj, property) {
      if (hiddenFields.includes(property)) {
        return false; // این ویژگی‌ها را «نامرئی» می‌کند
      }
      return property in obj;
    },
  }
);

console.log("username" in secureObject); // true
console.log("password" in secureObject); // false، با وجود اینکه واقعاً وجود دارد

جدول برخی از trapهای رایج Proxy

Trap عملیاتی که رهگیری می‌کند
get خواندن یک ویژگی
set تنظیم مقدار یک ویژگی
has استفاده از عملگر in
deleteProperty حذف یک ویژگی با delete

Reflect: همراه طبیعی Proxy

داخل توابع trap، به‌جای دسترسی مستقیم به obj[property]، معمولاً از شیء داخلی Reflect استفاده می‌شود که همان عملیات پیش‌فرض جاوااسکریپت را به‌صورت یک متد قابل‌فراخوانی در اختیار می‌گذارد و رفتاری دقیق‌تر و سازگارتر با موارد خاص (مثل زنجیره‌ی prototype) دارد:

const proxy = new Proxy(target, {
  get(obj, property, receiver) {
    console.log(`خواندن: ${String(property)}`);
    return Reflect.get(obj, property, receiver);
  },
});

در پروژه‌های واقعی، استفاده از Reflect به‌جای دسترسی مستقیم به‌عنوان بهترین شیوه (best practice) در نظر گرفته می‌شود، چون رفتار پیش‌فرض جاوااسکریپت را با دقت بیشتری بازتولید می‌کند.

نکات کلیدی

  • Proxy با گرفتن یک شیء اصلی و یک شیء handler، امکان رهگیری عملیات پایه‌ای مثل خواندن و نوشتن ویژگی را فراهم می‌کند.
  • trap مربوط به set باید مقدار true برگرداند تا عملیات موفقیت‌آمیز در نظر گرفته شود.
  • Proxy trapهای متعددی مثل get، set، has و deleteProperty را برای عملیات مختلف پشتیبانی می‌کند.
  • Reflect همراه طبیعی Proxy است و رفتار پیش‌فرض جاوااسکریپت را به‌صورت متدهای قابل‌فراخوانی در اختیار می‌گذارد.

جمع‌بندی

Proxy امکان سفارشی‌سازی عمیق رفتار پایه‌ای اشیاء را فراهم می‌کند، از اعتبارسنجی خودکار مقادیر گرفته تا پنهان‌سازی ویژگی‌های حساس. این ابزار در کنار Reflect، پایه‌ی بسیاری از کتابخانه‌های مدرن جاوااسکریپت برای مدیریت وضعیت واکنش‌گراست، جایی که تغییر یک مقدار باید به‌طور خودکار بخش‌های دیگر برنامه را نیز مطلع کند.