这一篇将介绍js继承的几种方式。

原型继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let Super = function(name = 'eric') {
this.name = name;
this.getName = function() {
return this.name;
}
}
Super.prototype.hello = function() {
alert('Hello, ' + this.name + '!');
}
let Sub = function(sex = 'male') {
this.sex = sex;
}
Sub.prototype = new Super('eric'); //通过改变原型对象实现继承
Sub.prototype.constructor = Sub // 保持构造函数和原型对象的完整性
let sub1 = new Sub('male')
sub2 = new Sub('female');

console.log(sub1.getName()); // eric
console.log(sub1.hasOwnProperty('name')) // false 说明是继承而来的属性
console.log(sub1.getName === sub2.getName) // true,复用了方法
console.log(sub1.hello()) //正常显示

可以看出,父元素的方法和属性都得到了复用。但是子类实例没有自己的属性。父元素的原型也被继承了。

构造函数继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let Super = function(name = 'eric') {
this.name = name;
this.getName = function() {
return this.name;
}
}
Super.prototype.hello = function() {
alert('Hello, ' + this.name + '!');
}
let Sub = function(name, sex) {
Super.call(this, name);
this.sex = sex;
}
let sub1 = new Sub('eric', 'male');
let sub2 = new Sub('ada', 'female');
console.log(sub1.name) // 'eric'
console.log(sub1.hasOwnProperty('name')) // true 说明不是继承而来,是自己的属性
console.log(sub1.getName === sub2.getName) // false 方法没有得到复用
console.log(sub1.hello()) //undefined 父元素的原型没被继承

子类的每个实例都有自己的属性(name), super相当于把父元素的属性和方法传给子类(不包括原型的方法)。这些方法和属性属于子类自身的了。

上面两种组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let Super = function(name = 'eric') {
this.name = name;
this.getName = function() {
return this.name;
}
}
Super.prototype.hello = function() {
alert('Hello, ' + this.name + '!');
}
let Sub = function(sex = 'male') {
Super.call(this, 'eric') //继承父类属性
this.sex = sex;
}
Sub.prototype = new Super('eric'); //通过改变原型对象实现继承
Sub.prototype.constructor = Sub // 保持构造函数和原型对象的完整性
let sub1 = new Sub('male')
sub2 = new Sub('female');

console.log(sub1.getName()); // eric
console.log(sub1.hasOwnProperty('name')) // true 说明是属于自己的属性
console.log(sub1.getName === sub2.getName) // true,复用了方法
console.log(sub1.hello()) //正常显示

综合了上面两种的优点,既让子类有了自己的属性,也实现了父类和原型方法的继承。

因为父类构造函数被执行了两次,子类的原型对象(Sub.prototype)中也有一份父类的实例属性(name),而且这些属性会被子类实例(sub1,sub2)的属性覆盖掉(即通过sub1.name访问不到Sub.prototype上的name属性),也存在内存浪费。

寄生组合式继承

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
let Super = function(name = 'eric') {
this.name = name;
this.getName = function() {
return this.name;
}
}
Super.prototype = {
constructor: Super,
hello() {
alert('Hello, ' + this.name + '!');
}
}
let Sub = function(sex, name) {
Super.call(this, name);
this.sex = sex;
}
// 组合继承的缺点就是在继承父类方法的时候调用了父类构造函数,从而造成内存浪费,
// 现在只要解决了这个问题就完美了。那在复用父类方法的时候,
// 使用Object.create方法也可以达到目的,没有调用父类构造函数,问题解决。
Sub.prototype = Object.create(Super.prototype);
// 当然这个地方也可以使用Object.setPrototypeOf(Sub.prototype, Super.prototype)
// 因为更改一个对象的隐士原型(__proto__)对浏览器和js引擎都是很慢对操作,所以建议使用Object.create()创建一个具有指定原型对象的新对象
Sub.prototype.constructor = Sub;
let sub1 = new Sub('male')
sub2 = new Sub('female');

console.log(sub1.getName()); // eric
console.log(sub1.hasOwnProperty('name')) // true 说明是属于自己的属性
console.log(sub1.getName === sub2.getName) // false, 方法没被复用
console.log(sub1.hello()) //正常显示

就相当于用call把父元素的属性和方法给子类,然后父元素的原型给子类,这样子类可以用父元素的原型里面的方法。

class

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 Super {
constructor(props = { name: 'eric' }) {
console.log(props);
this.name = props.name;
}
setName(name) {
this.name = name;
}
getName() {
return this.name;
}
}
class Sub extends Super {
constructor(props) {
super(props = { name: 'bob' }); // 创建实例,继承父类属性和方法
this.sex = props.sex;
}
}
let sub1 = new Sub({
name: 'eric',
sex: 'male'
})
let sub2 = new Sub({
name: 'eric',
sex: 'female'
})

console.log(sub1.hasOwnProperty('name')) // true 说明是属于自己的属性
sub1.setName('ada');
console.log(sub1.getName(),sub2.getName()) // ada,bob,属性没复用,各自实例都有自己的属性。
console.log(sub1.getName === sub2.getName) // true; 复用了父类的方法
console.log(Sub.prototype.sex) // undefined
// 子类原型对象上没有父类构造函数中赋值的属性,不是组合式继承

可以看出es6 的class是组合继承的特点。