JavaScript中的多重继承/原型

时间:2012-02-06 16:19:46

标签: javascript prototype multiple-inheritance

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

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

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

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

思想?

17 个答案:

答案 0 :(得分:37)

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

实施

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 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陷阱是用于调用或实例化的陷阱。它们仅在目标是函数或构造函数时才有用。

实施例

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

答案 1 :(得分: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();

答案 2 :(得分:5)

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

答案 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 :(得分:2)

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

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

答案 5 :(得分: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]());
    }
}

答案 6 :(得分:2)

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

Wallaby通过了我的4项测试。可以将example.js文件粘贴到控制台中,您可以看到&#39;包含&#39;属性是该类的原型。我还想明天再测试一下。

这是我的方法:(经过一段时间的睡眠后,我很可能会重构并重新包装成模块!)

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

答案 7 :(得分: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

答案 8 :(得分:1)

我有相当的功能允许使用多个继承定义类。它允许如下代码。总体而言,您会注意到完全不同于javascript中的本机分类技术(例如,您永远不会看到class关键字):

let human = new Running({ name: 'human', numLegs: 2 });
human.run();

let airplane = new Flying({ name: 'airplane', numWings: 2 });
airplane.fly();

let dragon = new RunningFlying({ name: 'dragon', numLegs: 4, numWings: 6 });
dragon.takeFlight();

产生这样的输出:

human runs with 2 legs.
airplane flies away with 2 wings!
dragon runs with 4 legs.
dragon flies away with 6 wings!

这是类定义的样子:

let Named = makeClass('Named', {}, () => ({
  init: function({ name }) {
    this.name = name;
  }
}));

let Running = makeClass('Running', { Named }, protos => ({
  init: function({ name, numLegs }) {
    protos.Named.init.call(this, { name });
    this.numLegs = numLegs;
  },
  run: function() {
    console.log(`${this.name} runs with ${this.numLegs} legs.`);
  }
}));

let Flying = makeClass('Flying', { Named }, protos => ({
  init: function({ name, numWings }) {
    protos.Named.init.call(this, { name });
    this.numWings = numWings;
  },
  fly: function( ){
    console.log(`${this.name} flies away with ${this.numWings} wings!`);
  }
}));

let RunningFlying = makeClass('RunningFlying', { Running, Flying }, protos => ({
  init: function({ name, numLegs, numWings }) {
    protos.Running.init.call(this, { name, numLegs });
    protos.Flying.init.call(this, { name, numWings });
  },
  takeFlight: function() {
    this.run();
    this.fly();
  }
}));

我们可以看到,每个使用makeClass函数的类定义都接受Object个映射到父类的父类名称。它还接受一个函数,该函数返回包含所定义类的属性的Object。此函数具有参数protos,该参数包含足够的信息来访问由任何父类定义的任何属性。

所需的最后一部分是makeClass函数本身,它可以完成很多工作。在这里,以及其余的代码。我对makeClass的评价很高:

let makeClass = (name, parents={}, propertiesFn=()=>({})) => {
  
  // The constructor just curries to a Function named "init"
  let Class = function(...args) { this.init(...args); };
  
  // This allows instances to be named properly in the terminal
  Object.defineProperty(Class, 'name', { value: name });
  
  // Tracking parents of `Class` allows for inheritance queries later
  Class.parents = parents;
  
  // Initialize prototype
  Class.prototype = Object.create(null);
  
  // Collect all parent-class prototypes. `Object.getOwnPropertyNames`
  // will get us the best results. Finally, we'll be able to reference
  // a property like "usefulMethod" of Class "ParentClass3" with:
  // `parProtos.ParentClass3.usefulMethod`
  let parProtos = {};
  for (let parName in parents) {
    let proto = parents[parName].prototype;
    parProtos[parName] = {};
    for (let k of Object.getOwnPropertyNames(proto)) {
      parProtos[parName][k] = proto[k];
    }
  }
  
  // Resolve `properties` as the result of calling `propertiesFn`. Pass
  // `parProtos`, so a child-class can access parent-class methods, and
  // pass `Class` so methods of the child-class have a reference to it
  let properties = propertiesFn(parProtos, Class);
  properties.constructor = Class; // Ensure "constructor" prop exists
  
  // If two parent-classes define a property under the same name, we
  // have a "collision". In cases of collisions, the child-class *must*
  // define a method (and within that method it can decide how to call
  // the parent-class methods of the same name). For every named
  // property of every parent-class, we'll track a `Set` containing all
  // the methods that fall under that name. Any `Set` of size greater
  // than one indicates a collision.
  let propsByName = {}; // Will map property names to `Set`s
  for (let parName in parProtos) {
    
    for (let propName in parProtos[parName]) {
      
      // Now track the property `parProtos[parName][propName]` under the
      // label of `propName`
      if (!propsByName.hasOwnProperty(propName))
        propsByName[propName] = new Set();
      propsByName[propName].add(parProtos[parName][propName]);
      
    }
    
  }
  
  // For all methods defined by the child-class, create or replace the
  // entry in `propsByName` with a Set containing a single item; the
  // child-class' property at that property name (this also guarantees
  // there is no collision at this property name). Note property names
  // prefixed with "$" will be considered class properties (and the "$"
  // will be removed).
  for (let propName in properties) {
    if (propName[0] === '$') {
      
      // The "$" indicates a class property; attach to `Class`:
      Class[propName.slice(1)] = properties[propName];
      
    } else {
      
      // No "$" indicates an instance property; attach to `propsByName`:
      propsByName[propName] = new Set([ properties[propName] ]);
      
    }
  }
  
  // Ensure that "init" is defined by a parent-class or by the child:
  if (!propsByName.hasOwnProperty('init'))
    throw Error(`Class "${name}" is missing an "init" method`);
  
  // For each property name in `propsByName`, ensure that there is no
  // collision at that property name, and if there isn't, attach it to
  // the prototype! `Object.defineProperty` can ensure that prototype
  // properties won't appear during iteration with `in` keyword:
  for (let propName in propsByName) {
    let propsAtName = propsByName[propName];
    if (propsAtName.size > 1)
      throw new Error(`Class "${name}" has conflict at "${propName}"`);
    
    Object.defineProperty(Class.prototype, propName, {
      enumerable: false,
      writable: true,
      value: propsAtName.values().next().value // Get 1st item in Set
    });
  }
  
  return Class;
};

let Named = makeClass('Named', {}, () => ({
  init: function({ name }) {
    this.name = name;
  }
}));

let Running = makeClass('Running', { Named }, protos => ({
  init: function({ name, numLegs }) {
    protos.Named.init.call(this, { name });
    this.numLegs = numLegs;
  },
  run: function() {
    console.log(`${this.name} runs with ${this.numLegs} legs.`);
  }
}));

let Flying = makeClass('Flying', { Named }, protos => ({
  init: function({ name, numWings }) {
    protos.Named.init.call(this, { name });
    this.numWings = numWings;
  },
  fly: function( ){
    console.log(`${this.name} flies away with ${this.numWings} wings!`);
  }
}));

let RunningFlying = makeClass('RunningFlying', { Running, Flying }, protos => ({
  init: function({ name, numLegs, numWings }) {
    protos.Running.init.call(this, { name, numLegs });
    protos.Flying.init.call(this, { name, numWings });
  },
  takeFlight: function() {
    this.run();
    this.fly();
  }
}));

let human = new Running({ name: 'human', numLegs: 2 });
human.run();

let airplane = new Flying({ name: 'airplane', numWings: 2 });
airplane.fly();

let dragon = new RunningFlying({ name: 'dragon', numLegs: 4, numWings: 6 });
dragon.takeFlight();

makeClass函数还支持类属性。这些属性是通过在属性名称前加上$符号来定义的(请注意,结果的最终属性名称将删除$)。考虑到这一点,我们可以编写一个专门的Dragon类来对Dragon的“类型”进行建模,其中可用Dragon类型的列表存储在Class本身上,而不是实例上:

let Dragon = makeClass('Dragon', { RunningFlying }, protos => ({

  $types: {
    wyvern: 'wyvern',
    drake: 'drake',
    hydra: 'hydra'
  },

  init: function({ name, numLegs, numWings, type }) {
    protos.RunningFlying.init.call(this, { name, numLegs, numWings });
    this.type = type;
  },
  description: function() {
    return `A ${this.type}-type dragon with ${this.numLegs} legs and ${this.numWings} wings`;
  }
}));

let dragon1 = new Dragon({ name: 'dragon1', numLegs: 2, numWings: 4, type: Dragon.types.drake });
let dragon2 = new Dragon({ name: 'dragon2', numLegs: 4, numWings: 2, type: Dragon.types.hydra });

多重继承的挑战

密切关注makeClass代码的任何人都会注意到,在上述代码运行时,无声地出现了相当严重的不良现象:实例化RunningFlying将导致两次调用{{1 }}构造函数!

这是因为继承图看起来像这样:

Named

当子类的继承图中有通往同一父类的多个路径时,子类的实例化将多次调用该父类的构造函数。

与之抗争并非易事。让我们看一些使用简化的类名的示例。我们将考虑类 (^^ More Specialized ^^) RunningFlying / \ / \ Running Flying \ / \ / Named (vv More Abstract vv) (最抽象的父类),类AB,它们都从C继承,而类A继承来自BCB(因此从概念上来说,来自C是“双重继承”):

A

如果我们想防止let A = makeClass('A', {}, () => ({ init: function() { console.log('Construct A'); } })); let B = makeClass('B', { A }, protos => ({ init: function() { protos.A.init.call(this); console.log('Construct B'); } })); let C = makeClass('C', { A }, protos => ({ init: function() { protos.A.init.call(this); console.log('Construct C'); } })); let BC = makeClass('BC', { B, C }, protos => ({ init: function() { // Overall "Construct A" is logged twice: protos.B.init.call(this); // -> console.log('Construct A'); console.log('Construct B'); protos.C.init.call(this); // -> console.log('Construct A'); console.log('Construct C'); console.log('Construct BC'); } })); 再次调用BC,我们可能需要放弃直接调用继承的构造函数的样式。我们将需要某种程度的间接检查来检查是否发生重复的呼叫,并在发生重复之前将其短路。

我们可以考虑更改提供给properties函数的参数:在A.prototype.init(包含描述继承属性的原始数据的protos)旁边,我们还可以包括一个实用程序函数,用于在这种情况下调用实例方法父方法也被调用的方式,但是检测到并防止了重复调用。让我们看一下在哪里为Object propertiesFn建立参数:

Function

let makeClass = (name, parents, propertiesFn) => { /* ... a bunch of makeClass logic ... */ // Allows referencing inherited functions; e.g. `parProtos.ParentClass3.usefulMethod` let parProtos = {}; /* ... collect all parent methods in `parProtos` ... */ // Utility functions for calling inherited methods: let util = {}; util.invokeNoDuplicates = (instance, fnName, args, dups=new Set()) => { // Invoke every parent method of name `fnName` first... for (let parName of parProtos) { if (parProtos[parName].hasOwnProperty(fnName)) { // Our parent named `parName` defines the function named `fnName` let fn = parProtos[parName][fnName]; // Check if this function has already been encountered. // This solves our duplicate-invocation problem!! if (dups.has(fn)) continue; dups.add(fn); // This is the first time this Function has been encountered. // Call it on `instance`, with the desired args. Make sure we // include `dups`, so that if the parent method invokes further // inherited methods we don't lose track of what functions have // have already been called. fn.call(instance, ...args, dups); } } }; // Now we can call `propertiesFn` with an additional `util` param: // Resolve `properties` as the result of calling `propertiesFn`: let properties = propertiesFn(parProtos, util, Class); /* ... a bunch more makeClass logic ... */ }; 进行上述更改的全部目的是,使我们在调用makeClass时为propertiesFn提供一个附加参数。我们还应该知道,在任何类中定义的每个函数现在都可以在所有其他函数之后接收到一个名为makeClass的参数,该参数是dup,其中包含由于以下原因而被调用的所有函数调用继承的方法:

Set

这种新样式实际上成功地确保了let A = makeClass('A', {}, () => ({ init: function() { console.log('Construct A'); } })); let B = makeClass('B', { A }, (protos, util) => ({ init: function(dups) { util.invokeNoDuplicates(this, 'init', [ /* no args */ ], dups); console.log('Construct B'); } })); let C = makeClass('C', { A }, (protos, util) => ({ init: function(dups) { util.invokeNoDuplicates(this, 'init', [ /* no args */ ], dups); console.log('Construct C'); } })); let BC = makeClass('BC', { B, C }, (protos, util) => ({ init: function(dups) { util.invokeNoDuplicates(this, 'init', [ /* no args */ ], dups); console.log('Construct BC'); } })); 的实例初始化时仅记录一次"Construct A"。但是存在三个缺点,其中第三个是非常关键

  1. 此代码的可读性和可维护性越来越差。 BC函数的背后隐藏着许多复杂性,而思考这种样式如何避免多次调用是非直觉和令人头痛的。我们也有那个令人讨厌的util.invokeNoDuplicates参数,它确实需要在该类中的每个单个函数上定义。哎呀。
  2. 此代码较慢-间接性要高得多,并且需要进行计算才能获得具有多个继承的理想结果。不幸的是,任何解决方案可以解决我们的多次调用问题。
  3. 最重要的是,依赖继承的功能结构变得非常僵化 。如果子类dups覆盖函数NiftyClass,并使用niftyFunction来运行它而没有重复调用,则util.invokeNoDuplicates(this, 'niftyFunction', ...)将调用名为NiftyClass.prototype.niftyFunction的函数。每个定义它的父类,都将忽略这些类的任何返回值,并最终执行niftyFunction的专用逻辑。这是唯一可能的结构。如果NiftyClass.prototype.niftyFunction继承了NiftyClassCoolClass,并且这两个父类都提供了自己的GoodClass定义,则niftyFunction将永远不会(不会冒着多次调用的危险)能够:
    • A。首先运行NiftyClass.prototype.niftyFunction的专用逻辑,然后然后运行父类的专用逻辑
    • B。。在所有专用父逻辑都已完成之后,在除之外的任何其他位置运行NiftyClass的专用逻辑。
    • C 。根据其父级专用逻辑的返回值有条件地进行操作
    • D。避免完全运行特定父母的专门NiftyClass

当然,我们可以通过在niftyFunction下定义专用功能来解决上述每个字母问题:

  • A。定义util
  • B。定义util.invokeNoDuplicatesSubClassLogicFirst(instance, fnName, ...)(其中util.invokeNoDuplicatesSubClassAfterParent(parentName, instance, fnName, ...)是父级的名称,其专用逻辑将紧随子类的专用逻辑)
  • C。定义parentName(在这种情况下,util.invokeNoDuplicatesCanShortCircuitOnParent(parentName, testFn, instance, fnName, ...)将收到名为testFn的父级的专用逻辑结果,并返回{ {1}}值,指示是否应发生短路)
  • D。定义parentName(在这种情况下,true/false将是util.invokeNoDuplicatesBlackListedParents(blackList, instance, fnName, ...)个父级名称,其专用逻辑应完全跳过)

这些解决方案都可用,但这完全是混乱!对于继承的函数调用可以采用的每个唯一结构,我们需要在blackList下定义的专用方法。真是一场灾难。

考虑到这一点,我们可以开始看到实现良好的多重继承的挑战。我在此答案中提供的Array的完整实现甚至没有考虑多重调用问题,也没有考虑与多重继承有关的许多其他问题。

这个答案越来越长。我希望我所包含的util实现仍然有用,即使它不是完美的。我也希望对这个主题感兴趣的人在继续阅读时,要记住更多的上下文!

答案 9 :(得分:0)

查看包IeUnit

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

答案 10 :(得分: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的&#34; class&#34; 的定义:

  

...一个JavaScript&#34;类&#34;只是一个Function对象,用作构造函数和附加的原型对象。 (Source: Guru Katz

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

答案 11 :(得分:0)

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

Merc的。

答案 12 :(得分: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();

答案 13 :(得分:0)

检查下面的代码,该代码表示​​支持多重继承。通过使用原型继承

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

答案 14 :(得分:0)

这如何,它在JavaScript中实现了多重继承:

    class Car {
        constructor(brand) {
            this.carname = brand;
        }
        show() {
            return 'I have a ' + this.carname;
        }
    }

    class Asset {
        constructor(price) {
            this.price = price;
        }
        show() {
            return 'its estimated price is ' + this.price;
        }
    }

    class Model_i1 {        // extends Car and Asset (just a comment for ourselves)
        //
        constructor(brand, price, usefulness) {
            specialize_with(this, new Car(brand));
            specialize_with(this, new Asset(price));
            this.usefulness = usefulness;
        }
        show() {
            return Car.prototype.show.call(this) + ", " + Asset.prototype.show.call(this) + ", Model_i1";
        }
    }

    mycar = new Model_i1("Ford Mustang", "$100K", 16);
    document.getElementById("demo").innerHTML = mycar.show();

这是specialize_with()实用程序功能的代码:

function specialize_with(o, S) { for (var prop in S) { o[prop] = S[prop]; } }

这是运行的真实代码。您可以将其复制粘贴到html文件中,然后自己尝试。确实可以。

这是在JavaScript中实现MI的努力。代码不多,更多的是专有技术。

请随时阅读我关于https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS

的完整文章

答案 15 :(得分:0)

我只是用来在其他人的属性中分配我需要的类,并添加一个代理以自动指向我喜欢的东西

class A {
    constructor()
    {
        this.test = "a test";
    }

    method()
    {
        console.log("in the method");
    }
}

class B {
    constructor()
    {
        this.extends = [new A()];

        return new Proxy(this, {
            get: function(obj, prop) {

                if(prop in obj)
                    return obj[prop];

                let response = obj.extends.find(function (extended) {
                if(prop in extended)
                    return extended[prop];
            });

            return response ? response[prop] : Reflect.get(...arguments);
            },

        })
    }
}

let b = new B();
b.test ;// "a test";
b.method(); // in the method

答案 16 :(得分:0)

我们可以通过这种方式在构造函数中实现多重继承。它会正常工作,但我不确定这是否是一个好习惯:) 谢谢

const Person = function(name) {
  this.name = name;
}

Person.prototype.getName = function() {
  return this.name;
}

const Batch = function(year, course) {
  this.year = year;
  this.course = course;
}

Batch.prototype.getCourse = function() {
  return this.course;
}
const Student = function(name, year, course) {
  Person.call(this, name);
  Batch.call(this, year, course);
}

const multipleInherit = {...Batch.prototype, ...Person.prototype}
Student.prototype = Object.create(multipleInherit);

Student.prototype.getDetails = function() {
  return `Name: ${this.name} and Batch: ${this.year}`;
}


const john = new Student('john', 1994, 'BCA');


console.log(john.getDetails())
console.log(john.getName())
console.log(john.getCourse())