在面向对象中,继承分为接口继承和实现继承。而JavaScript中,ECMAScript中无法实现接口继承,只支持实现继承,而实现继承主要依靠的是原型链来实现的。
在JS中实现继承的方式有如下6种:
1. 原型链
2. 借用构造函数
3. 组合继承
4. 原型式继承
5. 寄生式继承
6. 寄生组合式继承
原型链
ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型链 上让一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个纸箱原型对象的内部指针。
// 《JavaScript高级程序设计》中给出了如下实现方式
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subProperty = false;
}
//继承 SuperType
SubType.prototype = new SuperType();//SubType.prototype被重写,SubType.prototype.constructor也一同被重写
SubType.prototype.getSubVaule = function(){
return this.subProperty;
}
// 比较严谨的写法要添加下边这段
SuperType.prototype.getSuperValue = function(){
return false;
}
var instance = new SubType();
alert(instance.getSuperValue());//true
但是这种写法存在一个很严重的问题:
当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;
在创建子类型(例如创建SubType实例)时,不能向超类型(SuperType)的构造函数中传递参数。
构造函数
为了解决上面的两个问题,所以要借用构造函数的技术(也叫经典继承)。主要原理就是在子类型构造函数的内部调用超类型构造函数。
function SuperType(){
this.colors = ["red","blue","green"];
}
function SubType(){
//继承了SuperType,且向父类型传递参数
SuperType.call(this);
}
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" 可见引用类型值是独立的
借用构造函数的方式解决了上面原型链方法带来的问题:
保证了原型链中引用类型值的独立,不再被所有实例共享;
子类型在创建时也能向父类型传递参数。
但是如果仅仅借用构造函数,那么将无法避免构造函数模式存在的问题—方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的。结果所有类型都只能使用构造函数模式。考虑到这些,借用构造函数的技术也是很少单独使用的。
组合继承
组合继承,也称作伪经典继承,指的是将原型链和借用构造函数的技术组合到一块。其背后的思路是使用原型属性和方法的继承,而通过借用构造函数来实现对实力属性的继承。这样,即通过原型上定义方法实现了函数复用,又能保证每个势力都有自己的属性。
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
SuperType.call(this,name);//继承实例属性,第一次调用SuperType() t
this.age = age;
}
SubType.prototype = new SuperType();//继承父类方法,第二次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType("louis",5);
instance1.colors.push("black"); console.log(instance1.colors);//"red,blue,green,black" =
instance1.sayName();//louis
instance1.sayAge();//5
var instance1 = new Son("zhai",10); console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10
组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,成为JavaScript中最常见的继承模式,而且,instanceof和isPrototypeOf()也能用于识别基本组合继承的对象。但是调用了两次父类构造函数,造成了不必要的消耗。
原型继承
基本思路是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。在object()函数内部,先创建一个历史性的构造函数,然后将传入对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅拷贝。
在ES5中,新增了一个 Object.create()方法规范了原型式继承。这个方法接受两个参数:一个是用做新对象原型的对象和一个为新对象定义额外属性的对象。在传入一个参数的情况下,和Object()方法行为相同。
var person = {
name: 'Jokul',
friends:['JS','CSS','HTML']
}
var anotherPerson = Object.create(person, { name: { value: 'HEIHEI' } })
alert(anotherPerson.name) //"HEIHEI"
注意:在原型继承中,包含引用类型值的属性,始终都会共享相应的值,就像使用原型模式一样。
寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路。它的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function createAnother(original){
var clone = object(original);//通过调用object函数创建一个新对象
clone.sayHi = function(){//以某种方式来增强这个对象
alert("hi");
};
return clone;//返回这个对象
}
注意:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率。
寄生组合式继承
组合继承是JavaScript最常见的继承模式,不过它最大的缺点就是无论在什么时候,都会调用两次超类型构造函数:一次是在创建子类型原型的时候;一次是在子类型构造函数内部。寄生组合式继承就是为了降低调用父类构造函数的开销而出现的。
function inheritPrototype(subClass,superClass){
var prototype = object(superClass.prototype);//创建对象
prototype.constructor = subClass;//增强对象
subClass.prototype = prototype;//指定对象
}
终极版继承实现方式
function Rectangle(length,width){
this.l = length
this.w = width
}
Rectangle.prototype.getArea = function(){
return this.l*this.w
}
function Square(length){
// 继承Rectangle的变量及私有变量
Rectangle.call(this,length,length)
}
// Square对Rectangle的原型的继承 将Square写入Rectangle的constructor
Square.prototype = Object.create(Rectangle.prototype,{
constructor:{
value:Square
}
})
// 上面这段等于下边的,
// Rectangle.prototype.constructor = Square
var square = new Square(3)
console.log(square.getArea())
console.log(square instanceof Square)
console.log(square instanceof Rectangle)
New操作符
new操作符具体进行下面这三步操作:
创建一个空对象;
将这个对象的proto成员指向父函数对象的prototype成员对象;
将父函数对象的this指针替换成子对象,然后在调用父函数
https://juejin.im/post/58f94c9bb123db411953691b#heading-3