JavaScript中的多重inheritance/原型

我已经到了一个地步,我需要在JavaScript中发生一些基本的多重inheritance。 (我不是在这里讨论这是不是一个好主意,所以请把这些评论留给自己。)

我只想知道是否有人试图取得任何成功,以及他们是如何去做的。

为了解决这个问题,我真正需要的是能够拥有一个能够从一个以上的原型inheritance一个属性的对象(即每个原型可以拥有自己的链),但是按照给定的优先顺序search链为了第一个定义)。

为了certificate这在理论上是可能的,可以通过将次链连接到主链的末端来实现,但是这会影响所有以前原型的所有实例,这不是我想要的。

思考?

编辑欣赏响应人员,但虽然共识似乎是静态复制两个树的属性,这将在大多数情况下工作(也可能是我最终做的),我最感兴趣的dynamic解决scheme,将允许单独的原型链被改变,并且仍然具有由实例“拾取”的那些改变。

Mixins可以在JavaScript中使用,以实现您可能想通过多重inheritance来解决的相同的目标。

使用Proxy对象可以在ECMAScript 6中实现多重inheritance。

履行

 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, })); } 

说明

代理对象由一个目标对象和一些陷阱组成,这些陷阱定义了基本操作的自定义行为。

当创build一个从另一个inheritance的对象时,我们使用Object.create(obj) 。 但在这种情况下,我们想要多重inheritance,所以而不是obj我使用代理将基本操作redirect到适当的对象。

我使用这些陷阱:

  • has陷阱是运营商的陷阱。 我使用some来检查是否至less有一个原型包含该属性。
  • get陷阱是获得财产价值的陷阱。 我使用find来查找包含该属性的第一个原型,并返回该值,或者在适当的接收方上调用getter。 这由Reflect.get处理。 如果没有原型包含该属性,我返回undefined
  • set陷阱是设置属性值的陷阱。 我使用find来查找包含该属性的第一个原型,并在适当的接收器上调用setter。 如果没有setter或没有原型包含属性,则在适当的接收器上定义该值。 这由Reflect.set处理。
  • enumerate陷阱是for...in循环中的陷阱。 我从第一个原型迭代枚举属性,然后从第二个原型迭代,依此类推。 一旦一个属性被迭代,我将它存储在一个散列表中,以避免再次迭代它。
    警告 :此陷阱已在ES7草案中被删除,并在浏览器中被弃用。
  • ownKeys陷阱是Object.getOwnPropertyNames()的陷阱。 自ES7以来, for...in循环继续调用[[GetPrototypeOf]]并获取每个属性的属性。 所以为了使它迭代所有原型的属性,我使用这个陷阱来使所有可枚举的inheritance属性看起来像自己的属性。
  • getOwnPropertyDescriptor陷阱是Object.getOwnPropertyDescriptor()的陷阱。 让所有可枚举的属性在自己的ownKeys陷阱中显示为自己的属性是不够的, for...in循环将获取描述符来检查它们是否可枚举。 所以我使用find来查找包含该属性的第一个原型,并且遍历其原型链,直到find属性所有者,然后返回其描述符。 如果没有原型包含该属性,我返回undefined 。 描述符被修改,使其可configuration,否则我们可以打破一些代理不variables。
  • 仅包含preventExtensionsdefineProperty陷阱以防止这些操作修改代理目标。 否则,我们最终可能会打破一些代理不variables。

有更多的陷阱可用,我不使用

  • 可以添加getPrototypeOf陷阱 ,但没有正确的方法来返回多个原型。 这意味着instanceof不会工作。 因此,我让它得到目标的原型,最初是空的。
  • 可以添加setPrototypeOf陷阱并接受一个对象数组,它将取代原型。 这是留给读者的一个练习。 在这里,我只是让它修改目标的原型,这是没有太大的用处,因为没有陷阱使用目标。
  • deleteProperty陷阱是删除自己的属性的陷阱。 代理代表inheritance,所以这没有多大意义。 我让它尝试删除目标,无论如何应该没有财产。
  • isExtensible陷阱是获得可扩展性的陷阱。 没有多大用处,因为一个不variables迫使它返回与目标相同的可扩展性。 所以我只是让它将操作redirect到目标,这将是可扩展的。
  • applyconstruct陷阱是调用或实例化的陷阱。 它们仅在目标是函数或构造函数时才有用。

 // 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" 

多重inheritance[编辑,不是types的适当的inheritance,而是属性; mixins]在Javascript中是非常简单的,如果你使用构造的原型而不是通用对象。 这里有两个要inheritance的父类:

 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(); 

请注意,在每种情况下,我都使用了相同的“姓名”成员,如果父母不同意应如何处理“姓名”,这可能会成为问题。 但是在这种情况下它们是兼容的(多余的,真的)。

现在我们只需要一个从两个inheritance的类。 inheritance是通过调用原型和对象构造函数的构造函数(不使用new关键字)来完成的。 首先,原型必须从父原型inheritance

 function FoodPlantPrototype() { FoodPrototype.call(this); PlantPrototype.call(this); // plus a function of its own this.harvest = function () { console.log("harvest at", this.maturity); }; } 

而构造函数必须从父构造函数inheritance:

 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(); 

我喜欢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 } 

这将允许您传递多个对象来inheritance。 你将会在这里失去instanceOffunction,但是如果你想要多重inheritance的话,这是一个给定的。


我的相当复杂的例子可以在https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js

请注意,该文件中存在一些死代码,但是如果您想查看,它允许多重inheritance。


如果你想链接inheritance(不是多重inheritance,但对于大多数人来说是同样的事情),它可以用类来完成:

 var newClass = Class.extend( cls1 ).extend( cls2 ).extend( cls3 ) 

这将保留原始的原型链,但是你也会有很多无意义的代码在运行。

这一个使用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 === 1obj.b === 3

我不是javascript OOP方面的专家,但是如果我正确地理解了你,你想要的东西像(伪代码):

 Earth.shape = 'round'; Animal.shape = 'random'; Cat inherit from (Earth, Animal); Cat.shape = 'random' or 'round' depending on inheritance order; 

在这种情况下,我会尝试这样的:

 var Earth = function(){}; Earth.prototype.shape = 'round'; var Animal = function(){}; Animal.prototype.shape = 'random'; Animal.prototype.head = true; var Cat = function(){}; MultiInherit(Cat, Earth, Animal); console.log(new Cat().shape); // yields "round", since I reversed the inheritance order console.log(new Cat().head); // true function MultiInherit() { var c = [].shift.call(arguments), len = arguments.length while(len--) { $.extend(c.prototype, new arguments[len]()); } } 

不要混淆多重inheritance的JavaScript框架实现。

所有你需要做的就是使用Object.create()来创build一个新的对象,每次使用指定的原型对象和属性,那么一定要改变Object.prototype.constructor的每一步,如果你打算实例化B未来。

要inheritance实例属性thisAthisB我们在每个对象函数的末尾使用Function.prototype.call() 。 如果你只关心inheritance原型,这是可选的。

在某处运行以下代码并观察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(); 
  • Binheritance了A的原型
  • Cinheritance了B的原型
  • objCC一个实例

这是对以上步骤的一个很好的解释:

JavaScript中的OOP:你需要知道的

我今天正在做这个工作,并试图在ES6中实现这一点。 我这样做的方式是使用Browserify,Babel,然后我用袋鼠进行testing,它似乎工作。 我的目标是扩展当前的数组,包括ES6,ES7,并添加一些额外的自定义function,我需要在处理audio数据的原型。

沃拉比通过了我的四个testing。 example.js文件可以粘贴到控制台中,您可以看到“includes”属性位于类的原型中。 我明天还想再testing一下。

这是我的方法:(我会很有可能重构和重新打包后作为一个模块睡觉!)

 var includes = require('./polyfills/includes'); var keys = Object.getOwnPropertyNames(includes.prototype); keys.shift(); class ArrayIncludesPollyfills extends Array {} function inherit (...keys) { keys.map(function(key){ ArrayIncludesPollyfills.prototype[key]= includes.prototype[key]; }); } inherit(keys); module.exports = ArrayIncludesPollyfills 

Github回购: https : //github.com/danieldram/array-includes-polyfill

可以在JavaScript中实现多重inheritance,但是很less有这样的库。

我可以指出Ring.js ,这是我知道的唯一例子。

我觉得这简直太可笑了。 这里的问题是,子类只会引用您调用的第一个类的instanceof

https://jsfiddle.net/1033xzyt/19/

 function Foo() { this.bar = 'bar'; return this; } Foo.prototype.test = function(){return 1;} function Bar() { this.bro = 'bro'; return this; } Bar.prototype.test2 = function(){return 2;} function Cool() { Foo.call(this); Bar.call(this); return this; } var combine = Object.create(Foo.prototype); $.extend(combine, Object.create(Bar.prototype)); Cool.prototype = Object.create(combine); Cool.prototype.constructor = Cool; var cool = new Cool(); console.log(cool.test()); // 1 console.log(cool.test2()); //2 console.log(cool.bro) //bro console.log(cool.bar) //bar console.log(cool instanceof Foo); //true console.log(cool instanceof Bar); //false 

看看IeUnit包。

在IeUnit中实现的概念同化似乎提供了你正在寻找一个相当dynamic的方式。

以下是使用构造函数进行原型链接的示例:

 function Lifeform () { // 1st Constructor function this.isLifeform = true; } function Animal () { // 2nd Constructor function this.isAnimal = true; } Animal.prototype = new Lifeform(); // Animal is a lifeform function Mammal () { // 3rd Constructor function this.isMammal = true; } Mammal.prototype = new Animal(); // Mammal is an animal function Cat (species) { // 4th Constructor function this.isCat = true; this.species = species } Cat.prototype = new Mammal(); // Cat is a mammal 

这个概念使用耶胡达·卡茨对JavaScript的“类”定义:

…一个JavaScript“类”只是一个Function对象,作为一个构造函数加上一个附加的原型对象。 ( 来源:Guru Katz )

与Object.create方法不同的是,当类以这种方式构build并且我们想要创build“类”的实例时,我们不需要知道每个“类”是从哪个类inheritance的。 我们只是使用new

 // Make an instance object of the Cat "Class" var tiger = new Cat("tiger"); console.log(tiger.isCat, tiger.isMammal, tiger.isAnimal, tiger.isLifeform); // Outputs: true true true true 

优先顺序应该是有道理的。 首先看实例对象,然后是原型,然后是下一个原型,等等。

 // Let's say we have another instance, a special alien cat var alienCat = new Cat("alien"); // We can define a property for the instance object and that will take // precendence over the value in the Mammal class (down the chain) alienCat.isMammal = false; // OR maybe all cats are mutated to be non-mammals Cat.prototype.isMammal = false; console.log(alienCat); 

我们也可以修改会影响所有build立在类上的对象的原型。

 // All cats are mutated to be non-mammals Cat.prototype.isMammal = false; console.log(tiger, alienCat); 

我原来写了一些这个答案 。

在场的后来者是SimpleDeclare 。 但是,在处理多重inheritance时,您仍然会得到原始构造函数的副本。 这是必要的Javascript …

芝加哥商业交易所。

我会使用ds.oop 。 它与prototype.js和其他类似。 使得多重inheritance非常容易,而且极简。 (只有2或3 kb)还支持一些其他整洁的function,如接口和dependency injection

 /*** multiple inheritance example ***********************************/ var Runner = ds.class({ run: function() { console.log('I am running...'); } }); var Walker = ds.class({ walk: function() { console.log('I am walking...'); } }); var Person = ds.class({ inherits: [Runner, Walker], eat: function() { console.log('I am eating...'); } }); var person = new Person(); person.run(); person.walk(); person.eat();