我已经到了需要在 JavaScript 中进行某种基本多重继承的地步。(我不是来讨论这是否是一个好主意,所以请把这些评论留给自己。)
我只想知道是否有人尝试过(或没有)成功,以及他们是如何做到的。
归根结底,我真正需要的是能够拥有一个能够从多个原型链继承属性的对象(即每个原型可以有自己的适当链),但以给定的优先级顺序(它将搜索链以找到第一个定义)。
为了证明这在理论上是如何可能的,可以通过将辅助链附加到主链的末端来实现,但这会影响任何先前原型的所有实例,这不是我想要的。
想法?
我已经到了需要在 JavaScript 中进行某种基本多重继承的地步。(我不是来讨论这是否是一个好主意,所以请把这些评论留给自己。)
我只想知道是否有人尝试过(或没有)成功,以及他们是如何做到的。
归根结底,我真正需要的是能够拥有一个能够从多个原型链继承属性的对象(即每个原型可以有自己的适当链),但以给定的优先级顺序(它将搜索链以找到第一个定义)。
为了证明这在理论上是如何可能的,可以通过将辅助链附加到主链的末端来实现,但这会影响任何先前原型的所有实例,这不是我想要的。
想法?
在 ECMAScript 6 中可以通过使用Proxy 对象来实现多重继承。
function getDesc (obj, prop) {
var desc = Object.getOwnPropertyDescriptor(obj, prop);
return desc || (obj=Object.getPrototypeOf(obj) ? getDesc(obj, prop) : void 0);
}
function multiInherit (...protos) {
return Object.create(new Proxy(Object.create(null), {
has: (target, prop) => protos.some(obj => prop in obj),
get (target, prop, receiver) {
var obj = protos.find(obj => prop in obj);
return obj ? Reflect.get(obj, prop, receiver) : void 0;
},
set (target, prop, value, receiver) {
var obj = protos.find(obj => prop in obj);
return Reflect.set(obj || Object.create(null), prop, value, receiver);
},
*enumerate (target) { yield* this.ownKeys(target); },
ownKeys(target) {
var hash = Object.create(null);
for(var obj of protos) for(var p in obj) if(!hash[p]) hash[p] = true;
return Object.getOwnPropertyNames(hash);
},
getOwnPropertyDescriptor(target, prop) {
var obj = protos.find(obj => prop in obj);
var desc = obj ? getDesc(obj, prop) : void 0;
if(desc) desc.configurable = true;
return desc;
},
preventExtensions: (target) => false,
defineProperty: (target, prop, desc) => false,
}));
}
代理对象由一个目标对象和一些陷阱组成,它们定义了基本操作的自定义行为。
创建从另一个对象继承的对象时,我们使用Object.create(obj)
. 但在这种情况下,我们想要多重继承,所以obj
我没有使用将基本操作重定向到适当对象的代理。
我使用这些陷阱:
has
陷阱是为陷阱in
运营商。我some
用来检查是否至少有一个原型包含该属性。get
陷阱是获得属性值陷阱。我find
用来查找包含该属性的第一个原型,然后返回该值,或者在适当的接收器上调用 getter。这由 处理Reflect.get
。如果没有原型包含该属性,则返回undefined
.set
陷阱是设置属性值陷阱。我find
用来查找包含该属性的第一个原型,并在适当的接收器上调用它的 setter。如果没有 setter 或没有原型包含该属性,则在适当的接收器上定义该值。这由 处理Reflect.set
。enumerate
陷阱是陷阱for...in
循环。我从第一个原型迭代可枚举属性,然后从第二个原型迭代,依此类推。一旦一个属性被迭代,我将它存储在一个哈希表中以避免再次迭代它。ownKeys
陷阱为陷阱Object.getOwnPropertyNames()
。从 ES7 开始,for...in
循环不断调用 [[GetPrototypeOf]] 并获取每个循环的属性。所以为了让它迭代所有原型的属性,我使用这个陷阱让所有可枚举的继承属性看起来像自己的属性。getOwnPropertyDescriptor
陷阱为陷阱Object.getOwnPropertyDescriptor()
。使所有可枚举属性在ownKeys
陷阱中看起来像自己的属性是不够的,for...in
循环将获取描述符以检查它们是否可枚举。因此,我使用find
查找包含该属性的第一个原型,并迭代其原型链,直到找到属性所有者,然后返回其描述符。如果没有原型包含该属性,则返回undefined
. 修改描述符以使其可配置,否则我们可能会破坏一些代理不变量。preventExtensions
和defineProperty
陷阱只包括防止修改代理目标这些操作。否则我们最终可能会破坏一些代理不变量。有更多的陷阱可用,我不使用
getPrototypeOf
陷阱可以添加,但返回多个原型不正确的方法。这意味着instanceof
也不会起作用。因此,我让它获取目标的原型,该原型最初为空。setPrototypeOf
陷阱可以添加并接受对象的数组,其将取代原型。这留给读者作为练习。这里我只是让它修改了目标的原型,没有多大用处,因为没有陷阱使用目标。deleteProperty
陷阱是删除自己的属性陷阱。代理代表继承,所以这没有多大意义。我让它尝试删除目标,无论如何它应该没有属性。isExtensible
陷阱是获得可扩展性陷阱。没有多大用处,因为不变量迫使它返回与目标相同的可扩展性。所以我只是让它将操作重定向到目标,这将是可扩展的。apply
和construct
陷阱陷阱调用或实例。它们仅在目标是函数或构造函数时才有用。// Creating objects
var o1, o2, o3,
obj = multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3});
// Checking property existences
'a' in obj; // true (inherited from o1)
'b' in obj; // true (inherited from o2)
'c' in obj; // false (not found)
// Setting properties
obj.c = 3;
// Reading properties
obj.a; // 1 (inherited from o1)
obj.b; // 2 (inherited from o2)
obj.c; // 3 (own property)
obj.d; // undefined (not found)
// The inheritance is "live"
obj.a; // 1 (inherited from o1)
delete o1.a;
obj.a; // 3 (inherited from o3)
// Property enumeration
for(var p in obj) p; // "c", "b", "a"
更新(2019 年):原帖已经过时了。这篇文章(现在是互联网存档链接,因为域名消失了)及其相关的GitHub 库是一种很好的现代方法。
原帖: 多重继承[编辑,不是类型的正确继承,而是属性的继承;如果您使用构造原型而不是通用对象原型,Javascript 中的 mixins] 非常简单。这里有两个要继承的父类:
function FoodPrototype() {
this.eat = function () {
console.log("Eating", this.name);
};
}
function Food(name) {
this.name = name;
}
Food.prototype = new FoodPrototype();
function PlantPrototype() {
this.grow = function () {
console.log("Growing", this.name);
};
}
function Plant(name) {
this.name = name;
}
Plant.prototype = new PlantPrototype();
请注意,我在每种情况下都使用了相同的“姓名”成员,如果父母不同意如何处理“姓名”,这可能是一个问题。但是在这种情况下它们是兼容的(实际上是冗余的)。
现在我们只需要一个继承两者的类。继承是通过为原型和对象构造函数调用构造函数(不使用 new 关键字)来完成的。首先,原型必须从父原型继承
function FoodPlantPrototype() {
FoodPrototype.call(this);
PlantPrototype.call(this);
// plus a function of its own
this.harvest = function () {
console.log("harvest at", this.maturity);
};
}
并且构造函数必须从父构造函数继承:
function FoodPlant(name, maturity) {
Food.call(this, name);
Plant.call(this, name);
// plus a property of its own
this.maturity = maturity;
}
FoodPlant.prototype = new FoodPlantPrototype();
现在您可以种植、食用和收获不同的实例:
var fp1 = new FoodPlant('Radish', 28);
var fp2 = new FoodPlant('Corn', 90);
fp1.grow();
fp2.grow();
fp1.harvest();
fp1.eat();
fp2.harvest();
fp2.eat();
这个Object.create
用来制作一个真正的原型链:
function makeChain(chains) {
var c = Object.prototype;
while(chains.length) {
c = Object.create(c);
$.extend(c, chains.pop()); // some function that does mixin
}
return c;
}
例如:
var obj = makeChain([{a:1}, {a: 2, b: 3}, {c: 4}]);
将返回:
a: 1
a: 2
b: 3
c: 4
<Object.prototype stuff>
所以obj.a === 1
, obj.b === 3
, 等等
我喜欢 John Resig 对类结构的实现:http : //ejohn.org/blog/simple-javascript-inheritance/
这可以简单地扩展为:
Class.extend = function(prop /*, prop, prop, prop */) {
for( var i=1, l=arguments.length; i<l; i++ ){
prop = $.extend( prop, arguments[i] );
}
// same code
}
这将允许您传入多个要继承的对象。您将在instanceOf
这里失去功能,但如果您想要多重继承,这是给定的。
我相当复杂的上述示例可在https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js 获得
请注意,该文件中有一些死代码,但如果您想看一看,它允许多重继承。
如果你想要链式继承(不是多重继承,但对大多数人来说它是一样的),它可以用 Class 来完成,如:
var newClass = Class.extend( cls1 ).extend( cls2 ).extend( cls3 )
这将保留原始原型链,但您也会运行大量无意义的代码。
不要与多重继承的 JavaScript 框架实现混淆。
所有你需要做的是使用的Object.create()每次创建新对象与指定的原型对象和属性,然后一定要改变Object.prototype.constructor的每一步,如果你打算在实例B
中未来。
为了继承实例属性thisA
,thisB
我们在每个对象函数的末尾使用Function.prototype.call()。如果您只关心继承原型,这是可选的。
在某处运行以下代码并观察objC
:
function A() {
this.thisA = 4; // objC will contain this property
}
A.prototype.a = 2; // objC will contain this property
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
function B() {
this.thisB = 55; // objC will contain this property
A.call(this);
}
B.prototype.b = 3; // objC will contain this property
C.prototype = Object.create(B.prototype);
C.prototype.constructor = C;
function C() {
this.thisC = 123; // objC will contain this property
B.call(this);
}
C.prototype.c = 2; // objC will contain this property
var objC = new C();
B
继承原型 A
C
继承原型 B
objC
是一个实例 C
这是对上述步骤的一个很好的解释: