ECMAScript只支持实现继承,其实现继承主要是依靠原型链来实现的
我们来简单回忆一下构造函数,原型和实例的关系
1.只要创建一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象;
2.所有原型对象都会自动获得一个constructor属性,这个属性是一个指向prototype属性所在函数的指针;
3.每个实例都包含一个指向原型对象的指针。
上述关系如图所示
然后现在我们让原型对象等于另一个类型的实例,然后层层递进,也就构成了实例与原型的链条,也就是我们一开始所说的原型链。
function SuperType() {
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subproperty=false;
}
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subproperty;
};
var instance=new SubType();
alert(instance.getSuperValue());
我们定义了两个类型SubType和SuperType,现在我们让SubType继承SuperType,是通过创建后者的实例,并将此实例赋给前者的原型,通过此步骤前者将包含后者实例中的所有属性和方法,并且还有一个指向SuperType原型对象的指针。
所以说,我们并没有使用SubType的默认原型,而是用SuperType的实例作为了他的新原型,到这里我们就得出了继承实现的本质,即重写原型对象,代之以一个新类型的实例。
需要注意的是,instance.constructor指向的是SuperType,因为SubType.prototype中的constructor被重写了。
原型链的问题:
1.原型对象上引用类型的值可以通过实例进行修改,致使所有实例共享着的该引用类型的值也会随之改变。
2.其次是在创建子类型实例时,不能向超类型的构造函数中传递参数。
在解决采用原型链继承带来问题的过程中,开发人员开始使用一种叫做借用构造函数的技术,这种技术的基本思想相当简单,—即在子类构造函数的内部
调用超类构造函数。
function Person () {
this.head = 'head';
this.emotion = ['喜', '怒', '哀', '乐'];
}
function Student(studentID) {
this.studentID = studentID;
Person.call(this);
}
var stu1 = new Student(1001);
console.log(stu1.emotion); //['喜', '怒', '哀', '乐']
stu1.emotion.push('愁');
console.log(stu1.emotion); //["喜", "怒", "哀", "乐", "愁"]
var stu2 = new Student(1002);
console.log(stu2.emotion); //["喜", "怒", "哀", "乐"]
实现原理:在子类的构造函数中,通过 apply ( ) 或 call ( )的形式,调用父类构造函数,以实现继承。
关于this的指向
(1)在 stu1 = new Student ( ) 构造函数时,是 stu1 调用 Student 方法,所以其内部 this 的值指向的是 stu1, 所以 Person.call ( this ) 就相当于Person.call ( stu1 ),就相当于 stu1.Person( )。
(2)stu1 去调用 Person 方法时,Person 内部的 this 指向就指向了 stu1。那么Person 内部this 上的所有属性和方法,都被拷贝到了 stu1 上。stu2 也是同理。
总之,每个实例都具有自己的 emotion 属性副本。他们互不影响。在子类函数中,通过call ( ) 方法调用父类函数后,子类实例 stu1, 可以访问到 Student 构造函数和 Person 构造函数里的所有属性和方法。这样就实现了子类向父类的继承,而且还解决了原型对象上对引用类型值的误修改操作。
借用构造函数的问题
1.如果仅仅借用构造函数,那么将无法避免构造函数模式存在的问题:方法都在构造函数中定义,因此函数复用就无从谈起。
2.在超类型的原型中定义的方法,对子类而言也是不可见的,结果所有类型都只能使用构造函数模式。
总结
利用原型链的特性和借用构造函数(call、apply)均能实现继承,二者都各有优势和不足。
原型链虽然强大,但存在一些问题,最主要的问题来自包含引用类型值得原型;其次是在创建子类型实例时,不能向超类型的构造函数中传递参数。
而在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数的技术,这种技术的基本思想相当简单,—即在子类构造函数的内部
调用超类构造函数。同时,相对于原型而言,借用构造函数有还有一个很大的优势,它可以在子类构造函数中向超类型构造函数传递参数。
但是,借用构造函数无法避免构造函数模式存在的问题,—即方法都在构造函数中定义,函数复用就无从谈起了。
所以最好的办法是采用组合继承,发挥各自的优势。