حسب المواصفات، يمكن استخدام نوعين بدائيين فقط كمفاتيح لخصائص الكائن:
نوع السلسلة، أو
نوع الرمز.
بخلاف ذلك، إذا استخدم أحدهم نوعًا آخر، مثل الرقم، فسيتم تحويله تلقائيًا إلى سلسلة. لذا فإن obj[1]
هو نفسه obj["1"]
، و obj[true]
هو نفسه obj["true"]
.
حتى الآن كنا نستخدم السلاسل فقط.
الآن دعونا نستكشف الرموز، ونرى ما يمكنهم فعله لنا.
يمثل "الرمز" معرفًا فريدًا.
يمكن إنشاء قيمة من هذا النوع باستخدام Symbol()
:
دع المعرف = الرمز ()؛
عند الإنشاء، يمكننا إعطاء الرموز وصفًا (يُسمى أيضًا اسم الرمز)، وهو مفيد في الغالب لأغراض تصحيح الأخطاء:
// المعرف هو رمز بالوصف "معرف" اسمحوا معرف = رمز("معرف");
الرموز مضمونة لتكون فريدة من نوعها. حتى لو قمنا بإنشاء العديد من الرموز بنفس الوصف تمامًا، فهي ذات قيم مختلفة. الوصف مجرد تسمية لا تؤثر في أي شيء.
على سبيل المثال، هنا رمزان لهما نفس الوصف - فهما غير متساويين:
دع id1 = الرمز("id"); دع id2 = الرمز("id"); تنبيه(id1 == id2); // خطأ شنيع
إذا كنت معتادًا على لغة Ruby أو لغة أخرى تحتوي أيضًا على نوع من "الرموز" - من فضلك لا تضلل. رموز جافا سكريبت مختلفة.
لذا، لتلخيص، الرمز هو "قيمة فريدة بدائية" مع وصف اختياري. دعونا نرى أين يمكننا استخدامها.
لا يتم تحويل الرموز تلقائيًا إلى سلسلة
تدعم معظم القيم في JavaScript التحويل الضمني إلى سلسلة. على سبيل المثال، يمكننا alert
أي قيمة تقريبًا، وسوف ينجح الأمر. الرموز خاصة. لا يقومون بالتحويل التلقائي.
على سبيل المثال، سيظهر هذا alert
خطأ:
اسمحوا معرف = رمز("معرف"); تنبيه (المعرف)؛ // TypeError: لا يمكن تحويل قيمة الرمز إلى سلسلة
وهذا بمثابة "حارس لغوي" ضد العبث، لأن السلاسل والرموز مختلفة بشكل أساسي ولا ينبغي أن تحول إحداها إلى أخرى عن طريق الخطأ.
إذا أردنا حقًا إظهار رمز ما، فنحن بحاجة إلى استدعاء .toString()
عليه بشكل صريح، كما هو الحال هنا:
اسمحوا معرف = رمز("معرف"); تنبيه (id.toString ())؛ // الرمز (المعرف)، الآن يعمل
أو احصل على خاصية symbol.description
لإظهار الوصف فقط:
اسمحوا معرف = رمز("معرف"); تنبيه (id.description)؛ // بطاقة تعريف
تسمح لنا الرموز بإنشاء خصائص "مخفية" لكائن ما، بحيث لا يمكن لأي جزء آخر من التعليمات البرمجية الوصول إليها أو الكتابة فوقها عن طريق الخطأ.
على سبيل المثال، إذا كنا نعمل مع كائنات user
، التي تنتمي إلى رمز جهة خارجية. نود أن نضيف معرفات لهم.
دعونا نستخدم مفتاح رمز لذلك:
السماح للمستخدم = {// ينتمي إلى رمز آخر الاسم: "جون" }; اسمحوا معرف = رمز("معرف"); المستخدم [المعرف] = 1؛ تنبيه (المستخدم [المعرف])؛ // يمكننا الوصول إلى البيانات باستخدام الرمز كمفتاح
ما الفائدة من استخدام Symbol("id")
على سلسلة "id"
؟
نظرًا لأن كائنات user
تنتمي إلى قاعدة تعليمات برمجية أخرى، فمن غير الآمن إضافة حقول إليها، نظرًا لأننا قد نؤثر على السلوك المحدد مسبقًا في قاعدة التعليمات البرمجية الأخرى تلك. ومع ذلك، لا يمكن الوصول إلى الرموز عن طريق الخطأ. لن يكون رمز الطرف الثالث على علم بالرموز المحددة حديثًا، لذلك من الآمن إضافة رموز إلى كائنات user
.
تخيل أيضًا أن نصًا برمجيًا آخر يريد أن يكون له معرف خاص به داخل user
لأغراضه الخاصة.
ثم يمكن لهذا البرنامج النصي إنشاء Symbol("id")
، مثل هذا:
// ... اسمحوا معرف = رمز("معرف"); user[id] = "قيمة المعرف الخاص بهم";
لن يكون هناك تعارض بين معرفاتنا ومعرفاتهم، لأن الرموز مختلفة دائمًا، حتى لو كانت تحمل نفس الاسم.
…ولكن إذا استخدمنا سلسلة "id"
بدلاً من الرمز لنفس الغرض، فسيكون هناك تعارض:
السماح للمستخدم = { الاسم: "جون" }؛ // يستخدم البرنامج النصي خاصية "المعرف". user.id = "قيمة المعرف الخاص بنا"; // ...يريد برنامج نصي آخر أيضًا "المعرف" لأغراضه... user.id = "قيمة المعرف الخاص بهم" // بوم! الكتابة فوقها بواسطة نص آخر!
إذا أردنا استخدام رمز في كائن حرفي {...}
، فسنحتاج إلى أقواس مربعة حوله.
مثله:
اسمحوا معرف = رمز("معرف"); السماح للمستخدم = { الاسم: "جون"، [المعرف]: 123 // ليس "المعرف": 123 };
وذلك لأننا نحتاج إلى القيمة من id
المتغير كمفتاح، وليس السلسلة "id".
الخصائص الرمزية لا تشارك في حلقة for..in
.
على سبيل المثال:
اسمحوا معرف = رمز("معرف"); السماح للمستخدم = { الاسم: "جون"، العمر: 30, [المعرف]: 123 }; لـ (السماح بإدخال المستخدم) تنبيه (مفتاح) ؛ // الاسم والعمر (بدون رموز) // يعمل الوصول المباشر عن طريق الرمز تنبيه("مباشر:" + user[id]); // مباشر: 123
يتجاهلها Object.keys(user) أيضًا. وهذا جزء من مبدأ "إخفاء الخصائص الرمزية" العام. إذا تم تكرار برنامج نصي أو مكتبة أخرى فوق كائننا، فلن يتمكن من الوصول بشكل غير متوقع إلى خاصية رمزية.
في المقابل، يقوم Object.sign بنسخ خصائص السلسلة والرمز:
اسمحوا معرف = رمز("معرف"); السماح للمستخدم = { [المعرف]: 123 }; Let clone = Object.assis({}, user); تنبيه (استنساخ [المعرف])؛ // 123
ليس هناك مفارقة هنا. هذا حسب التصميم. الفكرة هي أنه عندما نقوم باستنساخ كائن أو دمج الكائنات، فإننا عادةً ما نريد نسخ جميع الخصائص (بما في ذلك الرموز مثل id
).
كما رأينا، عادةً ما تكون جميع الرموز مختلفة، حتى لو كانت تحمل نفس الاسم. لكن في بعض الأحيان نريد أن تكون الرموز ذات الأسماء نفسها هي نفس الكيانات. على سبيل المثال، تريد أجزاء مختلفة من تطبيقنا الوصول إلى الرمز "id"
الذي يعني نفس الخاصية تمامًا.
ولتحقيق ذلك، يوجد سجل رمز عالمي . يمكننا إنشاء رموز فيه والوصول إليها لاحقًا، ويضمن أن يؤدي الوصول المتكرر بنفس الاسم إلى إرجاع نفس الرمز تمامًا.
من أجل قراءة (إنشاء إذا كان غائبا) رمزا من التسجيل، استخدم Symbol.for(key)
.
يتحقق هذا الاستدعاء من السجل العام، وإذا كان هناك رمز موصوف على أنه key
، فسيتم إرجاعه، وإلا فسيقوم بإنشاء رمز جديد Symbol(key)
ويخزنه في السجل بواسطة key
المحدد.
على سبيل المثال:
// القراءة من السجل العالمي اسمحوا معرف = رمز.for("id"); // إذا لم يكن الرمز موجودًا، فسيتم إنشاؤه // اقرأها مرة أخرى (ربما من جزء آخر من الكود) دع idAgain = الرمز.for("id"); // نفس الرمز تنبيه (المعرف === idAgain)؛ // حقيقي
تسمى الرموز الموجودة داخل السجل بالرموز العامة . إذا أردنا رمزًا على مستوى التطبيق، يمكن الوصول إليه في كل مكان في الكود - فهذا هو الهدف.
هذا يبدو مثل روبي
في بعض لغات البرمجة، مثل روبي، يوجد رمز واحد لكل اسم.
في جافا سكريبت، كما نرى، ينطبق هذا على الرموز العالمية.
لقد رأينا أنه بالنسبة للرموز العامة، Symbol.for(key)
يقوم بإرجاع رمز بالاسم. ولفعل العكس – قم بإرجاع اسم برمز عام – يمكننا استخدام: Symbol.keyFor(sym)
:
على سبيل المثال:
// الحصول على الرمز بالاسم دع سيم = رمز.for("اسم"); دعsym2 = الرمز.for("id"); // الحصول على الاسم بالرمز تنبيه(Signal.keyFor(sym)); // اسم تنبيه(Signal.keyFor(sym2)); // بطاقة تعريف
يستخدم Symbol.keyFor
داخليًا سجل الرموز العام للبحث عن مفتاح الرمز. لذا فهو لا يعمل مع الرموز غير العامة. إذا لم يكن الرمز عالميًا، فلن يتمكن من العثور عليه ويعود undefined
.
ومع ذلك، فإن جميع الرموز لها خاصية description
.
على سبيل المثال:
دع globalSymbol = الرمز.for("name"); دع localSymbol = الرمز("name"); تنبيه(Signal.keyFor(globalSymbol)); // الاسم، الرمز العالمي تنبيه(Signal.keyFor(localSymbol)); // غير محدد، وليس عالميًا تنبيه (localSymbol.description)؛ // اسم
هناك العديد من رموز "النظام" التي تستخدمها JavaScript داخليًا، ويمكننا استخدامها لضبط الجوانب المختلفة لكائناتنا.
وهي مدرجة في المواصفات في جدول الرموز المعروفة:
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
…وهلم جرا.
على سبيل المثال، يسمح لنا Symbol.toPrimitive
بوصف التحويل من كائن إلى بدائي. وسنرى استخدامه قريبا جدا.
ستصبح الرموز الأخرى مألوفة أيضًا عندما ندرس ميزات اللغة المقابلة.
Symbol
هو نوع بدائي للمعرفات الفريدة.
يتم إنشاء الرموز باستخدام استدعاء Symbol()
مع وصف اختياري (الاسم).
الرموز هي دائمًا قيم مختلفة، حتى لو كانت تحمل نفس الاسم. إذا أردنا أن تكون الرموز ذات الأسماء نفسها متساوية، فيجب علينا استخدام السجل العام: يقوم Symbol.for(key)
بإرجاع (إنشاء إذا لزم الأمر) رمزًا عالميًا مع key
كاسم. تُرجع الاستدعاءات المتعددة لـ Symbol.for
بنفس key
نفس الرمز تمامًا.
للرموز حالتان استخدام رئيسيتان:
خصائص الكائن "المخفي".
إذا أردنا إضافة خاصية إلى كائن "ينتمي" إلى برنامج نصي آخر أو مكتبة، فيمكننا إنشاء رمز واستخدامه كمفتاح خاصية. الخاصية الرمزية لا تظهر في for..in
، لذلك لن تتم معالجتها عن طريق الخطأ مع الخصائص الأخرى. كما لن يتم الوصول إليه مباشرة، لأن البرنامج النصي الآخر لا يحتوي على الرمز الخاص بنا. لذلك سيتم حماية الخاصية من الاستخدام العرضي أو الكتابة الفوقية.
لذلك يمكننا إخفاء شيء ما "سرًا" في الأشياء التي نحتاجها، ولكن لا ينبغي للآخرين رؤيتها، وذلك باستخدام الخصائص الرمزية.
هناك العديد من رموز النظام التي تستخدمها JavaScript والتي يمكن الوصول إليها Symbol.*
. يمكننا استخدامها لتغيير بعض السلوكيات المضمنة. على سبيل المثال، في وقت لاحق من البرنامج التعليمي، سنستخدم Symbol.iterator
للتكرارات، Symbol.toPrimitive
لإعداد التحويل من الكائن إلى البدائي وما إلى ذلك.
من الناحية الفنية، الرموز ليست مخفية بنسبة 100%. هناك طريقة مدمجة Object.getOwnPropertySymbols(obj) تسمح لنا بالحصول على جميع الرموز. هناك أيضًا طريقة تسمى Reflect.ownKeys(obj) تقوم بإرجاع جميع مفاتيح الكائن بما في ذلك المفاتيح الرمزية. لكن معظم المكتبات والوظائف المضمنة وبنيات بناء الجملة لا تستخدم هذه الأساليب.