JS中的继承

继承指的是可以让某个类型的对象获得另一个类型的对象的属性和方法. JavaScript本身并没有继承这一现有的机制, 下面看看我们是如何自己实现继承的.

类式继承

缺点

  1. 如果父类有引用类型的属性, 示例变量直接会相互影响
  2. 创建父类的时候, 无法向父类构造函数传递参数初始化

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 声明父类
function superClass() {
this.superValue = 'super'
this.list = [1, 2, 3]
}
// 为父类添加共有方法
superClass.prototype.getsuperValue = function () {
return this.superValue
}
// 声明子类
function subClass() {
this.subValue = 'sub'
}
// 继承父类
subClass.prototype = new superClass()
// 为子类添加共有方法
subClass.prototype.getsubValue = function () {
return this.subValue
}
// 创建两个class
var sub1 = new subClass()
sub1.list.push(4)
var sub2 = new subClass()

输出示例

1
2
3
4
5
// 获取父类属性
console.log(sub2.getsuperValue()) // super
// 另一个对象的list也被修改了
console.log(sub1.list) // [1, 2, 3, 4]
console.log(sub2.list) // [1, 2, 3, 4]

构造函数继承

缺点

  1. 没有涉及到原型prototype, 所以父类的原型方法不会被子类继承

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 声明父类
function superclass(id) {
// 引用类型共有属性
this.books = [1, 2, 3]
// 值共有属性
this.id = id
}
// 父类声明原型方法
superclass.prototype.showBooks = function () {
return this.books
}
// 声明子类
function subclass(id) {
// 继承父类
superclass.call(this, id)
}
// 创建第一个子类的实例
var sub1 = new subclass(10)
sub1.books.push(4)
// 创建第二个子类的实例
var sub2 = new subclass(20)

输出示例

1
2
3
console.log(sub1.books, sub1.id)  // [1, 2, 3, 4] 10
console.log(sub2.books, sub2.id) // [1, 2, 3] 20
sub1.showBooks() // Uncaught TypeError: sub1.showBooks is not a function

组合继承

缺点

  1. 在使用构造函数继承时执行了一遍父类的构造函数, 在实现子类原型的类式继承时又调用了一遍父类构造函数. 因此父类构造函数被调用了两遍

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 声明父类
function superclass(name) {
// 值类型共有属性
this.name = name
// 引用类型共有属性
this.books = [1, 2, 3]
}
// 父类原型共有方法
superclass.prototype.getName = function () {
return this.name
}
// 声明子类
function subclass(name, time) {
// 构造函数式继承父类name属性
superclass.call(this, name)
// 子类新增共有属性
this.time = time
}
// 类式继承 子类原型继承父类
subclass.prototype = new superclass()
// 子类原型方法
subclass.prototype.getTime = function () {
return this.time
}
// 创建2个子类
var sub1 = new subclass('js book', 200)
sub1.books.push('java book')
var sub2 = new subclass('ios book', 300)

输出示例

1
2
3
4
console.log(sub1.books) // [1, 2, 3, 'java book']
console.log(sub2.books) // [1, 2, 3]
console.log(sub1.getTime()) // 200
console.log(sub2.getName()) // ios book

* 寄生组合式继承

在组合式继承中, 通过构造函数继承的属性和方法是没有问题的. 所以我们主要研究通过寄生式继承重新继承父类原型. 即 我们需要继承的仅仅是父类的原型, 不再需要调用父类的构造函数. 换句话说, 在构造函数继承中我们已经调用了父类的构造函数, 因此我们需要的就是父类的原型对象的一个副本, 而这个副本我们通过原型继承便可得到.

但是如果直接赋值给子类, 对父类原型对象复制得到的复制对象p中的constructor指向的不是subClass子类对象, 因此在寄生式继承中要对复制对象p做一次增强, 修复其constructor属性指向不正确的问题. 最后将得到的复制对象p赋值给子类的原型, 这样子类的原型就继承了父类的原型并且没有执行父类的构造函数

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 原型式继承
function inheritobject(o) {
// 声明一个过渡函数对象
function F() {}
// 过渡对象的原型继承对象
F.prototype = o
// 返回过渡对象的一个实例, 该实例的原型继承了父对象
return new F()
}

// 寄生式继承
function inheritPrototype(subclass, superclass) {
// 复制一份父类的原型副本保存在变量中
var p = inheritobject(superclass.prototype)
// 修正因为重写子类原型导致子类的constructor属性被修改
p.constructor = subclass
// 设置子类原型
subclass.prototype = p
}

寄生组合式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 定义父类
function superclass(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
// 定义父类原型方法
superclass.prototype.getName = function () {
return this.name
}
// 定义子类
function subclass(name, time) {
// 构造函数式继承
superclass.call(this, name)
// 子类新增属性
this.time = time
}
// 寄生式继承父类原型
inheritPrototype(subclass, superclass)
// 子类新增原型方法
subclass.prototype.getTime = function () {
return this.time
}
// 创建两个测试方法
var sub1 = new subclass('js book', 2014)
sub1.colors.push('black')
var sub2 = new subclass('css book', 2013)

输出示例

1
2
3
4
console.log(sub1.colors) //  ['red', 'blue', 'green', 'black']
console.log(sub2.colors) // ['red', 'blue', 'green']
console.log(sub2.getName()) // css book
console.log(sub2.getTime()) // 2013

多继承

JavaScript中的继承时依赖于原型prototype链实现的, 只有一条原型链. 所以理论上是不能继承多个父类的. 然而JavaScript是灵活的, 通过一些技巧可以继承多个对象的属性来实现类似的多继承.这边的实现比较简单, 仅仅进行了值拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var mix = function () {
var i = 0 // 从第一个参数起为被继承对象
var len = arguments.length // 获取参数长度
var arg // 缓存参数对象
// 遍历被继承的对象
for (; i < len; i++) {
// 缓存当前对象
arg = arguments[i]
// 遍历被继承对象中的属性
for (const property in arg) {
this[property] = arg[property]
}
}
}
Object.prototype.mix = mix
function color() {
this.color = '黑 白'
}
function animation() {
this.food = '鱼'
}
function qie() {
this.name = '企鹅'
}
let sub = new qie()
sub.mix(new color(), new animation())
console.log(sub.color, sub.name, sub.food) // 黑 白 企鹅 鱼