با توجه به خصوصیات زبان، تنها دو نوع از مقدارهای اصلی میتوانند به عنوان کلید ویژگی شیءها استفاده شوند:
- نوع رشته، یا
- نوع سمبل(symbol).
در غیر این صورت، اگر کسی از نوع دیگری استفاده کند، مثل عدد، به صورت خودکار به رشته تبدیل میشود. پس obj[1]
با obj["1"]
یکسان است و obj[true]
با obj["true"]
.
تا اینجا ما فقط از رشتهها استفاده میکردیم.
حال بیایید سمبلها و کاری که میتوانند برای ما انجام دهند را ببینیم.
سمبلها (Symbols)
یک «سمبل» نشان دهندهی شناسهای یکتا است.
یک مقدار از این نوع میتواند با استفاده از Symbol()
ساخته شود:
let id = Symbol();
بعد از ساختن، میتوانیم به سمبل یک سری توضیحات بدهیم (همچنین به آن اسم سمبل هم میگویند)، که اکثرا برای رفع خطا استفاده میشود:
// است "id" یک سمبل به همراه توضیحات id
let id = Symbol("id");
سمبلها برای یکتا بودن تضمینشده هستند. حتی اگر ما چند سمبل را با توضیحات یکسان بسازیم، آنها مقدارهایی متفاوت هستند. توضیحات فقط یک برچسب است که روی چیزی تاثیر نمیگذارد.
برای مثال، اینجا دو سمبل با توضیحات یکسان داریم – آنها برابر نیستند:
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
اگر شما با Ruby یا زبان دیگری که یک جورایی “سمبل” دارد آشنایی دارید – لطفا گمراه نشوید. سمبلهای جاوااسکریپت متفاوت هستند.
پس، به طور خلاصه، یک سمبل یک «مقدار یکتای اصلی» همراه با یک توضیح اختیاری است. بیایید ببینیم کجا میتوانیم از آنها استفاده کنیم.
اکثر مقدارهای در جاوااسکریپت تبدیل به رشته به صورت ضمنی را انجام میدهند. برای مثال، ما میتوانیم هر مقداری را alert
کنیم، و این کار خواهد کرد. سمبلها خاص هستند. آنها به صورت خودکار تبدیل نمیشوند.
برای مثال، این alert
یک ارور را نمایش میدهد:
let id = Symbol("id");
alert(id); // TypeError: Cannot convert a Symbol value to a string
این موضوع یک “گارد زبان” در برابر خرابکاری کردن است، چون رشتهها و سمبلها از پایه متفاوت هستند و نباید به صورت تصادفی به یکدیگر تبدیل شوند.
اگر ما واقعا نیاز داریم که یک سمبل را نمایش دهیم، باید به طور ضمنی همراه آن .toString()
را هم صدا بزنیم، مثل اینجا:
let id = Symbol("id");
alert(id.toString()); // Symbol(id)، حالا کار میکند
یا فقط ویژگی symbol.description
را برای نمایش توضیحات دریافت کنیم:
let id = Symbol("id");
alert(id.description); // id
ویژگیهای «مخفی» شیء
سمبلها به ما این امکان را میدهند که در یک شیء ویژگیهای “مخفی” بسازیم، که هیچ کجای کد نتواند به صورت تصادفی به آن دسترسی داشته باشد یا آن را تغییر دهد.
برای مثال، اگر ما در حال کار کردن با شیءهای user
که متعلق به یک شخص ثالث است باشیم. ما میخواهیم شناسههایی به آنها اضافه کنیم.
بیایید از یک کلید سمبلی برای این کار استفاده کنیم:
let user = { // به یک کد دیگر تعلق دارد
name: "John"
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // میتوانیم با استفاده سمبل به عنوان کلید به داده دسترسی داشته باشیم
مزیت استفاده از Symbol("id")
به جای رشته "id"
چیست؟
به دلیل اینکه شیءهای user
به کد دیگری تعلق دارند، آن کد هم با آنها کار میکند، ما نباید همینجوری به آن چیزی اضافه کنیم. این کار ایمن نیست. اما نمیتوان به طور تصادفی به یک سمبل دسترسی پیدا کرد، کد شخص ثالث احتمالا آن را نمیبیند، پس اضافه کردن سمبلها به شیءهای user
ایمن است.
همچنین تصور کنید که یک اسکریپت دیگر بنا به دلایلی، بخواهد شناسه خودش را درون user
داشته باشد.
سپس آن اسکریپت میتواند Symbol("id")
خودش را بسازد، مثل این:
// ...
let id = Symbol("id");
user[id] = "آنها id مقدار";
هیچ تعارضی بین شناسه ما و آنها وجود نخواهد داشت، چون سمبلها همیشه متفاوت هستند، حتی اگر اسم یکسانی داشته باشند.
…اما اگر ما از رشته "id"
به جای سمبل برای مقصود مشابه استفاده میکردیم، آنگاه تعارضی وجود داشت:
let user = { name: "John" };
// استفاده میکند "id" اسکریپت ما از ویژگی
user.id = "ما id مقدار";
// ...میخواهد "id" یک اسکریپت دیگر هم بنا به دلیلی...
user.id = "آنها id مقدار"
// !بوم! توسط یک اسکریپت دیگر بازنویسی شد
سمبلها در یک شیء لیترال
اگر ما بخواهیم از یک سمبل در شیء لیترال {...}
استفاده کنیم، باید دور آن براکت قرار دهیم.
مثل این:
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // "id": 123 نه
};
به این دلیل که ما به مقدار متغیر id
به عنوان کلید نیاز داریم، نه رشتهی “id”.
سمبلها توسط for…in رد میشوند
ویژگیهای سمبلی در حلقه for..in
شرکت نمیکنند.
برای مثال:
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name, age (بدون سمبل)
// دسترسی مستقیم از طریق سمبل کار میکند
alert( "به طور مستقیم: " + user[id] );
همچنین Object.keys(user)
هم آنها را نادیده میگیرد. این بخشی از اصل کلی «مخفیسازی ویژگیهای سمبلی» است. اگر یک اسکریپت یا کتابخانه دیگر در شیء ما حلقه بزند، به تصادفی به ویژگی سمبلی ما دسترسی نخواهد داشت.
در مقابل، Object.assign هم ویژگیهای رشته و هم سمبل را کپی میکند:
let id = Symbol("id");
let user = {
[id]: 123
};
let clone = Object.assign({}, user);
alert( clone[id] ); // 123
هیچ تناقضی اینجا وجود ندارد. طراحی آن اینگونه است. زمانی که ما یک شیء را شبیهسازی میکنیم یا شیءها را ادغام میکنیم، معمولا میخواهیم که تمام ویژگیها کپی شوند (شامل سمبلهایی مثل id
).
سمبلهای گلوبال (global)
همانطور که دیدیم، معمولا تمام سمبلها متفاوت هستند، حتی اگر اسم یکسان داشته باشند. اما گاهی اوقات ما میخواهیم که سمبلهای هم نام با یکدیگر برابر باشند. برای مثال، قسمتهای مختلف برنامه ما میخواهند به سمبل "id"
که ویژگی یکسانی است دسترسی داشته باشند.
برای بدست آوردن آن، یک رجیستری ثبت سمبل گلوبال وجود دارد. ما میتوانیم سمبلها را درون آن بسازیم و بعدا به آن دسترسی داشته باشیم، و تضمین میکند که دسترسی تکراری با استفاده از یک اسم به ما دقیقا سمبل یکسان را برمیگرداند.
برای خواندن (یا در صورت ناموجود بودن، ساختن) یک سمبل از رجیستری ثبت، از Symbol.for(key)
استفاده کنید.
این صدازدن رجیستری ثبت گلوبال را بررسی میکند، و اگر یک سمبل توصیف شده به عنوان key
موجود باشد، آن را برمیگرداند، در غیر این صورت یک سمبل جدید Symbol(key)
میسازد و آن را در رجیستری ثبت با استفاده از key
داده شده ذخیره میکند.
برای مثال:
// گلوبال خواندن از رجیستری ثبت
let id = Symbol.for("id"); // اگر سمبل وجود نداشته باشد، ساخته میشود
// دوباره آن را میخواند (شاید از یک جای دیگر کد باشد)
let idAgain = Symbol.for("id");
// سمبل یکسان
alert( id === idAgain ); // true
سمبلهای درون رجیستری ثبت سمبلهای گلوبال نامیده میشوند. اگر ما یک سمبل در تمام سطح برنامه بخواهیم که در همه جای کد قابل دسترس باشد – این سمبلها مناسب هستند.
در بعضی از زبانهای برنامهنویسی، مثل Ruby، به ازای هر اسم فقط یک سمبل وجود دارد.
در جاوااسکریپت، همانطور که میبینیم، برای سمبلهای گلوبال این موضوع صدق میکند.
متد Symbol.keyFor
دیدیم که برای سمبلهای گلوبال، Symbol.for(key)
یک سمبل را بر اساس اسم آن برمیگراند. برای انجام کار برعکس یعنی برگرداندن اسم بر اساس یک سمبل گلوبال میتوانیم از این استفاده کنیم: Symbol.keyFor(sym)
:
برای مثال:
// گرفتن سمبل بر اساس اسم
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// گرفتن اسم بر اساس سمبل
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
متد Symbol.keyFor
درون خود از رجیستری ثبت سمبل گلوبال برای پیدا کردن کلید (key) سمبل استفاده میکند. پس برای سمبلهای غیر گلوبال کار نمیکند. اگر سمبل گلوبال نباشد، قادر به پیدا کردن آن نخواهد بود و undefined
را برمیگرداند.
همانطور که گفته شد، هر سمبل ویژگی description
را دارد.
برای مثال:
let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");
alert( Symbol.keyFor(globalSymbol) ); // سمبل گلوبال ،name
alert( Symbol.keyFor(localSymbol) ); // گلوبال نیست ،undefined
alert( localSymbol.description ); // name
سمبلهای سیستمی
تعداد زیادی سمبل “سیستمی” وجود دارد که جاوااسکریپت درون خود از آنها استفاده میکند، و ما میتوانیم از آنها برای ایجاد تغییرات کوچک در جنبههای متنوع شیءها استفاده کنیم.
آنها در مشخصات زبان در جدول سمبلهای شناختهشده لیست شدهاند:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
- …و غیره.
برای مثال، Symbol.toPrimitive
به ما امکان توصیف تبدیل شیء به مقدار اصلی (primitive) را میدهد. ما کاربرد آن را به زودی میبینیم.
شما با بقیه سمبلها هم زمانی که ویژگی زبان مربوط به آنها را مطالعه کنیم آشنا میشوید.
خلاصه
Symbol
یک نوع مقدار اصلی (primitive) برای شناسههای یکتا است.
سمبلها با صدازدن Symbol()
به همراه توضیحات (اسم) اختیاری ساخته میشوند.
سمبلها همیشه مقدارهای متفاوت دارند، حتی اگر اسم یکسان داشته باشند. اگر بخواهیم که سمبلهای همنام برابر باشند، باید از رجیستری ثبت گلوبال استفاده کنیم: Symbol.for(key)
یک سمبل گلوبال را با استفاده از key
به عنوان اسم برمیگرداند (اگر نیاز باشد آن را میسازد). چند مرتبه صدازدن Symbol.for
با key
یکسان دقیقا سمبل یکسان را برمیگرداند.
سمبلها در دو مورد زیاد استفاده میشوند:
-
ویژگیهای «مخفی» شیء. اگر ما بخواهیم یک ویژگی را درون یک شیء که به اسکریپت یا کتابخانه دیگری “تعلق دارد” اضافه کنیم، میتوانیم یک سمبل بسازیم و از آن به عنوان کلید ویژگی استفاده کنیم. یک ویژگی سمبلی در
for..in
نمایان نمیشود، پس با ویژگیهای دیگر به طور تصادفی روی آن فرایندی انجام نمیگیرد. همچنین دسترسی مستقیم به آن وجود ندارد، چون اسکریپت دیگر سمبل ما را ندارد. پس ویژگی از استفاده یا بازنویسی تصادفی در امان میماند.پس ما میتوانیم با استفاده از ویژگیهای سمبلی، به صورت “مخفیانه” چیزی را که نیاز داریم درون شیءها پنهان کنیم، اما بقیه آن را نباید ببینند.
-
سمبلهای سیستمی زیادی وجود دارند که توسط جاوااسکریپت استفاده میشوند و با
Symbol.*
قابل دسترس هستند. ما میتوانیم از آنها برای تغییر بعضی از رفتارهای درون زبان استفاده کنیم. برای مثال، بعدا در همین آموزش ما ازSymbol.iterator
برای قابل تکرارها (iterables)،Symbol.toPrimitive
برای ایجاد تبدیل شیء به مقدار اصلی و غیره استفاده خواهیم کرد.
از لحاظ فنی، سمبلها 100% مخفی نیستند. یک متد درونساخت Object.getOwnPropertySymbols(obj) وجود دارد که به ما امکان دریافت تمام سمبلها را میدهد. همچنین یک متد به نام Reflect.ownKeys(obj) وجود دارد که تمام کلیدهای یک شیء که شامل کلیدهای سمبلی هم هست را برمیگرداند. پس آنها در واقع پنهان نیستند. اما اکثر کتابخانهها، توابع درونساخت و ساختارهای سینتکس از این متدها استفاده نمیکنند.