JavaScriptinheritance和构造函数属性

考虑下面的代码。

function a() {} function b() {} function c() {} b.prototype = new a(); c.prototype = new b(); console.log((new a()).constructor); //a() console.log((new b()).constructor); //a() console.log((new c()).constructor); //a() 
  • 为什么不更新为b和c的构造函数?
  • 我做inheritance错了吗?
  • 什么是更新构造函数的最好方法?

此外,请考虑以下事项。

 console.log(new a() instanceof a); //true console.log(new b() instanceof b); //true console.log(new c() instanceof c); //true 
  • 假设(new c()).constructor等于a()Object.getPrototypeOf(new c())a{ } ,那么instanceof怎么可能知道new c()c一个实例呢?

http://jsfiddle.net/ezZr5/

好的,让我们玩一个小小的游戏:

从上面的图片我们可以看到:

  1. 当我们创build一个像function Foo() {}Function ,JavaScript会创build一个Function实例。
  2. 每个Function实例(构造函数)都有一个属性prototype ,它是一个指针。
  3. 构造函数的prototype属性指向它的原型对象。
  4. 原型对象有一个属性constructor也是一个指针。
  5. 原型对象的constructor属性指向它的构造函数。
  6. 当我们像new Foo()一样创build一个新的Foo实例时,JavaScript会创build一个新的对象。
  7. 实例的内部[[proto]]属性指向构造函数的原型。

现在,问题出现了:JavaScript为什么不把constructor属性附加到实例对象而不是原型。 考虑:

 function defclass(prototype) { var constructor = prototype.constructor; constructor.prototype = prototype; return constructor; } var Square = defclass({ constructor: function (side) { this.side = side; }, area: function () { return this.side * this.side; } }); var square = new Square(10); alert(square.area()); // 100 

正如你所看到的, constructor属性只是原型的另一种方法,如上例中的area 。 使constructor属性特别的是它用来初始化原型的一个实例。 否则,它和原型的其他方法完全一样。

在原型上定义constructor属性是有利的,原因如下:

  1. 这在逻辑上是正确的。 例如考虑Object.prototypeObject.prototypeconstructor属性指向Object 。 如果在实例上定义了constructor属性,那么Object.prototype.constructor将是undefined因为Object.prototype是一个null实例。
  2. 这与其他原型方法没有区别。 这使得new的工作更容易,因为它不需要在每个实例上定义constructor属性。
  3. 每个实例共享相同的constructor属性。 因此它是有效的。

现在当我们谈论inheritance时,我们有以下的场景:

从上面的图片我们可以看到:

  1. 派生的构造函数的prototype属性被设置为基础构造函数的实例。
  2. 因此,派生构造函数实例的内部[[proto]]属性也指向它。
  3. 因此,派生的构造函数实例的constructor属性现在指向基础构造函数。

至于操作符的instanceof ,与stream行的看法相反,它不依赖于实例的constructor属性。 从上面我们可以看出,这会导致错误的结果。

instanceof运算符是一个二元运算符(它有两个操作数)。 它在一个实例对象和一个构造函数上运行。 就像Mozilla开发者networking上的解释一样,它只是简单地做了以下工作

 function instanceOf(object, constructor) { while (object != null) { if (object == constructor.prototype) { //object is instanceof constructor return true; } else if (typeof object == 'xml') { //workaround for XML objects return constructor.prototype == XML.prototype; } object = object.__proto__; //traverse the prototype chain } return false; //object is not instanceof constructor } 

简单来说,如果FooBarinheritance,那么Foo实例的原型链将是:

  1. foo.__proto__ === Foo.prototype
  2. foo.__proto__.__proto__ === Bar.prototype
  3. foo.__proto__.__proto__.__proto__ === Object.prototype
  4. foo.__proto__.__proto__.__proto__.__proto__ === null

如您所见,每个对象都从Object构造函数inheritance。 原型链在内部[[proto]]属性指向null

instanceof函数只是遍历实例对象(第一个操作数)的原型链,并将每个对象的内部[[proto]]属性与构造函数(第二个操作数)的prototype属性进行比较。 如果匹配,则返回true ; 否则,如果原型链结束,则返回false

默认,

 function b() {} 

那么b.prototype具有自动设置为b.constructor属性。 但是,您现在正在覆盖原型,因此丢弃该variables:

 b.prototype = new a; 

那么b.prototype不再具有.constructor属性; 它被覆盖擦除。 它确实inheritance了a ,然后(new a).constructor === a ,因此(new b).constructor === a (它指的是原型链中的同一个属性)。

最好的办法是简单地手动设置它之后:

 b.prototype.constructor = b; 

你也可以为此做一个小函数:

 function inherit(what, from) { what.prototype = new from; what.prototype.constructor = what; } 

http://jsfiddle.net/79xTg/5/

constructor是函数对象的prototype属性的默认值的常规,不可枚举的属性。 因此,分配prototype将失去财产。

instanceof仍然可以工作,因为它不使用constructor ,而是扫描对象的原型链,以获取函数prototype属性的(当前)值,即foo instanceof Foo等价于

 var proto = Object.getPrototypeOf(foo); for(; proto !== null; proto = Object.getPrototypeOf(proto)) { if(proto === Foo.prototype) return true; } return false; 

在ECMAScript3中,没有办法设置一个constructor属性,它的行为与内置行为相同,因为用户定义的属性总是可枚举的(即对于for..in可见)。

这改变了ECMAScript5。 但是,即使您手动设置constructor ,您的代码仍然存在问题:特别是,将prototype设置为父类的实例是个坏主意 – 父类构造函数在子类中不应调用'被定义,而是当创build子实例时。

以下是一些ECMAScript5示例代码,用于说明如何完成该操作:

 function Pet(name) { this.name = name; } Pet.prototype.feed = function(food) { return this.name + ' ate ' + food + '.'; }; function Cat(name) { Pet.call(this, name); } Cat.prototype = Object.create(Pet.prototype, { constructor : { value : Cat, writable : true, enumerable : false, configurable : true } }); Cat.prototype.caress = function() { return this.name + ' purrs.'; }; 

如果您遇到ECMAScript3,您将需要使用自定义的clone()函数而不是Object.create()并且不能使constructor不可枚举:

 Cat.prototype = clone(Pet.prototype); Cat.prototype.constructor = Cat;