原型链继承
继承是OO(Object oriented 面向对象)语言中的一个最为津津乐道的概念。 许多面向对象语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则是继承实际的方法,例如在Java中通过关键字implements实现接口即对应接口继承, 通过extends关键字继承类即对应的实现继承;但是在Javascript中, 由于函数没有签名, 所以无法实现接口继承,只支持实现继承,而继承则主要是依靠原型链实现(在ES6中,引入了class类的概念,可通过extends关键字继承)。
实现原型链继承的基本模式(本质是重写原型对象):
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; } function SubType() { this.subPrototype = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subPrototype; } var instance = new SubType(); alert(instance.getSuperValue()); //true
在上面这个例子中, 没用使用SubType的默认提供的原型, 而是给他换了一个新原型(因为原型本身就是一个对象,所以可以这样赋值);因此,SubType的原型实际上就是SuperType的一个实例,所以也就拥有该SuperType实例的所有属性和方法,而且其内部还有一个指针指向了SuperType的原型。此外要注意的是, instance.constructor现在指向的是SuperType,这是因为原来的SubType.prototype中的constructor被重写了的缘故。
该例子的原型链如下:
简单的一句话, SubType继承了SuperType, 而SuperType继承了Object。当调用instance.toString() 方法时,实际上调用的是保存在Object.protoType中的那个方法。
理解:
首先声明了SuperType和SubType构造函数, SuperType构造函数中有属性property, SubType构造函数中有属性subProperty;
此时执行
SubType.prototype = new SuperType();
则是将SuperType的一个实例new SuperType() 赋值给 SubType.prototype, 即重写了SubType的原型对象。因为SuperType的实例包含属性property, 所以SubType的原型对象中会有property属性。又因为实例有一个特性prototype指向SuberType的原型,所以SubType的原型也会有一个prototype特性指向SuperType的原型,而不是指向Object的原型。
此时执行
SubType.prototype.getSubValue = function() { return this.subPrototype; }
则是在SubType当前的原型上添加一个getSubValue方法, 所以SubType的原型对象中会有getSubValue方法; SubType中的原型如下:
同理, SuperType的原型也是如此。
判断一个实例对象是什么类型可以使用 instanceof操作符:
console.log(instance instanceof Object); //true console.log(instance instanceof SuperType); //true console.log(instance instanceof SubType); //true
上面三个语句的执行结果都是true,说明instance 即是 Object,也是SuperType, 同时也是SubType;
和自然界的继承是一样的,就好比: 实例是一只东北虎(SubType), SubType是东北虎, SuperType是老虎, Object是动物, 这一只东北虎,可以称呼为东北虎,也可以称呼为老虎, 也可以称之为动物。
判断一个实例是否继承自一个原型,可以使用isPrototypeOf()方法:
console.log(Object.prototype.isPrototypeOf(instance)); //true console.log(SuperType.prototype.isPrototypeOf(instance)); //true console.log(SubType.prototype.isPrototypeOf(instance)); //true
从该例子的原型链图能够直接看出来, 这个三条语句都是正确的。
问题:
原型链虽然强大,可以使用它来继承, 但它也存在一些问题。 其中最主要的问题来之包含引用类型值得原型。 因为引用类型保存的实际上是一个地址值,虽然实例不能改变原型对象中引用类型保存的地址值,但是却可以改变地址值所指向的值(地址值就相当于一个门牌号, 而实际的内容则是房子里的东西;门牌号可以不变, 但是却可以往房子搬东西,或者从房子里往外搬东西)。
例:
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { } SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors); //"red", "blue", "green", "black" var instance2 = new SubType(); console.log(instance2.colors); //"red", "blue", "green", "black"
通过上面可以知道, 在SubType的原型对象中含有 colors 属性, 而 colors 是一个数组; 在Javascript中,数组是引用类型,所以instance1数组进行操作的时候,实际上是通过colors保存的地址值,找到内存中的位置, 然后修改内存中的值, 而当instance2访问colors属性的时候,会通过colors保存的地址值找到内存中对应的值。 因此,instance1修改的colors的值会在instance2中体现。
因为在实际应用中,往往需要一个对象拥有自己独立的属性, 所以通常很少单独使用原型链。
备注: 例子及截图均来自《JAVASCRIPT高级程序设计:第3版》