JS继承的几种方式

定义一个父类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function People(name) {
// 实例属性
this.name = name || '人类';
// 实例方法
this.walk = function() {
console.log(this.name + '在行走');
}
// 父类实例引用属性
this.likes = [];
}
// 原型属性
People.prototype.age = 30;
People.prototype.hates = [];
// 原型方法
People.prototype.speak = function(text) {
console.log(this.name + '说:“' + text + '“');
}

原型链继承

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
41
42
43
44
45
46
47
48
49
// 子类定义
function Student(name) {
this.grade = '六年级';
this.study = function(type) {
console.log(this.name + '在学习:' + type);
}
}
Student.prototype = new People('学生'); // 关键点
Student.prototype.school = '实验小学';
Student.prototype.sleep = function() {
console.log(this.name + '在睡觉');
}
// 父类新增原型方法和属性
People.prototype.gender = 'male';
People.prototype.run = function(text) {
console.log(this.name + '在跑步');
}
// 测试代码
var student = new Student('Jack');
console.log(student.grade); // 六年级
console.log(student.study('英语')); // 学生在学习:英语
console.log(student.school); // 实验小学
console.log(student.sleep()); // 学生在睡觉
console.log(student.name); // 学生
console.log(student.walk()); // 学生在行走
console.log(student.age); // 30
console.log(student.speak('你好')); // 学生说:“你好“
console.log(student.gender); // male
console.log(student.run()); // 学生在跑步
console.log(student instanceof People); // true
console.log(student instanceof Student); // true
// another student
var another = new Student('Tomas');
another.likes.push('football');
// student
student.likes.push('Basketball');
student.hates.push('Apple');
console.log(another.name); // 学生
console.log(another.age); // 30
console.log(another.walk()); // 学生在行走
// 父类实例属性likes
console.log(student.likes); // ["football", "Basketball"]
console.log(another.likes); // ["football", "Basketball"]
// 父类原型属性hates
console.log(student.hates); // ["Apple"]
console.log(another.hates); // ["Apple"]

特点:

  • 子类将父类的实例作为原型,实例是子类的实例,也是父类的实例
  • 父类的原型方法、原型属性、实例方法和实例属性都能访问到
  • 父类新增的原型方法、原型属性,子类都能访问到
  • 创建子类实例时,无法向父类构造函数传参,仅在父类实例化时(new People('学生'))可传参
  • 父类的引用属性是所有子类实例共享的

注意:要为子类新增原型属性和方法,则必须放在 new People('学生') 这样的语句之后执行。

构造函数继承

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
41
42
43
44
45
46
47
// 子类定义
function Student(name) {
People.call(this, name); // 关键点
this.grade = '六年级';
this.study = function(type) {
console.log(this.name + '在学习:' + type);
}
}
Student.prototype.school = '实验小学';
Student.prototype.sleep = function() {
console.log(this.name + '在睡觉');
}
// 父类新增原型方法和属性
People.prototype.gender = 'male';
People.prototype.run = function(text) {
console.log(this.name + '在跑步');
}
// 测试代码
var student = new Student('Jack');
console.log(student.grade); // 六年级
console.log(student.study('英语')); // Jack在学习:英语
console.log(student.school); // 实验小学
console.log(student.sleep()); // Jack在睡觉
console.log(student.name); // Jack
console.log(student.walk()); // Jack在行走
console.log(student.age); // undefined
console.log(student.speak('你好')); // Uncaught TypeError: student.speak is not a function
console.log(student.gender); // undefined
console.log(student.run()); // Uncaught TypeError: student.speak is not a function
console.log(student instanceof People); // false
console.log(student instanceof Student); // true
// another student
var another = new Student('Tomas');
console.log(another.name); // Tomas
console.log(another.age); // undefined
another.likes.push('football'); // 1
student.likes.push('Basketball'); // 1
student.hates.push('Apple'); // Uncaught TypeError: Cannot read property 'push' of undefined
// 父类实例属性likes
console.log(student.likes); // ["Basketball"]
console.log(another.likes); // ["football"]
// 父类原型属性hates
console.log(student.hates); // undefined
console.log(another.hates); // undefined

特点:

  • 创建子类实例时,可以向父类构造函数传参
  • 解决了子类实例共享父类实例属性问题,但同时每个实例都有父类实例函数的副本,影响性能
  • 子类使用父类构造函数增强子类实例,等于是复制父类实例属性给子类
  • 父类的原型方法、原型属性,子类都无法访问
  • 实例仅是子类的示例,不是父类的实例

组合继承

通过调用父类构造函数,继承父类的属性,然后通过将父类实例作为子类原型,实现函数复用

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
41
42
43
44
45
46
47
48
49
// 子类定义
function Student(name) {
People.call(this, name); // 关键点
this.grade = '六年级';
this.study = function(type) {
console.log(this.name + '在学习:' + type);
}
}
Student.prototype = new People(); // 关键点
Student.prototype.constructor = Student;
Student.prototype.school = '实验小学';
Student.prototype.sleep = function() {
console.log(this.name + '在睡觉');
}
// 父类新增原型方法和属性
People.prototype.gender = 'male';
People.prototype.run = function(text) {
console.log(this.name + '在跑步');
}
// 测试代码
var student = new Student('Jack');
console.log(student.grade); // 六年级
console.log(student.study('英语')); // Jack在学习:英语
console.log(student.school); // 实验小学
console.log(student.sleep()); // Jack在睡觉
console.log(student.name); // Jack
console.log(student.walk()); // Jack在行走
console.log(student.age); // 30
console.log(student.speak('你好')); // Jack说:“你好“
console.log(student.gender); // male
console.log(student.run()); // Jack在跑步
console.log(student instanceof People); // true
console.log(student instanceof Student); // true
// another student
var another = new Student('Tomas');
another.likes.push('football');
// student
student.likes.push('Basketball');
student.hates.push('Apple');
console.log(another.name); // Tomas
console.log(another.walk()); // Tomas在行走
// 父类实例属性likes
console.log(student.likes); // ["Basketball"]
console.log(another.likes); // ["football"]
// 父类原型属性hates
console.log(student.hates); // ["Apple"]
console.log(another.hates); // ["Apple"]

特点:

  • 实例是子类的实例,也是父类的实例
  • 父类的原型方法、原型属性、实例方法和实例属性都能访问到
  • 父类新增的原型方法、原型属性,子类都能访问到
  • 创建子类实例时,可以向父类构造函数传参
  • 子类共享父类的原型属性,不共享父类实例属性
  • 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

唯一缺点是多消耗了一点内存

寄生组合继承

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 子类定义
function Student(name) {
People.call(this, name); // 关键点
this.grade = '六年级';
this.study = function(type) {
console.log(this.name + '在学习:' + type);
}
}
(function() { // 关键点
// 创建一个没有实例方法的类,砍掉父类的实例属性
var Super = function(){};
Super.prototype = People.prototype;
// 将实例作为子类的原型
Student.prototype = new Super();
})();
Student.prototype.constructor = Student;
Student.prototype.school = '实验小学';
Student.prototype.sleep = function() {
console.log(this.name + '在睡觉');
}
// 父类新增原型方法和属性
People.prototype.gender = 'male';
People.prototype.run = function(text) {
console.log(this.name + '在跑步');
}
// 测试代码
var student = new Student('Jack');
console.log(student.grade); // 六年级
console.log(student.study('英语')); // Jack在学习:英语
console.log(student.school); // 实验小学
console.log(student.sleep()); // Jack在睡觉
console.log(student.name); // Jack
console.log(student.walk()); // Jack在行走
console.log(student.age); // 30
console.log(student.speak('你好')); // Jack说:“你好“
console.log(student.gender); // male
console.log(student.run()); // Jack在跑步
console.log(student instanceof People); // true
console.log(student instanceof Student); // true
// another student
var another = new Student('Tomas');
another.likes.push('football');
// student
student.likes.push('Basketball');
student.hates.push('Apple');
console.log(another.name); // Tomas
console.log(another.walk()); // Tomas在行走
// 父类实例属性likes
console.log(student.likes); // ["Basketball"]
console.log(another.likes); // ["football"]
// 父类原型属性hates
console.log(student.hates); // ["Apple"]
console.log(another.hates); // ["Apple"]

特点:

  • 包含组合继承的所有优点,并解决了父类实例属性初始化两次的问题(多消耗内存问题)

唯一缺点是实现稍复杂