JavaScript的[[prototype]]机制

 一,原型链

       对于原型和原型链,相信了解过JavaScript这门解释性语言的人都多少理解它们的概念。简单地讲,我们创建的每一个函数都有一个prototype属性,该属性指向相应函数的原型对象,一般开发人员通过new来创建构造函数(准确地说通过构造函数调用,毕竟所谓的构造函数与普通函数唯一不同的地方就在于调用方式不同)的实例对象并在该函数的原型对象中定义所有实例对象共享的属性和方法;假如一个实例对象的原型是另一个原型对象的实例,那么该实例对象的原型将“继承”另一个原型对象的属性和方法(姑且这样说吧,这里只是在探讨利用构造函数的方式实现原型链),如果另一个原型对象又是另一个原型对象的的实例呢,结果显然也是实现了属性和方法的“继承“,如此层层递进,这就构成了实例与原型的链条,称之为原型链。

二,类和prototype

       刚开始接触JavaScript的时候,我时常感到这样那样的困惑,其中部分来自于JavaScript中的相关”类”。如上一篇博文中提到,JavaScript根本没有我们所认知的类,这也是我总是将继承二字加上引号的原因。传统面向对象语言中类理论很容易理解,子类继承父类的方法(这里继承的本质是复制),也可自定义一 些特殊方法 以及重写父类方法(多态),然后实例化子类的一些副本,这些实例会有父类的通用方法和子类的特殊方法。而JavaScript中的[[prototype]]机制则与类有着本质的不同,上述的原型链中,实例与原型的关系并非复制,而可用委托(参见《你不知道的JavaScript》系列丛书)一词来形容,委托的原理也就是原型链的查找机制,如在实例对象中访问一个属性或方法,在实例对象中的一次查找没有结果,就会在它的原型对象中进行一次查找,一直沿着原型链向上查找至最顶层,值得注意的是我们要尽量避免在原型链的不同级别中使用相同的命名(伪多态),否则会造成很多麻烦,此外JavaScript代码的最佳实践也要求标识符有自身独特的意义,现在看来,JavaScript的原型机制其实就是对象之间的关联关系。

 三、[[prototype]]机制的两种设计模式

1、面向对象设计模式

function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
function Bar(who) {
Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
b1.speak();
b2.speak();

       上述代码的编写风格很常见,构造函数Bar生成实例b1,b2,两个实例委托了Bar.prototype,后者委托了Foo.prototype,值得一提的是,通过在Bar构造函数中调用Foo.call()实现了Bar的实例对象“继承”了Foo函数的属性me,这种手法确实让人一言难尽。然而这段代码还存在着其他问题,其逻辑思维图如下所示:

       这张图全面的展示了上述代码中各函数、对象和原生引用类型Function、Object以及它们的prototype对象间的关系网,它看起来十分复杂,我认为难以了解到的地方主要有三点:

     (a),Object.prototype对象是所有对象的顶层原型对象,所以所有对象都可以调用它的方法,且它的构造函数Object()的构造函数是Function(),说明了所有函数都是Function()的实例,Object()也不例外。

     (b),实例对象与其构造函数无直接关系,但实例是可以访问到它,因为实例对象的原型中存在.consructor属性,这个属性是共享的。这段代码的问题是Bar.prototype的.constructor属性丢失了,导致了该原型链中b1,b2,Bar.prototype共享了Foo.prototype的.constructor属性。

     (c),函数也是对象,所有函数同样有prototype属性并关联到Function.prototype对象,所以可以调用其中定义的默认方法。

       上述网络图虽然复杂,但理解它是十分有必要的,当我们把平常不太关注的细节去掉,逻辑关系就会清晰很多,如下图:

2,对象关联设计模式

Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();

       这段代码中没有构造函数,原型(因此也没有了.constructor的问题),直接使对象b1,b2委托了对象Bar,后者委托了对象Foo。显然这种风格代码只关注对象之间的关联关系(Object.create()方法在其中扮演了关键作用,它会返回一个关联了指定对象的一个新对象),其逻辑网络简单明了,但实现了上一种设计模式的全部功能,如下所示:

四、总结

        本篇博文是借鉴了《你不知道的JavaScript(上卷)》这本书来写的(其中的代码示例和逻辑图均来自于它),是一篇理解说明性博文。该书作者比较推崇对象关联代码编写风格,认为面向对象设计风格是在模仿类编写风格并导致了很多问题,带来了很多麻烦。毫无疑问,对象关联风格比面向对象风格简洁的多,但前者是否能完全覆盖后者的所有功能呢?由于本人目前对JavaScript的了解仍然不够深入,有待后续进一步学习。若过路大神有其它见解想法,还请不吝赐教,另本人水平有限,若行文中存在错误,也烦请各位不吝指正。

posted @ 2018-10-29 16:23  keepWatching  阅读(317)  评论(0编辑  收藏  举报