لا يمكننا في جافاسكربت إلّا أن نرث كائنًا واحدًا فقط. وليس هناك إلّا كائن [[Prototype]]
واحد لأيّ كائن. ولا يمكن للصنف توسعة أكثر من صنف واحد.
وأحيانًا يكون ذلك مُقيّدًا نوعًا ما. فنقل بأنّ لدينا صنف ”كنّاسة شوارع“ StreetSweeper
وصنف ”دراجة هوائية“ Bicycle
، وأردنا صنع شيء يجمعهما: ”كنّاسة شوارع بدراجة هوائية“ StreetSweepingBicycle
.
أو ربّما لدينا صنف المستخدم User
وصنف ”مُطلق الأحدث“ EventEmitter
حيث فيه خاصية توليد الأحداث، ونريد إضافة ميزة EventEmitter
إلى User
كي يُطلق المستخدمون الأحداث أيضًا.
هناك مفهوم في اللغة يساعد في حلّ هذه وهو ”المخاليط“ (Mixins).
كما تقول ويكيبيديا، المخلوط هو صنف يحتوي على توابِع يمكن للأصناف الأخرى استعمالها دون أن ترث ذاك الصنف.
أي بعبارة أخرى، يُقدّم المخلوط توابِع فيها وظائف وسلوك معيّن، ولكنّا لا نستعمله لوحده بل نُضيف تلك الوظيفة أو ذاك السلوك إلى الأصناف الأخرى.
مثال عن المخاليط
أبسط طريقة لكتابة تنفيذ مخلوط في جافاسكربت هو صناعة كائن فيه توابِع تُفيدنا في أمور معيّنة كي ندمجها بسهولة داخل كائن prototype لأيّ صنف كان.
لنقل مثلًا لدينا المخلوط sayHiMixin
ليُضيف بعض الكلام للمستخدم أو الصنف User
:
// مخلوط let sayHiMixin = { sayHi() { alert(`Hello ${this.name}`); // مرحبًا يا فلان }, sayBye() { alert(`Bye ${this.name}`); // وداعًا يا فلان } }; // الاستعمال: class User { constructor(name) { this.name = name; } } // ننسخ التوابِع Object.assign(User.prototype, sayHiMixin); // الآن يمكن أن يرحّب المستخدم بغيره new User("Dude").sayHi(); // Hello Dude! مرحبًا يا Dude!
ما من وراثة هنا، بل نسخ بسيط للتوابِع. هكذا يمكن أن يرث صنف المستخدم من أيّ صنف آخر وأن يُضيف هذا المخلوط ليدمج فيه توابِع أخرى، هكذا:
class User extends Person { // ... } Object.assign(User.prototype, sayHiMixin);
يمكن أن تستفيد المخاليط من الوراثة بينها ذاتها المخاليط. فمثلًا، نرى هنا كيف يرث sayHiMixin
المخلوطَ sayMixin
:
let sayMixin = { say(phrase) { alert(phrase); } }; let sayHiMixin = { __proto__: sayMixin, // (أو نستعمل Object.create لضبط كائن prototype هنا) sayHi() { // نستدعي التابِع الأب super.say(`Hello ${this.name}`); // (*) }, sayBye() { super.say(`Bye ${this.name}`); // (*) } }; class User { constructor(name) { this.name = name; } } // ننسخ التوابِع Object.assign(User.prototype, sayHiMixin); // الآن يمكن أن يرحّب المستخدم بالناس new User("Dude").sayHi(); // مرحبًا يا Dude!
لاحظ كيف يبحث استدعاء التابِع الأب super.say()
في sayHiMixin
(في الأسطر عند (*)
) - كيف يبحث عن التابِع داخل كائن prototype المخلوطَ وليس داخل الصنف.
إليك صورة (طالع الجانب الأيمن فيها):
يعزو ذلك إلى أنّ التابِعين sayHi
وsayBye
أُنشآ في البداية داخل sayHiMixin
. فحتّى حين ننسخهما تُشير خاصية [[HomeObject]]
الداخلية فيهما إلى sayHiMixin
كما نرى في الصورة.
وطالما يبحث super
عن التوابِع الأب في [[HomeObject]].[[Prototype]]
يكون البحث داخل sayHiMixin.[[Prototype]]
وليس داخل User.[[Prototype]]
.
مخلوط الأحداث EventMixin
الآن حان وقت استعمال المخاليط في الحياة العملية.
يُعدّ توليد الأحداث ميزة مهمّة جدًا للكائنات بشكل عام، وكائنات المتصفّحات بشكل خاص. وتُعدّ هذه الأحداث الطريقة المُثلى ”لنشر المعلومات“ لمن يريدها. لذا هيًا نصنع مخلوطًا يُتيح لنا إضافة دوال أحداث إلى أيّ صنف أو كائن.
-
سيقدّم المخلوط تابِعًا باسم
.trigger(name, [...data])
”يُولّد حدثًا“ متى حدث شيء مهم. وسيط الاسمname
هو… اسم الحدث، وبعده تأتي بيانات الحدث إن احتجناها. -
كما والتابِع
.on(name, handler)
الذي سيُضيف دالة ”معاملة“handler
لتستمع إلى الأحداث حسب الاسم الذي مرّرناه. ستُستدعى الدالة متى تفعّل الحدث بالاسمname
وسيُمرّر لها الوسطاء في استدعاء.trigger
. -
وأيضًا… التابِع
.off(name, handler)
الذي سيُزيل المستمعhandler
.
بعدما نُضيف المخلوط سيقدر كائن المستخدم user
على توليد حدث ولوج "login"
متى ولج الزائر إلى الموقع. ويمكن لكائن آخر (لنقل التقويم calendar
) الاستماع إلى هذا الحدث للقيام بمهمة ما (مثلًا تحميل تقويم المستخدم الذي ولج).
أو يمكن أن تُولّد القائمة menu
حدثَ الاختيار "select"
متى اختار المستخدم عنصرًا منها، ويمكن للكائنات الأخرى إسناد ما تحتاج من معالجات للاستماع إلى ذلك الحدث. وهكذا.
إليك الشيفرة:
let eventMixin = { /** *طريقة الانضمام إلى الحدث * menu.on('select', function(item) { ... } */ on(eventName, handler) { if (!this._eventHandlers) this._eventHandlers = {}; if (!this._eventHandlers[eventName]) { this._eventHandlers[eventName] = []; } this._eventHandlers[eventName].push(handler); }, /** * طريقة إلغاء الانضمام إلى الحدث * menu.off('select', handler) */ off(eventName, handler) { let handlers = this._eventHandlers && this._eventHandlers[eventName]; if (!handlers) return; for (let i = 0; i < handlers.length; i++) { if (handlers[i] === handler) { handlers.splice(i--, 1); } } }, /** * توليد حدث من خلال الاسم والبيانات المعطاة * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { if (!this._eventHandlers || !this._eventHandlers[eventName]) { return; // no handlers for that event name } // call the handlers this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } };
-
.on(eventName, handler)
-- يضبط الدالةhandler
لتُشغّل متى ما حدث الحدث بهذا الاسم. تقنيًا تُخزّن خاصية_eventHandlerslrm
مصفوفة من دوال المعاملة لكلّ حدث، وتُضيف الدالة إلى القائمة. -
.off(eventName, handler)
-- يحذف الدالّةhandler
من القائمة. -
.trigger(eventName, ...args)
-- يولّد حدث: كلّ المعاملات تُستدعى من_eventHandlers[eventName]
مع قائمة الوسطاء...args
.
الاستعمال:
// أنشئ صنف class Menu { choose(value) { this.trigger("select", value); } } // أضف مخلوط مع التوابع المرتبط به Object.assign(Menu.prototype, eventMixin); let menu = new Menu(); // أضف معالج حدث من أجل أن يستدعى عند الإختيار *!* menu.on("select", value => alert(`Value selected: ${value}`)); */!* // ينطلق الحدث=> يُشغّل المعالج مباشرةً ويعرُض // القيمة المختارة:123 menu.choose("123");
يمكننا الآن أن نحوّل أيّ شيفرة لتتفاعل متى ما اخترنا شيئًا من القائمة بالاستماع إلى الحدث عبر menu.on(...)
.
كما ويُسهّل المخلوط eventMixin
من إضافة هذا السلوك (أو ما يشابهه) إلى أيّ صنف نريد دون أن نتدخّل في عمل سلسلة الوراثة.
خلاصة
مصطلح المخلوط في البرمجة كائنية التوجّه: صنف يحتوي على توابِع نستعملها في أصناف أخرى.
بعض اللغات تتيح لنا الوراثة من أكثر من صنف، أمّا جافاسكربت فلا تتيح ذلك، ولكن يمكننا التحايل على الأمر وذلك بنسخ التوابع إلى النموذج الأولي prototype.
يمكننا استعمال المخاليط بطريقة لتوسيع الصنف وذلك بإضافة سلوكيات متعددة إليه، مثل: معالج الأحداث كما رأينا في المثال السابق.
من الممكن أن تصبح المخاليط نقطة تضارب إذا قاموا بدون قصد بإعادة تعريف توابع الأصناف. لذا عمومًا يجب التفكير مليًا بأسماء المخاليط لتقليل احتمالية حدوث ذلك.
ترجمة -وبتصرف- للفصل Mixins من كتاب The JavaScript language
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.