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/
好的,让我们玩一个小小的游戏:
从上面的图片我们可以看到:
- 当我们创build一个像
function Foo() {}
的Function
,JavaScript会创build一个Function
实例。 - 每个
Function
实例(构造函数)都有一个属性prototype
,它是一个指针。 - 构造函数的
prototype
属性指向它的原型对象。 - 原型对象有一个属性
constructor
也是一个指针。 - 原型对象的
constructor
属性指向它的构造函数。 - 当我们像
new Foo()
一样创build一个新的Foo
实例时,JavaScript会创build一个新的对象。 - 实例的内部
[[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
属性是有利的,原因如下:
- 这在逻辑上是正确的。 例如考虑
Object.prototype
。Object.prototype
的constructor
属性指向Object
。 如果在实例上定义了constructor
属性,那么Object.prototype.constructor
将是undefined
因为Object.prototype
是一个null
实例。 - 这与其他原型方法没有区别。 这使得
new
的工作更容易,因为它不需要在每个实例上定义constructor
属性。 - 每个实例共享相同的
constructor
属性。 因此它是有效的。
现在当我们谈论inheritance时,我们有以下的场景:
从上面的图片我们可以看到:
- 派生的构造函数的
prototype
属性被设置为基础构造函数的实例。 - 因此,派生构造函数实例的内部
[[proto]]
属性也指向它。 - 因此,派生的构造函数实例的
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 }
简单来说,如果Foo
从Bar
inheritance,那么Foo
实例的原型链将是:
-
foo.__proto__ === Foo.prototype
-
foo.__proto__.__proto__ === Bar.prototype
-
foo.__proto__.__proto__.__proto__ === Object.prototype
-
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; }
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;