ویژگی‌های پنهان هر Property: آشنایی با Property Descriptors

مقدمه

در طول این دوره، Property ها را همیشه به‌شکلی ساده تعریف کرده‌ایم: یک نام و یک مقدار. اما در پس‌زمینه، هر Property در جاوااسکریپت، فراتر از مقدار خودش، مجموعه‌ای از تنظیمات پنهان دارد که رفتار آن را کنترل می‌کند؛ مثلاً این‌که آیا می‌توان مقدار آن را تغییر داد، یا آیا در حلقه‌ی for...in نمایش داده می‌شود یا نه. این تنظیمات را Property Descriptor می‌نامند.

Property Descriptor چیست؟

هر Property معمولی (Data Property) در جاوااسکریپت، علاوه بر value (مقدار)، سه ویژگی پنهان دیگر هم دارد:

  • writable — آیا مقدار Property قابل تغییر است؟
  • enumerable — آیا Property در حلقه‌هایی مثل for...in یا Object.keys نمایش داده می‌شود؟
  • configurable — آیا می‌توان این Property را حذف کرد یا تنظیمات آن را دوباره تغییر داد؟

وقتی یک Property را به روش معمولی تعریف می‌کنیم، جاوااسکریپت به‌طور پیش‌فرض هر سه مورد بالا را true قرار می‌دهد.

مشاهده‌ی Descriptor یک Property

بررسی Descriptor یک Property با getOwnPropertyDescriptor
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

خروجی این کد شیئی است شامل چهار Property: value: "علی"، writable: true، enumerable: true و configurable: true. این نشان می‌دهد که وقتی name را به‌روش معمولی تعریف کردیم، جاوااسکریپت به‌طور خودکار هر سه ویژگی کنترلی را فعال (true) در نظر گرفته است؛ یعنی این Property هم قابل تغییر، هم قابل شمارش، و هم قابل حذف یا بازپیکربندی است.

تعریف یا تغییر Descriptor با Object.defineProperty

برای تنظیم دستی این ویژگی‌ها، از Object.defineProperty استفاده می‌کنیم:

ساخت یک Property غیرقابل تغییر با writable: false
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل رفتار جاوااسکریپت

با اینکه config.apiVersion = "2.0" بدون هیچ خطای ظاهری اجرا می‌شود (در حالت غیر-Strict Mode)، مقدار آن هرگز واقعاً تغییر نمی‌کند؛ همچنان "1.0" باقی می‌ماند. دلیل این رفتار، writable: false است که صریحاً به جاوااسکریپت می‌گوید این Property را نباید بتوان بازنویسی کرد. در Strict Mode (که، همان‌طور که در فصل قبل دیدیم، بدنه‌ی Class همیشه در آن اجرا می‌شود)، تلاش برای تغییر یک Property با writable: false، به‌جای نادیده گرفته شدن بی‌سروصدا، خطای TypeError صادر می‌کند.

enumerable — کنترل نمایش در حلقه‌ها

در فصل حلقه‌ها و همچنین فصل قبل (هنگام بررسی متدهای Class)، به مفهوم Enumerable اشاره کردیم؛ اکنون می‌توانیم مستقیماً آن را کنترل کنیم:

مخفی کردن یک Property از for...in و Object.keys
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

با اینکه internalId واقعاً روی شیء user وجود دارد و مقدار آن (user.internalId) به‌درستی قابل خواندن است، اما نه در خروجی Object.keys ظاهر می‌شود و نه در حلقه‌ی for...in پیمایش می‌شود؛ فقط name در هر دو نمایش داده می‌شود. این دقیقاً همان مکانیزمی است که در درس قبل دیدیم چرا متدهای داخل Class، برخلاف متدهایی که دستی روی prototype اضافه می‌شوند، توسط for...in پیمایش نمی‌شوند: جاوااسکریپت به‌طور خودکار آن‌ها را با enumerable: false می‌سازد.

configurable — قفل کردن ساختار یک Property

وقتی configurable: false باشد، دو کار دیگر هم ممکن نخواهد بود: حذف Property با delete، و تغییر دوباره‌ی تنظیمات آن (به‌جز writable که در برخی شرایط خاص همچنان قابل تغییر به false است):

اثر configurable: false روی delete
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل رفتار جاوااسکریپت

تلاش برای حذف id با delete بی‌اثر می‌ماند (در غیر-Strict Mode) و مقدار settings.id همچنان 42 باقی می‌ماند؛ چون configurable: false صریحاً از حذف این Property جلوگیری می‌کند.

مقایسه‌ی سه ویژگی در یک نگاه

ویژگیوقتی false باشد
writableمقدار Property دیگر قابل تغییر نیست
enumerableProperty در for...in و Object.keys ظاهر نمی‌شود
configurableProperty دیگر قابل حذف یا بازپیکربندی نیست

دریافت تمام Descriptor های یک شیء

برای بررسی همزمان تمام Property های یک شیء، می‌توان از Object.getOwnPropertyDescriptors (با s در انتها) استفاده کرد:

let user = { name: "علی", age: 25 };
console.log(Object.getOwnPropertyDescriptors(user));

جمع‌بندی

هر Property در جاوااسکریپت، فراتر از مقدار خودش، دارای سه ویژگی کنترلی پنهان است: writable که قابلیت تغییر مقدار را تعیین می‌کند، enumerable که قابلیت پیمایش در حلقه‌هایی مثل for...in را تعیین می‌کند، و configurable که قابلیت حذف یا بازپیکربندی Property را تعیین می‌کند. به‌طور پیش‌فرض، Property هایی که به روش معمولی تعریف می‌شوند، هر سه ویژگی را true دارند؛ اما با Object.defineProperty می‌توان هرکدام را به‌طور دقیق کنترل کرد.