16

为了更好地理解它,我一直在 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 的控制台中完成的)

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

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

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

4

5 回答 5

12

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

所有新对象都持有对在构造时存在于其构造函数中的原型的引用。但是,用于存储此引用的属性名称prototype与构造函数本身不同。一些 Javascript 实现确实允许通过某些属性名称访问这个“隐藏”属性,就像__proto__其他人不允许的那样(例如 Microsoft)。

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”。当一个对象被构造时,保存在 Functions 原型属性中的当前引用被转移到该对象对其原型的隐藏引用。

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

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

于 2008-12-24T18:13:59.207 回答
7

对象的原型一旦被实例化就无法更改new

在你上面的例子中,像

fido.prototype = new KillerDog();

prototype只需在 object 上创建一个名为的新属性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.

于 2008-12-24T15:06:03.093 回答
4

用数字回答你的问题:

  1. 不调用对象的原型属性prototype。该标准用于[[prototype]]指定它。Firefox 以 __proto__ 的名义将此属性公开。
  2. 继承链是[obj][prototype object]。您最初的假设 ( [obj][constructor]⇒ ) 是不正确的,您可以通过修改和/或[prototype]轻松地反驳它,并检查您可以调用哪些方法- 您会发现这些修改不会改变任何东西。constructorconstructor.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的回答中找到对象创建序列。

于 2008-12-24T18:19:10.443 回答
2

我知道它已经得到了回答,但是,有更好的继承方法。仅出于继承目的调用构造函数是不可取的。不良影响之一是。

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

Child.prototype = new Base();

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

这是正确的方法(我没有发明这个,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(BaseCtor, DerivedCtor, {
  getB: function() {return this.b}
});

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

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

于 2010-07-30T23:00:41.763 回答
2

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

Object.getPrototypeOf(fido) === Dog.prototype
于 2011-09-17T21:30:29.157 回答