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个不同的属性
posted @ 2012-10-19 18:37  紫云飞  阅读(2038)  评论(4编辑  收藏  举报