V JavaScriptu můžeme dÄdit jen z jednoho objektu. Objekt může mÃt jen jeden [[Prototype]] a tÅÃda může rozÅ¡iÅovat pouze jednu jinou tÅÃdu.
To se vÅ¡ak nÄkdy může zdát omezujÃcÃ. Máme napÅÃklad tÅÃdu ZametaÄUlic a tÅÃdu Bicykl a chtÄli bychom vytvoÅit jejich smÄs: ZametacÃBicykl.
Nebo máme tÅÃdu Uživatel a tÅÃdu GenerátorUdálostÃ, která implementuje generovánà událostÃ, a rádi bychom pÅidali funkcionalitu tÅÃdy GenerátorUdálostà do tÅÃdy Uživatel, aby naÅ¡i uživatelé mohli generovat události.
Koncept, který nám s tÃm pomůže, existuje a nazývá se âmixinyâ.
Jak je definováno ve Wikipedii, mixin je tÅÃda obsahujÃcà metody, které mohou být použÃvány v jiných tÅÃdách, aniž by z nà tyto tÅÃdy musely být zdÄdÄny.
Jinými slovy, mixin poskytuje metody, které implementujà urÄité chovánÃ, ale nepoužÃváme ho samostatnÄ, nýbrž pÅidáváme jeho chovánà do jiných tÅÃd.
PÅÃklad mixinu
Nejjednoduššà způsob, jak implementovat mixin v JavaScriptu, je vytvoÅit objekt s užiteÄnými metodami, abychom je mohli snadno pÅipojit do prototypu libovolné tÅÃdy.
NapÅÃklad zde je použit mixin mixinÅekniAhoj, aby do tÅÃdy Uživatel pÅidal nÄjaké âmluvenÃâ:
// mixin
let mixinÅekniAhoj = {
ÅekniAhoj() {
alert(`Ahoj ${this.jméno}`);
},
ÅekniNashle() {
alert(`Nashle ${this.jméno}`);
}
};
// použitÃ:
class Uživatel {
constructor(jméno) {
this.jméno = jméno;
}
}
// zkopÃrujeme metody
Object.assign(Uživatel.prototype, mixinÅekniAhoj);
// nynà Uživatel může ÅÃci ahoj
new Uživatel("Jan").ÅekniAhoj(); // Ahoj Jan
Nenà tady žádná dÄdiÄnost, ale jen prosté kopÃrovánà metod. Uživatel tedy může dÄdit z jiné tÅÃdy a souÄasnÄ zahrnout mixin, aby âpÅimÃchalâ (âmix-inâ) dalšà metody, napÅÃklad:
class Uživatel extends Osoba {
// ...
}
Object.assign(Uživatel.prototype, mixinÅekniAhoj);
Mixiny mohou využÃvat dÄdiÄnost mezi sebou.
NapÅÃklad zde mixinÅekniAhoj dÄdà z mixinÅekni:
let mixinÅekni = {
Åekni(vÄta) {
alert(vÄta);
}
};
let mixinÅekniAhoj = {
__proto__: mixinÅekni, // (nebo můžeme k nastavenà prototypu použÃt Object.setPrototypeOf)
ÅekniAhoj() {
// volánà rodiÄovské metody
super.Åekni(`Ahoj ${this.jméno}`); // (*)
},
ÅekniNashle() {
super.Åekni(`Nashle ${this.jméno}`); // (*)
}
};
class Uživatel {
constructor(jméno) {
this.jméno = jméno;
}
}
// zkopÃrujeme metody
Object.assign(Uživatel.prototype, mixinÅekniAhoj);
// nynà Uživatel může ÅÃci ahoj
new Uživatel("Jan").ÅekniAhoj(); // Ahoj Jan
ProsÃme vÅ¡imnÄte si, že volánà rodiÄovské metody super.Åekni() z mixinÅekniAhoj (na ÅádcÃch oznaÄených (*)) hledá metodu v prototypu onoho mixinu, ne této tÅÃdy.
Zde je diagram (viz pravou Äást):
Je to proto, že metody ÅekniAhoj a ÅekniNashle byly původnÄ vytvoÅeny v mixinÅekniAhoj. I když jsou tedy zkopÃrovány, jejich internà vlastnost [[HomeObject]] se odkazuje na mixinÅekniAhoj, jak je vidÄt na uvedeném obrázku.
Když super hledá rodiÄovské metody v [[HomeObject]].[[Prototype]], znamená to, že prohledává mixinÅekniAhoj.[[Prototype]].
MixinUdálosti
VytvoÅme nynà mixin pro skuteÄný život.
Důležitou vlastnostà mnoha objektů prohlÞeÄe (napÅÃklad) je, že mohou generovat události. Události jsou skvÄlý způsob, jak âvysÃlat informaciâ každému, kdo ji chce. VytvoÅme tedy mixin, který nám umožnà snadno pÅidat funkce vztažené k události do jakékoli tÅÃdy nebo objektu.
- Mixin bude poskytovat metodu
.spusÅ¥(název, [...data]), která bude âgenerovat událostâ, když se stane nÄco důležitého. Argumentnázevje název události, za nÃmž mohou následovat dalšà argumenty s daty události. - Dále metodu
.zapni(název, handler), která pÅidá funkcihandlerjako posluchaÄe událostà se zadaným názvem. Funkcehandlerbude volána, když se spustà událost se zadaným názvemnázev, a pÅevezme argumenty z volánÃ.spusÅ¥. - â¦A metodu
.vypni(název, handler), která odstranà posluchaÄehandler.
Po pÅidánà mixinu bude objekt uživatel moci generovat událost "pÅihlášen", když se uživatel pÅihlásÃ. A jiný objekt, tÅeba kalendáÅ, bude moci takovým událostem naslouchat, aby pak naÄetl kalendáŠpro pÅihlášenou osobu.
Nebo menu může generovat událost "vybrán", když je vybrán jeho prvek, a jiné objekty mohou pÅiÅazovat handlery, které budou na tuto událost reagovat. A tak dále.
Zde je kód:
let mixinUdálosti = {
/**
* PÅihlášenà k naslouchánà události, použitÃ:
* menu.zapni('vybrán', function(prvek) { ... })
*/
zapni(názevUdálosti, handler) {
if (!this._handleryUdálostÃ) this._handleryUdálostà = {};
if (!this._handleryUdálostÃ[názevUdálosti]) {
this._handleryUdálostÃ[názevUdálosti] = [];
}
this._handleryUdálostÃ[názevUdálosti].push(handler);
},
/**
* Odhlášenà z naslouchánà události, použitÃ:
* menu.vypni('vybrán', handler)
*/
vypni(názevUdálosti, handler) {
let handlery = this._handleryUdálost�.[názevUdálosti];
if (!handlery) return;
for (let i = 0; i < handlery.length; i++) {
if (handlery[i] === handler) {
handlery.splice(i--, 1);
}
}
},
/**
* Generovánà události se zadaným názvem a daty
* this.spusť('vybrán', data1, data2);
*/
spusť(názevUdálosti, ...argumenty) {
if (!this._handleryUdálost�.[názevUdálosti]) {
return; // pro událost s tÃmto názvem nejsou žádné handlery
}
// volánà handlerů
this._handleryUdálostÃ[názevUdálosti].forEach(handler => handler.apply(this, argumenty));
}
};
.zapni(názevUdálosti, handler)â pÅiÅadà funkcihandler, která se má spustit vždy, když nastane událost s uvedeným názvem. Technicky je zde vlastnost_handleryUdálostÃ, do nÞ se ukládá pole handlerů pro každý název události, a funkce je prostÄ pÅidána do tohoto seznamu..vypni(názevUdálosti, handler)â odstranà funkci ze seznamu handlerů..spusÅ¥(názevUdálosti, ...argumenty)â generuje událost: vÅ¡echny handlery z_handleryUdálostÃ[názevUdálosti]jsou volány se seznamem argumentů...argumenty.
PoužitÃ:
// VytvoÅÃme tÅÃdu
class Menu {
vyber(hodnota) {
this.spusť("vybrán", hodnota);
}
}
// PÅidáme mixin s metodami vztahujÃcÃmi se k událostem
Object.assign(Menu.prototype, mixinUdálosti);
let menu = new Menu();
// pÅidáme handler, který bude volán pÅi výbÄru:
menu.zapni("vybrán", hodnota => alert(`Vybrána hodnota: ${hodnota}`));
// spustà událost => výše uvedený handler se spustà a zobrazÃ:
// Vybrána hodnota: 123
menu.vyber("123");
Kdybychom nynà chtÄli pÅidat jakýkoli kód, který bude reagovat na výbÄr z menu, můžeme mu naslouchat pomocà menu.zapni(...).
A mixin mixinUdálosti usnadÅuje pÅidánà takového chovánà do tolika tÅÃd, do kolika bychom chtÄli, aniž bychom naruÅ¡ovali ÅetÄzec dÄdiÄnosti.
ShrnutÃ
Mixin â je generický pojem objektovÄ orientovaného programovánÃ: tÅÃda, která obsahuje metody pro jiné tÅÃdy.
NÄkteré jiné jazyky umožÅujà vÃcenásobnou dÄdiÄnost. JavaScript ji nepodporuje, ale mixiny mohou být implementovány zkopÃrovánÃm metod do prototypu.
Mixiny můžeme použÃvat jako způsob rozÅ¡iÅovánà tÅÃdy pÅidávánÃm dalÅ¡Ãho chovánÃ, napÅÃklad oÅ¡etÅovánà událostÃ, jak jsme vidÄli výše.
Mixiny se mohou stát pÅÃÄinou konfliktu, jestliže náhodou pÅepÚà již existujÃcà metody tÅÃdy. ObecnÄ bychom si tedy mÄli dobÅe rozmyslet názvy metod v mixinu, abychom minimalizovali pravdÄpodobnost, že se tak stane.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)