تعد الأحرف الأولى من ابتكارات المعيار ES6 ، والتي جلبت بعض الميزات القيمة إلى JavaScript. تعد الرموز الممثلة بنوع بيانات Symbol مفيدة بشكل خاص عند استخدامها كمعرفات لخصائص الكائن. فيما يتعلق بمثل هذا السيناريو الخاص بتطبيقها ، فإن السؤال الذي يطرح نفسه هو ما الذي يمكنهم وماهية الخطوط.

في المادة ، التي ننشر ترجمتها اليوم ، سنتحدث عن نوع بيانات Symbol في JavaScript. سنبدأ بمراجعة بعض ميزات جافا سكريبت التي تحتاج إليها للتنقل من أجل التعامل مع الرموز.
معلومات أولية
في JavaScript ، في الواقع ، هناك نوعان من القيم. النوع الأول - القيم البدائية ، الكائن الثاني - (تشمل أيضًا الوظائف). تتضمن القيم البدائية أنواعًا بسيطة من البيانات مثل الأرقام (ويشمل ذلك كل شيء من الأعداد الصحيحة إلى أرقام
Infinity
العائمة وقيم
Infinity
و
NaN
) والقيم المنطقية والسلاسل والقيم
undefined
والقيم
null
. لاحظ أنه أثناء التحقق من
typeof null === 'object'
تسفر عن القيمة
true
، تعتبر
null
قيمة بدائية.
القيم البدائية غير قابلة للتغيير. لا يمكن تغييرها. بالطبع ، يمكنك كتابة شيء جديد في متغير تخزين قيمة بدائية. على سبيل المثال ، يكتب هذا قيمة جديدة للمتغير
x
:
let x = 1; x++;
ولكن في الوقت نفسه ، لا يوجد تغيير (طفرة) في القيمة العددية البدائية
1
.
في بعض اللغات ، على سبيل المثال ، في C ، هناك مفاهيم لتمرير وسيطات الدالات حسب المرجع والقيمة. جافا سكريبت لديها أيضا شيء مماثل. كيف يتم تنظيم العمل مع البيانات بالضبط يعتمد على نوعها. إذا تم تمرير قيمة أولية ممثلة بمتغير معين إلى الوظيفة ، ثم تم تغييرها في هذه الوظيفة ، فإن القيمة المخزنة في المتغير الأصلي لا تتغير. ومع ذلك ، إذا قمت بتمرير قيمة الكائن التي يمثلها المتغير إلى الوظيفة وتعديلها ، فسيتم أيضًا تغيير ما يتم تخزينه في هذا المتغير.
النظر في المثال التالي:
function primitiveMutator(val) { val = val + 1; } let x = 1; primitiveMutator(x); console.log(x);
إن القيم البدائية (باستثناء
NaN
الغامضة ، والتي لا تساوي نفسها) ، تتحول دائمًا إلى مساوية للقيم البدائية الأخرى التي تشبه نفسها تمامًا. على سبيل المثال:
const first = "abc" + "def"; const second = "ab" + "cd" + "ef"; console.log(first === second);
ومع ذلك ، فإن بناء قيم الكائنات التي تبدو متشابهة إلى الخارج لن يؤدي إلى حقيقة أنه سيتم الحصول على الكيانات ، عند المقارنة ، سيتم الكشف عن مساواتها مع بعضها البعض. يمكنك التحقق من ذلك عن طريق:
const obj1 = { name: "Intrinsic" }; const obj2 = { name: "Intrinsic" }; console.log(obj1 === obj2);
تلعب الكائنات دورًا أساسيًا في JavaScript. يتم استخدامها حرفيًا في كل مكان. على سبيل المثال ، يتم استخدامها غالبًا في شكل مجموعات مفاتيح / قيمة. ولكن قبل ظهور نوع البيانات
Symbol
، يمكن استخدام السلاسل فقط كمفاتيح كائن. كان هذا قيدًا خطيرًا على استخدام الكائنات في شكل مجموعات. عند محاولة تعيين قيمة غير سلسلة كمفتاح كائن ، تم نقل هذه القيمة إلى سلسلة. يمكنك التحقق من ذلك عن طريق:
const obj = {}; obj.foo = 'foo'; obj['bar'] = 'bar'; obj[2] = 2; obj[{}] = 'someobj'; console.log(obj);
بالمناسبة ، على الرغم من أن هذا يبعدنا قليلاً عن موضوع الأحرف ، إلا أنني أود الإشارة إلى أنه تم إنشاء بنية بيانات
Map
للسماح باستخدام مخازن بيانات المفتاح / القيمة في المواقف التي لا يكون فيها المفتاح سلسلة.
ما هو الرمز؟
الآن وبعد أن اكتشفنا ميزات القيم البدائية في JavaScript ، أصبحنا جاهزون أخيرًا لبدء الحديث عن الشخصيات. الرمز هو معنى بدائي فريد من نوعه. إذا اقتربت من الرموز من هذا الموضع ، فستلاحظ أن الرموز في هذا الصدد تشبه الكائنات ، لأن إنشاء عدة مثيلات للرموز سيؤدي إلى إنشاء قيم مختلفة. لكن الرموز ، علاوة على ذلك ، هي قيم بدائية ثابتة. فيما يلي مثال للعمل مع الشخصيات:
const s1 = Symbol(); const s2 = Symbol(); console.log(s1 === s2);
عند إنشاء مثيل لحرف ، يمكنك استخدام وسيطة السلسلة الأولى الاختيارية. هذه الوسيطة هي وصف للرمز المخصص للاستخدام في تصحيح الأخطاء. لا تؤثر هذه القيمة على الرمز نفسه.
const s1 = Symbol('debug'); const str = 'debug'; const s2 = Symbol('xxyy'); console.log(s1 === str);
الرموز كمفاتيح لخاصية الكائنات
يمكن استخدام الرموز كمفاتيح خاصية للكائنات. هذا مهم جدا فيما يلي مثال لاستخدامها على هذا النحو:
const obj = {}; const sym = Symbol(); obj[sym] = 'foo'; obj.bar = 'bar'; console.log(obj);
يرجى ملاحظة أنه لا يتم إرجاع المفاتيح المحددة بواسطة الأحرف عند
Object.keys()
الأسلوب
Object.keys()
. التعليمة البرمجية المكتوبة قبل ظهور الأحرف في JS لا تعرف أي شيء عنها ، ونتيجة لذلك ، لا ينبغي أن تعاد المعلومات حول مفاتيح الكائنات الممثلة بالأحرف بواسطة الأسلوب
Object.keys()
القديم.
للوهلة الأولى ، قد يبدو أن ميزات الأحرف أعلاه تسمح لك باستخدامها لإنشاء خصائص خاصة لكائنات JS. في العديد من لغات البرمجة الأخرى ، يمكنك إنشاء خصائص كائن مخفية باستخدام الفئات. يعتبر نقص هذه الميزة لفترة طويلة أحد أوجه القصور في JavaScript.
لسوء الحظ ، يمكن الوصول إلى التعليمات البرمجية التي تعمل مع الكائنات بحرية مفاتيح السلسلة الخاصة بهم. يمكن للرمز أيضًا الوصول إلى المفاتيح التي تحددها الأحرف ، علاوة على ذلك ، حتى لو لم يكن الرمز الذي يعملون به مع الكائن قادرًا على الوصول إلى الحرف المقابل. على سبيل المثال ، باستخدام طريقة
Reflect.ownKeys()
، يمكنك الحصول على قائمة بجميع مفاتيح الكائن ، سواء تلك التي هي سلاسل وتلك التي هي أحرف:
function tryToAddPrivate(o) { o[Symbol('Pseudo Private')] = 42; } const obj = { prop: 'hello' }; tryToAddPrivate(obj); console.log(Reflect.ownKeys(obj));
لاحظ أن العمل جاري حاليًا لتزويد الفئات بالقدرة على استخدام الخصائص الخاصة. تسمى هذه الميزة
الحقول الخاصة . صحيح ، لا يؤثر بشكل مطلق على جميع الكائنات ، في إشارة فقط إلى تلك الكائنات التي تم إنشاؤها على أساس الفئات المعدة مسبقًا. يتوفر دعم الحقول الخاصة بالفعل في الإصدار 72 من متصفح Chrome والإصدارات الأقدم.
منع الاصطدامات أسماء الممتلكات الكائن
لا تضيف الرموز ، بالطبع ، إلى JavaScript القدرة على إنشاء خصائص خاصة للكائنات ، ولكنها تمثل ابتكارًا قيمًا في اللغة لأسباب أخرى. وهي مفيدة في المواقف التي تحتاج فيها مكتبات معينة إلى إضافة خصائص إلى الكائنات الموصوفة خارجها ، وفي الوقت نفسه لا تخاف من تضارب أسماء خصائص الكائنات.
اطلع على مثال تريد فيه مكتبتان مختلفتان إضافة بيانات أولية إلى كائن. من الممكن أن تحتاج كلتا المكتبات إلى تجهيز الكائن ببعض المعرفات. إذا كنت تستخدم ببساطة شيءًا مثل سلسلة
id
مكونة من حرفين لاسم هذه الخاصية ، فقد تواجه موقفًا حيث تقوم إحدى المكتبات بالكتابة فوق الخاصية المحددة بواسطة الآخر.
function lib1tag(obj) { obj.id = 42; } function lib2tag(obj) { obj.id = 369; }
إذا استخدمنا الرموز في مثالنا ، فبإمكان كل مكتبة إنشاء الرموز التي تحتاجها عند التهيئة. يمكن بعد ذلك استخدام هذه الرموز لتعيين الخصائص إلى الكائنات والوصول إلى هذه الخصائص.
const library1property = Symbol('lib1'); function lib1tag(obj) { obj[library1property] = 42; } const library2property = Symbol('lib2'); function lib2tag(obj) { obj[library2property] = 369; }
من خلال النظر إلى مثل هذا السيناريو ، يمكنك الاستفادة من ظهور الأحرف في JavaScript.
ومع ذلك ، قد يكون هناك سؤال يتعلق باستخدام المكتبات لأسماء خصائص الكائنات أو سلاسل عشوائية أو سلاسل ذات بنية معقدة ، بما في ذلك ، على سبيل المثال ، اسم المكتبة. يمكن أن تشكل السلاسل المتشابهة شيئًا مثل مساحات الأسماء للمعرفات التي تستخدمها المكتبات. على سبيل المثال ، قد يبدو كالتالي:
const library1property = uuid();
بشكل عام ، يمكنك القيام بذلك. في الواقع ، تتشابه المقاربات المتشابهة مع ما يحدث عند استخدام الرموز. وإذا ، باستخدام معرّفات عشوائية أو مساحات أسماء ، فلن تنشئ بعض المكتبات ، عن طريق الصدفة ، نفس أسماء الخصائص ، فلن تكون هناك مشكلة في الأسماء.
قد يقول القارئ الذكي الآن أن الطريقتين قيد النظر في تسمية خصائص الكائن ليست متكافئة تمامًا. تحتوي أسماء الخصائص التي يتم إنشاؤها بشكل عشوائي أو باستخدام مساحات الأسماء على عيب: من السهل جدًا العثور على المفاتيح المطابقة ، خاصةً إذا كان الرمز يبحث في مفاتيح الكائنات أو يقوم بإجراء تسلسل لها. النظر في المثال التالي:
const library2property = 'LIB2-NAMESPACE-id';
إذا تم استخدام رمز لاسم المفتاح في هذا الموقف ، فلن يحتوي تمثيل JSON للكائن على قيمة الرمز. لماذا هذا هكذا؟ الحقيقة هي أن حقيقة ظهور نوع بيانات جديد في JavaScript لا تعني إجراء تغييرات على مواصفات JSON. يدعم JSON ، كمفاتيح خاصية ، السلاسل فقط. عند إجراء تسلسل لكائن ما ، لم تُبذل أية محاولة لتمثيل الأحرف بأي طريقة خاصة.
يمكن حل مشكلة النظر في الحصول على أسماء الخصائص في تمثيل JSON للكائنات باستخدام
Object.defineProperty()
:
const library2property = uuid();
تعمل مفاتيح السلسلة "المخفية" عن طريق تعيين
enumerable
على التصرف
false
بنفس الطريقة التي تعمل بها المفاتيح الممثلة بالأحرف. لا يتم عرض
Object.keys()
عند
Object.keys()
، ويمكن اكتشافهما باستخدام
Reflect.ownKeys()
. إليك ما يبدو عليه:
const obj = {}; obj[Symbol()] = 1; Object.defineProperty(obj, 'foo', { enumberable: false, value: 2 }); console.log(Object.keys(obj));
هنا ، يجب أن أقول ، لقد أعدنا تقريبًا إمكانيات الرموز ، باستخدام وسائل أخرى من JS. على وجه الخصوص ، لا يقع كلا المفاتيح الممثلة بالرموز والمفاتيح الخاصة في تمثيل JSON للكائن. يمكن التعرف على
Reflect.ownKeys()
بالرجوع إلى طريقة
Reflect.ownKeys()
. نتيجة لذلك ، كلاهما لا يمكن أن يطلق عليهما خصوصية حقًا. إذا افترضنا أن بعض القيم العشوائية أو مساحات أسماء المكتبات تستخدم لإنشاء أسماء المفاتيح ، فهذا يعني أننا تخلصنا من خطر تضارب الأسماء.
ومع ذلك ، هناك اختلاف بسيط واحد بين استخدام أسماء الرموز والأسماء التي تم إنشاؤها باستخدام آليات أخرى. نظرًا لأن الأوتار غير قابلة للتغيير ، ويضمن أن تكون الأحرف فريدة ، فهناك دائمًا احتمال أن يتسبب شخص ما ، بعد المرور بجميع مجموعات الأحرف الممكنة في السلسلة ، في تضارب الأسماء. من وجهة نظر رياضية ، هذا يعني أن الشخصيات تمنحنا حقًا فرصة ثمينة لا توجد بها سلاسل.
في Node.js ، عند فحص الكائنات (على سبيل المثال ، استخدام
console.log()
) ، إذا
inspect
اكتشاف طريقة كائن تسمى
inspect
، فإن هذه الطريقة تُستخدم للحصول على تمثيل سلسلة للكائن ثم عرضها على الشاشة. من السهل أن نفهم أنه لا يمكن لأي شخص أن يأخذ ذلك في الحسبان على الإطلاق ، وبالتالي فإن مثل هذا السلوك للنظام يمكن أن يؤدي إلى استدعاء طريقة
inspect
الكائن ، والتي تم تصميمها لحل المشكلات التي لا تتعلق بتكوين سلسلة تمثيل الكائن. تم إهمال هذه الميزة في Node.js 10 ، حيث يتم ببساطة تجاهل طرق الإصدار 11 التي تحمل اسمًا مشابهًا. الآن ، لتنفيذ هذه الميزة ، يتم
require('util').inspect.custom
. هذا يعني أنه لن يتمكن أي شخص من تعطيل النظام عن غير قصد عن طريق إنشاء طريقة كائن تسمى
inspect
.
تقليد الممتلكات الخاصة
إليك طريقة مثيرة للاهتمام يمكنك استخدامها لمحاكاة الخصائص الخاصة للكائنات. يتضمن هذا النهج استخدام ميزة JavaScript حديثة أخرى - كائنات الوكيل. تعمل هذه الكائنات كمغلفات للكائنات الأخرى التي تسمح للمبرمج بالتدخل في الإجراءات التي يتم تنفيذها مع هذه الكائنات.
تقدم الكائنات الوكيل العديد من الطرق لاعتراض الإجراءات التي يتم تنفيذها على الكائنات. نحن مهتمون بالقدرة على التحكم في تشغيل مفاتيح قراءة كائن ما. لن ندخل في التفاصيل حول كائنات الوكيل هنا. إذا كنت مهتمًا ، ألق نظرة على
هذا المنشور.
يمكننا استخدام الوكلاء للتحكم في خصائص الكائن المرئية من الخارج. في هذه الحالة ، نريد إنشاء وكيل يخفي خاصيتين نعلمهما. واحد لديه اسم السلسلة
_favColor
، ويتم تمثيل الثاني بحرف مكتوب إلى متغير
favBook
:
let proxy; { const favBook = Symbol('fav book'); const obj = { name: 'Thomas Hunter II', age: 32, _favColor: 'blue', [favBook]: 'Metro 2033', [Symbol('visible')]: 'foo' }; const handler = { ownKeys: (target) => { const reportedKeys = []; const actualKeys = Reflect.ownKeys(target); for (const key of actualKeys) { if (key === favBook || key === '_favColor') { continue; } reportedKeys.push(key); } return reportedKeys; } }; proxy = new Proxy(obj, handler); } console.log(Object.keys(proxy));
التعامل مع خاصية يمثلها السلسلة
_favColor
ليس بالأمر الصعب: فقط اقرأ الكود المصدري. يمكن مطابقة المفاتيح الديناميكية (مثل مفاتيح uuid التي رأيناها أعلاه) بقوة قاسية. لكن بدون الرجوع إلى الرمز ، لا يمكنك الوصول إلى قيمة
Metro 2033
من الكائن
proxy
.
تجدر الإشارة إلى أنه في Node.js هناك ميزة واحدة تنتهك خصوصية الكائنات الوكيل. هذه الميزة غير موجودة في اللغة نفسها ، لذلك فهي غير مناسبة لأنظمة تشغيل JS الأخرى ، مثل المستعرض. الحقيقة هي أن هذه الميزة تسمح لك بالوصول إلى الكائن المخفي خلف الكائن الوكيل ، إذا كان لديك وصول إلى الكائن الوكيل. فيما يلي مثال يوضح القدرة على تجاوز الآليات الموضحة في مقتطف الشفرة السابق:
const [originalObject] = process .binding('util') .getProxyDetails(proxy); const allKeys = Reflect.ownKeys(originalObject); console.log(allKeys[3]);
الآن ، لمنع استخدام هذه الميزة في مثيل محدد من Node.js ، يجب عليك إما تعديل كائن
Reflect
أو ربط عملية
util
. ومع ذلك ، هذه مهمة أخرى. إذا كنت مهتمًا ، فقم بإلقاء نظرة على
هذا المنشور حول حماية واجهات برمجة التطبيقات المستندة إلى JavaScript.
النتائج
في هذا المقال ، تحدثنا عن نوع بيانات
Symbol
، وعن الميزات التي يوفرها لمطوري JavaScript ، وعن آليات اللغة الحالية التي يمكن استخدامها لمحاكاة هذه الميزات.
أعزائي القراء! هل تستخدم الرموز في مشاريع JavaScript الخاصة بك؟
