٢٥ مارس ٢٠٢١

الرمز (Symbol type)

كما ذُكر فى المصدر, فإن صفات الكائنات (Object properties keys) يمكن أن تكون نصًا (string) أو رمزًا (symbol) . ليست رقما أو قيمه منطقيه (boolean) وإنما عباره عن نصوص أو رموز, فقط هذين النوعين.

لقد استخدمنا حتى الآن النص فقط. فهيا نرى الفوائد التى يمكن أن توفرها لنا الرموز.

الرموز

كلمة رمز فى الإنجليزيه تعنى معَرٌِف فريد من نوعه أى لا شئ مماثل له Unique Identifier.

يمكن إنشاء قيمه من نوع الرمز باستخدام الداله Symbol():

// id is a new symbol
let id = Symbol();

عند الإنشاء, يمكننا إعطاء الرمز وصفًا (ويمكن تسميته أيضا إسم الرمز), وهذا مفيد غالبا فى البحث عن الأخطاء وحلها.

// id is a symbol with the description "id"
let id = Symbol('id');

إن الرموز مضمون بتفرُّدها. حتى فى حالة إنشاء عدة رموز بنفس الوصف, ولكنهم مختلفين فى القيمه. فالوصف مجرد وَسْم لا يؤثر على أى شيء.

على سبيل المثال, هذان الرمزان لهما نفس الوصف – ولكنهما غير متساويين:

let id1 = Symbol("id");
let id2 = Symbol("id");

alert(id1 == id2); // false

إذا كنت تعرف لغة برمجه مثل (Ruby) أو أى لغة برمجة أخرى لديها شئ قريب من الرموز فلا تحتار

الرموز لا تتحول إلى نص تلقائياً

أغلب القيم فى جافا سكريبت يمكن تحويلها ضمنيًا إلى نص (string). على سبيل المثال, يمكننا أن نعرض أى قيمه فى دالة التنبيه (alert()), وستعمل, ولكن الرموز (Symbols) لها طابع خاص. فلا تسرى عليهم القاعده نفسها.

على سبيل المثال, هذا الكود سيؤدى إلى ظهور خطأ:

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), now it works

أو نستدعى الخاصيه symbol.description لعرض الوصف فقط:

let id = Symbol("id");
alert(id.description); // id

الخصائص المخفيه

باستخدام الرموز يمكننا أن ننشئ خصائص مخفيه لكائن ما (object) حيث لا يمكن لأى جزء آخر فى الكود أن يصل إليها ولا أن يعدل قيمتها عن طريق الخطأ.

على سبيل المثال, إذا كنا نعمل على كائن user والذى لا ينتمي إلى هذا الكود ولكننا نريد أن نضيف بعض الخصائص إليه.

هيا نستخدم رمزا من أجل ذلك:

let user = {
  // belongs to another code
  name: 'John',
};

let id = Symbol('id');

user[id] = 1;

alert(user[id]); // we can access the data using the symbol as the key

إذا ماهى فائدة استخدام Symbol("id") بدلا من النص "id" ؟

حيث أن الكائن user ينتمي لكود خارجي وهذا الكود يعمل جيدا, إذا فلا يصح أن نضيف أى خاصيه لهذا الكائن. ولكن الرمز لا يمكن الوصول إليه عن طريق الخطأ مثل النص حيث أن الكود الخارجى لا يمكن أن يراه من الأساس, ولذلك هذه الطريقه تُعد صحيحه.

تخيل أيضا لو أن هناك برنامج (script) آخر يريد أن يضيف خاصية بداخل الكائن user لأغراضه الخاصه, هذا البرنامج الآخر يمكنه أن يكون مكتبه مبنية بجافا سكريبت ولذلك فإن هذه البرامج لا تعرف شيئا عن بعضها البعض.

لذلك هذا البرنامج يمكنه أن ينشئ Symbol("id") الخاص به كالآتي:

// ...
let id = Symbol('id');

user[id] = 'Their id value';

لن يكون هناك أى تعارض بين الخصائص وبعضها لأن الرموز دائما مختلفه ولا يمكن أن تتساوى حتى إن كان لهم نفس الإسم.

…ولكن ماذا لو استخدمنا نصًا بدلًا من رمز لنفس الغرض, فسيكون هناك تعارض:

let user = { name: 'John' };

// Our script uses "id" property
user.id = 'Our id value';

// ...Another script also wants "id" for its purposes...

user.id = 'Their id value';
// Boom! overwritten by another script!

Symbols in an object literal

إذا كنا نريد أن نضع رمزا بداخل كائن كخاصيه, فإننا نحتاج أن نضع حول الرمز أقواس مربعه []

كالمثال الآتى:

let id = Symbol("id");

let user = {
  name: "John",
  [id]: 123 // not "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 (no symbols)

// the direct access by the symbol works
alert( "Direct: " + user[id] );

وأيضا يتم تجاهل الخصائص من نوع الرمز عند استخدام Object.keys(user). لأن هذا جزء من المبدأ العام “إخفاء الخصائص الرمزيه” “hiding symbolic properties”. وبالمثل إذا كان هناك أى برنامج آخر يقوم أو مكتبه تقوم بالتكرار على الخصائص فى هذا الكائن فإنها لن تستطيع أن تصل إلى الخاصيه من نوع الرمز.

على النقيض تماما فإن Object.assign تقوم بنسخ خصائص الكائن كلها سواءًا النصيه أو الرمزيه.

let id = Symbol('id');
let user = {
  [id]: 123,
};

let clone = Object.assign({}, user);

alert(clone[id]); // 123

هذا ليس تناقضا. هذا موجود فى تصميم اللغة نفسها. فالفكره وراء هذا هى أنه عندما نريد أن ننسخ كائنا ما فإننا نريد أن ننسخ كل الخصائص بما فيها من خصائص رمزيه.

الرموز العامه

كما قلنا سابقًا, فإن الرموز عادةً ما تكون مختلفة حتى وإن كان لها نفس الوصف. ولكن فى بعض الأوقات أن نصل إلى هذا الرمز بالتحديد. على سبيل المثال, إذا كان هناك أجزاء مختلفه من البرنامج الخاص بنا تريد أن تصل إلى الرمز "id" تحديدا.

ليتم تنفيذ ذلك, فإن هناك مايسمى مكان تسجيل الرموز العامه global symbol registry. حيث يمكننا أن ننشئ رمزًا ومن خلال هذا المكان نستطيع أن نصل إليه فى وقت لاحق, ومكان تسجيل الرموز يضمن لنا أن تكرار محاولات الوصول إلى الرمز بنفس الإسم سيقوم بإرجاع نفس هذا الرمز فى كل محاوله.

لقراءة (أو يمكن أن نقول إنشاء رمز فى حالة عدم وجوده) فى مكان التسجيل, استخدم Symbol.for(key).

هذه الداله ترى إن كان هناك رمز فى مكان التسجيل تم وصفه باستخدام key ستقوم بإرجاعها. أما غير ذلك فستقوم بإنشاء رمز جديد Symbol(key) وتخزينه فى مكان التسجيل باستخدام الوصف key.

على سبيل المثال:

// read from the global registry
let id = Symbol.for('id'); // if the symbol did not exist, it is created

// read it again (maybe from another part of the code)
let idAgain = Symbol.for('id');

// the same symbol
alert(id === idAgain); // true

الرموز بداخل مكان التسجيل تسمي رموزًا عامه. فإذا كنا نريد رمزا متاحا فى البرنامج بأكمله ويمكن الوصول إليه من أى مكان – فهذا هو سبب وجودها.

هذا يبدو مثل لغة البرمجه Ruby

فى بعض لغات البرمجه مثل Ruby فإن هناك رمزًا لكل إسم.

فى جافا سكريبت كما نرى فإن هذا صحيح بالنسبة إلى الرموز العامه.

Symbol.keyFor

بالنسبة إلى الرموز العامه فإنه لايوجد Symbol.for(key) التى تقوم بإرجاع الرمز باستخدام الإسم فقط، ولكن يوجد أيضا العكس Symbol.keyFor(sym) الذى يقوم بإرجاع الإسم باستخدام الرمز العام.

على سبيل المثال:

// get symbol by name
let sym = Symbol.for('name');
let sym2 = Symbol.for('id');

// get name by symbol
alert(Symbol.keyFor(sym)); // name
alert(Symbol.keyFor(sym2)); // id

الداله Symbol.keyFor عندما تعمل تقوم باستخدام مكان تسجيل الرموز العام للبحث عن إسم للرمز, ولذلك فإنها لا تعمل إلا مع الرموز العامه. فإذا كان الرمز غير عام فلن تستطيع إيجاده وستقوم بإرجاع undefined.

يقال بأن كل رمز يملك الخاصيه description.

على سبيل المثال:

let globalSymbol = Symbol.for('name');
let localSymbol = Symbol('name');

alert(Symbol.keyFor(globalSymbol)); // name, global symbol
alert(Symbol.keyFor(localSymbol)); // undefined, not global

alert(localSymbol.description); // name

الرموز الموجودة تلقائيا

هناك رموز كثيرة تستخدمها جافا سكريبت داخليًا ويمكننا استخدامها لأغراض معينه فى الكائنات الخاصه بنا.

هذه الرموز موجوده فى المصدر فى جدول Well-known symbols:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • …وغيرها.

على سبيل المثال، Symbol.toPrimitive تمكننا أن نحول الكائن إلى قيمه فرديه (primitive). وسنرى هذا فى القريب العاجل.

الرموز الأخرى أيضا ستكون مألوفه عندما ندرس استخداماتها فى مواضيع أخرى.

الملخص

الرمز هو قيمه فرديه لخصائص فريده من نوعها (unique identifiers).

يتم إنشاء الرموز باستخدام الداله Symbol() ويمكن إضافه وصف بشكل اختيارى.

الرموز دائما ما تكون قيَمها مختلفه حتى وإن كانت بنفس الإسم. وإذا كنا نريد أن نصل إلى رمز بعينه فيجب علينا استخدام مكان التسجيل العام global registry: Symbol.for(key) تقوم بإرجاع (أو إنشاء فى حالة عدم وجوده) رمزًا عامًا حيث jey هو الإسم. والإستدعاء المتكرر للداله Symbol.for بنفس الإسم key ستقوم دائما بإرجاع نفس الرمز.

يتم استخدام الرموز فى حالتين:

  1. خصائص الكائن المخفيه.

إذا كنا نريد أن نضيف خاصيه إلى كائن لا ينتمى إلى هذا الكود بل إلى برنامج آخر أو مكتبه، فعندئذ يمكننا إنشاء رمز واستخدامه كخاصيه. والخاصيه من نوع الرمز لا تظهر فى التكرار for .. in, ولذلك لا يمكن الوصول إلى الخاصيه عن طريق الخطأ أو أن تتعارض مع أى خاصية أخرى وذلك لأن البرنامج الآخر لا يملك الرمز الخاص بنا. وبالتالى ستظل الخاصيه محميه من الوصول إليها أو التعديل عليها.

ولذلك يمكننا أن نُخفى أى شئ بداخل كائنات نحتاجها ولا يستطيع أى برنامج الوصول إليها باستخدام الخصائص من نوع الرموز.

  1. هناك الكثير من الرموز الموجوده بالفعل فى جافا سكريبت والتى يمكن الوصول إليها عن طريق Symbol.*. ويمكننا استخدامهم لتغيير بعض السلوك الموجود بالفعل فى اللغه. على سبيل المثال فإننا فى موضوع مقبل سنستخدم Symbol.iterator من أجل التكراريات iterables وغيرها.

عمليًا، فإن الرموز لا تكون مخفية بالكامل. ولكن هناك داله موجوده تسمى Object.getOwnPropertySymbols(obj) والتى تمكننا من الوصول إلى كل الرموز. ,توجد أيضًا دالة تسمي Reflect.ownKeys(obj) والتى تقوم بإرجاع كل الخصائص بداخل كائن معين بما فيها الخصائص التى من نوع الرمز. ولذلك فإن هذه الخصائص ليست مخفية بالكامل. ولكن أعلب المكتبات والدوال لا تستخدم هذه الوسائل.

خريطة الدورة التعليمية

التعليقات

إقرأ هذا قبل أن تضع تعليقًا…
  • إذا كان لديك اقتراحات أو تريد تحسينًا - من فضلك من فضلك إفتح موضوعًا فى جيتهاب أو شارك بنفسك بدلًا من التعليقات.
  • إذا لم تستطع أن تفهم شيئّا فى المقال - وضّح ماهو.
  • إذا كنت تريد عرض كود استخدم عنصر <code> ، وللكثير من السطور استخدم <pre>، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)