Javascript Prototypal继承?

Javascript Prototypal Inheritance?

为了更好地理解JS,我在JS中做了一些继承,发现了一些让我困惑的东西。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)的"原型"属性?如果是这样…为什么(最后一部分)没有定义"死亡之咬"?


1) Why if all new objects contain a
reference to the creator function's
prototype, fido.prototype is
undefined?

所有新对象都具有对构建时其构造函数上存在的原型的引用。但是,用于存储此引用的属性名不是prototype,因为它在构造函数本身上。一些JavaScript实现允许通过一些属性名(如__proto__)访问这个"隐藏"属性,而其他的则不允许(例如microsofts)。

2) Is the inheritance chain [obj] ->
[constructor] -> [prototype] instead
of [obj] -> [prototype] ?

不,看看这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();

这会提醒"第一个"而不是第二个。如果继承链通过构造函数,我们将看到"第二个"。构造对象时,函数原型属性中的当前引用将传输到对象对其原型的隐藏引用。

3) is the 'prototype' property of our
object (fido) ever checked? if so...
why is 'deathBite' undefined (in the
last part)?

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


一旦用new实例化了对象原型,就不能更改它。

在上面的示例中,行如下

1
fido.prototype = new KillerDog();

只需在对象fido上创建名为prototype的新属性,并将该属性设置为新的KillerDog对象。这和

1
fido.foo = new KillerDog();

正如你的代码所示…

1
2
3
4
5
6
7
// 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调用。


用数字回答你的问题:

  • 对象的原型属性不称为prototype。本标准使用[[prototype]]来表示。火狐以"Proto"的名义将此属性公开。
  • 继承链为[obj]→[prototype object]。您最初的假设([obj]→[constructor]→[prototype]是不正确的,您可以通过修改constructor和/或constructor.prototype很容易地反驳它,并检查可以对您的[obj]调用什么方法—您会发现这些修改不会改变任何东西。
  • 不检查和不使用对象上的prototype属性。你可以随意设置。Javascript仅在对象构造期间对函数对象使用它。
  • 为了演示3,这里是Dojo的代码:

    4

    正如您所看到的,它利用了这样一个事实:通过对具有不同原型的所有委托对象重复使用相同的函数TMPprototype只在一个地方使用。实际上,在使用new调用函数之前,直接分配prototype,之后它将被更改,不会影响任何创建的对象。

    你可以在我的答案中找到对象创建序列,它与JavaScript中的原型之间的关系。


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

    1
    Object.getPrototypeOf(fido) === Dog.prototype

    我知道已经有人回答过了,但有更好的方法来继承遗产。仅仅为了继承而调用构造函数是不可取的。其中一个不受欢迎的影响是。

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

    Child.prototype = new Base();

    现在,您已经将属性"A"添加到了您不打算添加的儿童原型中。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // 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,然后将方法添加到原型中。

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

    还有很多其他的事情你可以做的句法糖。

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