在JavaScript秘密花园(http://bonsaiden.github.io/JavaScript-Garden/zh/#object.prototype)里看到了一个比较经典的原型链的例子,拿出来分享一下。 案例:
<!-- lang: js -->
function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// 设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// 修正Bar.prototype.constructor为Bar本身
Bar.prototype.constructor = Bar;
var test = new Bar() // 创建Bar的一个新实例
结果:
<!-- lang: js -->
// 原型链
test [Bar的实例]
Bar.prototype [Foo的实例]
{ foo: 'Hello World' }
Foo.prototype
{method: ...};
Object.prototype
{toString: ... /* etc. */};
分析: 1 Foo&Foo.prototype
<!-- lang: js -->
function Foo() {
this.value = 42;
}
Foo的prototype指向了Foo.prototype,Foo.prototype的constructor指向了Foo。此时Foo.prototype.constructor=Foo。另外,Foo.constructor指向了Function。后面都用图来表示,更清晰。
2 Foo.prototype
<!-- lang: js -->
Foo.prototype = {
method: function() {}
};
由于Foo.prototype通过{}创建了一个新的对象,这个对象继承了Object.prototype。所以,Foo.prototype.constructor指向了Object。
3 Bar&Bar.prototype
<!-- lang: js -->
function Bar() {}
Bar和第一步的Foo很像,什么都不说了。
4 Bar.prototype&Foo.prototype
<!-- lang: js -->
// 设置Bar的prototype属性为Foo的实例对象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
Bar.prototype创建了Foo的对象实例,根据原型链回溯原理,Bar.prototype回溯到Foo.prototype,又因为Foo.prototype的constructor指向Object,所以Bar.prototype的constructor也指向了Object,Bar.prototype拥有了value属性和method属性。不同的是,Foo.value改变了之后,Bar.prototype.value不会随之改变。而Foo.prototype.method改变了之后,Bar.prototype.method会随之改变。也就是说,Foo.prototype中的属性会共享到Bar.prototype中。 如果执行的是Bar.prototype=Foo,就不会执行Foo.prototype,而是指向Foo,原型链会回溯到Function.prototype。这样的话,method就不会出现在Bar.prototype上。
5 Bar.prototype
<!-- lang: js -->
// 修正Bar.prototype.constructor为Bar本身
Bar.prototype.constructor = Bar;
这里什么都不说了。在下面一步中详解。
6 test
<!-- lang: js -->
var test = new Bar() // 创建Bar的一个新实例
不得不说,高潮来了。 如果没有第5步的修正,那么根据回溯原理,test.constructor是指向Object的,为了让test.constructor指向Bar,所以执行了第5步。那么意义何在?看了http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html之后,豁然开朗。理由是为了避免继承链的紊乱,所以要强势回归。 test对象继承了Bar.prototype,而Bar.prototype又继承了Foo.prototype,所以可以访问method。同时,也可以访问Foo的实例属性value。因为是Bar.prototype创建了Foo的实例,所以new Bar()不会创建新的Foo实例,而是重复使用Bar.prototype创建的那个Foo实例。所有的Bar实例都会共享Bar.prototype.value属性。