简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
这样就通过利用原型链让一个引用类型继承另一个引用类型的属性和方法实现了继承。
下面通过代码看一下实现原型链的一种基本模式:
function SuperType(){
this.property = true;
} SuperType.prototype.getSuperValue = function(){
return this.property;
}; function SubType() {
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
在上面的代码中,我们没有使用SubType默认提供的原型,而是给它换了一个新原型; 这个新原型就是SuperType的实例。于是,新原型不仅具有作为个SuperType 的实例所拥有的全部属性和方法,而且其内部还有一一个指针,指向了SuperType的原型。最终结果就是这样的: instance 指向SubType的原型,SubType 的原型又指向SuperType的原型。getSuperValue() 方法仍然还在SuperType.prototype中,但property则位于SubType.prototype中。这是因为property是一个实例属性,而getSuperValue()则是一个原型方法。 既然SubType.prototype 现在是SuperType的实例,那么property当然就位于该实例中了。此外,要注意instance.constructor现在指向的是SuperType,这是因为原来SubType.prototype中的constructor被重写了的缘故。(实际上,不是SubType的原型的constructor属性被重写了,而是SubType的原型指向了另一个对象SuperType的原型,而这个原型对象的constructor属性指向的是SuperType。)
通过实现原型链,本质上扩展了前面博客中说的原型搜索机制。当访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。就拿上面的例子来说, 调用instance.getSuperValue()会经历三个搜索步骤: 1)搜索实例; 2)搜索SubType.prototype;3)搜索SuperType.prototype,最后一步才会找到该方法。在找不到属性或方法的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。
其实上例中展示的原型链还缺少一环。在JS中,所有的引用类型默认都继承了Object,而这个继承也是通过原型链实现的,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。下面展示该例的完整原型链:
SubType继承了SuperType,而SuperType继承了Object。当调用instance.toString()时,实际上调用的是保存在Object.prototype中的那个方法。