دليل تعلم جافاسكربت النوع الرمزي (Symbol) في جافاسكربت


Emq Mohammed

وفقًا للمواصفات، قد تكون مفاتيح خصائص الكائنات من نوع نصي (string) أو رمزي (Symbol)، ولا تكون أرقامًا ولا قيمًا منطقية، إما نصًا أو رمزًا فقط.

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

الرموز

يُمَثِّل الرمز مُعَرِّفًا فريدًا، ويمكن إنشاء قيمة من هذا النوع باستخدام Symbol()‎:

// هو رمز جديد id 
let id = Symbol();

يمكننا إعطاء وصف للرمز أثناء الإنشاء (ما يُسمَّى أيضًا باسم الرمز)، ويكون مفيدًا لعملية تصحيح الأخطاء:

// "id" هو رمز مع الوصف id
let id = Symbol("id");

تتصف الرموز بكونها فريدة حتى إن أنشأنا أكثر من رمز بالوصف ذاته، تبقى الرموز قيمًا مختلفة. يُعد الوصف مجرد طابع ولا يؤثر على أي شيء.

مثلًا، هنا رمزين بالوصف ذاته لكنهما غير متساويان:

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

alert(id1 == id2); // false

إن كانت لديك خلفية عن لغة Ruby أو أي لغة برمجية أخرى تستخدم الرموز فلا تخلط الأمور. فالرموز في JavaScript مختلفة.

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

تتضمن أغلب القيم في JavaScript تحويلًا ضمنيًا إلى نص. مثلًا، يمكننا عرض أي قيمة تقريبا باستخدام 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

خاصيات "خفية"

تتيح لنا الرموز إنشاء خاصيات "خفية" للكائن، والتي لا يمكن لأي شيفرة الوصول إليها وتغيير محتواها حتى عن طريق الخطأ. مثلا، إن كنا نتعامل مع كائنات تنتمي إلى شيفرة من طرف ثالث ونريد إضافة مُعرفات لها، نستخدم مفتاحًا رمزيًا لذلك:

let user = { // ينتمي لِشيفرة أخرى
  name: "John"
};

let id = Symbol("id");

user[id] = 1;

alert( user[id] ); // يمكننا الوصول إلى البيانات باستخدام الرمز كمفتاح

ما الفائدة من استخدام Symbol("id")‎ على النص "id"؟ بما أن كائنات user تنتمي لِشيفرة أخرى، وبما أن الشيفرة أعلاه تتعامل معها فليس آمنًا إضافة أي حقل لها. لكن لا يمكن الوصول إلى الرمز حتى عن طريق الخطأ، قد لا تراه شيفرة الطرف الثالث أيضًا، لذا فإنها الطريقة الصحيحة للقيام بذلك.

تخيل أيضًا أن سكريبت آخر يريد إضافة معاملاته الخاصة بِداخل user لغرضه الخاص. قد يكون هذا السكريبت مكتبة JavaScript أخرى، لذا فإن كل سكريبت لا يعلم بتواجد الآخر بتاتًا، ثم يمكن لهذا السكريبت إنشاء رمزه الخاص Symbol("id")‎ هكذا:

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

user[id] = "Their id value";

بهذه الطريقة، لن يوجد أي تعارض بين المُعاملات التي انشأناها ومُعاملات السكريبت الآخر لأن الرموز تختلف دومًا حتى إن كان لديها الاسم ذاته. لكن إن استخدمنا النص "id" بدلًا من الرمز للغرض ذاته، فَسيوجد تعارض:

let user = { name: "John" };

// "id" يستخدم السكربت الذي أنشأناه الخاصية 
user.id = "Our id value";

// لغرض آخر "id" يريد سكريبت آخر استخدام الخاصية

user.id = "Their id value"
// !تعارض! تم تغيير المحتوى من قِبل السكريبت الآخر

الرموز في التعريف المختصر لكائن

إن أردنا استخدام الرموز عند تعريف كائن بالطريقة المختصرة عبر الأقواس {...} فَسنحتاج إلى تغليفها بأقواس مربعة هكذا:

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( "Direct: " + user[id] );

يتجاهل Object.keys(user)‎ الرموز أيضًا. يُعد هذا من جزءًا من مبدأ "إخفاء الخاصيات الرمزية". إن حاول سكريبت آخر أو مكتبة JavaScript الدخول إلى كائن والتنقل فيه، فلن يصل إلى الخاصيات الرمزية بتاتًا.

في المقابل، تنسخ Object.assign كلًا من النصوص والخاصيات الرمزية:

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

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

alert( clone[id] ); // 123

لا يوجد لَبسٌ هنا، فهذا الأمر ضمن التصميم. الفكرة هي أنه عند استنساخ كائن أو دمج كائنين، فإننا نريد أن تُنسَخ جميع الخصائص (بما فيها الرموز مثل id).

تُحوَّل مفاتيح الخاصيات التي من نوع آخر غير الرمز إلى نصوص جبريًا

يمكننا استخدام النصوص أو الرموز فقط كمفاتيح في الكائنات، ويُحوَّل أي نوع آخر إلى نص. مثلًا، الرقم 0 يصبح النص "0" عندما يستخدم كمفتاح لخاصية:

let obj = {
  0: "test" // "0": "test" مثل
};

// "إلى الخاصية ذاتها أي يحوَّل الرقم 0 إلى نص "0 alert تصل الدالة
alert( obj["0"] ); // اختبار
alert( obj[0] ); // (اختبار (الخاصية ذاتها

الرموز العامة

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

لنتمكن من القيام بذلك، يوجد سجل للرموز العامة. يمكننا إنشاء رموز فيه والوصول إليها لاحقًا، كما يضمن أن الوصول المتكرر إلى الاسم ذاته يُرجِع الرمز ذاته في كل مرة.

لقراءة، أو إنشاء رمز في حال عدم تواجده في السجل، نستخدم Symbol.for(key)‎. يفحص هذا الاستدعاء السجل العام للرموز، إن كان هناك رمزًا بالوصف key، فإنه يُرجِعه، وإن لم يجده فإنه يُنشئ رمزًا جديدًا بالاستدعاء Symbol(key)‎ ويُخزِّنه في السجل بالوصف المُعطى key. إليك المثال التالي:

// يقرأ من السجل العام
let id = Symbol.for("id"); // إن لم يجد الرمز، ينشئه

// يُقرأ مجددًا، ربما من جزء آخر في الشيفرة
let idAgain = Symbol.for("id");

// الرمز ذاته
alert( id === idAgain ); // true

تُدعى الرموز داخل السجل العام رموزًا عامة، ونستعلمها في حال أردنا رمزًا على مستوى تطبيق كامل، وقابلًا للوصول في الشيفرة.

هذا الأمر مشابه لما في لغة Ruby

يوجد في بعض اللغات البرمجية مثل Ruby رمزًا واحدًا لكل اسم. كما رأينا، في JavaScript الأمر ذاته صحيح بالنسبة للرموز العامة.

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 سجل الرموز العام داخليًا للبحث عن مفتاح الرمز. لذا فإنها لا تعمل مع الرموز الغير عامة. إن كان الرمز غير عام، فلن يتمكن من العثور عليه وسيُرجِع 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

رموز النظام

يوجد العديد من رموز "النظام" التي تستخدمها JavaScript داخليًا، ويمكن استخدامها لضبط نواحي متعددة من الكائنات بدقة. هذه الرموز مُدرَجة في وصف جدول الرموز المتعارف عليها:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • وهكذا…

مثلًا، يتيح لنا الرمز Symbol.toPrimitive وصف كائن إلى تحويل أولي. سنرى استخدامها قريبًا. ستعتاد على باقي الرموز عند دراسة ميزات اللغة المُقابلة.

الخلاصة

النوع Symbol هو نوع أولي للمُعاملات الفريدة. يتم إنشاء الرموز عبر استدعاء الدالة Symbol()‎ مع وصف اختياري (اسم).

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

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

  1. خاصيات الكائن "الخفية". إن أردنا إضافة خاصية إلى كائن ينتمي إلى سكريبت أو مكتبة أخرى، يمكننا إنشاء رمز واستخدامه كمفتاح خاصية. لا تظهر الخاصية الرمزية في for..in، حتى لا تتم معالجته عن طريق الخطأ مع باقي الخاصيات. لن يتم الوصول إليه مباشرةً أيضًا لأن السكريبت الخارجي لا يحوي على هذا الرمز. هكذا تكون الخاصية محمية من الاستخدام الخارجي. لذا، يمكننا إخفاء شيء ما بشكل تام في الكائنات التي نحتاجها، والتي لا يجب أن يراها الآخرون باستخدام الخاصيات الرمزية.
  2. يوجد العديد من رموز النظام المستخدمة بواسطة JavaScript التي يمكن الوصول إليها بواسطة Symbol.*‎. يمكننا استخدامها لتعديل سلوك مُدمَج، مثلا، سنستخدم Symbol.iterato لاحقَا في الشرح للحلقات، و Symbol.toPrimitive لإعداد التحويل من كائن لقيمة أولية وهكذا…

عمليًا، الرموز ليست خفية 100%. يوجد دالة مدمجة Object.getOwnPropertySymbols(obj) تتيح لنا الوصول إلى جميع الرموز. كما يوجد دالة تُدعى Reflect.ownKeys(obj) والتي تُرجِع جميع مفاتيح الكائن بما فيها الرموز. لذا فإن الرموز ليست مخفية فعلًا، لكن أغلب المكاتب، والدوال المدمجة والهياكل لا تستخدم هذه الدوال.

ترجمة -وبتصرف- للفصل Symbol type من كتاب The JavaScript Language

اقرأ أيضًا





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن