JS 面向对象之继承 -- 原型链
继承是OO语言中一个重要的特性和概念。许多的OO语言中都支持两种继承方式:接口继承和实现继承。
ECMAScript只支持实现继承,其实现继承主要是靠原型链来实现。在PHP语言中,是使用extend来实现继承。那么我们就先来讲讲原型链。
原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
简单回顾下构造函数、原型和实例的关系:
每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针(prototype),而实例则包含一个指向原型对象的内部指针(__proto__)。
实现原型链有一种基本模式,其代码大致如下:
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.getSubValue()); //true
以上代码是定义了2个类型:SuperType()和SubType(),每个类型分别有一个属性和方法。2者的区别是SubType继承了SuperType。通过创建SuperType实例并将该实例赋给SubType.prototype。原来存在于SuperType中所有的属性和方法也都存在于SubType.prototype中了。在确立了这种继承关系后,又在SubType.prototype中添加了一个方法。
请看下图展示的实例以及构造函数和原型之间的关系:PS:找线画了半天没画好,先这么看吧:)
确定原型和实例的关系:
可以通过两种方式来确定原型和实例之间的关系。第一种方式是使用instanceof操作符;第二种是使用isPrototypeOf()方法。
alert(instance instanceof Object); //true alert(instance instanceof SuperType); //true alert(instance instanceof SubType); //true alert(Object.prototype.isPrototypeOf(instance)); //true alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance)); //true
原型链的问题:
虽然很强大,可以用它来实现继承,但它也存在一些问题。最主要的问题来自包含引用类型值的原型。在通过原型来实现继承时,原型实际上会变成一个类型的实例。于是,原先的实例属性也顺理成章的变成了现在的原型的属性了。看例子吧!
function SuperType() { this.colors = ["red", "green", "blue"]; } function SubType() { } //继承了SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //red,green,blue,black var instance2 = new SubType(); alert(instance2.colors); //red,green,blue,black
SuperType构造函数定义了一个属性,该属性包含一个数组(引用类型值)。当SubType通过原型继承了SuperType之后,SubType.prototype就变成了SuperType的一个实例。因此也拥有了SuperType的所有属性和方法。结果SubType会共享这个colors属性,我们通过对instance1.colors的修改,能够通过instance2.colors反映出来。
第二个问题是创建子类型的实例时,不能向超类型的构造函数中传递函数。鉴于这几点问题,实践中很少会单独使用原型链。