Javascript 中 Mixins 和 Inheritance 之间的实际区别是什么?
What are the practical differences between Mixins and Inheritance in Javascript?
只是(模拟)多重继承混合的唯一优势:
Object.assign( MyClassA.prototype, MyMixinB )
与继承
class MyClass extends MyClassB {
// MyClassB = class version of MyMixinB
在 ES6 中 Javascript?
谢谢
不会有那么一个简短的答案。 JavaScript (JS) 确实为构建 type/object 系统提供了许多不同的技术方法(以及如何组合它们),实用性随之而来。
首先,正如 OP(原发布者)已经知道的那样,JS 中没有 real/true 多重继承。这种语言只提供基于原型的单一继承(可以链接),无论选择 classes 还是传统的基于纯函数的风格。
但是,其次,与其他编程语言 (PL) 一样,还有另一种不基于继承的代码重用和对象组合方式……它是混合 and/or 基于特征的组合。
但我会坚持使用 OP 提供的示例代码。
Object.assign(MyClassA.prototype, MyMixinB)
如果有人认为 MyMixinB
提供额外的行为作为 基于对象的 mixin ,例如...
var arr = ['object', 'based', 'mixin', 'approach'];
var list = { length: 4, 0: 'the', 1: 'quick', 2: 'brown', 3: 'fox' };
console.log('(typeof arr.last) : ', (typeof arr.last));
console.log('(typeof list.last) : ', (typeof list.last));
const withObjectBasedListLastBehavior = {
last: function () {
return this[this.length - 1];
}
}
Object.assign(Array.prototype, withObjectBasedListLastBehavior);
Object.assign(list, withObjectBasedListLastBehavior);
console.log('(typeof arr.last) : ', (typeof arr.last));
console.log('arr.last() : ', arr.last());
console.log('(typeof list.last) : ', (typeof list.last));
console.log('list.last() : ', list.last());
console.log('(arr.last === list.last) : ', (arr.last === list.last));
console.log('(arr.last === Array.prototype.last) : ', (arr.last === Array.prototype.last));
.as-console-wrapper { max-height: 100%!important; top: 0; }
上述方法是组合和继承的结合。 withObjectBasedListLastBehavior
mixin 为类似列表的结构提供了一个通用实现的 last
方法。这种结构本身不会产生任何影响。通过 Object.assign
可以将 last
列表行为分配给任何类似列表的结构。但是第一个示例的 OP 确实将混合分配给 prototype
。因此,除非没有自己的 last
方法的实例要调用它,否则不会使用这种新获得的行为。然后进行原型委托……现在的原型 last
将在调用对象的上下文中被调用。
通过 mixin(具有此类行为的结构)提供额外 behavior/functionality 的优势在于可以灵活地重复使用这些结构。理想的 mixin 是原子的,并且只有一种特定的行为(一种方法)。这种行为可以从 class 构造函数/构造函数的主体中混合,或者像上面的例子一样,它被分配给任何对象(原型对象或任何其他类型)。因此,通过 mixins/traits 在 JavaScript 中的代码重用可以在 2 个级别......在 class 级别(在 construction/instantiation 时间)和在对象级别的任何时间。
这种灵活性应该不足为奇,因为 mixins/traits 仅从行为的角度有助于组合(对象 具有 行为) 而真正的继承,也在 JS 中,负责 是一个 事物 关系。
应该意识到基于 mixin/trait 的组合和继承之间的差异并不是 JavaScript 特定的东西。这些概念也适用于其他 PL。从技术上讲,JS 只是更灵活,因为它是基于对象的,并且具有两种委托方式,implicit 通过自动 运行 原型委托和 explicit 通过调用函数的 call
和 apply
方法直接传递上下文。
以上提供的实际证明考虑了 OP 的第二个示例代码...
class MyClass extends MyClassB {
// MyClassB = class version of MyMixinB
...并确实将其更改为类似的东西...
const withFunctionBasedListLastBehavior = (function () {
function last() { // single implementation
return this[this.length - 1];
}
return function() { // always going to share the above
this.last = last; // single implementation, thus always
}; // featuring one and the same signature.
}());
const withFunctionBasedListFirstBehavior = (function () {
function first() {
return this[0];
}
return function() {
this.first = first;
};
}());
const withFunctionBasedListItemBehavior = (function () {
function item(idx) {
return this[idx];
}
return function() {
this.item = item;
};
}());
class ListWrapper {
constructor(list) {
// mixin in / explicit delegation
withFunctionBasedListFirstBehavior.call(list);
withFunctionBasedListLastBehavior.call(list);
withFunctionBasedListItemBehavior.call(list);
// forwarding
this.first = function() {
return list.first();
};
this.last = function() {
return list.last();
};
this.item = function(idx) {
return list.item(idx);
}
}
}
class Queue extends ListWrapper { // inheritance part I.
constructor() {
const list = [];
super(list); // inheritance part II.
// queue specific behavior
this.enqueue = function(value) {
list.push(value);
return value;
};
this.dequeue = function() {
return list.shift();
};
}
}
var queue = new Queue;
console.log("queue.enqueue('the') : ", queue.enqueue('the'));
console.log("queue.enqueue('quick') : ", queue.enqueue('quick'));
console.log("queue.enqueue('brown') : ", queue.enqueue('brown'));
console.log("queue.enqueue('fox') : ", queue.enqueue('fox'));
console.log("queue.first() : ", queue.first());
console.log("queue.last() : ", queue.last());
console.log("queue.item(2) : ", queue.item(2));
console.log("queue.dequeue() : ", queue.dequeue());
console.log("queue.dequeue() : ", queue.dequeue());
console.log("queue.dequeue() : ", queue.dequeue());
console.log("queue.first() : ", queue.first());
console.log("queue.last() : ", queue.last());
console.log("queue.item(2) : ", queue.item(2));
console.log("queue.dequeue() : ", queue.dequeue());
console.log("queue.dequeue() : ", queue.dequeue());
console.log('(queue instanceof Queue) : ', (queue instanceof Queue));
console.log('(queue instanceof ListWrapper) : ', (queue instanceof ListWrapper));
.as-console-wrapper { max-height: 100%!important; top: 0; }
只是(模拟)多重继承混合的唯一优势:
Object.assign( MyClassA.prototype, MyMixinB )
与继承
class MyClass extends MyClassB {
// MyClassB = class version of MyMixinB
在 ES6 中 Javascript?
谢谢
不会有那么一个简短的答案。 JavaScript (JS) 确实为构建 type/object 系统提供了许多不同的技术方法(以及如何组合它们),实用性随之而来。
首先,正如 OP(原发布者)已经知道的那样,JS 中没有 real/true 多重继承。这种语言只提供基于原型的单一继承(可以链接),无论选择 classes 还是传统的基于纯函数的风格。
但是,其次,与其他编程语言 (PL) 一样,还有另一种不基于继承的代码重用和对象组合方式……它是混合 and/or 基于特征的组合。
但我会坚持使用 OP 提供的示例代码。
Object.assign(MyClassA.prototype, MyMixinB)
如果有人认为 MyMixinB
提供额外的行为作为 基于对象的 mixin ,例如...
var arr = ['object', 'based', 'mixin', 'approach'];
var list = { length: 4, 0: 'the', 1: 'quick', 2: 'brown', 3: 'fox' };
console.log('(typeof arr.last) : ', (typeof arr.last));
console.log('(typeof list.last) : ', (typeof list.last));
const withObjectBasedListLastBehavior = {
last: function () {
return this[this.length - 1];
}
}
Object.assign(Array.prototype, withObjectBasedListLastBehavior);
Object.assign(list, withObjectBasedListLastBehavior);
console.log('(typeof arr.last) : ', (typeof arr.last));
console.log('arr.last() : ', arr.last());
console.log('(typeof list.last) : ', (typeof list.last));
console.log('list.last() : ', list.last());
console.log('(arr.last === list.last) : ', (arr.last === list.last));
console.log('(arr.last === Array.prototype.last) : ', (arr.last === Array.prototype.last));
.as-console-wrapper { max-height: 100%!important; top: 0; }
上述方法是组合和继承的结合。 withObjectBasedListLastBehavior
mixin 为类似列表的结构提供了一个通用实现的 last
方法。这种结构本身不会产生任何影响。通过 Object.assign
可以将 last
列表行为分配给任何类似列表的结构。但是第一个示例的 OP 确实将混合分配给 prototype
。因此,除非没有自己的 last
方法的实例要调用它,否则不会使用这种新获得的行为。然后进行原型委托……现在的原型 last
将在调用对象的上下文中被调用。
通过 mixin(具有此类行为的结构)提供额外 behavior/functionality 的优势在于可以灵活地重复使用这些结构。理想的 mixin 是原子的,并且只有一种特定的行为(一种方法)。这种行为可以从 class 构造函数/构造函数的主体中混合,或者像上面的例子一样,它被分配给任何对象(原型对象或任何其他类型)。因此,通过 mixins/traits 在 JavaScript 中的代码重用可以在 2 个级别......在 class 级别(在 construction/instantiation 时间)和在对象级别的任何时间。
这种灵活性应该不足为奇,因为 mixins/traits 仅从行为的角度有助于组合(对象 具有 行为) 而真正的继承,也在 JS 中,负责 是一个 事物 关系。
应该意识到基于 mixin/trait 的组合和继承之间的差异并不是 JavaScript 特定的东西。这些概念也适用于其他 PL。从技术上讲,JS 只是更灵活,因为它是基于对象的,并且具有两种委托方式,implicit 通过自动 运行 原型委托和 explicit 通过调用函数的 call
和 apply
方法直接传递上下文。
以上提供的实际证明考虑了 OP 的第二个示例代码...
class MyClass extends MyClassB {
// MyClassB = class version of MyMixinB
...并确实将其更改为类似的东西...
const withFunctionBasedListLastBehavior = (function () {
function last() { // single implementation
return this[this.length - 1];
}
return function() { // always going to share the above
this.last = last; // single implementation, thus always
}; // featuring one and the same signature.
}());
const withFunctionBasedListFirstBehavior = (function () {
function first() {
return this[0];
}
return function() {
this.first = first;
};
}());
const withFunctionBasedListItemBehavior = (function () {
function item(idx) {
return this[idx];
}
return function() {
this.item = item;
};
}());
class ListWrapper {
constructor(list) {
// mixin in / explicit delegation
withFunctionBasedListFirstBehavior.call(list);
withFunctionBasedListLastBehavior.call(list);
withFunctionBasedListItemBehavior.call(list);
// forwarding
this.first = function() {
return list.first();
};
this.last = function() {
return list.last();
};
this.item = function(idx) {
return list.item(idx);
}
}
}
class Queue extends ListWrapper { // inheritance part I.
constructor() {
const list = [];
super(list); // inheritance part II.
// queue specific behavior
this.enqueue = function(value) {
list.push(value);
return value;
};
this.dequeue = function() {
return list.shift();
};
}
}
var queue = new Queue;
console.log("queue.enqueue('the') : ", queue.enqueue('the'));
console.log("queue.enqueue('quick') : ", queue.enqueue('quick'));
console.log("queue.enqueue('brown') : ", queue.enqueue('brown'));
console.log("queue.enqueue('fox') : ", queue.enqueue('fox'));
console.log("queue.first() : ", queue.first());
console.log("queue.last() : ", queue.last());
console.log("queue.item(2) : ", queue.item(2));
console.log("queue.dequeue() : ", queue.dequeue());
console.log("queue.dequeue() : ", queue.dequeue());
console.log("queue.dequeue() : ", queue.dequeue());
console.log("queue.first() : ", queue.first());
console.log("queue.last() : ", queue.last());
console.log("queue.item(2) : ", queue.item(2));
console.log("queue.dequeue() : ", queue.dequeue());
console.log("queue.dequeue() : ", queue.dequeue());
console.log('(queue instanceof Queue) : ', (queue instanceof Queue));
console.log('(queue instanceof ListWrapper) : ', (queue instanceof ListWrapper));
.as-console-wrapper { max-height: 100%!important; top: 0; }