# 继承
参考文章:继承的八种写法·面试必问-掘金 (opens new window)
JavaScript语言没有类概念,所以JavaScript并非是基于类的继承,而是基于原型的继承
(ps:原型的概念参考这边文章 (opens new window))
JavaScript 摒弃类转而使用原型作为实现继承的基础,是因为基于原型的继承相比基于类的继承上在概念上更为简单。首先我们明确一点,类存在的目的是为了实例化对象,而 JavaScript 可以直接通过对象字面量语法轻松的创建对象。
作用
简化代码逻辑和结构,实现代码重用
# 组合继承
将原型链技术和构造函数技术结合起来,二者取其长处
// 父类构造函数School
function School() {
this.schName = '育英'
this.test = [1, 2, 3]
}
// 在构造函数School的原型上添加schoolName方法,打印学校名称
School.prototype.schoolName = function() {
console.log('学校名称是:', this.schName)
}
// 子类构造函数Student
function Student(name, age) {
// 继承父类构造函数的属性
School.call(this)
this.stuName = name
this.age = age
}
// 继承父类
Student.prototype = new School()
// 将子类的构造函数手动改为Student,否则还是School
Student.prototype.constructor = Student
// 为子类添加showInfo方法,打印学生的信息
Student.prototype.showInfo = function () {
console.log('姓名:', this.stuName, '年龄:', this.age)
}
// 测试
let boy = new Student('小明', 24)
let girl = new Student('木子', 18)
console.log(boy) // {schName: "育英", test: Array(3), stuName: "小明", age: 24}
boy.schoolName() // 学校名称是: 育英
boy.showInfo() // 姓名: 小明 年龄: 24
// 实例不会修改原型中引用类型的值
girl.test.push(4)
girl.test // [1, 2, 3, 4]
boy.test // [1, 2, 3]
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
29
30
31
32
33
34
35
36
37
38
39
40
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
29
30
31
32
33
34
35
36
37
38
39
40
# 确定自身属性和原型属性
使用hasOwnProperty()区分自身属性和原型属性
接着上面的组合继承的示例
boy.hasOwnProperty('stuName') // true
boy.hasOwnProperty('showInfo') // false
boy.hasOwnProperty('schName') // true
boy.hasOwnProperty('schoolName') // false
1
2
3
4
2
3
4
# 确定原型和实例的关系
使用isPrototypeOf()确定原型和实例的关系, j检测一个对象是否被包含在另一个对象的原型链中
接着上面的组合继承的示例
Student.prototype.isPrototypeOf(boy) // true
School.prototype.isPrototypeOf(Student) // false
School.prototype.isPrototypeOf(boy) // true
1
2
3
2
3
# 缺点
School 会被调用 2 次:第 1 次是Student.prototype = new School();,第 2 次是调用 School.call(this)。
# 寄生组合式继承
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype); // 创建对象,父原型的副本
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象,赋给子的原型
}
// 父类构造函数School
function School() {
this.schName = '育英'
this.test = [1, 2, 3]
}
// 在构造函数School的原型上添加schoolName方法,打印学校名称
School.prototype.schoolName = function() {
console.log('学校名称是:', this.schName)
}
// 子类构造函数Student
function Student(name, age) {
// 继承父类构造函数的属性
School.call(this)
this.stuName = name
this.age = age
}
// 将父类原型指向子类
this.inheritPrototype(Student, School)
// 或者可以用下面两行注释的代码替换inheritPrototype方法
// Student.prototype = Object.create(School.prototype)
// Student.prototype.constructor = Student
// 为子类添加showInfo方法,打印学生的信息
// 注意!! 子类的方法必须在上一步 也就是将父类原型指向子类后 定义
Student.prototype.showInfo = function () {
console.log('姓名:', this.stuName, '年龄:', this.age)
}
let girl = new Student('木木', 18) // {schName: "育英", test: Array(3), stuName: "木木", age: 18}
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
29
30
31
32
33
34
35
36
37
38
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
29
30
31
32
33
34
35
36
37
38
# 缺点
子类的方法必须在上一步 也就是将父类原型指向子类后 定义
# ES6中class的继承
Class 可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多
class School {
constructor() {
this.schName = '育英'
}
printSchoolName() {
console.log('学校名称:', this.schName)
}
}
class Student extends School {
constructor(name, age) {
super()
this.stuName = name
this.age = age
}
printInfo() {
console.log('姓名:', this.stuName, '年龄:', this.age)
}
}
let boy = new Student('小修', 8) // {schName: "育英", stuName: "小修", age: 8}
boy.printSchoolName() // 学校名称: 育英
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
super
关键字,它在这里表示父类的构造函数,用来新建父类的 this 对象。
注意
- 子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的this 对象,而是继承父类的 this 对象,然后对其进行加工。
- 只有调用 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有 super 方法才能返回父类实例。
# ES5 和 ES6 实现继承的区别
- ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。
- ES6 的继承机制完全不同,实质是先创造父类的实例对象 this (所以必须先调用 super() 方法),然后再用子类的构造函数修改 this