Function、构造函数、实例关系理解

294 阅读2分钟

话不多说,直接上代码,读者可以先不看答案猜猜输出是什么。

function Parent(){
  this.realName = "Parent";
}
Parent.__proto__.print = function(){
  console.log(this.realName);
}
​
function Tom(){
  this.realName = "Tom";
}
​
Parent.print();
Tom.print();
​
const tom = new Tom();
tom.print();

下 面 揭 晓 答 案

先说一下本人第一直觉给出的答案:12行输出「Parent」,13行输出「Tom」,16行输出「Tom」。但是实际答案是12,13行输出undefined,16行报错。

本人之所以在此出现判断错误,主要是因为在此认为构造函数是Function的子类,但是实际上构造函数是Function的实例,其次在构造函数上调用print的方法是Function类的方法,而不是构造函数的静态方法。

经过从原型的角度对Function、函数、实例进行分析很容易得到结果。从JavaScript规范我们知道,所有函数都是Function类的实例,定义函数function name(params) {}其实是name = new Function()的语法糖。而JavaScript通过原型实现继承,那么可以知道函数name包含一个指向Function原型的内部指针,即name.__proto__指向Function的prototype。因此上面代码块第四行实际上可以改写成:

Function.prototype.print = function(){
  console.log(this.realname);
}

所以12,13行可以直接在「Parent」函数和「Tom」函数上直接调用print方法。之所以会输出undefined是由于,上述两个构造函数都没有执行,还没给realname赋值。

到这里对于12,13行的输出结果还比较好理解,至于为什么16行报错,则可通过下图来彻底搞清楚上面所有问题的情况。

从上图可以看到,「Tom」实例通过一个内部指针指向Tom原型,而「Tom」的原型没有指向Function的原型,所以Tom实例上无法获取到print方法。