我完全同意贾里德·史密斯的观点。就像使用任何工具或工具集一样,需要知道是否要使用它。如果出于任何原因选择了 mixins 的概念,那么你应该真正知道它的能力和遗漏了什么,尤其是在应用于 JavaScript 的编程范式/概念时。
我是固执己见,因此将从我自己的角度提供以下想法和技术方法。其他解决方案更为广泛,其中一些我有时也会使用。
让我们以 OP 提供的第一个来源为例。例如,如果有人将上面给出的示例重写为...
const calculatorMixin = Base => class extends Base {
calc() { }
};
const randomizerMixin = Base => class extends Base {
randomize() { }
};
class Baz { }
const calcAndRandomizeMixin = calculatorMixin(randomizerMixin(Baz));
class Biz extends calcAndRandomizeMixin { }
const biz = new Biz;
console.log('(biz instanceof Biz) ? ', (biz instanceof Biz));
console.log('(biz instanceof Baz) ? ', (biz instanceof Baz));
console.log('(biz instanceof calcAndRandomizeMixin) ? ', (biz instanceof calcAndRandomizeMixin));
.as-console-wrapper { max-height: 100%!important; top: 0; }
...那么我最大的担忧来自于方法本身,因为它完全基于类及其扩展。 “mixins” 是类,由类工厂即时创建。他们总是扩展另一个类。因此它是纯粹的继承。
尽管有人正在编写这样的 mixin-class 作为行为容器,“可以” 做一些事情,而后来的类型 “有" 某些行为,从技术上讲,这种方法根本不承认 mixins 背后的概念,因为它的本质是基于子-父或 “是” 关系。
第二种方法,使用对象和Object.assign,乍一看像是许多古代混合方法的现代变体,当时都使用与对象相关的行为和自写extends的组合方法...就像...extends(targetObject, mixinSourceObject).
这种方法的独特之处在于它如何支持/解决 "composite-mixins" ...从其他 mixins 创建的 mixins。在我看来,通过super 委托链接行为并将另一个基于对象的混合分配给混合的__proto__ 属性是可行且优雅的。
我个人会花更多的时间来研究这种二次提供的方法。
还有另一种方法……基于函数的混入。第二个基于对象的 mixin-example 的代码重写为它的基于函数的挂件确实看起来像这样......
const sayMixin = (function () { // basic function-based mixin.
// shared code. //
function say(phrase) { // a single implementation
console.log(phrase); // of `say` behavior ...
} //
// return function based mixin. //
return function sayMixin () { // ... that might get applied
this.say = say; // many times but always as
}; // reference / shared code.
}());
const sayHiMixin = (function () { // function-based *composite-mixin*.
// shared code.
// object that helps with behavior forwarding.
const sayProxy = {};
// apply behavior of `sayMixin`.
sayMixin.call(sayProxy);
// a single implementation of `sayHi` behavior.
function sayHi() {
sayProxy.say(`Hello ${this.name}!`); // forwarding.
}
// a single implementation of `sayBye` behavior.
function sayBye() {
sayProxy.say(`Bye ${this.name}!`); // forwarding.
}
// return function based composite mixin.
return function sayHiMixin () {
this.sayHi = sayHi; // - always shares one and the ...
this.sayBye = sayBye; // ... same implementation(s).
};
}());
class User {
constructor(name) {
// public property.
this.name = name;
}
}
// apply the composite `sayHiMixin`.
sayHiMixin.call(User.prototype);
// now a `User` can say hi and bye
const dude = new User('Dude');
dude.sayHi(); // Hello Dude!
dude.sayBye(); // Bye Dude!
console.log('dude.name : ', dude.name); // Dude
.as-console-wrapper { max-height: 100%!important; top: 0; }
即使在今天,选择这种方法也有一些很好的论据。首先,它与 OP 提到的其他两个有共同点……不需要额外的库。其次,与其他环境不同,它确实可以在每个给定的 ES3 环境中运行。第三,基于函数的方法将mixin实现为“适用类型”,因此可以免费获得委托和封装。
现在将展示两者的力量。仍然使用第二个示例代码和引入的基于函数的 mixin 方法很容易创建另一个用户,该用户将其初始 name 隐藏起来,但确实通过它的 say 行为公开它。当然,下面的代码只是为了理解这些概念。在实践中很难实现像这样的“混合复合混合” ...
const sayMixin = (function () { // basic function-based mixin.
// shared code. //
function say(phrase) { // a single implementation
console.log(phrase); // of `say` behavior ...
} //
// return function based mixin. //
return function sayMixin () { // ... that might get applied
this.say = say; // many times but always as
}; // reference / shared code.
}());
const sayHiMixin = (function () { // function-based *composite-mixin*.
// shared code.
// object that helps with behavior forwarding.
const sayProxy = {};
// apply behavior of `sayMixin`.
sayMixin.call(sayProxy);
// a single implementation of `sayHi` behavior.
function sayHi() {
sayProxy.say(`Hello ${this.name}!`); // forwarding.
}
// a single implementation of `sayBye` behavior.
function sayBye() {
sayProxy.say(`Bye ${this.name}!`); // forwarding.
}
// // return function based composite mixin.
// return function sayHiMixin () {
// this.sayHi = sayHi; // - always shares one and the ...
// this.sayBye = sayBye; // ... same implementation(s).
// };
// return function based hybrid composite mixin.
return function sayHiMixin (properties) {
if (properties && (typeof properties === 'object')) {
console.log('sayHiMixin :: payload bound to behavior');
this.sayHi = sayHi.bind(properties); // - creates each a ...
this.sayBye = sayBye.bind(properties); // ... new reference.
} else {
console.log('sayHiMixin :: direct behavior reference');
this.sayHi = sayHi; // - always shares one and the ...
this.sayBye = sayBye; // ... same implementation(s).
}
};
}());
class User {
constructor(name) {
// public property.
this.name = name;
}
}
// apply the composite `sayHiMixin`.
sayHiMixin.call(User.prototype);
// now a `User` can say hi and bye
const dude = new User('Dude');
dude.sayHi(); // Hello Dude!
dude.sayBye(); // Bye Dude!
console.log('dude.name : ', dude.name); // Dude
class AnotherUser {
constructor(name) {
// local property + public accessor methods.
sayHiMixin.call(this, { name: name });
}
}
// now a `User` can say hi and bye
const john = new AnotherUser('John');
john.sayHi(); // Hello John!
john.sayBye(); // Bye John!
console.log('john.name : ', john.name); // undefined
.as-console-wrapper { max-height: 100%!important; top: 0; }
关于基于函数的 mixins 的观点总结
正如自 ES3 以来已经提供的功能,通过闭包进行封装,显式委托功能以及通过 call/apply 应用不同的上下文,人们已经可以从基于 mixin 的组合开始。结合这些技术可以实现更强大的概念,例如冲突解决,可以/将基于已经演示的通过代理引用和一些功能组合的转发。注入和传递附加状态也是可能的。因此,人们甚至可以实现超越 mixins 的概念,例如 Traits、Stateful Traits 和 Talents,后者是真正符合 JavaScript 语言范式的组合概念。
是否使用 mixin 的经验法则
只有在代码重用可以用像 observable 这样的形容词来描述和/或在整个系统中,不相似的类和/或异构类型需要相同的附加行为。