【我眼中的】 - 【2】继承


1. 原型链继承

过程:①定义父类构造函数 ➡ ②给父类的原型添加方法 ➡ ③定义子类型的构造函数 ➡④将创建的父类型对象赋值给子类型的原型⑤将子类型的构造属性设置为子类型 ➡ ⑥给子类型的原型添加方法 ➡ ⑦创建子类型的对象,可以调用父类型的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function People () {
this.pName = '人类'
}
People.prototype.getPName = function () {
console.log(this.pName);

}

function Student () {
this.sName = '学生'
}

Student.prototype = new People()
Student.prototype.constructor = Student

Student.prototype.getSName = function () {
console.log(this.sName);
}

var s = new Student()
console.log(s);

var p = new People()
console.log(p)

执行结果:

存在的问题

  1. 引用类型的属性被所有实例共享
    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 () {
    }

    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"]
  2. 在创建子类型实例时,不能向父类型传参

2.借用构造函数继承(假继承)

过程:①定义父类型构造函数 ➡ ②定义子类型的构造函数 ➡ ③在子类型构造函数中调用父类型构造

关键点:在子类型构造函数中通过call调用父类型的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
function People (name, age) {
this.name = name;
this.age = age;
}

function Student (name, age, price) {
People.call(this, name, age); //相当于 this.Person(name,age)
this.price = price;
}

var s = new Student('Tom', 12, 13000);
console.log(s); // Student { name: 'Tom', age: 12, price: 13000 }

优点

  1. 避免了引用类型的属性被所有实例共享
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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"]
  2. 在创建子类型实例时,可以向父类型传参

缺点

方法在构造函数中定义,每次创建实例都会创建一遍

3.组合继承(原型链继承 + 借用构造函数继承)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function People (name, age) {
this.name = name;
this.age = age;
}
People.prototype.setName = function (name) {
this.name = name;
}

function Student (name, age, price) {
People.call(this, name, age);
this.price = price;
}
Student.prototype = new People();
Student.prototype.constructor = Student;

Student.prototype.setPrice = function (price) {
this.price = price;
}

var s = new Student('Tom', 12, 3500);
s.setName('Jack');
s.setPrice(1500);
console.log(s); // People { name: 'Jack', age: 12, price: 1500 }

优点

融合继承原型链继承和构造函数继承的优点,是JavaScript中最常用的继承模式。

4.原型式继承

原型式继承适用于这种情况:你有一个对象,想在它的基础上再创建一个新的对象。你需要把这个对象先传给object(),然后再对返回的对象进行适当的修改。

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

※ 本质上,object()是对传入的对象执行了一次浅复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function object (o) {
function F () { }
F.prototype = o;
return new F();
}

var person = {
name: 'Jack',
frineds: ['Daming', 'Lingling']
}

var p1 = object(person);
var p2 = object(person);

p1.name = 'Sam';
console.log(p2.name); // Jack

p1.frineds.push('Amy');
console.log(p2.frineds); // [ 'Daming', 'Lingling', 'Amy' ]

※ 修改p1.name的值,p2.name的值并未发生改变,并不是因为p1p2有独立的name值,而是因为p1.name= 'Sam' 是给p1添加了name的属性,并赋值为Sam,并非修改了原型上name的值。

缺点

包含引用类型的属性值会被所有实例共享(与原型链继承相同)。

5.寄生式继承

创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来做增强对象,最后返回对象。

1
2
3
4
5
6
7
function createAnother(original){
let clone = object(original);
clone.sayHi = function(){
console.log('Hi');
}
return clone;
}

缺点

与借用构造函数继承模式相同,每次创建对象都会创建一遍方法。

6.寄生组合式继承

组合继承的方式也存在效率的问题,最主要的效率问题就是父类构造函数始终会被调用两次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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); // 第二次调用Parent()
this.age = age;
}

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

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

console.log(child1)

该如何精益求精,避免重复调用呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
}

// 关键的三步
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();


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

封装一下这个继承方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function object(o) {
function F() {}
F.prototype = o;
return new F();
}

function prototype(child, parent) {
var prototype = object(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}

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

这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceofisPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。 ——《JavaScript高级程序设计》

文章作者: qinwei
文章链接: https://qw-null.github.io/2022/04/07/2-我眼中的-继承/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 QW's Blog