V JavaScriptu můžeme dědit pouze z jednoho objektu. Může být pouze jeden [[Prototype]]
pro objekt. A třída může rozšířit pouze jednu další třídu.
Ale někdy mi to přijde omezující. Máme například třídu StreetSweeper
a třída Bicycle
a chtějí vytvořit svůj mix:StreetSweepingBicycle
.
Nebo máme třídu User
a třída EventEmitter
který implementuje generování událostí a rádi bychom přidali funkcionalitu EventEmitter
na User
, aby naši uživatelé mohli vysílat události.
Existuje koncept, který zde může pomoci, zvaný „mixiny“.
Jak je definováno ve Wikipedii, mixin je třída obsahující metody, které mohou být použity jinými třídami, aniž by z ní bylo nutné dědit.
Jinými slovy, mixin poskytuje metody, které implementují určité chování, ale nepoužíváme je samostatně, používáme je k přidání chování do jiných tříd.
Příklad mixování
Nejjednodušší způsob, jak implementovat mixin v JavaScriptu, je vytvořit objekt s užitečnými metodami, abychom je mohli snadno sloučit do prototypu jakékoli třídy.
Například zde mixin sayHiMixin
se používá k přidání nějaké „řeči“ pro User
:
// mixin
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
}
};
// usage:
class User {
constructor(name) {
this.name = name;
}
}
// copy the methods
Object.assign(User.prototype, sayHiMixin);
// now User can say hi
new User("Dude").sayHi(); // Hello Dude!
Neexistuje žádná dědičnost, ale jednoduchá metoda kopírování. Takže User
může dědit z jiné třídy a také zahrnovat mixin pro „přimíchání“ dalších metod, jako je tento:
class User extends Person {
// ...
}
Object.assign(User.prototype, sayHiMixin);
Mixiny mohou využívat dědičnost uvnitř sebe.
Například zde sayHiMixin
dědí z sayMixin
:
let sayMixin = {
say(phrase) {
alert(phrase);
}
};
let sayHiMixin = {
__proto__: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here)
sayHi() {
// call parent method
super.say(`Hello ${this.name}`); // (*)
},
sayBye() {
super.say(`Bye ${this.name}`); // (*)
}
};
class User {
constructor(name) {
this.name = name;
}
}
// copy the methods
Object.assign(User.prototype, sayHiMixin);
// now User can say hi
new User("Dude").sayHi(); // Hello Dude!
Upozorňujeme, že volání nadřazené metody super.say()
od sayHiMixin
(na řádcích označených (*)
) hledá metodu v prototypu tohoto mixinu, ne ve třídě.
Zde je schéma (viz pravá část):
Je to proto, že metody sayHi
a sayBye
byly původně vytvořeny v sayHiMixin
. Takže i když byly zkopírovány, jejich [[HomeObject]]
interní odkazy na vlastnosti sayHiMixin
, jak je znázorněno na obrázku výše.
Jako super
hledá rodičovské metody v [[HomeObject]].[[Prototype]]
, to znamená, že hledá sayHiMixin.[[Prototype]]
, nikoli User.[[Prototype]]
.
EventMixin
Nyní udělejme mix pro skutečný život.
Důležitou vlastností mnoha objektů prohlížeče (například) je to, že mohou generovat události. Události jsou skvělým způsobem, jak „vysílat informace“ každému, kdo je chce. Udělejme tedy mix, který nám umožní snadno přidávat funkce související s událostmi do jakékoli třídy/objektu.
- Mixin poskytne metodu
.trigger(name, [...data])
„vytvořit událost“, když se jí stane něco důležitého.name
argument je název události, volitelně následovaný dalšími argumenty s daty události. - Také metoda
.on(name, handler)
který přidáhandler
fungovat jako posluchač událostí s daným názvem. Bude volána při události s danýmname
spouští a získejte argumenty z.trigger
zavolejte. - …A metoda
.off(name, handler)
který odstraníhandler
posluchač.
Po přidání mixinu objekt user
bude moci vygenerovat událost "login"
když se návštěvník přihlásí. A další objekt, řekněme, calendar
může chtít poslouchat takové události, aby načetl kalendář pro přihlášenou osobu.
Nebo menu
může vygenerovat událost "select"
když je vybrána položka nabídky a jiné objekty mohou přiřadit obslužné rutiny, které mají na tuto událost reagovat. A tak dále.
Zde je kód:
let eventMixin = {
/**
* Subscribe to event, usage:
* 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);
},
/**
* Cancel the subscription, usage:
* menu.off('select', handler)
*/
off(eventName, handler) {
let handlers = this._eventHandlers?.[eventName];
if (!handlers) return;
for (let i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
handlers.splice(i--, 1);
}
}
},
/**
* Generate an event with the given name and data
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
if (!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)
– přiřadí funkcihandler
spustit, když dojde k události s tímto názvem. Technicky vzato existuje_eventHandlers
vlastnost, která ukládá pole handlerů pro každý název události a pouze ji přidá do seznamu..off(eventName, handler)
– odebere funkci ze seznamu handlerů..trigger(eventName, ...args)
– vygeneruje událost:všechny handlery z_eventHandlers[eventName]
jsou volány se seznamem argumentů...args
.
Použití:
// Make a class
class Menu {
choose(value) {
this.trigger("select", value);
}
}
// Add the mixin with event-related methods
Object.assign(Menu.prototype, eventMixin);
let menu = new Menu();
// add a handler, to be called on selection:
menu.on("select", value => alert(`Value selected: ${value}`));
// triggers the event => the handler above runs and shows:
// Value selected: 123
menu.choose("123");
Nyní, pokud chceme, aby nějaký kód reagoval na výběr nabídky, můžeme si jej poslechnout pomocí menu.on(...)
.
A eventMixin
mixin usnadňuje přidání takového chování do tolika tříd, kolik chceme, aniž by to zasahovalo do řetězce dědičnosti.
Shrnutí
Míchání – je obecný termín pro objektově orientované programování:třída, která obsahuje metody pro jiné třídy.
Některé další jazyky umožňují vícenásobnou dědičnost. JavaScript nepodporuje vícenásobnou dědičnost, ale mixiny lze implementovat zkopírováním metod do prototypu.
Můžeme použít mixiny jako způsob, jak rozšířit třídu přidáním více druhů chování, jako je zpracování událostí, jak jsme viděli výše.
Mixiny se mohou stát bodem konfliktu, pokud náhodně přepíší existující metody třídy. Obecně by se tedy mělo dobře přemýšlet o metodách pojmenování mixinu, aby se minimalizovala pravděpodobnost, že k tomu dojde.