在学习继承前希望读者先去学习下js原型和原型对象的知识。
对于js原型和原型对象的理解

原型链继承

让一个函数的原型指向另一个引用类型的对象,这个对象中又有指针是指向另一个引用类型的原型对象的,原型对象也可以继续指下去,直到原型链的末端

1
2
3
4
5
6
7
8
9
10
11
12
13
function Animal(){
this.name='动物'
}
Animal.prototype.say=function(){
return this.name
}
function Cat(){
this.name='猫'
}
Cat.prototype=new Animal()
let c=new Cat()
c.say() //猫
//可以看到Cat继承了say这个方法

这里的Cat的prototype指向了Animal的实例,而Animal的实例的属性_proto_指向了Animal.prototype,Animal.prototype的constructor又指向了Animal构造函数,这就是构成了一条原型链,原型链到这里其实还没有结束,因为我们知道所有的对象都是继承Object的,所以

1
2
Animal.prototype._proto_==Object.prototype //true
Object.prototype._proto_==Object //true

这样就构成了一个完整的原型链,这边要注意的是所有函数的原始原型对象都是指向Object原型对象的。原型链的末端就是Object.prototype
如图所示:
红色线条就构成了一条原型链

在这里插入图片描述
在这里插入图片描述

原型链的特点就是它既是子类的实例也是父类的实例,且如果继承的对象中是个引用类型的,那么这将会被所有实例全部引用一个引用类型的数据,原型链继承一般也很少使用。

1
2
c instanceof Animal //true
c instanceof Cat //true

构造函数继承

这个方法其实很简单,就是通过call或者是apply在一个构造函数中执行一下另一个构造函数即可。

1
2
3
4
5
6
7
8
9
10
function Animal(){
this.type=['狗','猪']
}
function Cat(){
Animal.call(this)
}
let c=new Cat()
c.type.push('猫')
c.type //['狗','猪','猫']
//可以看到Cat继承了Animal的type属性。

构造函数继承中不存在函数复用,所有都是借用构造函数来初始化的,所以我们也很少用

组合继承

组合继承顾名思义,就是将原型链继承和构造函数继承放在一块来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Animal(name){
this.name=name
}
Animal.prototype.say=function(){
console.log('--动物--')
}
function Cat(name){
Animal.call(this,name)
}
Cat.prototype=new Animal()
Cat.prototype.constructor=Cat
let c=new Cat('猫')
c.name//猫
c.say() //--动物--

组合继承最大的缺点就是它会调用两次父类的构造函数。

原型式继承

最开始原型式的继承提出方案是这个样子的

1
2
3
4
5
6
7
8
9
function object(obj){
function f(){}
f.prototype=obj
let F=new f()
return F
}
let people={name:'姓名'}
let p=new object(people)
p.name //姓名

object函数接受一个对象 然后在函数里面声明一个局部的构造函数,让其原型指向传过来的参数,然后实例化并返回就可

ECMAScript5规范化了原型式继承,即Object.create接收两个参数,一个是用作新对象原型的对象和一个新对象作为额外的属性

1
2
3
let Animal={name:'动物'}
let Cat=Object.create(Animal)
Cat.name //动物

只是一个对象要类似于另一个对象,这是完全可以胜任的,要注意的就是引用类型的继承还是会共享相应的值。

寄生式继承

与原型式基本类似,只不过函数中创建对象的方式不同 ,他只考虑最基本的

1
2
3
4
5
6
7
8
9
10
11
function create(obj){
let clone=object(obj) //调用原型式的函数
clone.say=function(){
console.log('说话')
}
return clone
}
let p={name:'姓名'}
let o=create(p)
o.name// 姓名
o.say// 说话

如果只考虑最基本的对象而不考虑通过构造函数来生成对象的话,寄生式继承也是一种好的方式

寄生组合继承

在介绍寄生组合前我们先来看下组合继承的两次生成实例。

1
2
3
4
5
6
7
8
9
function Animal(){
this.name='动物'
}
function Cat(){
Animal.call(this) //第二次调用
}
Cat.prototype=new Animal() //第一次调用
Cat.Prototype.constructor=Cat
let c=new Cat()

我们可以看到组合继承调用了两次父类的构造函数,第二次调用会使得原型中的属性被实例中的同名属性所覆盖,因此要解决这个问题,就有了寄生组合继承,其实就是在定义子类的原型时不去new父类,我们只要一开始的时候获取到父类的原型对象的副本即可

1
2
3
4
5
6
7
8
9
10
11
function Animal(){
this.name='动物'
}
function Cat(){
Animal.call(this) //只有这一次执行
}
let o=Object.create(Animal.prototype)//获取一个父类原型的副本
Cat.prototype=o
Cat.prototype.constructor=Cat
let c=new Cat()
c.name //动物

es6中的继承

es6中新增了class extends关键字,让js具有了与其他OO语言一样的对象写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A{
constructor(){
console.log('aaa')
this.a='a'
}
}
class B extends A{
constructor(){
super()
console.log('bbb')
this.b='b'
}
}
let b=new B()
b.a //'a'
b.b //'b'