Javascript Prototypal继承?

问题描述 投票:16回答:5

我在js做了一些继承,以便更好地理解它,我发现了让我困惑的东西。

我知道当你用new关键字调用'constructor function'时,你得到一个新对象,引用该函数的原型。

我也知道,为了进行原型继承,你必须用你想成为'超类'的对象的实例替换构造函数的原型。

所以我做了这个愚蠢的例子来尝试这些概念:

function Animal(){}
function Dog(){}

Animal.prototype.run = function(){alert("running...")};

Dog.prototype = new Animal(); 
Dog.prototype.bark = function(){alert("arf!")};

var fido = new Dog();
fido.bark() //ok
fido.run() //ok

console.log(Dog.prototype) // its an 'Object' 
console.log(fido.prototype) // UNDEFINED
console.log(fido.constructor.prototype == Dog.prototype) //this is true

function KillerDog(){};
KillerDog.prototype.deathBite = function(){alert("AAARFFF! *bite*")}

fido.prototype = new KillerDog();

console.log(fido.prototype) // no longer UNDEFINED
fido.deathBite(); // but this doesn't work!

(这是在Firebug的控制台中完成的)

1)为什么如果所有新对象都包含对creator函数原型的引用,fido.prototype是未定义的?

2)继承链[obj] - > [constructor] - > [prototype]而不是[obj] - > [prototype]?

3)我们的对象(fido)的“原型”属性是否曾被检查过?如果是这样的话......为什么'deathBite'未定义(在最后一部分)?

javascript inheritance prototype prototypal-inheritance
5个回答
12
投票

1)为什么如果所有新对象都包含对creator函数原型的引用,fido.prototype是未定义的?

所有新对象都会在构造时对其构造函数中存在的原型进行引用。但是,用于存储此引用的属性名称不是prototype,因为它在构造函数本身上。一些Javascript实现允许通过某些属性名称(如__proto__)访问此“隐藏”属性,而其他属性名称则不允许(例如微软)。

2)继承链[obj] - > [constructor] - > [prototype]而不是[obj] - > [prototype]?

不。看看这个: -

function Base() {}
Base.prototype.doThis = function() { alert("First"); }

function Base2() {}
Base2.prototype.doThis = function() { alert("Second"); }

function Derived() {}
Derived.prototype = new Base()

var x = new Derived()

Derived.prototype = new Base2()

x.doThis();

这警告“第一”不是第二。如果继承链通过构造函数进行,我们将看到“Second”。构造对象时,函数prototype属性中保存的当前引用将转移到其原型的对象隐藏引用。

3)我们的对象(fido)的“原型”属性是否曾被检查过?如果是这样的话......为什么'deathBite'未定义(在最后一部分)?

分配给对象(函数除外)一个名为prototype的属性没有特殊含义,如前所述,对象不通过这样的属性名维护对其原型的引用。


7
投票

一旦用new实例化了对象的原型,就无法对其进行更改。

在上面的例子中,行如

fido.prototype = new KillerDog();

只需在对象prototype上创建一个名为fido的新属性,并将该属性设置为新的KillerDog对象。它没有什么不同

fido.foo = new KillerDog();

你的代码就是......

// Doesn't work because objects can't be changed via their constructors
fido.deathBite();

// Does work, because objects can be changed dynamically, 
// and Javascript won't complain when you use prototype 
//as an object attribute name
fido.prototype.deathBite();

特殊的prototype行为仅适用于javascript中的构造函数,其中构造函数是functions,将使用new调用。


4
投票

用数字回答你的问题:

  1. Object的prototype属性不称为prototype。该标准使用[[prototype]]来指定它。 Firefox以__proto__的名称公开此属性。
  2. 继承链是[obj][prototype object]。你原来的假设([obj][constructor][prototype])是不正确的,你可以通过修改constructor和/或constructor.prototype,并检查你的[obj]上可以调用哪些方法来轻易反驳它 - 你会发现这些修改不会改变任何东西。
  3. 不检查和不使用对象上的prototype属性。您可以将其设置为您喜欢的任何内容。 JavaScript仅在对象构造期间在函数对象上使用它。

为了演示#3,这里是来自Dojo的代码:

dojo.delegate = dojo._delegate = (function(){
  // boodman/crockford delegation w/ cornford optimization
  function TMP(){}
  return function(obj, props){
    TMP.prototype = obj;
    var tmp = new TMP();
    if(props){
      dojo._mixin(tmp, props);
    }
    return tmp; // Object
  }
})();

正如您所看到的那样,利用prototype仅在一个地方使用相同的函数TMP用于具有不同原型的所有委托对象的事实。实际上prototype是在使用new调用函数之前直接分配的,并且在不影响任何创建的对象之后它将被更改。

您可以在我对Relation between [[Prototype]] and prototype in JavaScript的回答中找到对象创建的序列。


2
投票

我知道它已经得到了解答但是,有一种更好的方法可以继承。仅仅为了继承而调用构造函数是不可取的。其中一个不受欢迎的影响是。

function Base() {this.a = "A"}
function Child() {this.b = "B"};

Child.prototype = new Base();

现在,您已将属性“a”添加到您不想要的Child原型中。

这是正确的方法(我没有发明这个,Ext-JS和其他libs使用这个)

// This is used to avoid calling a base class's constructor just to setup inheritance.
function SurrogateCtor() {}

/**
 * Sets a contructor to inherit from another constructor
 */
function extend(BaseCtor, DerivedCtor) {
  // Copy the prototype to the surrogate constructor
  SurrogateCtor.prototype = BaseCtor.prototype;
  // this sets up the inheritance chain
  DerivedCtor.prototype = new SurrogateCtor();
  // Fix the constructor property, otherwise it would point to the BaseCtor
  DerivedCtor.prototype.constructor = DerivedCtor;
  // Might as well add a property to the constructor to 
  // allow for simpler calling of base class's method
  DerivedCtor.superclass = BaseCtor;
}

function Base() {
  this.a = "A";
}

Base.prototype.getA = function() {return this.a}

function Derived() {
  Derived.superclass.call(this);  // No need to reference the base class by name
  this.b = "B";
}

extend(Base, Derived);
// Have to set methods on the prototype after the call to extend
// otherwise the prototype is overridden;
Derived.prototype.getB = function(){return this.b};
var obj = new Derived();

更简单的方法是添加第三个参数来扩展您指定派生类方法的位置,这样您就不必调用extend然后将方法添加到原型中

extend(BaseCtor, DerivedCtor, {
  getB: function() {return this.b}
});

然后你还可以为语法糖做很多其他事情。

博客:http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html


2
投票

值得注意的是,在ECMAScript 5(即最新版本的JavaScript语言)中,您可以通过Object.getPrototypeOf访问实例的内部[[Prototype]]属性:

Object.getPrototypeOf(fido) === Dog.prototype
© www.soinside.com 2019 - 2024. All rights reserved.