Javascript中的寄生继承使用纯Prototypal方法

Parasitic Inheritance in Javascript using pure Prototypal approach

本文关键字:Prototypal 方法 继承 寄生 Javascript      更新时间:2023-09-26

我正在用JavaScript学习OOP,并浏览了关于同一领域的各种帖子。据我所知,道格拉斯·克罗克福德(Douglas Crockford(规定了一种纯粹的原型遗传方法,而不是经典方法。

从这里获取的以下代码实现了 Crockford 的方法:

var superInstance = {
  member1: 'superMember1',
  member2: 'superMember2'
};
var subInstance = Object.create(superInstance);
subInstance.member3 = 'subMember3';

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

Crockford在他的文章中提到了"制造商函数",但没有给出任何示例代码。如果有人可以使用Crockford的纯原型方法证明寄生遗传,那就太好了。

谢谢。

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

  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
另一种继承方式是不要乱用原始链。这种方法在Jimmy Breck-McKye的文章中有所描述。我将跳过之前提供的详细评论,并将重点放在更改上

 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

结论
我希望这个描述足够清晰,可以理解

克拉·克罗克福德的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());

希望这有帮助