继承

# 继承

继承的本质就是原型链。

参考教程:一张文章理解JS继承 (opens new window)

inheritance

[TOC]

# 一、不使用Object.create()

# 1.1 原型链继承

  • 核心:将父类的实例作为子类的原型。
Son.prototype = new Father(); 
// 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向Father。
Son.prototype.constructor = Son;
1
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

# 1.2 构造函数继承

  • 核心:将父类构造函数的内容复制给了子类的构造函数。这是所有继承中唯一一个不涉及到prototype的继承。
function Son() {
    Father.call(this);
}
1
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

# 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
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
  • 优点
    • 父类的方法可以被复用
    • 父类的引用属性不会被共享
    • 子类构建实例时可以向父类传递参数
  • 缺点
    • 调用了两次父类的构造函数,造成性能上的浪费。第一次给子类的原型添加了父类的属性,第二次又给子类的构造函数添加了父类的属性,从而覆盖了子类原型中的参数。
    • 优化方案
// 第一种
// 不足:因此当你执行类似 Son.prototype.myLabel = ... 的赋值语句时会直接修改 Father.prototype 对象本身
Son.prototype = Father.prototype;

// 第二种
// 寄生组合继承
1
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
  • 优点
    • 父类方法可以复用
  • 缺点
    • 父类的引用属性被所有子类实例共享
    • 子类构造实例时不能向父类传递参数
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
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.2 寄生式继承

  • 核心:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象。
function createAnother(father){
    let son = Object.create(father);
    son.say = function(){};
    return son;
}
1
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 寄生组合继承

  • 核心:解决组合继承构造函数被调用两次的问题。

  • 优点:完美,子类不会受父类的改变而影响。

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

# 三、Class extends

class Father{}
class Son extends Father {
    constructor{
        // 调用父类的构造函数,返回子类的实例
        super();
    }
}
1
2
3
4
5
6
7