JavaScript 中的 Mixin
Javascript 預設不支援多重繼承。但有時我們需要將多個物件的屬性混合到單個物件中。可以使用 mixin 來實現物件屬性共享。在本文中,我們將介紹 JavaScript 中的 mixin。
Mixin 的定義可以描述為:Mixin 是一個包含方法的類,其他類可以使用這些方法而無需繼承該類。Mixin 中的方法提供某些行為,這些行為不會單獨使用,但可以用來向其他類新增這些行為。
Mixin:一個簡單的例子
請看下面的例子,我們有一個名為 DoodleHome 的智慧家居裝置和另一個名為 DoodleSpeak 的揚聲器物件。DoodleSpeak 物件中已經定義了一些訊息,這些訊息在 DoodleHome 中被使用。
示例
<!DOCTYPE html> <html> <head> <title>HTML Console</title> </head> <body> <h3> Output Console </h3> <p> Output: </p> <div id="output"> </div> <div id="opError" style="color : #ff0000"> </div> <script> var content = '' var error = '' var opDiv = document.querySelector('#output') var opErrDiv = document.querySelector('#opError') // actual javascript code try { let DoodleSpeak = { doodleHello() { content += 'Hello! ' + this.name + "<br>"; }, doodleBye() { content += 'Good Bye! ' + this.name + "<br>"; }, }; class DoodleHome { constructor(name) { this.name = name; } } // copy the methods from Object.assign(DoodleHome.prototype, DoodleSpeak); doodly = new DoodleHome("Doodly"); mixi = new DoodleHome("Mixi") doodly.doodleHello(); mixi.doodleHello(); doodly.doodleBye(); mixi.doodleBye(); } catch (err) { error += err } finally { // display on output console opDiv.innerHTML = content opErrDiv.innerHTML = error } </script> </body> </html>
在這個例子中,DoodleHome 物件被賦值給另一個類 DoodleSpeak。這個賦值 () 方法在 mixin 中扮演著重要的角色。Mixin 可以在其內部使用繼承。例如,如果我們建立一個 DoodleSay() 物件型別,並在 DoodleSpeak 中以更通用的形式實現它,那將是一個更好的實現。讓我們看下面的例子來理解這個概念。
示例
<!DOCTYPE html> <html> <head> <title>HTML Console</title> </head> <body> <h3> Output Console </h3> <p> Output: </p> <div id="output"> </div> <div id="opError" style="color : #ff0000"> </div> <script> var content = '' var error = '' var opDiv = document.querySelector('#output') var opErrDiv = document.querySelector('#opError') // actual javascript code try { let DoodleSay = { saySomething(statement) { content += statement + "<br>"; } }; let DoodleSpeak = { __proto__: DoodleSay, doodleHello() { super.saySomething(`Hello! ${this.name}`); }, doodleBye() { super.saySomething(`Good Bye! ${this.name}`); }, }; class DoodleHome { constructor(name) { this.name = name; } } // copy the methods from DoodleSpeak Object.assign(DoodleHome.prototype, DoodleSpeak); doodly = new DoodleHome("Doodly"); mixi = new DoodleHome("Mixi") doodly.doodleHello(); mixi.doodleHello(); doodly.doodleBye(); mixi.doodleBye(); } catch (err) { error += err } finally { // display on output console opDiv.innerHTML = content opErrDiv.innerHTML = error } </script> </body> </html>
示例
讓我們看另一個包含超類和類 mixin 的例子
<!DOCTYPE html> <html> <head> <title>HTML Console</title> </head> <body> <h3> Output Console </h3> <p> Output: </p> <div id="output"> </div> <div id="opError" style="color : #ff0000"> </div> <script> var content = '' var error = '' var opDiv = document.querySelector('#output') var opErrDiv = document.querySelector('#opError') // actual javascript code try { let DoodleSay = { saySomething(statement) { content += statement + "<br>"; } }; class Animal { constructor() { this._state = 'not moving, stays idle'; } get state() { return this._state; } } class Bird extends Animal { walk() { this._state = 'walking on ground'; } } function Flyer(parentClass) { return class extends parentClass { fly() { this._state = 'flying in the sky'; } } } function Swimmer(parentClass) { return class extends parentClass { swim() { this._state = 'swimming in water'; } } } class Crow extends Flyer(Bird) { } class Duck extends Swimmer(Flyer(Bird)) { } class Penguin extends Bird { } const crowObj = new Crow(); crowObj.fly(); content += 'State of the CROW: ' + JSON.stringify(crowObj.state) + "<br>"; const duckObj = new Duck(); duckObj.fly(); duckObj.swim(); content += 'State of the DUCK: ' + JSON.stringify(duckObj.state) + "<br>"; const penguinObj = new Penguin(); penguinObj.fly(); // Throws an error as the Penguin have no fly method } catch (err) { error += err } finally { // display on output console opDiv.innerHTML = content opErrDiv.innerHTML = error } </script> </body> </html>
在這個例子中,我們定義了一些類,例如 Animal 和 Bird,Bird 是 Animal 的子類。目前所有動物都是靜止的,鳥類具有 walk 屬性,以便所有鳥類都可以行走。Flyer 函式將建立一個類,該類接受一個父類型別,並將為其分配 fly() 屬性,類似地,Swimmer 類將返回另一個具有 swim 屬性的類型別。這些不同的類型別被合併到某些物件中。在這個例子中,Crow、Duck 和 Penguin 都是鳥類,但 swim 屬性被分配給 Duck,Crow 和 Duck 也可以飛行,但 Penguin 沒有 fly() 屬性,所以它返回一個錯誤,指出:“TypeError: penguinObj.fly is not a function”
Mixin 的優點和缺點
在 JavaScript 中,Mixin 減少了系統中的功能重複和多個函式的重複使用。有時我們需要跨物件例項共享行為,我們可以透過在 Mixin 中維護此共享功能來輕鬆避免任何重複,從而專注於僅實現系統中真正需要且唯一的那些功能。
Mixin 的侷限性有點令人困惑。一些開發者認為將功能注入物件原型不是一個好主意,因為它會在物件內部插入不必要的程式碼,並對函式的來源產生一定程度的不確定性。
結論
Javascript 不是一門完全面向物件的語言。因此,Javascript 不直接支援多重繼承。我們可以使用 Mixin 的概念來實現將不同物件的屬性複製到單個物件的效果。我們可以定義多個類或物件型別及其屬性,然後使用 apply() 方法將它們從一個類合併到另一組類中。