JavaScript 中的 Mixins
默认情况下,JavaScript 不支持多重继承。但有时我们需要将多个对象属性混合到一个对象中。可以使用 mixins 实现对象属性共享。在本文中,我们将介绍 JavaScript 中的 mixins。
mixins 的定义可以表述为 mixins 是一个包含方法的类,其他类可以使用这些方法而无需从该类继承。mixin 中的方法提供了某些行为,这些行为不能单独使用,但可用于将这些行为添加到其他类。
Mixin:一个简单的例子
请参见以下示例,其中我们有一个名为 DoodleHome 的智能家电和另一个名为 DoodleSpeak 的扬声器对象。一些消息已在 DoodleHome 中使用的 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 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。此assigns()方法在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>
Example
让我们看另一个带有超类和类混合的示例
<!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,它们是 Animal 的子类。所有动物目前都处于空闲状态,并且 bird 类类型具有 walk 属性,因此所有鸟类都可以行走。Flyer 函数将创建一个类,该类采用一个父类类型,该父类类型将分配 fly() 属性,类似地,Swimmer 类将返回另一个具有 swim 属性的类类型。这些不同的类类型合并为一些对象。在此示例中,Crow、Duck 和 Penguin 都是鸟类,但 swim 属性被分配给 Duck,Crow 和 Duck 也可以飞行,但 Penguin 没有 fly() 属性,因此它返回错误,指出 - "TypeError:penguinObj.fly 不是函数"
Mixins 的优点和局限性
在 JavaScript 中,Mixins 减少了系统中的功能重复和多个功能重用。有时我们需要跨对象实例共享行为,通过在 Mixin 中维护此共享功能,我们可以轻松避免任何重复,从而专注于仅实现系统中真正需要且独特的功能。
Mixin 中的限制有点令人困惑。一些开发人员认为将功能注入对象原型不是一个好主意,因为它会在对象内部插入不必要的代码,并且对我们函数的来源存在一定程度的不确定性。
结论
Javascript 不是完全面向对象的语言。因此,JavaScript 不直接支持多重继承。我们可以使用 Mixin 的概念来实现从不同对象到单个对象的属性复制的效果。我们可以使用它们的属性定义多个类或对象类型,然后使用 apply() 方法将它们从一个类合并到另一组类。