W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
在 JavaScript 中,我們只能繼承單個(gè)對(duì)象。每個(gè)對(duì)象只能有一個(gè) ?[[Prototype]]
?。并且每個(gè)類只可以擴(kuò)展另外一個(gè)類。
但是有些時(shí)候這種設(shè)定(譯注:?jiǎn)卫^承)會(huì)讓人感到受限制。例如,我有一個(gè) StreetSweeper
類和一個(gè) Bicycle
類,現(xiàn)在想要一個(gè)它們的 mixin:StreetSweepingBicycle
類。
或者,我們有一個(gè) User
類和一個(gè) EventEmitter
類來實(shí)現(xiàn)事件生成(event generation),并且我們想將 EventEmitter
的功能添加到 User
中,以便我們的用戶可以觸發(fā)事件(emit event)。
有一個(gè)概念可以幫助我們,叫做 “mixins”。
根據(jù)維基百科的定義,mixin 是一個(gè)包含可被其他類使用而無需繼承的方法的類。
換句話說,mixin 提供了實(shí)現(xiàn)特定行為的方法,但是我們不單獨(dú)使用它,而是使用它來將這些行為添加到其他類中。
在 JavaScript 中構(gòu)造一個(gè) mixin 最簡(jiǎn)單的方式就是構(gòu)造一個(gè)擁有實(shí)用方法的對(duì)象,以便我們可以輕松地將這些實(shí)用的方法合并到任何類的原型中。
例如,這個(gè)名為 sayHiMixin
的 mixin 用于給 User
添加一些“語言功能”:
// mixin
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
}
};
// 用法:
class User {
constructor(name) {
this.name = name;
}
}
// 拷貝方法
Object.assign(User.prototype, sayHiMixin);
// 現(xiàn)在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!
這里沒有繼承,只有一個(gè)簡(jiǎn)單的方法拷貝。所以 User
可以從另一個(gè)類繼承,還可以包括 mixin 來 "mix-in“ 其它方法,就像這樣:
class User extends Person {
// ...
}
Object.assign(User.prototype, sayHiMixin);
Mixin 可以在自己內(nèi)部使用繼承。
例如,這里的 sayHiMixin
繼承自 sayMixin
:
let sayMixin = {
say(phrase) {
alert(phrase);
}
};
let sayHiMixin = {
__proto__: sayMixin, // (或者,我們可以在這兒使用 Object.setPrototypeOf 來設(shè)置原型)
sayHi() {
// 調(diào)用父類方法
super.say(`Hello ${this.name}`); // (*)
},
sayBye() {
super.say(`Bye ${this.name}`); // (*)
}
};
class User {
constructor(name) {
this.name = name;
}
}
// 拷貝方法
Object.assign(User.prototype, sayHiMixin);
// 現(xiàn)在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!
請(qǐng)注意,在 sayHiMixin
內(nèi)部對(duì)父類方法 super.say()
的調(diào)用(在標(biāo)有 (*)
的行)會(huì)在 mixin 的原型中查找方法,而不是在 class 中查找。
這是示意圖(請(qǐng)參見圖中右側(cè)部分):
這是因?yàn)榉椒?nbsp;sayHi
和 sayBye
最初是在 sayHiMixin
中創(chuàng)建的。因此,即使復(fù)制了它們,但是它們的 [[HomeObject]]
內(nèi)部屬性仍引用的是 sayHiMixin
,如上圖所示。
當(dāng) super
在 [[HomeObject]].[[Prototype]]
中尋找父方法時(shí),意味著它搜索的是 sayHiMixin.[[Prototype]]
,而不是 User.[[Prototype]]
。
現(xiàn)在讓我們?yōu)閷?shí)際運(yùn)用構(gòu)造一個(gè) mixin。
例如,許多瀏覽器對(duì)象的一個(gè)重要功能是它們可以生成事件。事件是向任何有需要的人“廣播信息”的好方法。因此,讓我們構(gòu)造一個(gè) mixin,使我們能夠輕松地將與事件相關(guān)的函數(shù)添加到任意 class/object 中。
.trigger(name, [...data])
? 方法,以在發(fā)生重要的事情時(shí)“生成一個(gè)事件”。?name
? 參數(shù)(arguments)是事件的名稱,?[...data]
? 是可選的帶有事件數(shù)據(jù)的其他參數(shù)(arguments)。
.on(name, handler)
? 方法,它為具有給定名稱的事件添加了 ?handler
? 函數(shù)作為監(jiān)聽器(listener)。當(dāng)具有給定 ?name
? 的事件觸發(fā)時(shí)將調(diào)用該方法,并從 ?.trigger
? 調(diào)用中獲取參數(shù)(arguments)。
.off(name, handler)
? 方法,它會(huì)刪除 ?handler
? 監(jiān)聽器(listener)。添加完 mixin 后,對(duì)象 user
將能夠在訪客登錄時(shí)生成事件 "login"
。另一個(gè)對(duì)象,例如 calendar
可能希望監(jiān)聽此類事件以便為登錄的人加載日歷。
或者,當(dāng)一個(gè)菜單項(xiàng)被選中時(shí),menu
可以生成 "select"
事件,其他對(duì)象可以分配處理程序以對(duì)該事件作出反應(yīng)。諸如此類。
下面是代碼:
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?.[eventName];
if (!handlers) return;
for (let i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
handlers.splice(i--, 1);
}
}
},
/**
* 生成具有給定名稱和數(shù)據(jù)的事件
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
if (!this._eventHandlers?.[eventName]) {
return; // 該事件名稱沒有對(duì)應(yīng)的事件處理程序(handler)
}
// 調(diào)用事件處理程序(handler)
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
}
};
.on(eventName, handler)
? — 指定函數(shù) ?handler
? 以在具有對(duì)應(yīng)名稱的事件發(fā)生時(shí)運(yùn)行。從技術(shù)上講,這兒有一個(gè)用于存儲(chǔ)每個(gè)事件名稱對(duì)應(yīng)的處理程序(handler)的 ?_eventHandlers
? 屬性,在這兒該屬性就會(huì)將剛剛指定的這個(gè) ?handler
? 添加到列表中。
.off(eventName, handler)
? — 從處理程序列表中刪除指定的函數(shù)。
.trigger(eventName, ...args
?) — 生成事件:所有 ?_eventHandlers[eventName]
? 中的事件處理程序(handler)都被調(diào)用,并且 ?...args
? 會(huì)被作為參數(shù)傳遞給它們。用法:
// 創(chuàng)建一個(gè) class
class Menu {
choose(value) {
this.trigger("select", value);
}
}
// 添加帶有事件相關(guān)方法的 mixin
Object.assign(Menu.prototype, eventMixin);
let menu = new Menu();
// 添加一個(gè)事件處理程序(handler),在被選擇時(shí)被調(diào)用:
menu.on("select", value => alert(`Value selected: ${value}`));
// 觸發(fā)事件 => 運(yùn)行上述的事件處理程序(handler)并顯示:
// 被選中的值:123
menu.choose("123");
現(xiàn)在,如果我們希望任何代碼對(duì)菜單選擇作出反應(yīng),我們可以使用 menu.on(...)
進(jìn)行監(jiān)聽。
使用 eventMixin
可以輕松地將此類行為添加到我們想要的多個(gè)類中,并且不會(huì)影響繼承鏈。
Mixin —— 是一個(gè)通用的面向?qū)ο缶幊绦g(shù)語:一個(gè)包含其他類的方法的類。
一些其它編程語言允許多重繼承。JavaScript 不支持多重繼承,但是可以通過將方法拷貝到原型中來實(shí)現(xiàn) mixin。
我們可以使用 mixin 作為一種通過添加多種行為(例如上文中所提到的事件處理)來擴(kuò)充類的方法。
如果 Mixins 意外覆蓋了現(xiàn)有類的方法,那么它們可能會(huì)成為一個(gè)沖突點(diǎn)。因此,通常應(yīng)該仔細(xì)考慮 mixin 的命名方法,以最大程度地降低發(fā)生這種沖突的可能性。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: