【问题标题】:Parasitic Inheritance in Javascript using pure Prototypal approach使用纯原型方法的 Javascript 中的寄生继承
【发布时间】:2015-06-23 07:50:49
【问题描述】:

我正在学习 JavaScript 中的 OOP,并且已经阅读过各种相同的帖子。据我所知,Douglas Crockford 为继承规定了 pure prototypal approach,而不是经典方法。

以下取自 here 的代码实现了 Crockford 的方法:

var superInstance = {
  member1: 'superMember1',
  member2: 'superMember2'
};

var subInstance = Object.create(superInstance);
subInstance.member3 = 'subMember3';

我了解 Crockford 的方法取消了构造函数(如果我错了,请纠正我)。这是否意味着使用这种方法初始化对象成员的唯一方法是使用上面代码所示的对象文字?另外,我如何使用这种方法实现寄生继承,以允许父类中的共享成员、私有变量和非标量值 (refer this post)?

Crockford 在他的文章中提到了“创客函数”,但没有给出任何示例代码。如果有人可以使用 Crockford 的纯原型方法演示寄生继承,那就太好了。

谢谢。

【问题讨论】:

  • “Crackford”...你成就了我的一天。
  • 不清楚您到底在问什么...所有示例都在您提供的链接中给出。 Object.create(obj) 只是创建一个对象并将其原型设置为obj。所以如果你有一个构造函数,你必须先创建一个对象。例如:Object.create(new Parent())。这与 parasitic 继承无关,当您在原型中定义一个字段时,它就会丢失,因为原型是在实例之间共享的。

标签: javascript oop inheritance


【解决方案1】:

关于原型继承的误解源于与经典继承相反,基础构造函数不被调用来实例化基础对象的问题。将原型设置为基础对象并不等同于经典继承,因为原型是在实例之间共享的。正如 Jimmy Breck-McKye 详细描述的那样。因此,要实现 parasitic 继承,您必须遵循两条规则。

  1. 切勿直接在原型中定义字段成员。
  2. 在实例化后继者时始终调用基本构造函数

后者可以使用Object.create 或将基础对象的实例直接分配给原型来实现。鉴于Base 是一个构造函数,继承代码将如下所示
方式#1

 function Base(){
 //a new object is created which is assigned to 'this'
 //this object has __proto__ === Base.prototype
     this.baseMember = 'I am the parent';
 }
 Base.prototype.baseMethod = function(){
     console.log('I am Base');
 };

 function Successor(){
 //a new object is created which is assigned to 'this'
 //this object has __proto__ === Successor.prototype

 //we override the object's property which is used for prototypal lookup
 //we lose members defined in Successor.prototype
      this.__proto__ = new Base();
 //we add a new property in the inherited object
      this.successorMember = 'I am a child';
 }
 Successor.prototype.successorMethod = function(){
     console.log('I am Successor');
 };

我们将通过以下方式使用定义的构造函数

var child = new Successor();
//resulting in structure
//child: { //instance of Successor
//  successorMember: 'I am a child', 
//  __proto__: {//instance of Base
//     baseMember: 'I am the parent'
//     __proto__: {//Base.prototype
//        baseMethod : function 
//  }}}
console.log(child.successorMember);//accessible via direct property
console.log(child.baseMember);//accessible via prototype lookup
console.log('baseMethod' in child);//true, accessible via prototype lookup
console.log('successorMethod' in child);//false, method doesn't exist anywhere in the chain

注意通过Successor.prototype 定义的缺少的successorMethod。发生这种情况是因为我们覆盖了对象的 __proto__ 属性。

方式#2
覆盖__proto__ 属性的另一种方法是调用Object.create。然而这个函数返回一个新对象,因此我们将不得不覆盖Successor构造函数返回的对象

 function Successor(){
 //a new object #1 is created which is assigned to 'this'
 //this object has __proto__ === Successor.prototype

 //a new instance #2 of Base is created with __proto__ === Base.prototype
 //a new object #3 is created with a __proto__ set to #2
      var successor = Object.create(new Base());
 //a new property is added to #1
      this.neverShowMember = 'I will not exist in resulting object';
 //a new property is added to #3 
      successor.successorMember = 'I am a child';
 //return of a non-primitive type object from constructor overrides the result
      return successor;//return object #3
 }

让我们详细研究一下这种方法的用法:

var child = new Successor();
//child: { //instance of Object
//  successorMember: 'I am a child', 
//  __proto__: {//instance of Base
//     baseMember: 'I am the parent'
//     __proto__: {//Base.prototype
//        baseMethod : function 
//  }}}
console.log(child.successorMember);//accessible via direct property
console.log(child.baseMember);//accessible via prototype lookup
console.log('baseMethod' in child);//true, accessible via prototype lookup
console.log('successorMethod' in child);//false, method doesn't exist anywhere in the chain

产生的行为几乎相同。请注意缺少的neverShowMember,尽管它是在构造函数中为this 定义的。这可能是错误的根源。

方式#3
另一种继承方式是不要乱用 proto 链。这种方法在 Jimmy Breck-McKye 的文章中有所描述。我将跳过之前提供的详细 cmets,并将重点关注更改

 function Successor(){
 //a new instance  Base is created with __proto__ === Base.prototype
      var successor = new Base();
 //extend object with a new property 
      successor.successorMember = 'I am a child';
      return successor;//return instance of Base with extra properties
 }

 var child = new Successor();
//child: { //instance of Base
//  successorMember: 'I am a child', 
//  baseMember: 'I am the parent'
//  __proto__: {//Base.prototype
//        baseMethod : function 
//  }} 
console.log(child.successorMember);//accessible via direct property
console.log(child.baseMember);//accessible via direct property
console.log('baseMethod' in child);//true, accessible via prototype lookup
console.log('successorMethod' in child);//false, method doesn't exist anywhere in the chain

您会看到架构变得扁平。作为一个明显的结论,如果您覆盖它们,您将无法访问它们。所以如果在 Successor 内部我们定义

successor.baseMember = 'I am already grown enough!';

实例 (child) 将失去对等于“我是父母”的baseIntance.baseMember 的访问权限。与以前的方法相反,它可以通过child.__proto__.baseMember 访问。但我相信这在用 javascript 开发时并不常见,应该在另一个问题下讨论。

注意,在所有情况下,Successor.prototype 中定义的成员都会丢失。您应该注意在 Successor 构造函数中手动复制它们。

结论
我希望这个描述足够清楚,可以理解 CraCrockford 的 object 函数

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

总是需要一个作为参数o 传递的新实例来实现寄生继承。因此它的用法应该如下所示

var child = object(new Base());
child.successorMember = 'I am a child';

同样适用于 OP 的代码。要遵循 寄生 继承,superInstance 每次传递给 Object.create 时都应该是一个新实例。因此它应该是一个工厂函数

var superInstance = function(){
    return {
      member1: 'superMember1',
      member2: 'superMember2'
    }
};

var subInstance = Object.create(superInstance());

或构造函数

function superInstance(){
    this.member1: 'superMember1',
    this.member2: 'superMember2'
};

var subInstance = Object.create(new superInstance());

希望对你有帮助

【讨论】:

  • 嗨,Kirill,您能否详细说明最后一部分与 create(new Base) 与 create(baseInstance) 的关系。是 create(new Base) 类似于“new Object() ,如下所示breck-mckye.com/blog/2014/05/why-i-prefer-parasitic-inheritance (向下滚动 1/3)。
  • 这很简单......将父对象分配到原型和扩展它之间有一个更复杂的区别......我会考虑更好的措辞并在一段时间内更新关于这两个问题的答案。
  • 谢谢,@Kirill 今晚会去看看。
  • 我仍在解散这个概念,但这真的很有帮助且清晰,非常感谢。
猜你喜欢
  • 1970-01-01
  • 2013-05-09
  • 1970-01-01
  • 1970-01-01
  • 2015-03-19
  • 1970-01-01
  • 2016-07-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多