العربية

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

رموز JavaScript: مفاتيح خصائص فريدة وبيانات وصفية

رموز JavaScript، التي تم تقديمها في ECMAScript 2015 (ES6)، توفر آلية لإنشاء مفاتيح خصائص فريدة وغير قابلة للتغيير. على عكس السلاسل النصية أو الأرقام، يُضمن أن تكون الرموز فريدة عبر تطبيق JavaScript بأكمله. إنها توفر طريقة لتجنب تضارب الأسماء، وإرفاق بيانات وصفية بالكائنات دون التدخل في الخصائص الموجودة، وتخصيص سلوك الكائن. يقدم هذا المقال نظرة شاملة على رموز JavaScript، تغطي كيفية إنشائها وتطبيقاتها وأفضل الممارسات.

ما هي رموز JavaScript؟

الرمز (Symbol) هو نوع بيانات أولي في JavaScript، مشابه للأرقام والسلاسل النصية والقيم المنطقية (booleans) و null و undefined. ولكن، على عكس أنواع البيانات الأولية الأخرى، فإن الرموز فريدة من نوعها. في كل مرة تنشئ فيها رمزًا، تحصل على قيمة جديدة وفريدة تمامًا. هذه الميزة الفريدة تجعل الرموز مثالية لما يلي:

إنشاء الرموز

يمكنك إنشاء رمز باستخدام الدالة البانية Symbol(). من المهم ملاحظة أنه لا يمكنك استخدام new Symbol()؛ فالرموز ليست كائنات، بل قيم أولية.

الإنشاء الأساسي للرموز

أبسط طريقة لإنشاء رمز هي:

const mySymbol = Symbol();
console.log(typeof mySymbol); // الناتج: symbol

كل استدعاء لـ Symbol() يولد قيمة جديدة وفريدة:

const symbol1 = Symbol();
const symbol2 = Symbol();
console.log(symbol1 === symbol2); // الناتج: false

أوصاف الرموز

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

const mySymbol = Symbol("myDescription");
console.log(mySymbol.toString()); // الناتج: Symbol(myDescription)

الوصف هو لأغراض إعلامية بحتة؛ رمزان لهما نفس الوصف لا يزالان فريدين:

const symbolA = Symbol("same description");
const symbolB = Symbol("same description");
console.log(symbolA === symbolB); // الناتج: false

استخدام الرموز كمفاتيح للخصائص

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

إضافة خصائص الرموز

يمكنك استخدام الرموز كمفاتيح للخصائص تمامًا مثل السلاسل النصية أو الأرقام:

const mySymbol = Symbol("myKey");
const myObject = {};

myObject[mySymbol] = "Hello, Symbol!";

console.log(myObject[mySymbol]); // الناتج: Hello, Symbol!

تجنب تضارب الأسماء

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

// مكتبة خارجية (محاكاة)
const libraryObject = {
  name: "Library Object",
  version: "1.0"
};

// الكود الخاص بك
const mySecretKey = Symbol("mySecret");
libraryObject[mySecretKey] = "Top Secret Information";

console.log(libraryObject.name); // الناتج: Library Object
console.log(libraryObject[mySecretKey]); // الناتج: Top Secret Information

في هذا المثال، يضمن mySecretKey أن خاصيتك لا تتعارض مع أي خصائص موجودة في libraryObject.

تعداد خصائص الرموز

إحدى الخصائص الحاسمة لخصائص الرموز هي أنها مخفية عن طرق العد القياسية مثل حلقات for...in و Object.keys(). يساعد هذا في حماية سلامة الكائنات ويمنع الوصول العرضي أو تعديل خصائص الرموز.

const mySymbol = Symbol("myKey");
const myObject = {
  name: "My Object",
  [mySymbol]: "Symbol Value"
};

console.log(Object.keys(myObject)); // الناتج: ["name"]

for (let key in myObject) {
  console.log(key); // الناتج: name
}

للوصول إلى خصائص الرموز، تحتاج إلى استخدام Object.getOwnPropertySymbols()، والتي تعيد مصفوفة بجميع خصائص الرموز على كائن معين:

const mySymbol = Symbol("myKey");
const myObject = {
  name: "My Object",
  [mySymbol]: "Symbol Value"
};

const symbolKeys = Object.getOwnPropertySymbols(myObject);
console.log(symbolKeys); // الناتج: [Symbol(myKey)]
console.log(myObject[symbolKeys[0]]); // الناتج: Symbol Value

الرموز المعروفة (Well-Known Symbols)

توفر JavaScript مجموعة من الرموز المدمجة، والمعروفة باسم الرموز المعروفة، والتي تمثل سلوكيات أو وظائف محددة. هذه الرموز هي خصائص للدالة البانية Symbol (على سبيل المثال، Symbol.iterator، Symbol.toStringTag). تسمح لك بتخصيص سلوك الكائنات في سياقات مختلفة.

Symbol.iterator

Symbol.iterator هو رمز يحدد المكرر (iterator) الافتراضي للكائن. عندما يحتوي كائن على دالة بمفتاح Symbol.iterator، يصبح قابلاً للتكرار، مما يعني أنه يمكنك استخدامه مع حلقات for...of وعامل النشر (...).

مثال: إنشاء كائن مخصص قابل للتكرار

const myCollection = {
  items: [1, 2, 3, 4, 5],
  [Symbol.iterator]: function* () {
    for (let item of this.items) {
      yield item;
    }
  }
};

for (let item of myCollection) {
  console.log(item); // الناتج: 1, 2, 3, 4, 5
}

console.log([...myCollection]); // الناتج: [1, 2, 3, 4, 5]

في هذا المثال، myCollection هو كائن يطبق بروتوكول المكرر باستخدام Symbol.iterator. تقوم الدالة المولدة (generator function) بإنتاج كل عنصر في مصفوفة items، مما يجعل myCollection قابلاً للتكرار.

Symbol.toStringTag

Symbol.toStringTag هو رمز يسمح لك بتخصيص التمثيل النصي للكائن عند استدعاء Object.prototype.toString().

مثال: تخصيص تمثيل toString()

class MyClass {
  get [Symbol.toStringTag]() {
    return 'MyClassInstance';
  }
}

const instance = new MyClass();
console.log(Object.prototype.toString.call(instance)); // الناتج: [object MyClassInstance]

بدون Symbol.toStringTag، سيكون الناتج [object Object]. يوفر هذا الرمز طريقة لإعطاء تمثيل نصي وصفي أكثر لكائناتك.

Symbol.hasInstance

Symbol.hasInstance هو رمز يتيح لك تخصيص سلوك عامل instanceof. عادةً، يتحقق instanceof مما إذا كانت سلسلة نماذج (prototype chain) الكائن تحتوي على خاصية prototype الخاصة بالدالة البانية. يسمح لك Symbol.hasInstance بتجاوز هذا السلوك.

مثال: تخصيص التحقق من instanceof

class MyClass {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}

console.log([] instanceof MyClass); // الناتج: true
console.log({} instanceof MyClass); // الناتج: false

في هذا المثال، تتحقق دالة Symbol.hasInstance مما إذا كان المثيل عبارة عن مصفوفة. هذا يجعل MyClass يعمل بفعالية كأداة تحقق من المصفوفات، بغض النظر عن سلسلة النماذج الفعلية.

رموز أخرى معروفة

تُعرّف JavaScript العديد من الرموز المعروفة الأخرى، بما في ذلك:

سجل الرموز العالمي

في بعض الأحيان، تحتاج إلى مشاركة الرموز عبر أجزاء مختلفة من تطبيقك أو حتى بين تطبيقات مختلفة. يوفر سجل الرموز العالمي آلية لتسجيل واسترداد الرموز بواسطة مفتاح.

Symbol.for(key)

تتحقق دالة Symbol.for(key) مما إذا كان هناك رمز بالمفتاح المحدد موجودًا في السجل العالمي. إذا كان موجودًا، فإنها تعيد ذلك الرمز. إذا لم يكن موجودًا، فإنها تنشئ رمزًا جديدًا بالمفتاح وتسجله في السجل.

const globalSymbol1 = Symbol.for("myGlobalSymbol");
const globalSymbol2 = Symbol.for("myGlobalSymbol");

console.log(globalSymbol1 === globalSymbol2); // الناتج: true
console.log(Symbol.keyFor(globalSymbol1)); // الناتج: myGlobalSymbol

Symbol.keyFor(symbol)

تعيد دالة Symbol.keyFor(symbol) المفتاح المرتبط برمز في السجل العالمي. إذا لم يكن الرمز في السجل، فإنها تعيد undefined.

const mySymbol = Symbol("localSymbol");
console.log(Symbol.keyFor(mySymbol)); // الناتج: undefined

const globalSymbol = Symbol.for("myGlobalSymbol");
console.log(Symbol.keyFor(globalSymbol)); // الناتج: myGlobalSymbol

مهم: الرموز التي يتم إنشاؤها باستخدام Symbol() *لا* يتم تسجيلها تلقائيًا في السجل العالمي. فقط الرموز التي يتم إنشاؤها (أو استردادها) باستخدام Symbol.for() هي جزء من السجل.

أمثلة عملية وحالات استخدام

فيما يلي بعض الأمثلة العملية التي توضح كيفية استخدام الرموز في سيناريوهات واقعية:

1. إنشاء أنظمة الإضافات (Plugins)

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

// الكائن الأساسي
const coreObject = {
  name: "Core Object",
  version: "1.0"
};

// الإضافة 1
const plugin1Key = Symbol("plugin1");
coreObject[plugin1Key] = {
  description: "Plugin 1 adds extra functionality",
  activate: function() {
    console.log("Plugin 1 activated");
  }
};

// الإضافة 2
const plugin2Key = Symbol("plugin2");
coreObject[plugin2Key] = {
  author: "Another Developer",
  init: function() {
    console.log("Plugin 2 initialized");
  }
};

// الوصول إلى الإضافات
console.log(coreObject[plugin1Key].description); // الناتج: Plugin 1 adds extra functionality
coreObject[plugin2Key].init(); // الناتج: Plugin 2 initialized

في هذا المثال، تستخدم كل إضافة مفتاح رمز فريدًا، مما يمنع تضارب الأسماء المحتمل ويضمن أن الإضافات يمكن أن تتعايش بسلام.

2. إضافة بيانات وصفية إلى عناصر DOM

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

const element = document.createElement("div");

const dataKey = Symbol("elementData");
element[dataKey] = {
  type: "widget",
  config: {},
  timestamp: Date.now()
};

// الوصول إلى البيانات الوصفية
console.log(element[dataKey].type); // الناتج: widget

هذا النهج يبقي البيانات الوصفية منفصلة عن السمات القياسية للعنصر، مما يحسن من قابلية الصيانة ويتجنب التعارضات المحتملة مع CSS أو كود JavaScript آخر.

3. تنفيذ الخصائص الخاصة

بينما لا تحتوي JavaScript على خصائص خاصة حقيقية، يمكن استخدام الرموز لمحاكاة الخصوصية. باستخدام الرمز كمفتاح للخاصية، يمكنك جعل الوصول إلى الخاصية من قبل التعليمات البرمجية الخارجية صعبًا (ولكن ليس مستحيلًا).

class MyClass {
  #privateSymbol = Symbol("privateData"); // ملاحظة: صيغة '#' هذه هي حقل خاص *حقيقي* تم تقديمه في ES2020، وهو يختلف عن المثال

  constructor(data) {
    this[this.#privateSymbol] = data;
  }

  getData() {
    return this[this.#privateSymbol];
  }
}

const myInstance = new MyClass("Sensitive Information");
console.log(myInstance.getData()); // الناتج: Sensitive Information

// الوصول إلى الخاصية "الخاصة" (صعب، ولكنه ممكن)
const symbolKeys = Object.getOwnPropertySymbols(myInstance);
console.log(myInstance[symbolKeys[0]]); // الناتج: Sensitive Information

على الرغم من أن Object.getOwnPropertySymbols() لا يزال بإمكانه كشف الرمز، إلا أنه يقلل من احتمالية وصول التعليمات البرمجية الخارجية عن طريق الخطأ إلى الخاصية "الخاصة" أو تعديلها. ملاحظة: الحقول الخاصة الحقيقية (باستخدام البادئة #) متاحة الآن في JavaScript الحديثة وتوفر ضمانات خصوصية أقوى.

أفضل الممارسات لاستخدام الرموز

فيما يلي بعض أفضل الممارسات التي يجب مراعاتها عند العمل مع الرموز:

الخلاصة

توفر رموز JavaScript آلية قوية لإنشاء مفاتيح خصائص فريدة، وإرفاق بيانات وصفية بالكائنات، وتخصيص سلوك الكائن. من خلال فهم كيفية عمل الرموز واتباع أفضل الممارسات، يمكنك كتابة كود JavaScript أكثر قوة وقابلية للصيانة وخاليًا من التعارضات. سواء كنت تبني أنظمة إضافات، أو تضيف بيانات وصفية إلى عناصر DOM، أو تحاكي الخصائص الخاصة، فإن الرموز توفر أداة قيمة لتعزيز سير عمل تطوير JavaScript الخاص بك.