0%

JS中的继承

原型链继承

将父类的实例作为子类实例的原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Parent () {
this.names = ['kevin', 'daisy'];
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy", "yayu"]

问题:

  1. 父类引用类型的属性被所有实例共享

  2. 创建子类型实例时不能给父类型构造函数传参

    可以看到Child其实是 ‘’空’‘ 的,自然无法传参

借用构造函数

在子类构造函数中调用父类构造函数,可以在子类构造函数中使用call()apply()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Parent () {
this.names = ['kevin', 'daisy'];
}

function Child () {
Parent.call(this);
}

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy"]

优点:

  1. 避免了引用类型的属性被所有实例共享

  2. 可以在 Child 中向 Parent 传参

缺点:

  1. 子类不能访问父类prototype(这里即Parent.prototype)上的方法
  2. 所有方法属性都写在构造函数中,每次创建实例都会初始化

组合继承 *

基本的思路就是使用原型链继承父类prototype上的属性和方法,而通过构造函数继承实例属性,这样既可以实现方法重用,又可以让每个实例都有自己的属性

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
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
console.log(this.name)
}

function Child (name, age) {

Parent.call(this, name);

this.age = age;

}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child1 = new Child('kevin', '18');

child1.colors.push('black');

console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('daisy', '20');

console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

组合继承结合了原型链继承和借用构造函数继承的优点,这很棒,但是也存在一个小问题,在上述过程中父类构造函数执行了两次,带来的后果是什么呢?后果是子类实例和其原型上存在同名属性,最后子类属性会屏蔽原型上的属性,这虽然不影响使用,但却不够优雅,后面的寄生式组合继承会解决这个问题。

原型式继承

就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型

1
2
3
4
5
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}

缺点: 与原型链继承一样, 引用类型的属性会被所有实例共享,同时不能

寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。可以看到内部使用了Object.create(),因此其本质上是在原型式继承返回的新对象上增加了新的属性和方法,实现增强效果。

1
2
3
4
5
6
7
function createObj (o) {
var clone = Object.create(o);
clone.sayName = function () {
console.log('hi');
}
return clone;
}

缺点: 同借用构造函数继承,每次都会重新创建方法,且Object.create()执行浅复制,多个实例的引用类型指向相同,造成污染。

寄生组合式继承 *

组合式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
console.log(this.name)
}

function Child (name, age) {
Parent.call(this, name);
this.age = age;
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

组合式继承中父类构造函数Parent()调用了两次

一次是设置子类实例的原型,

1
Child.prototype = new Parent()

一次是调用子类构造函数,为什么在这里也调用了一次Parent()呢?

1
let child1 = new Child('kevin', '18');

因为首先Child是这样的:

1
2
3
4
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}

在用new创建Child实例的过程中会经历如下阶段:

  • 生成一个以Child.prototype为原型的对象
  • 使用给定的参数调用构造函数Child, 然后将this指向新创建的对象
  • 返回新创建的对象(Child中并没有return语句指定返回哪个对象,因此默认返回新创建的对象)

因此在第二步调用构造函数的过程中又调用了一次父类构造函数Parent

寄生组合式继承 就是为了避免重复调用父类构造函数:

1
2
3
4
Child.prototype = Object.create(Parent.prototype);
// Child.prototype = Parent.prototype
// 直接这样的话会存在隐患,如果修改Child.prototype的话会影响Parent.prototype
Child.prototype.constructor = Child;

这样就避免了在设置子类实例的原型时调用父类构造函数。

Object.create(o)的作用是返回一个新对象,该对象以给定对象o为原型

其实现类似:

1
2
3
4
5
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}

可以理解为原型式继承

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
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
console.log(this.name)
}

function Child (name, age) {
Parent.call(this, name);
this.age = age;
}

Child.prototype = Object.create(Parent.prototype);
// Child.prototype = Parent.prototype
// 直接这样的话会存在隐患,如果修改Child.prototype的话会影响Parent.prototype
Child.prototype.constructor = Child;



// 可以封装一下
// function prototype(child, parent) {
// var prototype = Object.create(parent.prototype);
// prototype.constructor = child;
// child.prototype = prototype;
// }

// // 当我们使用的时候:
// prototype(Child, Parent);

《JavaScript高级程序设计》

JavaScript深入之继承

JS原型链与继承别再被问倒了