在说继承方式之前,先来了解下js中几个较为抽象的概念。
原型和原型链
在js中万物皆对象,对象又分为两种:普通对象(Object)和函数对象(Function)。
任何对象都具有隐式原型属性(__proto__),只有函数对象有显式原型属性(prototype)。
原型链:原型链是针对原型对象的,在查找实例属性时,现在实例中查找,如果没有找到,再到obj.__proto__(=f1.prototype)原型对象,再到f1.prototype.__proto__(=f2.prototype),依次向上查找。
作用域链:作用域链是针对变量的,现在自己的变量范围内查找,找不到,再沿着作用域网上查找。
构造函数、原型对象和实例之间的联系(图示)
构造函数、原型对象、子类构造函数、子类原型对象和子类实例之间的联系(图示)
JS继承方式
一.原型继承
function Parent(){
this.color = 'red'
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){}
Child.prototype = new Parent();//将父类的实例赋给子类的原型
var child1 = new Child();
child1;
打印结果:
child1通过指针__proto__指向其原型对象(其原型对象又指向他的原型对象),其原型对象分为两部分,从构造函数中继承的实例属性color,和从原型对象中继承的。
补充:原型对象的构造函数是函数本身,因此每个构造函数的prototype对象可以看做一个实例化对象。
优点:可以继承构造函数属性,也可继承原型属性。
缺点:
1.首先属性值为引用类型值的原型属性被修改后可能会影响到所有的实例,而该种继承方式中,构造函数中的属性会继承到子类型,成为子类的原型对象的属性。(这也是为什么要在构造函数中定义属性,在原型中定义公共方法)
2.在创建子类的实例时,不能向父类的构造函数中传递参数。
二.借用构造函数
function Parent(name){
this.name = name;
this.colors = ['red','green','pink'];
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){
Parent.call(this,'Mary');//使用call()方法继承父类构造函数中的属性
this.age = '18';
}
var child1 = new Child();
var child2 = new Child();
打印结果:
优点:避免了原型继承中的两个缺点,可以向父类传参,且不会造成原型属性共享的问题(因为父类构造函数中的属性继承到子类构造函数中,而非原型对象中)。
缺点:不能继承原型属性,无法实现函数复用,所有方法都只能放在构造函数中。
三.组合继承
补充:为什么要进行构造函数修复?(https://blog.csdn.net/yaojxing/article/details/73612483)
组合继承(combination inheritance),有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
四.原型式继承
五.寄生式继承
六.寄生组合式继承
前面说过,组合继承是 JavaScript 最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。