1、原型链继承
熟悉圆原形链的朋友们应该知道,实例可以继承构造函数原型链上的属性和方法。假设我们有两个构造函数 A 和构造函数 B ,我们希望通过 B 构造函数生成的实例可以继承 A 构造函数的属性和方法。
思考1:把 B 构造函数的 B.prototype 指向 A 构造函数生成的实例 new A( ),那么 B 是不是可以访问 new A()的属性呢?通过 A 构造函数也可以进一步访问 构造函数 A 原型链上的属性和方法。
代码
function A() {
this.name = 'A'
}
// 父类的原型方法
A.prototype.getName = function() {
return this.name
}
// 子类
function B() {}
B.prototype = new A()
B.prototype.constructor = B
思考2:B.prototype.constructor = B 什么意思呀? 因为当我们把 B 的原型指向 A 的实例的时候 ,此刻 B.prototype.constructor指向的是原来的构造函数 A ,这里看起来怪怪的,因为看原型链远离我们知道,构造函数的原型里面的 constructor属性指向构造函数本身
怎么办呢?简单,我们只需要顺便把构造函数 B 的原型 B.prototype 里面的 constructr 属性重新指向 B 就可以了,完美。
原型链继承的缺点
1、由于所有子类实例原型都指向同一个父类实例, 因此对某个子类实例的父类引用类型变量修 改会影响所有的实例
注意:只有父级构造函数的某个变量属性值是引用数据类型时会发生,基本类型不会发生上述情况2、在创建子类实例时无法向父类构造传参, 即没有实现super()的功能
2、构造函数继承
构造函数继承,即在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
Parent.call(this, 'zhangsan') // 执行父类构造方法并绑定子类的this, 使得父类中的属性能够赋到子类的this上
}
//测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['zhangsan']
child2.getName() // 报错,找不到getName(), 构造函数继承的方式继承不到父类原型上的属性和方法可以看到,构造函数继承,在子类构造函数中只能继承到父类构造函数的属性和方法,却不能继承父类原型上的属性和方法
3. 组合式继承
既然原型链继承和构造函数继承各有互补的优缺点, 那么我们为什么不组合起来使用呢, 所以就有了综合二者的组合式继承
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 构造函数继承
Parent.call(this, 'zhangsan')
}
//原型链继承
Child.prototype = new Parent()
Child.prototype.constructor = Child
//测试
const child1 = new Child()
const child2 = new Child()
child1.name[0] = 'foo'
console.log(child1.name) // ['foo']
console.log(child2.name) // ['zhangsan']
child2.getName() // ['zhangsan']Parent.call()和new Parent()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅4. 寄生式组合继承
为了解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行
function Parent(name) {
this.name = [name]
}
Parent.prototype.getName = function() {
return this.name
}
function Child() {
// 构造函数继承
Parent.call(this, 'zhangsan')
}
//原型链继承
// Child.prototype = new Parent()
Child.prototype = Object.create(Parent.prototype) //将`指向父类实例`改为`指向父类原型`
Child.prototype.constructor = Child
//测试
const child = new Child()
const parent = new Parent()
child.getName() // ['zhangsan']
parent.getName() // 报错, 找不到getName()到这里我们就完成了ES5环境下的继承的实现,这种继承方式称为寄生组合式继承,是目前最成熟的继承方式,babel对ES6继承的转化也是使用了寄生组合式继承
我们回顾一下实现过程:
一开始最容易想到的是原型链继承,通过把子类实例的原型指向父类实例来继承父类的属性和方法,但原型链继承的缺陷在于对子类实例继承的引用类型的修改会影响到所有的实例对象以及无法向父类的构造方法传参。
因此我们引入了构造函数继承, 通过在子类构造函数中调用父类构造函数并传入子类this来获取父类的属性和方法,但构造函数继承也存在缺陷,构造函数继承不能继承到父类原型链上的属性和方法。
所以我们综合了两种继承的优点,提出了组合式继承,但组合式继承也引入了新的问题,它每次创建子类实例都执行了两次父类构造方法,我们通过将子类原型指向父类实例改为子类原型指向父类原型的浅拷贝来解决这一问题,也就是最终实现 —— 寄生组合式继承