Reflect: عملیات‌های پایه‌ای زبان به‌صورت متدهای قابل‌فراخوانی

در درس قبل، Reflect به‌طور مختصر در کنار Proxy معرفی شد. در این درس، خود Reflect به‌طور مستقل بررسی می‌شود: شیئی سراسری که مجموعه‌ای از متدها را ارائه می‌دهد که هرکدام دقیقاً معادل یکی از عملیات‌های داخلی و پایه‌ای زبان جاوااسکریپت هستند؛ عملیات‌هایی که پیش از این با عملگرهایی مثل نقطه، in یا delete انجام می‌شدند.

چرا Reflect لازم است؟

عملیات‌های پایه‌ای مثل خواندن یک ویژگی یا حذف آن، در جاوااسکریپت همیشه از طریق عملگر انجام می‌شدند، نه یک متد:

const obj = { name: "علی" };

console.log(obj.name);       // خواندن با عملگر نقطه
console.log("name" in obj);  // بررسی وجود با عملگر in
delete obj.name;             // حذف با عملگر delete

مشکل این رویکرد این است که این عملگرها به‌عنوان تابع قابل انتقال یا فراخوانی به‌عنوان یک مقدار مستقل نیستند. Reflect دقیقاً همین عملیات‌ها را به‌صورت متد ارائه می‌دهد که فراخوانی، انتقال، یا استفاده در سایر ساختارهای برنامه را بسیار ساده‌تر می‌کند:

const obj = { name: "علی" };

console.log(Reflect.get(obj, "name"));   // "علی"
console.log(Reflect.has(obj, "name"));   // true
Reflect.deleteProperty(obj, "name");
console.log(obj.name);                   // undefined

جدول متدهای رایج Reflect و معادل عملگری آن‌ها

متد Reflect معادل با عملگر یا روش سنتی
Reflect.get(obj, prop) obj.prop یا obj[prop]
Reflect.set(obj, prop, value) obj[prop] = value
Reflect.has(obj, prop) prop in obj
Reflect.deleteProperty(obj, prop) delete obj[prop]
Reflect.ownKeys(obj) ترکیب Object.keys و Object.getOwnPropertySymbols

تفاوت مهم Reflect.set با عملگر معمولی

برخلاف obj[prop] = value که همیشه بی‌سروصدا اجرا می‌شود (حتی اگر ناموفق باشد)، Reflect.set یک مقدار boolean برمی‌گرداند که نشان می‌دهد آیا عملیات واقعاً موفق بوده یا نه؛ این ویژگی به‌خصوص هنگام کار با شیءهای غیرقابل‌تغییر (frozen) اهمیت پیدا می‌کند:

بررسی موفقیت عملیات با Reflect.set
خروجی کنسول اینجا نمایش داده می‌شود...

رابطه‌ی مستقیم Reflect با Proxy

همان‌طور که در درس قبل دیدید، بهترین شیوه‌ی نوشتن trapهای Proxy، استفاده از متد متناظر Reflect برای اجرای رفتار پیش‌فرض است، به‌جای دسترسی مستقیم به شیء اصلی:

const handler = {
  get(target, property, receiver) {
    console.log(`دسترسی به: ${String(property)}`);
    return Reflect.get(target, property, receiver);
  },
  set(target, property, value, receiver) {
    console.log(`تنظیم: ${String(property)} = ${value}`);
    return Reflect.set(target, property, value, receiver);
  },
};

دلیل این توصیه این است که هر متد Reflect دقیقاً همان الگوریتم داخلی موتور جاوااسکریپت را برای آن عملیات اجرا می‌کند، از جمله رفتار درست در مواردی مثل زنجیره‌ی prototype که پارامتر سوم (receiver) در آن نقش دارد؛ دسترسی مستقیم مثل target[property] در برخی موارد خاص می‌تواند رفتار کمی متفاوتی داشته باشد.

Reflect.ownKeys: دیدن همه‌چیز، حتی کلیدهای Symbol

برخلاف Object.keys که کلیدهای Symbol را نادیده می‌گیرد (مفهومی که در درس قبل بررسی شد)، Reflect.ownKeys تمام کلیدهای مستقیم یک شیء، از جمله رشته‌ای و Symbol، را برمی‌گرداند:

const ID = Symbol("id");
const obj = { name: "سارا", [ID]: 1024 };

console.log(Object.keys(obj));     // ["name"]
console.log(Reflect.ownKeys(obj)); // ["name", Symbol(id)]

نکات کلیدی

  • Reflect عملیات‌های پایه‌ای زبان مثل خواندن، نوشتن و حذف ویژگی را به‌صورت متد قابل‌فراخوانی ارائه می‌دهد.
  • Reflect.set برخلاف عملگر معمولی، مقدار boolean برمی‌گرداند که موفقیت یا شکست عملیات را نشان می‌دهد.
  • استفاده از متدهای Reflect داخل trapهای Proxy، بهترین شیوه است چون دقیقاً همان رفتار پیش‌فرض موتور جاوااسکریپت را بازتولید می‌کند.
  • Reflect.ownKeys برخلاف Object.keys، کلیدهای Symbol را نیز در نتیجه‌ی خود شامل می‌شود.

جمع‌بندی

Reflect با ارائه‌ی عملیات‌های پایه‌ای زبان به‌صورت متد، ابزاری قابل‌اعتماد برای اجرای رفتار پیش‌فرض جاوااسکریپت فراهم می‌کند؛ به‌خصوص در کنار Proxy، جایی که این متدها امکان بازتولید دقیق رفتار طبیعی شیء را در کنار منطق سفارشی می‌دهند. با تکمیل این درس، ترکیب Symbol، Proxy و Reflect، مجموعه‌ای از ابزارهای پیشرفته‌ی جاوااسکریپت برای کنترل دقیق‌تر رفتار اشیاء را کامل می‌کند.