ECMAScript只支持实现继承,其实现继承主要是靠原型链来实现。
原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
简单回顾下构造函数、原型和实例的关系:
每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针(prototype),而实例则包含一个指向原型对象的内部指针(__proto__)。
实现原型链有一种基本模式,其代码大致如下:
function aa() { this.boolean_1 = true; } aa.prototype.value = function() { return this.boolean_1; } function bb() { this.boolean_2 = false; } //继承了aa bb.prototype = new aa(); bb.prototype.value = function() { return this.boolean_2 } var A = new bb(); console.log(A.value()) console.log(aa.prototype.isPrototypeOf(A)); console.log(A instanceof Object);
以上代码是定义了2个类型:aa()和bb(),每个类型分别有一个属性和方法。2者的区别是bb继承了aa。通过创建aa实例并将该实例赋给bb.prototype。原来存在于aa中所有的属性和方法也都存在于bb.prototype中了。在确立了这种继承关系后,又在bb.prototype中添加了一个方法。
可以通过两种方式来确定原型和实例之间的关系。第一种方式是使用instanceof操作符;第二种是使用isPrototypeOf()方法。
当bb继承了aa后,结果为:
false true true
当把继承去掉后,结果为:
false false true
原型链的问题:
虽然很强大,可以用它来实现继承,但它也存在一些问题。最主要的问题来自包含引用类型值的原型。在通过原型来实现继承时,原型实际上会变成一个类型的实例。于是,原先的实例属性也顺理成章的变成了现在的原型的属性了。看例子吧!
function aa(){ this.colors=['red','blue','orange'] } function bb(){} bb.prototype=new aa(); var A=new bb(); A.colors.push('black'); console.log(A.colors); // ==> ["red", "blue", "orange", "black"] var B=new bb(); console.log(B.colors); // ==> ["red", "blue", "orange", "black"]
aa构造函数定义了一个属性,该属性包含一个数组(引用类型值)。当bb通过原型继承了aa之后,bb.prototype就变成了aa的一个实例。因此也拥有了aa的所有属性和方法。结果bb会共享这个colors属性,我们通过对A.colors的修改,能够通过B.colors反映出来。
第二个问题是创建子类型的实例时,不能向超类型的构造函数中传递函数。鉴于这几点问题,实践中很少会单独使用原型链。