继承的6种方式

原型、构造函数、实例对象的关系

构造函数能够生成实例对象,实例对象拥有构造函数里的属性和方法,并继承构造函数原型上的属性及方法。

继承方式

原型链继承

子类原型等于父类构造函数的实例

特点:
子类实例能够继成父类及父类原型中属性和方法

问题:

  1. 如果父类构造函数中有引用类型的属性,会导致通过子类创建的所有实例都共享该属性,其中一个实例修改了这个引用值,其他实例的这个属性都会同步变化,造成数据污染。
  2. 子类构造函数无法向父类构造函数传值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 声明父类构造函数
function Super(){
this.name = 'super'
this.arr = [1,2,3]
}
// 声明父类原型属性
Super.prototype.getName = function(){
console.log(this.name)
}

// 子类构造函数
function Sub(){
this.age = 18
}
// 子类原型指向父类实例
Sub.prototype = new Super()

const sub1 = new Sub()
const sub2 = new Sub()
// 修改sub1的引用值会影响sub2的引用值
sub.arr.push(4)
console.log(sub1.name, sub1.arr) // super [ 1, 2, 3, 4 ]
console.log(sub2.name, sub2.arr) // super [ 1, 2, 3, 4 ]

借用构造函数继承

使用call或者apply在子类构造函数中调用父类构造函数

特点:子类构造函数能够向父类构造函数中传值,继承了父类构造函数的属性和方法,但无法继承父类原型上的属性和方法

问题:

  1. 只能在子类构造函数中定义方法,因此不能重用函数
  2. 子类不能访问父类原型上定义的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 声明父类构造函数
function Super(name){
this.name = name
}
// 声明父类原型属性
Super.prototype.getName = function(){
console.log(this.name)
}

// 子类构造函数
function Sub(name){
Super.call(this, name)
this.age = 18
}

const sub1 = new Sub('lili')
console.log(sub1.name, sub1.age)

组合继承(伪经典继承)

特点:解决了上述两种的缺陷;

问题:父类构造函数会执行两次

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
// 声明父类构造函数
function Super(name){
this.name = name
this.arr = [1,2,3]
}
// 声明父类原型属性
Super.prototype.getName = function(){
console.log(this.name)
}

// 子类构造函数
function Sub(name){
Super.call(this, name)
this.age = 18
}

Sub.prototype = new Super()

const sub1 = new Sub('sub1')
const sub2 = new Sub('sub2')
// 修改sub1实例的引用值,不会影响到sub2
sub1.arr.push(4)

console.log(sub1.name, sub1.arr) // sub1 [ 1, 2, 3, 4 ]
console.log(sub2.name, sub2.arr) // sub2 [ 1, 2, 3 ]

原型式继承

本质是进行了浅拷贝,与原型链相似,引用值会被共享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 核心函数:创建临时的构造函数,传入的对象作为临时构造函数的原型,然后返回这个临时构造函数的实例
function objectCreate(fatherPrototype){
function Fun(){}
Fun.prototype = prototype // 浅拷贝
return new Fun()
}

const person = {
name: 'person',
arr: [1,2,3]
}

const son1 = objectCreate(person)
son1.name = 'son1'
son1.arr.push(4)

const son2 = objectCreate(person)
son2.name = 'son2'
son2.arr.push(5)

console.log(son1.name, son1.arr) //son1 [ 1, 2, 3, 4, 5 ]
console.log(son2.name, son2.arr) //son2 [ 1, 2, 3, 4, 5 ]

寄生式继承

背后思路类似于寄生构造函数和工厂模式:先创造一个实现继承的构造函数,然后再增强对象(给继承后的对象添加方法),再将这个对象返回。

特点:类似借用构造函数,函数不能重用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function objectCreate(fatherPrototype){
function Fun(){}
Fun.prototype = fatherPrototype // 浅拷贝
return new Fun()
}

function createAnother(fatherPrototype){
let cloneObj = objectCreate(fatherPrototype) // 通过调用函数创建新对象
cloneObj.sayHi = function(){ // 增强对象,类似于给对象扩展功能,添加方法
console.log('hi')
}
return cloneObj
}

const person = {
name: 'person',
arr: [1,2,3]
}

const son1 = createAnother(person)
son1.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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function objectCreate(fatherPrototype){
function Fun(){}
Fun.prototype = fatherPrototype // 浅拷贝
return new Fun()
}

function inheritPrototype(subType, superType){
let prototype = objectCreate(superType) // 创建对象
prototype.consructor = subType // 增强对象
subType.prototype = prototype
}
function Super(name){
this.name = name
this.arr = [1,2,3]
}
// 声明父类原型属性
Super.prototype.getName = function(){
console.log(this.name)
}

// 子类构造函数
function Sub(name){
Super.call(this, name)
this.age = 18
}

inheritPrototype(Sub, Super)

Sub.prototype.sayHi = function(){
console.log('hi')
}


const son1 = new Sub()
son1.name = 'son1'
son1.arr.push(4)

const son2 = new Sub()
son2.name = 'son2'
son2.arr.push(5)

console.log(son1.name, son1.arr) //Super [ 1, 2, 3, 4 ]
console.log(son2.name, son2.arr) //Super [ 1, 2, 3, 5 ]

得多看几遍,多理解几遍~

-------------本文结束&感谢您的阅读-------------