原型

Javascript中的对象是基于原型的,原型是其它对象的基础,定义并实现一个新对象必须包含成员的列表。

当我们创建一个函数时,它自动获得一个prototype属性,这个属性同时也是一个对象,它包含了所有实例共享的属性和方法。使用原型的好处是:可以让所有对象实例共享它所包含的属性和方法,如下所示:

function Person( name, age ) { this.name = name; this.age = age; }  
Person.prototype.getName = function() { return this.name; }
 Person.prototype.getAge = function() { return this.age; } 
 var Person1 = new Person( 'sam', 24 );
 Person1.getName(); 
 var Person2 = new Person( 'dary', 30 );
 Person2.getAge();

在以上Person1和Person2的实例中,方法getName()和getAge()是共享的,下面我们来看原型模式的工作原理:

无论什么时候,只要创建了一个新函数,就会自动为函数创建一个prototype属性,在默认情况下,所有prototype属性都会自动获得一个constructor( 构造函数 )属性,这个属性包含一个指向prototype属性所在函数的指针。如上面的代码示例:Person.prototype.constructor指向Person。

当创建了构造函数之后,其原型对象默认只会取得constructor属性,至于其它的方法,如hasOwnProperty(), toString() …都是从Object继承而来的。当调用构造函数报建的实例后,该实例的内部包含一个指针(_proto_),指向原型对象。这个内部属性_proto_在firefox, chrome, safari浏览器中对开发者来说是可见的。如下图展示了各个对象的关系:

以上图展示了Person构造函数,Person的原型属性,以及Person的两个实例之间的关系。如上图所示:Person.prototype指向了原型对象,而Person.prototype.constructor又指回了Person。Person的两个实例Person1和Person2的内部属性_proto_指向了原型对象,也就是实例与构造函数之间没有直接的关系。

虽然在些浏览器中我们无法访问到实例的内部属性_proto_,但可以通过isPrototypeOf()方法来验证这种关系是否存在,如下代码所示:

console.log( Person.prototype.isPrototypeOf( Person1 ) ); //true console.log( Person.prototype.isPrototypeOf( Person1 ) ); //true

当然我们还可以采用对象自面量的方式在原型上添加方法,如下代码所示:

function Person( name, age ) { this.name = name; this.age = age; } Person.prototype = { getName: function() { return this.name; }, getAge: function() { return this.age; } }

改用这种方式后,最终的结果和之前的没有什么区别,但是会有一个例外,原型对象中的constructor属性不再指向构造函数Person了,因为这里我们完全重写了原型对象,因此constructor属性也就成了新对象的constructor属性不再指向Person函数,可以用以下代码去验证:

var person = new Person(); console.log( person instanceof Object ); //true console.log( person instanceof Person ); //true console.log( person.constructor == Object ); //true console.log( person.constructor == Person ); //false

由结果可知,用instanceof操作符没有Object和Person都返回true, 但constructor属性等于Object面不是Person。如果constructor很重要,我们可以强制设置它的constructor属性值为Person,如下代码所示:

function Person( name, age ) { this.name = name; this.age = age; } Person.prototype = { constructor: Person, getName: function() { return this.name; }, getAge: function() { return this.age; } } var person = new Person(); console.log( person instanceof Object ); //true console.log( person instanceof Person ); //true console.log( person.constructor == Object ); //false console.log( person.constructor == Person ); //true

原型链

原型链作为实现继承的主要方法,其基本思想是:让原型对象等于另一个类型的实例,这样原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针,假如另一个原型又是另一个类型的实例,如此层层递进,就构成了实例与原型的链条,这个链条就称之为原型链

前面Person1的实例中我们用了isPrototypeOf()方法,但是我们并没有在Person的原型对象中定义该方法,我们知道,所有的引用类型都默认继承了Object,而这个继承也正是通过原型链实现的,其实现过程如下所示:

那两个自定义的对象这种继承关系通过代码是如何实现的呢?如下代码所示:

function SuperClass( name ) { this.name = name; } SuperClass.prototype.getName = function() { return this.name; } function SubClass( name, age ) { //继承属性 SuperClass.call( this, name ); } //继承方法 SubClass.prototype = new SuperClass(); SubClass.prototype.getAge = function() { return this.age; } var instance = new SubClass( 'sam', 25 ); console.log( instance.getName() );

继承的思想是:通过原型链实现对原型方法的继承,通过借用构造函数实现对实例属性的继承,这样通过在原型上定义的方法实现了函数的复用,又能保证每个实例都有它自己的属性。如下图展示了其完整的原型链:

通过以上原型链图可以看到,实例instance指向SubClass.prototype, subClass.prototype又指向SuperClass.prototype, SuperClass.prototpe最终指向Object.prototype, 这就是原型链的继承方式。注意,instance.constructor现在指向的是SuperClass, 因为现在SubClass.prototype指向了SuperClass.prototype, SuperClass.prototype指向SuperClass.

解析对象成员的过程同解析变量的过程很类似,都是经历一次搜索的过程,当调用instance.getName()方法时,首先会在实例中查找有没有这个方法,如果实例中存在这个方法则直接没有调用,如没有找到,则到SubClass.prototype的原型上去找,如找到则停止搜索,如没有找到,则继续向SuperClass.prototype的原型上去找,直到getName()找到并执行为止。当我们调用的hasOwnProperty()方法时,都是通过这种搜索方式在Object.prototype上找到的并执行。

注意:在通过原型链实现继承时,不能使用对象字面量的方式创建原型方法,因为这样会重写原型链,如下代码所示:

function SuperClass( name ) { this.name = name; } SuperClass.prototype.getName = function() { return this.name; } function SubClass( name, age ) { //继承属性 SuperClass.call( this, name ); } //继承方法 SubClass.prototype = new SuperClass(); SubClass.prototype = { getAge: function() { return this.age; } } var instance = new SubClass( 'sam', 25 ); console.log( instance.getName() );	//Object #<Object> has no method 'getName'

以上代码先SuperClass.prototype的实例赋值给原型,然后又将原型替换成一个对象自面量,这里就相当于重写了原型对象,切断了原有的原型链,现在的原型对象只是Object的实例,SubClass和SuperClass已经没有关系了。

性能问题:当调用instance.toString()方法时,搜索过程必须深入原型链中直到找到对象成员”toString”,对象在原型链中存在的位置越深,找到它就越慢。每深入一深原型链都会有性能上的损失,搜索实例成员比直接量或局部变量中访问数据的代价要高。

posted on 2012-10-31 00:16  osccur123  阅读(239)  评论(0)    收藏  举报