JavaScript中的多重继承/原型

问题描述 投票:121回答:14

我已经到了需要在JavaScript中进行某种基本的多重继承的地步。 (我不是来讨论这是否是个好主意,所以请将这些意见保留给自己。)

我只是想知道是否有人尝试过任何(或没有)成功,以及他们如何去做。

简而言之,我真正需要的是能够拥有一个能够从多个原型链继承属性的对象(即每个原型可以拥有自己的正确链),但是在给定的优先顺序中(它将会搜索链以便第一个定义)。

为了证明这在理论上是如何可能的,可以通过将辅助链附加到主链的末端来实现,但这会影响任何先前原型的所有实例,而这不是我想要的。

思考?

javascript prototype multiple-inheritance
14个回答
38
投票

使用Proxy objects可以在ECMAScript 6中实现多重继承。

Implementation

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

Explanation

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

当创建一个继承自另一个的对象时,我们使用Object.create(obj)。但在这种情况下,我们需要多重继承,因此我使用代理将基本操作重定向到适当的对象,而不是obj

我使用这些陷阱:

  • has trapin operator的陷阱。我使用some检查是否至少有一个原型包含该属性。
  • get trap是获取房产价值的陷阱。我使用find找到包含该属性的第一个原型,然后返回值,或者在适当的接收器上调用getter。这由Reflect.get处理。如果没有原型包含该属性,我将返回undefined
  • set trap是设置属性值的陷阱。我使用find找到包含该属性的第一个原型,并在适当的接收器上调用它的setter。如果没有setter或没有原型包含该属性,则在适当的接收器上定义该值。这由Reflect.set处理。
  • enumerate trapfor...in loops的陷阱。我迭代第一个原型的可枚举属性,然后从第二个原型迭代,依此类推。迭代一个属性后,我将它存储在一个哈希表中,以避免再次迭代它。 警告:此陷阱已在ES7草稿中删除,在浏览器中已弃用。
  • ownKeys trapObject.getOwnPropertyNames()的陷阱。从ES7开始,for...in循环不断调用[[GetPrototypeOf]]并获取每个循环的属性。因此,为了使其迭代所有原型的属性,我使用此陷阱使所有可枚举的继承属性看起来像自己的属性。
  • getOwnPropertyDescriptor trapObject.getOwnPropertyDescriptor()的陷阱。使所有可枚举属性在ownKeys陷阱中看起来像自己的属性是不够的,for...in循环将获取描述符以检查它们是否可枚举。所以我使用find找到包含该属性的第一个原型,然后迭代它的原型链直到找到属性所有者,然后返回它的描述符。如果没有原型包含该属性,我将返回undefined。修改描述符以使其可配置,否则我们可能会破坏一些代理不变量。
  • 仅包含preventExtensionsdefineProperty陷阱以防止这些操作修改代理目标。否则我们最终可能会破坏一些代理不变量。

有更多的陷阱,我不使用

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

Example

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

0
投票

看看包装IeUnit

在IeUnit中实现的概念同化似乎以一种非常动态的方式提供了您正在寻找的东西。


0
投票

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

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

这个概念使用了Yehuda Katz对JavaScript的“类”的定义:

... JavaScript“class”只是一个Function对象,用作构造函数和附加的原型对象。 (Source: Guru Katz

Object.create approach不同,当类以这种方式构建并且我们想要创建“类”的实例时,我们不需要知道每个“类”继承的内容。我们只是使用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);

我们还可以修改原型,这将影响在类上构建的所有对象。

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

我最初用this answer写了一些。


0
投票

现场的后来者是SimpleDeclare。但是,在处理多重继承时,您仍然会得到原始构造函数的副本。这是Javascript中的必需品......

芝加哥商业交易所。


0
投票

我会用ds.oop。它类似于prototype.js和其他。使多重继承非常容易和极简主义。 (仅2或3 kb)还支持其他一些简洁的功能,如接口和依赖注入

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

0
投票

检查下面的代码,其中IS表示支持多重继承。通过使用PROTOTYPAL INHERITANCE完成

function A(name) {
    this.name = name;
}
A.prototype.setName = function (name) {

    this.name = name;
}

function B(age) {
    this.age = age;
}
B.prototype.setAge = function (age) {
    this.age = age;
}

function AB(name, age) {
    A.prototype.setName.call(this, name);
    B.prototype.setAge.call(this, age);
}

AB.prototype = Object.assign({}, Object.create(A.prototype), Object.create(B.prototype));

AB.prototype.toString = function () {
    return `Name: ${this.name} has age: ${this.age}`
}

const a = new A("shivang");
const b = new B(32);
console.log(a.name);
console.log(b.age);
const ab = new AB("indu", 27);
console.log(ab.toString());

11
投票

更新(2019年):原帖已经过时了。 This article(现在是互联网档案链接,因为域名消失了)及其相关的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();

6
投票

这个使用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


5
投票

我喜欢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上可以找到上述相当复杂的例子

请注意,该文件中有一些死代码,但如果您想查看,它允许多重继承。


如果你想要链式继承(不是多重继承,但是对于大多数人来说它是同样的事情),它可以通过类来实现:

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

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


4
投票

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

您需要做的就是使用Object.create()每次使用指定的原型对象和属性创建一个新对象,如果您计划将来实例化Object.prototype.constructor,请务必更改B的每一步。

要继承实例属性thisAthisB,我们在每个对象函数的末尾使用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();
  • BA继承了原型
  • CB继承了原型
  • objCC的一个例子

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

OOP In JavaScript: What You NEED to Know


2
投票

我不是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]());
    }
}

2
投票

可以在JavaScript中实现多重继承,尽管很少有库可以实现。

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


2
投票

我今天正在努力解决这个问题,并试图在ES6中实现这一目标。我这样做的方式是使用Browserify,Babel然后我用Wallaby测试它,它似乎工作。我的目标是扩展当前的Array,包括ES6,ES7,并在原型中添加一些我需要的自定义功能来处理音频数据。

Wallaby通过了我的4项测试。可以将example.js文件粘贴到控制台中,您可以看到'includes'属性位于类的原型中。我还想明天再试一次。

这是我的方法:(我很可能会在睡眠后重构并重新打包为模块!)

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 Repo:https://github.com/danieldram/array-includes-polyfill


2
投票

我认为这很荒谬。这里的问题是,子类只会引用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
© www.soinside.com 2019 - 2024. All rights reserved.