JavaScript中的多重继承/原型

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

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

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

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

思考?

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


Mixins可以用于JavaScript,以实现您可能想通过多重继承来解决的相同目标。


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

  • 可以添加getPrototypeOf陷阱,但没有正确的方法来返回多个原型。 这意味着instanceof也不会工作。 因此,我让它得到目标的原型,最初为空。
  • 可以添加setPrototypeOf陷阱并接受一组对象,这些对象将取代原型。 这留给读者一个练习。 在这里,我只是让它修改目标的原型,这没什么用处,因为没有陷阱使用目标。
  • deleteProperty陷阱是删除自己的属性的陷阱。 代理代表继承,所以这没有多大意义。 我让它试图删除目标,无论如何应该没有财产。
  • isExtensible陷阱是获得可扩展性的陷阱。 鉴于不变量迫使它返回与目标相同的可扩展性,它并没有多大用处。 所以我只是让它将操作重定向到可扩展的目标。
  • 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"
    

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

    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();
    
    链接地址: http://www.djcxy.com/p/27235.html

    上一篇: Multiple inheritance/prototypes in JavaScript

    下一篇: Relation between [[Prototype]] and prototype in JavaScript