اذهب إلى المحتوى

المخاليط (Mixins) في جافاسكربت


صفا الفليج

لا يمكننا في جافاسكربت إلّا أن نرث كائنًا واحدًا فقط. وليس هناك إلّا كائن [[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 المخلوطَ وليس داخل الصنف.

إليك صورة (طالع الجانب الأيمن فيها):

mixin-inheritance.png

يعزو ذلك إلى أنّ التابِعين 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


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...