JavaScript:关于"超长原型链"的解释
前两天我在一篇文章中讲到了"超长原型链"这个东西,有些读者不是很明白.我觉的有必要再详细的解释一下,首先出道题:
function Foo() {} //构造函数
Foo.prototype.say = function () {
return "I'm a Foo!";
}; //原型对象上添加一个say方法 var foo1 = new Foo; //生成一个Foo实例 foo1.say() //继承了原型对象上的say方法,输出"I'm a Foo!"
Foo.prototype = {} //原型对象指向一个空对象 foo1.say() //输出什么呢?
你也许会这么认为:
foo1本身并没有say方法,所以在最后执行foo1.say()时,foo1会从Foo.prototype上找say方法,但这是Foo.prototype已经是个空对象,say属性肯定是undefined,所以上面题目的答案是抛出异常"TypeError: foo1.say is not a function"
好像的确是这么回事,但是答案错了.正确的答案是仍然输出"I'm a Foo!".为什么呢?貌似上面的解释没问题啊?
的确.上面这样的解释大部分都对,错就错在了唯一的细节上:
foo1会从Foo.prototype上找say方法
更严格的说法应该是:foo1会从foo1的内部属性[[prototype]]指向的对象上找say方法,也就是foo1.__proto__.say.虽然Foo.prototype指向了一个空对象,但foo1.__proto__仍然指向旧的Foo.prototype指向的那个对象.因此,可以继承到say方法.
另一个需要注意的现象是,一旦你修改了Foo.prototype的指向.instanceof操作符不再认为foo1是Foo类型的实例了.
foo1 instanceof Foo //false
因为instanceof运算符的工作原理就是检查当前的Foo.prototype等于还是不等于foo1.__proto__或者foo1.__proto__.__proto__等等,一直到原型链的尽头null.当前的Foo.prototype指向一个空对象,foo1.__proto__指向原先Foo.prototype指向的那个对象,两者并不相等.所以instanceof会返回false.接下来,让我们再看一个例子:
function Foo() {} //构造函数 console.log(Foo.prototype.__proto__ === Object.prototype) //原型对象会自动建立,且类型是Object的实例. Foo.prototype.say = function () { return "I'm a Foo!"; }; //原型对象上添加一个say方法 var foo1 = new Foo; //生成一个Foo实例 foo1.say() //继承了原型对象上的say方法,输出"I'm a Foo!" Foo.prototype = new Foo; //Foo.prototype指向一个Foo实例,就像String.prototype是个String实例一样 Foo.prototype.say = function () { return "I'm a new Foo!"; }; //原型对象上添加一个新的say方法 console.log(foo1.say()) //仍然输出"I'm a Foo!" var foo2 = new Foo; //再次新建一个Foo实例 foo2.say() //输出"I'm a new Foo!"
这时foo1和foo2的原型链是这样的.
foo1->原来的Foo.prototype->Object.prototype->null
foo2->新的Foo.prototype->原来的Foo.prototype->Object.prototype->null
可以用我们上篇文章中写的getPrototypeChain函数验证一下:
>getPrototypeChain(foo2).forEach(function (obj) { console.log(obj.say.toString()) }) function () { //输出了新的Foo.say方法 return "I'm a new Foo!"; } function () { //输出了旧的Foo.say方法 return "I'm a Foo!"; } TypeError: Cannot call method 'toString' of undefined //Object.prototype没有say方法,抛出异常
现在应该能理解上篇文章中给出的代码了:
function Foo() {} for (var i = 0; i < 100; i++) { Foo.prototype["prop" + i] = i; Foo.prototype = new Foo; } var foo1 = new Foo; console.log(getPrototypeChain(foo1).length); //包含null,一共有103个上层原型 console.log(Object.getOwnPropertyNames(foo1)); //空数组,foo1没有任何自身属性 for(var prop in foo1){console.log(prop)} //foo1从100个不同的原型上继承了100个不同的属性