English 中文(简体)
JavaScript 原型式继承?
原标题:
  • 时间:2008-12-24 14:35:51
  •  标签:

我一直在用JS进行一些继承以更好地了解它,我发现了一些让我困惑的东西。

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

我也知道,为了实现原型继承,您必须将构造函数的原型替换为您想要成为超类的对象的实例。

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

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的控制台中完成的。

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

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

3) 我们的对象(fido)的原型属性有被检查过吗?如果有的话...为什么在最后一部分中 deathBite 没有定义?

最佳回答

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

所有新的对象都保留了对其构造函数在构造时存在的原型的引用。但是用于存储此引用的属性名称不是像构造函数本身上的prototype。一些Javascript实现确实允许通过某些属性名称(如__proto__)访问此隐藏属性,而另一些则不允许(例如微软)。

2) Is the inheritance chain [obj] -> [constructor] -> [prototype] instead of [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();

这会警报“第一”而不是“第二”。如果继承链通过构造函数传递,我们将看到“第二”。当对象被构造时,函数原型属性中保存的当前引用被传输到对象隐藏引用的原型。

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

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

问题回答

一旦使用new实例化了一个对象,就无法更改它的原型。

在您上面的例子中,像这样的行:

fido.prototype = new KillerDog();

只是在对象上创建一个名为prototype的新属性,并将该属性设置为一个新的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();

特殊的原型行为只适用于JavaScript中的构造函数,其中构造函数是将使用new调用的function

按数字回答你的问题:

  1. Object s prototype property is not called prototype. The standard uses [[prototype]] to designate it. Firefox makes this property public under the name of __proto__.
  2. The inheritance chain is [obj][prototype object]. Your original assumption ([obj][constructor][prototype]) is incorrect and you can easily disprove it by modifying constructor and/or constructor.prototype, and checking what methods can be called on your [obj] — you will discover that these modifications do not change anything.
  3. prototype property on objects are not checked and not used. You can set it to whatever you like. JavaScript uses it on function objects only during the object construction.

为了演示#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,并且在此之后将被更改而不影响任何已创建的对象。

你可以在我的回答中找到关于JavaScript中[[Prototype]]和prototype之间关系的对象创建序列,链接:Relation between [[Prototype]] and prototype in JavaScript

我知道这个问题已经有答案了,但是有一种更好的继承方法。仅仅为了继承而调用构造函数是不可取的。其中一个不希望出现的效果是。

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

Child.prototype = new Base();

现在你已经向Child的原型添加了一个你不打算添加的属性"a"。

这是正确的方式(我没有发明这个,Ext-JS 和其他库也使用此方式)

// 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,然后将方法添加到原型中。 (yī zhǒng gèng jiǎn dān de fāngfǎ shì zài kuòzhǎn shí tiānjiā dì sān gè cānshù, zhǐdìng pàishēng lèi de fāngfǎ, zhèyàng nín jiù bùbì diào yòng extend, ránhòu jiāng fāngfǎ tiānjiā dào yuánxíng zhōng.)

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

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

博客中提到了它:http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

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

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




相关问题
热门标签