继承
# 继承
继承的本质就是原型链。
参考教程:一张文章理解JS继承 (opens new window)
[TOC]
# 一、不使用Object.create()
# 1.1 原型链继承
- 核心:将父类的实例作为子类的原型。
Son.prototype = new Father();
// 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向Father。
Son.prototype.constructor = Son;
1
2
3
2
3
优点
- 父类方法可以复用
缺点
- 父类的引用属性会被所有子类实例共享
- 子类构建实例时不能向父类传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给父类的构造函数传参。
function Father(name) {
this.value = [1,2,3];
this.name = name;
this.say = function(){};
}
function Son(){}
// 只能在这里给父类传递参数
Son.prototype = new Father('Lin');
Son.prototype.constructor = Son;
// 父类的引用属性会被所有子类实例共享,因为共用一个原型对象
// son1.__proto__ === son2.__proto__ (new Father())
let son1 = new Son();
son1.value.push(4);
console.log(son1.value); // [1,2,3,4]
console.log(son1.name); // 'Lin'
let son2 = new Son();
console.log(son2); // {value:[1,2,3,4],name:'Lin'}
// 父类的方法可以复用
console.log(son1.say === son2.say); // true
let father1 = new Father();
father1.value.push(5);
console.log(father1); // {value:[1,2,3,5],name:undefined}
let father2 = new Father();
console.log(father2); // {value:[1,2,3],name: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
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
# 1.2 构造函数继承
- 核心:将父类构造函数的内容复制给了子类的构造函数。这是所有继承中唯一一个不涉及到prototype的继承。
function Son() {
Father.call(this);
}
1
2
3
2
3
优点:和原型链继承完全反过来
- 父类的引用属性不会被共享
- 子类构建实例时可以向父类传递参数
缺点
- 父类的方法不能复用,子类实例的方法每次都是单独创建的。
function Father(name) {
this.value = [1, 2, 3];
this.name = name;
this.say = function(){};
}
Father.prototype.hello = function(){};
function Son(name) {
Father.call(this, name);
}
let son1 = new Son('Lin');
son1.value.push(4);
console.log(son1); // {value:[1,2,3,4],name:'Lin'}
let son2 = new Son('hj');
console.log(son2); // {value:[1,2,3],name:'hj'}
// 父类的方法不能共用
console.log(son1.say === son2.say); // false
// 子类并没有继承父类原型上的方法
console.log(son1.hello()); // TypeError:son.hello is not a function!
let father1 = new Father();
console.log(father1); // {value:[1,2,3],name: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1.3 组合继承
- 核心:原型链继承和构造函数继承的组合,兼具了二者的优点。
// 构造函数继承属性
function Son() {
Father.call(this); // 第二次调用Father()
}
// 原型链继承方法
Son.prototype = new Father(); // 第一次调用Father()
Son.prototype.constructor = Son;
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
function Father(name) {
this.value = [1, 2, 3];
this.name = name;
}
Father.prototype.say = function (){};
function Son(name) {
// 继承属性
Father.call(this, name);
}
// 继承方法
Son.prototype = new Father();
Son.prototype.constructor = Son;
// 子类构造实例时可向父类传参
let son1 = new Son('Lin');
son1.value.push(4);
console.log(son1); // {value:[1,2,3,4],name:'Lin'}
// 父类的引用属性不会被共享
let son2 = new Son('hj');
console.log(son2); // {value:[1,2,3],name:'hj'}
// 父类的方法可复用
console.log(son1.say === son2.say); // true
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
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
- 优点
- 父类的方法可以被复用
- 父类的引用属性不会被共享
- 子类构建实例时可以向父类传递参数
- 缺点
- 调用了两次父类的构造函数,造成性能上的浪费。第一次给子类的原型添加了父类的属性,第二次又给子类的构造函数添加了父类的属性,从而覆盖了子类原型中的参数。
- 优化方案
// 第一种
// 不足:因此当你执行类似 Son.prototype.myLabel = ... 的赋值语句时会直接修改 Father.prototype 对象本身
Son.prototype = Father.prototype;
// 第二种
// 寄生组合继承
1
2
3
4
5
6
2
3
4
5
6
# 二、使用Object.create()
# 2.1 原型式继承
- 核心:创建一个参数对象的副本。
let son = Object.create(father);
// 在传入一个参数的情况下,object.create()与object()行为相同
// 创建一个新对象并把新对象内部的prototype关联到指定对象o
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 优点
- 父类方法可以复用
- 缺点
- 父类的引用属性被所有子类实例共享
- 子类构造实例时不能向父类传递参数
let father = {
value: [1, 2, 3],
name: 'Lin',
say: function () {}
}
// 子类构造实例时不能向父类传参
let son1 = Object.create(father);
son1.value.push(4);
console.log(son1); // {value:[1,2,3,4],name:'Lin',say:f()}
// 父类的引用属性会被共享
let son2 = Object.create(father);
console.log(son2); // {value:[1,2,3,4],name:'Lin',say:f()}
// 父类的方法可复用
console.log(son1.say === son2.say); // true
console.log(father); // {value:[1,2,3,4],name:'Lin',say:f()}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function create(obj) {
function f() {}
// 这里相当于每次都是新的一个对象,一个新的堆,不会再和father公用一个堆
let _obj = JSON.parse(JSON.stringify(obj));
f.prototype = _obj;
return new f();
}
let father = {value:[1,2,3]};
let s1 = create(father);
let s2 = create(father);
s1.value.push(4);
console.log(s1.value); // [1,2,3,4]
console.log(s2.value); // [1,2,3]
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 2.2 寄生式继承
- 核心:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象。
function createAnother(father){
let son = Object.create(father);
son.say = function(){};
return son;
}
1
2
3
4
5
2
3
4
5
- 优点:没有。
function createAnother(father) {
let son = Object.create(father);
son.say = function () {};
return son;
}
let father = {
value: [1, 2, 3],
name: 'Lin',
}
// 子类构造实例时不能向父类传参
let son1 = createAnother(father);
son1.value.push(4);
console.log(son1.value); // value:[1,2,3,4]
// 父类的引用属性会被共享
let son2 = createAnother(father);
console.log(son2.value); // [1,2,3,4]
// 父类的方法不可复用
console.log(son1.say === son2.say); // false
console.log(father.value); // [1,2,3,4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 2.3 寄生组合继承
核心:解决组合继承构造函数被调用两次的问题。
优点:完美,子类不会受父类的改变而影响。
function inherit(son, father) {
// 创建对象,创建一个父级原型的副本
let prototype = Object.create(father.prototype);
// 增强对象,弥补因重写原型而失去默认的constructor属性
prototype.constructor = son;
// 指定对象,将副本赋值给子类型的原型
son.prototype = prototype;
}
function Father(name) {
this.value = [1, 2, 3];
this.name = name;
}
Father.prototype.say = function () {};
function Son(name) {
// 继承属性
Father.call(this, name);
}
inherit(Son, Father);
// 子类构造实例时可父类传参
let son1 = new Son('Lin');
son1.value.push(4);
console.log(son1); // {value:[1,2,3,4],name:'Lin'}
// 父类的引用属性不会被共享
let son2 = new Son('hj');
console.log(son2); // {value:[1,2,3],name:'hj'}
// 父类的方法可复用
console.log(son1.say === son2.say); // true
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
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
# 三、Class extends
class Father{}
class Son extends Father {
constructor{
// 调用父类的构造函数,返回子类的实例
super();
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7