【问题标题】:javascript prototypical inheritance confusedjavascript原型继承混淆
【发布时间】:2016-05-13 12:12:22
【问题描述】:

考虑到像这样实现继承的标准方式

function BaseClass() {

}

function SubClass() {
    BaseClass.call(this);
}

SubClass.prototype = Object.create(BaseClass.prototype);
SubClass.prototype.constructor = SubClass;

为什么要这样做

SubClass.prototype = Object.create(BaseClass.prototype);

最终得到类似的东西

function F(){}
F.prototype = BaseClass.prototype;
SubClass.prototype = new F();

而不仅仅是做

Subclass.prototype = BaseClass.prototype;

【问题讨论】:

  • 如果你只是做Subclass.prototype = BaseClass.prototype 那么Sub 和Base 将有相同的原型——也就是说,它们的原型属性将引用相同的对象。您提到的其他技术使 Sub 的原型成为一个单独的对象,这是人们通常想要的。
  • 如果你熟悉其他语言的OO。我强烈建议您将函数命名为 BaseConstructor()SubConstructor(),因为它们就是这样。原型是实际的“类”,函数是构造函数。这使得考虑 javascript 变得更容易。在其他语言中,构造函数属于一个类,而在 js 中,类(原型)属于构造函数。

标签: javascript oop inheritance prototype


【解决方案1】:

在 JavaScript 中为事物赋值实际上只是复制一个引用(除非使用原始类型)。所以当你这样做时:

Subclass.prototype = BaseClass.prototype;

真正所做的是将SubClass 的原型分配到与BaseClass 的原型相同的内存位置,因此您对SubClass 所做的任何与原型相关的更改都会也影响BaseClass。这是一个小例子:

function BaseClass() {

}

function SubClass() {
    BaseClass.call(this);
}

SubClass.prototype = BaseClass.prototype;
SubClass.prototype.constructor = SubClass;

SubClass.prototype.subClassFunction = function(){
    console.log("Added this to SubClass");
}

var baseObj = new BaseClass();
baseObj.subClassFunction(); // => "Added this to SubClass"

这就是你想要使用的原因

SubClass.prototype = Object.create(BaseClass.prototype);

因为它会用指定的原型创建一个 newunique 对象。

您可以阅读有关此功能如何工作的更多信息here

【讨论】:

    【解决方案2】:

    对象参考!

    BaseClass有一个方法toString

    BaseClass.prototype.toString = function() {
      return 'foo'
    }
    

    但是你在SubClass中重新定义了toString方法:

    SubClass.prototype.toString = function() {
      return 'bar'
    }
    

    你会期望:

    var b = new BaseClass(), s = new SubClass()
    b.toString() //=> 'foo'
    s.toString() //=> 'bar'
    

    但是如果你使用赋值来创建继承,你会得到:

    var b = new BaseClass(), s = new SubClass()
    b.toString() //=> 'bar'
    s.toString() //=> 'bar'
    

    因为BaseClass.prototypeSubClass.prototype 现在引用同一个对象

    【讨论】:

      【解决方案3】:

      继承,__proto__

      当一个对象 SubClass 继承自另一个对象 BaseClass 时,在 JavaScript 中意味着有一个特殊的属性 SubClass.__proto__ = BaseClass

      代码:

      function BaseClass() {
      }
      
      function SubClass() {
      }
      
      var BaseClass = new BaseClass();
      var SubClass = new SubClass();
      
      BaseClass.a = 5;
      SubClass.b = 10;
      
      SubClass.__proto__ = BaseClass;
      
      console.log(SubClass);
      

      输出:

      这里,BaseClassSubClassBaseClass variable is accessable through the SubClass 继承。

      【讨论】:

      • 这将允许静态成员继承,而不是实例成员继承。这不是问题所在。
      【解决方案4】:

      JavaScript 的语法可能有些混乱。在 JS 中,没有类继承,而是基于实例的继承。实例的父级也称为其原型。

      当您编写instance.somethinginstance['something'] 时,JS 引擎会查看该实例是否有一个名为something 的成员。如果没有,那么它会查看实例的原型。如果该对象没有成员something,它会一次又一次地查看原型的原型,直到找到属性或到达继承自null 的实例。在这种情况下,它只会返回 undefined

      函数有一个称为prototype 的特殊属性。 函数的.prototype 是一个简单的对象,它有一个constructor 属性,它指向函数本身。当您使用new 关键字创建对象时,该对象的原型将设置为函数的.prototype 属性。这就是混淆的来源:构造函数的.prototype 属性可以看作是使用该构造函数创建的所有实例的默认原型。

      因此,当您通过编写如下内容向类添加方法时:

      MyClass.prototype.foo = function() {
        alert('foo');
      };
      

      ...您实际上将函数存储在所有 MyClass 实例的原型中。当 JS 引擎查看 MyClass 的实例时,它会寻找它找不到的 foo 成员。然后它会查看恰好设置为MyClass.prototype 的实例原型,它会找到foo 成员并获取它。

      区分实例的原型和函数的.prototype 很重要,但大多数人没有意识到这一点。当他们谈到一个类的原型时,他们指的是MyClass.prototype。在许多浏览器中,可以通过__proto__ 访问实例原型,但这不是 JavaScript 的标准功能,不应在您的代码中使用。

      现在让我们看看您用来模拟类继承的代码。

      SubClass.prototype = Object.create(BaseClass.prototype);
      SubClass.prototype.constructor = SubClass;
      

      Object.create(parent) 可以看作是执行此操作的函数:

      return {
        __proto__ : parent
      };
      

      换句话说,它创建了一个空白对象,其原型是传递的对象。由于所有 SubClass 实例都将继承自 Subclass.prototype,因此将 SubClass.prototype 替换为继承自 BaseClass.prototype 的对象可确保所有 SubClass 实例也继承自 BaseClass。

      但是,正如我之前所说,函数的默认.prototype 属性是一个空对象,其.constructor 设置为函数本身。因此,通过再次手动设置.constructor,我们完美地模仿了默认原型行为。如果我们不这样做,那么instance.constructor 将返回原型链中第一个定义的.constructor 属性,即BaseClass。除非我们的代码实际上依赖于 constructor 属性,否则它不会真正改变任何行为,但拥有它会更安全。

      最后一点,就像我之前提到的其他人一样,我终于可以发布这个答案了,你不能只做SubClass.prototype = BaseClass.prototype;,因为那样的话,如果不将方法添加到基类中,你就无法将它们添加到子类中。

      【讨论】:

      • 注意:Mozilla 开发者网络调用实例原型[[Prototype]] 以区别于MyClass.prototype 并避免鼓励使用__proto__
      猜你喜欢
      • 2016-11-19
      • 2015-10-12
      • 1970-01-01
      • 1970-01-01
      • 2011-04-25
      • 1970-01-01
      • 2015-05-13
      • 2012-09-13
      • 1970-01-01
      相关资源
      最近更新 更多