# 1. 原型链继承

优点:

缺点:

  • 创建子类实例时,不能向父类的构造函数中传递参数
  • 父类中所有引用类型的属性会被所有子类实例共享,也就说一个子类实例修改了父类中的某个引用类型的属性时,其他子类实例也会受到影响
function Parent() {
  this.name = "parent";
  this.hobby = ["sing", "rap"];
}
function Child() {
  this.type = "child";
}

Child.prototype = new Parent();

let child1 = new Child();
let child2 = new Child();
child1.hobby.push("basketball");
console.log(child1.hobby); // [ 'sing', 'rap', 'basketball' ]
console.log(child2.hobby); // [ 'sing', 'rap', 'basketball' ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 2. 借用构造函数继承(经典继承)

优点:

  • 在子类构造函数中可以向父类的构造函数中传递参数
  • 避免了父类中的引用类型属性在子类中共享的问题

缺点:

  • 父类原型对象上的方法子类继承不到
function Parent(age) {
  this.name = "parent";
  this.age = age;
  this.hobby = ["sing", "rap"];
}
Parent.prototype.sayHi = function() {
  console.log("Hi");
};
function Child(age) {
  Parent.call(this, age);
  this.type = "child";
}
let child1 = new Child(15);
let child2 = new Child(15);

child1.hobby.push("basketball");
console.log(child1.name); // parent
console.log(child1.age); // 15
console.log(child1.type); // child
console.log(child1.hobby); // [ 'sing', 'rap', 'basketball' ]
console.log(child2.hobby); // [ 'sing', 'rap' ]
child1.sayHi(); // 报错,child1.sayHi is not a function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 3. 组合继承

优点:

  • 在子类构造函数中可以向父类的构造函数中传递参数
  • 避免了父类中的引用类型属性在子类中共享的问题
  • 父类原型对象上的方法子类也可以继承到

缺点:

  • 父类构造函数被调用了两次
function Parent(age) {
  this.name = "parent";
  this.age = age;
  this.hobby = ["sing", "rap"];
}
Parent.prototype.sayHi = function() {
  console.log("Hi");
};
function Child(age) {
  Parent.call(this, age); // 第二次调用Parent
  this.type = "child";
}

Child.prototype = new Parent(); // 第一次调用Parent
Child.prototype.constructor = Child;

let child1 = new Child(15);
let child2 = new Child(15);

child1.hobby.push("basketball");
console.log(child1.name); // parent
console.log(child1.age); // 15
console.log(child1.type); // child
console.log(child1.hobby); // [ 'sing', 'rap', 'basketball' ]
console.log(child2.hobby); // [ 'sing', 'rap' ]
child1.sayHi(); // Hi
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

# 4. 组合式继承优化 1

优点:

  • 在子类构造函数中可以向父类的构造函数中传递参数
  • 避免了父类中的引用类型属性在子类中共享的问题
  • 父类原型对象上的方法子类也可以继承到
  • 父类构造函数也只调用了一次

缺点:

  • 向子类原型上增加属性或方法时会影响到父类原型
function Parent(age) {
  this.name = "parent";
  this.age = age;
  this.hobby = ["sing", "rap"];
}
Parent.prototype.sayHi = function() {
  console.log("Hi");
};
function Child(age) {
  Parent.call(this, age);
  this.type = "child";
}

Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;
// 向子类原型上增加testProp属性,同时会被添加到父类原型上
Child.prototype.testProp = 1;
console.log(Parent.prototype); // 1

let child1 = new Child(15);
let child2 = new Child(15);

child1.hobby.push("basketball");
console.log(child1.name); // parent
console.log(child1.age); // 15
console.log(child1.type); // child
console.log(child1.hobby); // [ 'sing', 'rap', 'basketball' ]
console.log(child2.hobby); // [ 'sing', 'rap' ]
child1.sayHi(); // Hi
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

# 5. 组合式继承优化 2(实现继承最有效的方式!)

优点:

  • 实现继承最有效的方式!

缺点:

function Parent(age) {
  this.name = "parent";
  this.age = age;
  this.hobby = ["sing", "rap"];
}
Parent.prototype.sayHi = function() {
  console.log("Hi");
};
function Child(age) {
  Parent.call(this, age);
  this.type = "child";
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

let child1 = new Child(15);
let child2 = new Child(15);

child1.hobby.push("basketball");
console.log(child1.name); // parent
console.log(child1.age); // 15
console.log(child1.type); // child
console.log(child1.hobby); // [ 'sing', 'rap', 'basketball' ]
console.log(child2.hobby); // [ 'sing', 'rap' ]
child1.sayHi(); // Hi
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

# 6. ES6 的 extends 关键字实现继承的内部原理

我们可以利用 ES6 里的 extends 的语法糖,使用关键词很容易直接实现 JavaScript 的继承,但是如果想深入了解 extends 语法糖是怎么实现的,就得深入研究 extends 的底层逻辑。

我们先看下用利用 extends 如何直接实现继承,代码如下。

class Person {

  constructor(name) {

    this.name = name

  }

  // 原型方法

  // 即 Person.prototype.getName = function() { }

  // 下面可以简写为 getName() {...}

  getName = function () {

    console.log('Person:', this.name)

  }

}

class Gamer extends Person {

  constructor(name, age) {

    // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。

    super(name)

    this.age = age

  }

}

const asuna = new Gamer('Asuna', 20)

asuna.getName() // 成功访问到父类的方法

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

因为浏览器的兼容性问题,如果遇到不支持 ES6 的浏览器,那么就得利用 babel 这个编译工具,将 ES6 的代码编译成 ES5,让一些不支持新语法的浏览器也能运行。

那么最后 extends 编译成了什么样子呢?我们看一下转译之后的代码片段。

function _possibleConstructorReturn (self, call) { 

		// ...

		return call && (typeof call === 'object' || typeof call === 'function') ? call : self; 

}

function _inherits (subClass, superClass) { 

    // 这里可以看到

	subClass.prototype = Object.create(superClass && superClass.prototype, { 

		constructor: { 

			value: subClass, 

			enumerable: false, 

			writable: true, 

			configurable: true 

		} 

	}); 

	if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 

}



var Parent = function Parent () {

	// 验证是否是 Parent 构造出来的 this

	_classCallCheck(this, Parent);

};

var Child = (function (_Parent) {

	_inherits(Child, _Parent);

	function Child () {

		_classCallCheck(this, Child);

		return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));

}

	return Child;

}(Parent));

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
56
57
58

从上面编译完成的源码中可以看到,它采用的也是我们上面所说的第5种继承方式,因此也证明了这种方式是较优的解决继承的方式。