JavaScript المتقدم (ES6+)

Symbols - معرفات فريدة وثابتة

13 دقيقة الدرس 23 من 40

Symbols - معرفات فريدة وثابتة

قدمت ES6 Symbols، وهو نوع بيانات أولي جديد ينشئ معرفات فريدة وثابتة. تُستخدم Symbols بشكل أساسي لإنشاء خصائص كائن خاصة، وتعريف السلوكيات المعروفة، وتجنب تصادمات أسماء الخصائص في الكائنات.

ما هي Symbols؟

Symbol هو قيمة أولية فريدة وثابتة يمكن استخدامها كمفتاح خاصية كائن. الخصائص الرئيسية:

  • كل Symbol فريد تماماً، حتى مع نفس الوصف
  • Symbols ثابتة (لا يمكن تغييرها بمجرد إنشائها)
  • يمكن استخدام Symbols كمفاتيح خصائص الكائن
  • خصائص Symbol لا تظهر في حلقات for...in أو Object.keys()
  • تمكّن Symbols البرمجة التلقائية من خلال الرموز المعروفة
حقيقة مهمة: لدى JavaScript الآن سبعة أنواع أولية: string و number و boolean و null و undefined و bigint و symbol.

إنشاء Symbols

تنشئ Symbols باستخدام دالة Symbol() (وليس منشئاً):

// إنشاء رمز أساسي const sym1 = Symbol(); const sym2 = Symbol(); console.log(sym1 === sym2); // false - كل Symbol فريد! // Symbols مع أوصاف (لتصحيح الأخطاء) const id = Symbol('id'); const userId = Symbol('userId'); const debugId = Symbol('debugId'); console.log(id.toString()); // 'Symbol(id)' console.log(id.description); // 'id' (ES2019+) // Symbols دائماً فريدة، حتى مع نفس الوصف const a = Symbol('mySymbol'); const b = Symbol('mySymbol'); console.log(a === b); // false // لا يمكن استخدام Symbol مع كلمة 'new' // const wrong = new Symbol(); // TypeError!
مهم: على عكس النصوص أو الأرقام، لا يمكن تحويل Symbols ضمنياً إلى نصوص. يجب استخدام .toString() أو .description بشكل صريح.

استخدام Symbols كمفاتيح كائن

Symbols مثالية لإنشاء خصائص لن تتعارض مع الخصائص الموجودة أو المستقبلية:

const id = Symbol('id'); const user = { name: 'Alice', age: 25, [id]: 12345 // Symbol كخاصية محسوبة }; console.log(user[id]); // 12345 console.log(user.name); // 'Alice' // خصائص Symbol مخفية من التعداد العادي console.log(Object.keys(user)); // ['name', 'age'] - لا Symbol! console.log(Object.values(user)); // ['Alice', 25] - لا قيمة Symbol! for (let key in user) { console.log(key); // فقط 'name' و 'age'، ليس Symbol } // لكن Symbols ليست مخفية تماماً console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)] console.log(Reflect.ownKeys(user)); // ['name', 'age', Symbol(id)]

سجل Symbol العام

أحياناً تحتاج إلى مشاركة Symbols عبر أجزاء مختلفة من تطبيقك. يساعد سجل Symbol العام في ذلك:

// Symbol.for() - الإنشاء أو الاسترجاع من السجل العام const globalSym1 = Symbol.for('app.id'); const globalSym2 = Symbol.for('app.id'); console.log(globalSym1 === globalSym2); // true - نفس Symbol! // Symbol.keyFor() - الحصول على المفتاح من السجل العام console.log(Symbol.keyFor(globalSym1)); // 'app.id' // الرموز المحلية ليست في السجل العام const localSym = Symbol('local'); console.log(Symbol.keyFor(localSym)); // undefined // مثال واقعي: ثوابت مشتركة // في file1.js const STATUS = Symbol.for('app.status'); // في file2.js const STATUS = Symbol.for('app.status'); // نفس Symbol!
أفضل ممارسة: استخدم Symbol.for() عندما تحتاج إلى مشاركة Symbols عبر الملفات أو الوحدات. استخدم Symbol() العادي للخصائص الخاصة داخل وحدة واحدة.

Symbols المعروفة جيداً

يوفر JavaScript رموزاً مدمجة تسمح لك بتخصيص سلوك الكائن. تسمى هذه "الرموز المعروفة جيداً":

Symbols المعروفة الشائعة: Symbol.iterator - تعريف سلوك التكرار المخصص Symbol.toStringTag - تخصيص Object.prototype.toString() Symbol.toPrimitive - التحكم في تحويل النوع Symbol.hasInstance - تخصيص سلوك instanceof Symbol.species - تحديد المنشئ للكائنات المشتقة Symbol.match - تعريف سلوك مطابقة النص Symbol.search - تعريف سلوك البحث في النص Symbol.replace - تعريف سلوك استبدال النص Symbol.split - تعريف سلوك تقسيم النص

Symbol.iterator - التكرار المخصص

أكثر الرموز المعروفة استخداماً هو Symbol.iterator:

// إنشاء كائن قابل للتكرار مخصص const range = { from: 1, to: 5, [Symbol.iterator]() { let current = this.from; const last = this.to; return { next() { if (current <= last) { return { value: current++, done: false }; } else { return { done: true }; } } }; } }; // الآن الكائن قابل للتكرار! for (let num of range) { console.log(num); // 1, 2, 3, 4, 5 } // يمكن استخدام مشغل النشر console.log([...range]); // [1, 2, 3, 4, 5] // يمكن استخدام Array.from() console.log(Array.from(range)); // [1, 2, 3, 4, 5]

Symbol.toStringTag

تخصيص النص الذي يُرجعه Object.prototype.toString():

class CustomClass { constructor(name) { this.name = name; } get [Symbol.toStringTag]() { return 'CustomClass'; } } const obj = new CustomClass('test'); console.log(Object.prototype.toString.call(obj)); // [object CustomClass] // مقارنة مع السلوك الافتراضي class RegularClass { constructor(name) { this.name = name; } } const regular = new RegularClass('test'); console.log(Object.prototype.toString.call(regular)); // [object Object]

Symbol.toPrimitive

التحكم في كيفية تحويل الكائنات إلى قيم أولية:

const temperature = { celsius: 25, [Symbol.toPrimitive](hint) { if (hint === 'number') { return this.celsius; } if (hint === 'string') { return `${this.celsius}°C`; } // تلميح افتراضي return this.celsius; } }; console.log(+temperature); // 25 (تلميح رقم) console.log(`${temperature}`); // '25°C' (تلميح نص) console.log(temperature + 5); // 30 (تلميح افتراضي) // مثال آخر: كائن مال مخصص const price = { amount: 100, currency: 'USD', [Symbol.toPrimitive](hint) { if (hint === 'number') { return this.amount; } return `${this.currency} ${this.amount}`; } }; console.log(+price); // 100 console.log(`Price: ${price}`); // 'Price: USD 100' console.log(price * 2); // 200

الخصائص الخاصة مع Symbols

قبل حقول الفئة الخاصة (#private)، كانت Symbols تُستخدم عادةً للخصوصية:

const _password = Symbol('password'); const _validatePassword = Symbol('validatePassword'); class User { constructor(username, password) { this.username = username; this[_password] = password; } [_validatePassword](input) { return input === this[_password]; } login(password) { if (this[_validatePassword](password)) { console.log('تسجيل دخول ناجح!'); return true; } console.log('كلمة مرور غير صحيحة'); return false; } } const user = new User('alice', 'secret123'); console.log(user.username); // 'alice' console.log(user.password); // undefined console.log(user[_password]); // يعمل فقط إذا كان لديك مرجع لـ Symbol user.login('wrong'); // كلمة مرور غير صحيحة user.login('secret123'); // تسجيل دخول ناجح! // خصائص Symbol لا تظهر في وحدة التحكم أو JSON console.log(Object.keys(user)); // ['username'] console.log(JSON.stringify(user)); // {"username":"alice"}
البديل الحديث: قدمت ES2022 حقولاً خاصة حقيقية باستخدام بادئة #، والتي توفر خصوصية أقوى من Symbols.

أمثلة عملية

// المثال 1: البيانات الوصفية دون تصادم الأسماء const metadata = Symbol('metadata'); function addMetadata(obj, data) { obj[metadata] = data; } const article = { title: 'ES6 Features', author: 'Alice' }; addMetadata(article, { created: new Date(), views: 0 }); console.log(article.title); // 'ES6 Features' console.log(article[metadata]); // {created: ..., views: 0} // المثال 2: الطرق الخاصة في كائن حرفي const privateMethod = Symbol('privateMethod'); const calculator = { [privateMethod](x, y) { return x + y; }, add(x, y) { console.log('جمع الأرقام...'); return this[privateMethod](x, y); } }; console.log(calculator.add(5, 3)); // جمع الأرقام... 8 // calculator[privateMethod] مخفي من الوصول العادي // المثال 3: منع تعارضات الخصائص في المكتبات const librarySymbol = Symbol.for('myLibrary.config'); // كود المكتبة function setupLibrary(element) { element[librarySymbol] = { initialized: true, version: '1.0.0' }; } // كود المستخدم لن يستبدل خصائص المكتبة عن طريق الخطأ const div = document.createElement('div'); div.config = 'user config'; // آمن - لن يتعارض setupLibrary(div);

طرق وخصائص Symbol

const sym = Symbol('mySymbol'); // الخصائص console.log(sym.description); // 'mySymbol' (ES2019+) // الطرق console.log(sym.toString()); // 'Symbol(mySymbol)' console.log(sym.valueOf()); // يرجع Symbol نفسه // الطرق الثابتة Symbol.for('key'); // الحصول/إنشاء Symbol عام Symbol.keyFor(sym); // الحصول على مفتاح Symbol العام // الحصول على خصائص Symbol const obj = { [Symbol('a')]: 1, [Symbol('b')]: 2, x: 3 }; console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(a), Symbol(b)] console.log(Reflect.ownKeys(obj)); // ['x', Symbol(a), Symbol(b)]

تمرين عملي:

التحدي: أنشئ فئة مجموعة بعداد حجم خاص باستخدام Symbols.

const _items = Symbol('items'); const _count = Symbol('count'); class Collection { constructor() { this[_items] = []; this[_count] = 0; } add(item) { this[_items].push(item); this[_count]++; } remove(item) { const index = this[_items].indexOf(item); if (index > -1) { this[_items].splice(index, 1); this[_count]--; } } get size() { return this[_count]; } [Symbol.iterator]() { return this[_items][Symbol.iterator](); } } const collection = new Collection(); collection.add('apple'); collection.add('banana'); console.log(collection.size); // 2 console.log([...collection]); // ['apple', 'banana']

جربه بنفسك: أضف طريقة clear() وطريقة has(item).

الملخص

في هذا الدرس، تعلمت:

  • Symbols هي قيم أولية فريدة وثابتة تُستخدم كمفاتيح خصائص
  • كل Symbol فريد، حتى مع نفس الوصف
  • خصائص Symbol مخفية من التعداد العادي (keys و values و for...in)
  • Symbol.for() و Symbol.keyFor() تديران سجل Symbol العام
  • Symbols المعروفة تخصص سلوك الكائن (iterator و toStringTag و toPrimitive)
  • تمكّن Symbols الخصائص الخاصة الزائفة قبل حقول # الخاصة
  • تمنع Symbols تصادمات أسماء الخصائص في الكائنات والمكتبات
التالي: في الدرس التالي، سنستكشف Iterators و Generators - التحكم المتقدم في التكرار!