Mixiny

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ým name 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í funkci handler 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.