原型链-读书笔记
原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
简单说一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层次递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
原型链的基本模式,举栗:
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(); console.log(instance.getSuperValue()); //true
以上代码定义了两个类型:SuperType 和SubType。每个类型分别有一个属性和方法。它们的区别在于SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。在确认了继承关系之后,我们给SubType.prototype添加了一个方法,这样就在继承了SuperType的属性和方法的基础上又添加了一个新方法。
在上面的代码中,我们没有使用SubType默认提供的原型,而是给它换了一个新原型,这个新原型就是SuperType的实例。
于是,新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向了SuperType的原型。
最终结果是这样的:instance指向SubType的原型,SubType的原型又指向了SuperType的原型,getSuperValue()方法仍然还在SuperType.prototype中,但property则位于SubType.prototype中。这是因为property是一个实例属性,而getSuperValue()则是一个原型方法。既然SubType.prototype 现在是SuperType的实例,那么property当然就位于该实例中了。
此外,要注意instance.constructor现在指向的是SuperType。因为SubType的原型指向了SuperType的原型,而这个原型对象的constructor属性指向的是SuperType。
1.默认的原型
所有的引用类型默认都继承了Object。如下:
一句话,SubType 继承了SuperType,而SuperType 继承了Object。
2.确定原型和实例的关系
a.使用instanceof操作符,只要用这个操作符测试实例与原型链中出现过的构造函数,结果就会返回true。举栗:
console.log(instance instanceof Object); //true console.log(instance instanceof SuperType); //true console.log(instance instanceof SubType); //true
我们可以说instance是Object、SuperType或SubType中的任何一个类型的实例。
b.使用isPrototypeOf()方法,只要是原型链中出现过得原型,都可以说是该原型链所派生的实例的原型。
console.log(Object.prototype.isPrototypeOf(instance)); //true console.log(SuperType.prototype.isPrototypeOf(instance)); //true console.log(SubType.prototype.isPrototypeOf(instance)); //true
3.谨慎定义方法
子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法。
给原型添加方法的代码一定要放在替换原型的语句之后。举栗:
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; } //重写超类型中的方法 SubType.prototype.getSuperValue = function(){ return false; } var instance = new SubType(); console.log(instance.getSuperValue()); //false
第一个方法getSubValue()被添加到了SubType中。第二个方法getSuperValue()是原型链中已经存在的一个方法,但重写这个方法将会屏蔽原来的那个方法。换句话说,当通过SubType 的实例调用getSuperValue()时,调用的就是这个重新定义的方法;但通过SuperType 的实例调用getSuperValue()时,还会继续调用原来的那个方法。这里要格外注意的是,必须在用SuperType 的实例替换原型之后,再定义这两个方法。
参考资料
《javascript高级程序设计(第3版)》第6章 面向对象的程序设计