JS的继承主要包括三种类型,原型链继承、借用构造函数继承以及组合继承,另外还有三种可供选择的继承方式,原型式继承、寄生式继承、寄生组合式继承。(本篇博客是通过对《JavaScript高级程序设计》-继承一章的整理归纳)
一、原型链继承
回顾梳理一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
核心代码:
SubType.prototype = new SuperType();
将一个类型的实例赋值给另一个类型的原型,在另一个类型中可以使用这个类型的实例及原型中的所有方法和属性。实现继承的本质是,重写原型对象,代之以一个新类型的实例。注意:1.此时的SubType的constructor指向SuperType,由于prototype被重写;2.默认原型为Object;3.用instance of 和 isPrototypeOf()确定原型和实例的关系;4.给原型添加方法必须放在替换原型之后;5.通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会重写原型链。
缺点:
1. 超类型中存在引用类型属性时,子类的所有实例中都会共享这个属性,造成一个实例在修改自己的引用类型实例时同步修改到所有该实例;
读过该书前面章节的同学应该已经知道,包含引用类型值的原型属性会被所有实例共享,所以应该在构造函数中而不是原型对象中定义属性。但这里通过原型继承,原型继承是继承到超类型的所有构造函数及原型中的方法和属性,原先的实例属性会顺其自然的变成现在的原型属性。
2.创建子类型属性时,不能向超类型的构造函数中传递函数;
确切的说是没办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。一开始不是很理解这句话,写本文的时候突然理解了,也就是在对新函数的原型赋值时,可以给超类型构造函数传递参数,但是对于每个子类型的实例来说,这个参数都是一样的,不能针对不同实例对超类型构造函数传递参数。
二、借用构造函数继承
为了原型包含引用类型值所带来的问题,借用构造函数继承被提出。核心思想就是:在子类型构造函数内部调用超类型构造函数。
核心代码:
function SubType(){
SuperType.call(this,'zengmengxia');//或用apply (SuperType的this对象为当前SubType的对象)
}
这样父类型中的引用类型的变量就不会跑到子类型的原型中了。
缺点:
1.方法在构造函数中定义,每个实例独立享用一个方法,无法实现函数复用;
2.在原型中定义的方法,对于子类型不可见。
三、组合继承
结合原型链继承和借用构造函数继承的优点,既通过在原型上定义方法实现函数复用,又能保证每个实例都有它自己的属性。
核心代码:
function SubType(){
SuperType.call(this,'zengmengxia');//或用apply (SuperType的this对象为当前SubType的对象)只会继承到来自父类型构造函数中的属性和方法 new 子类型时才执行
}
SubType.prototype = new SuperType();//先执行 会继承到来自父类型构造函数及原型中的方法和属性 先执行
除了上述三种方法以外,书中还提及了原型式继承、寄生式继承、寄生组合式继承。
四、原型式继承
核心代码:
function object(o){
function F(){}
P.prototype = o;
return new F();
}
将传入的对象进行一次浅复制,ECMAScript5新增Object.create()方法规范了浅复制,改方法可接收两个参数:1.作为新对象原型的对象;2.为新对象定义额外属性的对象。
在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。不过,它依然存在引用类型值得属性始终都会共享相应值。
五、寄生式继承
核心代码:
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
alert("Hi");
}
return clone;
}
与原型式继承很像,只是在内部以某种方式来增强对象。缺点是做不到函数复用。
六、寄生组合式继承
上面提到的组合继承,虽然结合了原型链继承和借用构造函数继承的优点,但仍然存在不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:1.创建子类型原型的时候;2.在子类型构造函数内部。第二次调用构造函数时,会重写第一次继承到的实例属性。于是,子类型的实例中就有两组相同的属性,一组在prototype中,一组在实例上。
于是需要用寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混合形式来继承方法。基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们需要的无非就是超类型原型的一个副本。本质上,就是用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型。
核心代码:
function inheritPrototype(subType,superType){
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
完整代码:
function SubType(){
SuperType.call(this,'zengmengxia');//或用apply (SuperType的this对象为当前SubType的对象)只会继承到来自父类型构造函数中的属性和方法 new 子类型时才执行
}
inheritPrototype(subType,superType);//代替SubType.prototype = new SuperType(); 只继承原型中的方法,不重复继承构造函数中的属性。最理想的继承方式。