首先我们要知道prototype、constructor及其关系:
- 每个函数都有 prototype属性(即原型对象)
原型对象
都有 constructor属性(指针):它指向着该原型对象的拥有者。
现在看下面例子:
function Child() {}
Child.prototype = {}; //重写prototype
Child.prototype.constructor = Child;
误解:
之所以执行Child.prototype.constructor = Child
,是因为:
我们重写了Child的prototype,导致prototype的constructor指向发生了改变,因而要让其指向正确的位置。
正解:
之所以执行Child.prototype.constructor = Child
,是因为:
constructor是原型对象特有的属性,所以在我们将一个空对象
赋给了prototype后,prototype对象压根就没有constructor属性(都没有,谈何改变呢?)
执行第三句仅仅是为了为prototype增加constructor属性,并规定其正确的指向。
如果在没有执行Child.prototype.constructor = Child
的情况下,强行访问constructor,那访问的便是其 父级原型(沿着原型链找)的constructor所指向的值:
function Child2() {}
Child2.prototype = {}; //重写prototype
console.log(Child2.prototype.constructor); //ƒ Object() { [native code] }
在Child2.prototype没有constructor属性的前提下, 又要得到Child2.prototype.constructor的值,那只好沿着原型链找,看看它的父级(Object)有没有constructor属性,发现:“啊有!!”
所以Child2.prototype.constructor的值为 ƒ Object() { [native code] }
,即指向Object()
总结:
- 只有prototype才有constructor属性,而其他实例对象没有。(不考虑继承)
- 若完全重写prototype,需要为重写后的prototype增加constructor属性,并规定其正确的指向。
我们再看一个例子:
function Parent () {
this.name = 'bty';
}
function Child () {}
(1)我们再执行:
console.log(new Parent());
console.log(Child.prototype);
在控制台输出:
我们看到此时Parent构造函数new出的实例没有constructor属性(不考虑继承),而Child.prototype有。
这也印证了上面的话:只有prototype才有constructor属性,而其他实例对象没有(不考虑继承)
(2)再接着,我们让Child继承Parent,并打印Child的原型对象:
Child.prototype = new Parent();
console.log(Child.prototype);
在控制台输出:
和new Parent()的输出结果一模一样,没有constructor属性。
(3)继续,我们为Child.prototype添加constructor属性并规定其指向,然后再打印Child的原型对象:
Child.prototype.constructor = Child;
console.log(Child.prototype);
控制台输出:
添加成功,Child.prototype的constructor指向Child
实现继承时为何总是要修正constructor的指向呢?
在知乎的一篇问答中看到一种说法:
constructor其实没有什么用处,只是JavaScript语言设计的历史遗留物。由于constructor属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。不过,从编程习惯上,我们应该尽量让对象的constructor指向其构造函数,以维持这个惯例。