Ank*_*aha 5 javascript inheritance composition mixins
最近,我看到了两篇关于 mixin 的文章。这让我在哪个比另一个更好之间感到困惑。
第一个来自mdn
var calculatorMixin = Base => class extends Base {
calc() { }
};
var randomizerMixin = Base => class extends Base {
randomize() { }
};
class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }Run Code Online (Sandbox Code Playgroud)
来自https://javascript.info/mixins 的第二个
let sayMixin = {
say(phrase) {
alert(phrase);
}
};
let sayHiMixin = {
__proto__: sayMixin, // (or we could use Object.create 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!Run Code Online (Sandbox Code Playgroud)
在这些场景中创建的对象也具有不同的组成/结构。
现在我很困惑,哪个更好。
一个比另一个提供什么优势。
因此,我应该更喜欢使用哪一个。
Pet*_*ger -1
我完全同意贾里德·史密斯的观点。就像任何工具或工具集一样,人们需要知道是否要使用它。如果出于任何原因选择mixin的概念,那么我们应该真正了解它的能力和缺失的能力,特别是应用于 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));Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100%!important; top: 0; }Run Code Online (Sandbox Code Playgroud)
...那么我最关心的是该方法本身,因为它完全基于类及其扩展。“ mixin ”是由类工厂立即创建的类。他们总是延长另一个班级。因此它是纯粹的继承。
即使有人编写这样一个mixin 类作为“可以做”事情的行为容器,并且稍后的类型“具有”某种行为,但这种方法在技术上根本不承认 mixin 背后的概念,因为它是它本质上是基于孩子-父母或“是”关系。
第二种方法,利用对象 和Object.assign,乍一看像是许多古老 mixin 方法的现代变体,当时所有方法都使用与对象相关的行为和自编写extends方法的组合......比如extends(targetObject, mixinSourceObject)......。
这种方法的独特之处在于它如何支持/解决“复合混合” ...从其他混合创建的混合。在我看来,通过委托链接行为super并将另一个基于对象的 mixin 分配给 mixin 的属性是可行且优雅的。__proto__
我个人会花更多的时间来研究/研究第二种提供的方法。
还有另一种方法......基于函数的混合。第二个基于对象的混合示例的代码重写为基于函数的挂件,看起来像这样......
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); // DudeRun Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100%!important; top: 0; }Run Code Online (Sandbox Code Playgroud)
即使在今天,也有一些很好的理由支持选择这种方法。首先,它与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); // undefinedRun Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100%!important; top: 0; }Run Code Online (Sandbox Code Playgroud)
基于函数的 mixins 的观点总结
凭借 ES3 以来已经提供的功能、通过闭包进行封装、显式功能委托以及通过call/应用不同的上下文apply,人们已经可以从基于 mixin 的组合开始了。结合这些技术可以实现更强大的概念,例如冲突解决,这些概念可以/将基于已经演示的通过代理引用和某些函数组合的转发。注入和传递附加状态也是可能的。因此,我们甚至可以实现超越 mixin 的概念,例如Traits、Stateful Traits和Talents,而后者是真正适合 JavaScript 语言范式的组合概念。
是否使用 mixin 的经验法则
仅当代码重用可以用可观察之类的形容词来描述和/或在整个系统内不相似的类和/或异构类型需要相同的附加行为时才采用它。
| 归档时间: |
|
| 查看次数: |
1663 次 |
| 最近记录: |