في JavaScript، يمكننا الوراثة من كائن واحد فقط. يمكن أن يكون هناك [[Prototype]]
واحد فقط للكائن. ولا يجوز للفئة أن تمتد إلا لفئة واحدة أخرى.
لكن في بعض الأحيان يبدو ذلك مقيدًا. على سبيل المثال، لدينا فئة StreetSweeper
وفئة Bicycle
، ونريد إنشاء مزيج منهما: StreetSweepingBicycle
.
أو لدينا فئة User
وفئة EventEmitter
التي تنفذ إنشاء الأحداث، ونرغب في إضافة وظيفة EventEmitter
إلى User
، حتى يتمكن مستخدمونا من إرسال الأحداث.
هناك مفهوم يمكن أن يساعد هنا، يسمى "mixins".
كما هو محدد في ويكيبيديا، mixin هو فئة تحتوي على أساليب يمكن استخدامها من قبل فئات أخرى دون الحاجة إلى وراثة منها.
بمعنى آخر، يوفر mixin طرقًا تنفذ سلوكًا معينًا، لكننا لا نستخدمه بمفرده، بل نستخدمه لإضافة السلوك إلى فئات أخرى.
إن أبسط طريقة لتنفيذ mixin في JavaScript هي إنشاء كائن باستخدام طرق مفيدة، حتى نتمكن من دمجها بسهولة في نموذج أولي لأي فئة.
على سبيل المثال هنا يتم استخدام mixin sayHiMixin
لإضافة بعض "الكلام" User
:
// ميكسين دعنا نقولHiMixin = { قل مرحبا () { تنبيه("مرحبا ${this.name}`); }, قل وداعًا () { تنبيه("وداعا ${this.name}`); } }; // الاستخدام: فئة المستخدم { منشئ (الاسم) { this.name = name; } } // انسخ الطرق Object.assis(User.prototype, sayHiMixin); // الآن يمكن للمستخدم أن يقول مرحبًا مستخدم جديد("المتأنق").sayHi(); // مرحبا المتأنق!
ليس هناك وراثة، ولكن طريقة نسخ بسيطة. لذلك قد يرث User
من فئة أخرى ويقوم أيضًا بتضمين المزيج "لخلط" الطرق الإضافية، مثل هذا:
فئة المستخدم تمتد الشخص { // ... } Object.assis(User.prototype, sayHiMixin);
يمكن للميكسينز الاستفادة من الميراث داخل أنفسهم.
على سبيل المثال، هنا sayHiMixin
يرث من sayMixin
:
دعنا نقولMixin = { قل (عبارة) { تنبيه(عبارة); } }; دعنا نقولHiMixin = { __proto__: sayMixin, // (أو يمكننا استخدام Object.setPrototypeOf لتعيين النموذج الأولي هنا) قل مرحبا () { // استدعاء الأسلوب الأصلي super.say(`مرحبا ${this.name}`); // (*) }, قل وداعًا () { super.say(`وداعا ${this.name}`); // (*) } }; فئة المستخدم { منشئ (الاسم) { this.name = name; } } // انسخ الطرق Object.assis(User.prototype, sayHiMixin); // الآن يمكن للمستخدم أن يقول مرحبًا مستخدم جديد("المتأنق").sayHi(); // مرحبا المتأنق!
يرجى ملاحظة أن استدعاء الطريقة الأصلية super.say()
من sayHiMixin
(في السطور التي تحمل علامة (*)
) يبحث عن الطريقة في النموذج الأولي لهذا المزيج، وليس الفئة.
إليك الرسم البياني (انظر الجزء الأيمن):
وذلك لأن الأساليب sayHi
و sayBye
تم إنشاؤها في البداية في sayHiMixin
. لذلك، على الرغم من نسخهم، فإن الملكية الداخلية لـ [[HomeObject]]
تشير إلى sayHiMixin
، كما هو موضح في الصورة أعلاه.
نظرًا لأن super
يبحث عن الأساليب الأصلية في [[HomeObject]].[[Prototype]]
، فهذا يعني أنه يبحث عن sayHiMixin.[[Prototype]]
.
الآن دعونا نصنع مزيجًا للحياة الحقيقية.
من الميزات المهمة للعديد من كائنات المتصفح (على سبيل المثال) أنها تستطيع إنشاء أحداث. تعد الأحداث طريقة رائعة "لبث المعلومات" لأي شخص يريدها. لذلك دعونا ننشئ مزيجًا يسمح لنا بإضافة الوظائف المتعلقة بالحدث بسهولة إلى أي فئة/كائن.
سيوفر mixin طريقة .trigger(name, [...data])
"لإنشاء حدث" عندما يحدث له شيء مهم. وسيطة name
هي اسم الحدث، متبوعًا بشكل اختياري بوسائط إضافية مع بيانات الحدث.
وأيضًا الطريقة .on(name, handler)
التي تضيف وظيفة handler
كمستمع للأحداث ذات الاسم المحدد. سيتم استدعاؤه عند تشغيل حدث يحمل name
المحدد، والحصول على الوسائط من استدعاء .trigger
.
… والطريقة .off(name, handler)
التي تزيل مستمع handler
.
بعد إضافة المزيج، سيتمكن user
الكائن من إنشاء حدث "login"
عندما يقوم الزائر بتسجيل الدخول. وكائن آخر، على سبيل المثال، قد يرغب calendar
في الاستماع لمثل هذه الأحداث لتحميل التقويم للشخص الذي قام بتسجيل الدخول.
أو يمكن menu
إنشاء الحدث "select"
عند تحديد عنصر قائمة، وقد تقوم الكائنات الأخرى بتعيين معالجات للتفاعل مع هذا الحدث. وهكذا.
إليك الكود:
دع حدث ميكسين = { /** * الاشتراك في الحدث، الاستخدام: * Menu.on('select', function(item) { ... } */ على (اسم الحدث، المعالج) { إذا (!this._eventHandlers) this._eventHandlers = {}; إذا (!this._eventHandlers[eventName]) { this._eventHandlers[eventName] = []; } this._eventHandlers[eventName].push(handler); }, /** * إلغاء الاشتراك والاستخدام: * القائمة.إيقاف ('حدد'، معالج) */ إيقاف (اسم الحدث، المعالج) { دع المعالجات = this._eventHandlers?.[eventName]; إذا عاد (! المعالجات)؛ لـ (دع i = 0؛ i < Handlers.length؛ i++) { إذا (معالجات [i] === معالج) { Handlers.splice(i--, 1); } } }, /** * إنشاء حدث بالاسم والبيانات المحددة * this.trigger('select', data1, data2); */ المشغل (اسم الحدث، ...الوسائط) { إذا (!this._eventHandlers؟.[eventName]) { يعود؛ // لا يوجد معالجات لاسم الحدث هذا } // استدعاء المعالجات this._eventHandlers[eventName].forEach(handler => Handler.apply(this, args)); } };
.on(eventName, handler)
- يقوم بتعيين handler
الوظيفة ليتم تشغيله عند وقوع الحدث بهذا الاسم. من الناحية الفنية، هناك خاصية _eventHandlers
تقوم بتخزين مجموعة من المعالجات لكل اسم حدث، وتضيفه فقط إلى القائمة.
.off(eventName, handler)
- يزيل الوظيفة من قائمة المعالجات.
.trigger(eventName, ...args)
- يُنشئ الحدث: يتم استدعاء كافة المعالجات من _eventHandlers[eventName]
، مع قائمة من الوسائط ...args
.
الاستخدام:
// أنشئ فصلًا دراسيًا قائمة الفصل { اختر (القيمة) { this.trigger("select"، value); } } // أضف المزيج بالطرق المتعلقة بالحدث Object.assis(Menu.prototype, eventsMixin); دع القائمة = قائمة جديدة ()؛ // إضافة معالج ليتم استدعاؤه عند الاختيار: Menu.on("select", value => تنبيه(`القيمة المحددة: ${value}`)); // يطلق الحدث => يعمل المعالج أعلاه ويظهر: // القيمة المحددة: 123 Menu.choose("123");
الآن، إذا أردنا أن يتفاعل أي رمز مع تحديد القائمة، فيمكننا الاستماع إليه باستخدام menu.on(...)
.
و eventMixin
mixin يجعل من السهل إضافة مثل هذا السلوك إلى العديد من الفئات التي نرغب فيها، دون التدخل في سلسلة الميراث.
Mixin – هو مصطلح برمجة موجه للكائنات العامة: فئة تحتوي على أساليب للفئات الأخرى.
تسمح بعض اللغات الأخرى بالوراثة المتعددة. لا تدعم JavaScript الوراثة المتعددة، ولكن يمكن تنفيذ عمليات المزج عن طريق نسخ الأساليب إلى النموذج الأولي.
يمكننا استخدام mixins كوسيلة لتعزيز فئة من خلال إضافة سلوكيات متعددة، مثل التعامل مع الأحداث كما رأينا أعلاه.
قد تصبح Mixins نقطة صراع إذا قامت عن طريق الخطأ بالكتابة فوق أساليب الفصل الموجودة. لذلك بشكل عام، ينبغي للمرء أن يفكر جيدًا في طرق تسمية المزيج، لتقليل احتمالية حدوث ذلك.