一、常见的几种继承方式
- 原型链继承
- 构造函数继承
- 组合继承
- 原型式继承
- 寄生继承
- 寄生组合继承
-
二、常见继承方式的优缺点
1 - 原型链继承
实现:手动绑定父类对象为子类的原型链,使用new关键字实例化子类
- 关键:子类和父类都是构造函数,不是具体的字面量表达式 ```javascript // 父类构造函数 function Parent1(){ this.name=’parent1’; this.friends = [‘Tom’,’Joly’,’Tim’]; } // 子类构造函数 function Child1(){ this.age = 12; } // 子类的原型 = 父类对象 Child1.prototype = new Parent1();
// 子类实例化 let child1 = new Child1(); console.log(child1.friends); // [‘Tom’,’Joly’,’Tim’] let child1_2 = new Child1(); child1_2.friends.push(“test”); console.log(child1.friends); // [ ‘Tom’, ‘Joly’, ‘Tim’, ‘test’ ] console.log(child1_2.friends); // [ ‘Tom’, ‘Joly’, ‘Tim’, ‘test’ ]
- 缺点:多个实例对象之间共享一个原型对象,当某个实例修改了原型对象中的数据时,其他的实例也随之改变。
<a name="BIoWY"></a>
### 2 - 构造函数继承
- 实现:在子类的构造函数中硬绑定this指针的指向,从而在子类中也可以访问父类的属性和方法
- 关键:父类和子类都是构造函数的形式
- 缺点:子无法继承父类原型上的属性。
```javascript
// 父类构造函数
function Parent2(){
this.name = 'Parent2';
this.friends = ['Tom','Joly','Tim'];
}
Parent2.prototype.getName = function (){
return this.name
};
Parent2.prototype.type = "type";
// 子类构造函数
function Child3(){
// 在子类构造函数中修改父类this指针的指向
Parent2.call(this);
this.age = 12;
}
// 实例化子类
const child2 = new Child3();
console.log(child2);
console.log(child2.type) // undefined
3 - 组合继承
- 实现:集合了原型链继承和构造函数继承
- 关键:父类和子类都是构造函数形式
缺点:在子类中调用了两遍父类,影响性能问题。使得子类中的属性重复。
// 父类
function Parent3(){
this.name = 'Parent3';
this.friends = ['Tom','Joly','Tim'];
}
Parent3.prototype.getName = function (){
return this.name;
}
// 子类
function Child3(){
// 在子类中使用构造函数继承继承父类的自有属性
Parent3.call(this);
this.age = 12;
}
// 通过原型链继承,继承父类的原型链上的属性
Child3.prototype = new Parent3();
// 因为修改了prototype的指向,所以需要手动绑定constructor指针
Child3.prototype.constructor = Child3;
let child3_1 = new Child3();
let child3_2 = new Child3();
console.log(child3_1); // { name: 'Parent3', friends: [ 'Tom', 'Joly', 'Tim' ], age: 12 }
console.log(child3_2); // { name: 'Parent3', friends: [ 'Tom', 'Joly', 'Tim' ], age: 12 }
child3_1.friends.push("test");
console.log(child3_1.friends); // [ 'Tom', 'Joly', 'Tim', 'test' ]
console.log(child3_2.friends); // [ 'Tom', 'Joly', 'Tim' ]
4 - 原型式继承
实现:直接通过父对象创建子对象,使用object.create 实现原型继承
- 关键:父对象是字面量对象
- 缺点:多个子对象之间共享一个原型对象 ```javascript let parent4 = { name:’parent4’, friends:[‘Tom’,’Joly’,’Tim’], getName(){ return this.name } }; let child42_1 = Object.create(parent4); let child42_2 = Object.create(parent4); child42_1.friends.push(“child43_1”); child42_2.friends.push(“child42_2”) console.log(child42_2.friends); // [ ‘Tom’, ‘Joly’, ‘Tim’, ‘child43_1’, ‘child42_2’ ] console.log(child42_1.friends); // [ ‘Tom’, ‘Joly’, ‘Tim’, ‘child43_1’, ‘child42_2’ ]
<a name="MSP8K"></a>
### 5 - 寄生式继承
- 实现:寄生式继承在原型式继承的基础上新增了更多了方法
- 关键:父对象是字面量对象
- 缺点:多个子对象共享一个对象
<a name="C9rFJ"></a>
### 6 - 寄生组合式继承
- 实现:基于组合继承的方式,使用Object.create() 代理 new 的原型链继承实现
- 关键:父类和子类是构造函数
- 目前主流的继承方式
```javascript
// 父类构造函数
function Parent6(){
this.name = 'Parent6';
this.friends = ['Tom','Joly','Tim'];
}
Parent6.prototype.getName = function (){
return this.name;
}
// 子类构造函数
function Child6(){
// 在子类中通过构造函数继承继承父类的自有属性
Parent6.call(this);
this.age = 12;
}
// 通过Object.create创建包含父类原型的新对象
// 对组合继承的改进,防止执行两遍父类并且在子类中有重复的属性
Child6.prototype = Object.create(Parent6.prototype);
Child6.prototype.constructor = Child6;
Child6.prototype.getFriend = function (){
return this.friends;
}
let child6_1 = new Child6();
let child6_2 = new Child6();
child6_1.friends.push("child6_1");
child6_2.friends.push("child6_2");
console.log(child6_1.getFriend()); // [ 'Tom', 'Joly', 'Tim', 'child6_1' ]
console.log(child6_2.getFriend()); // [ 'Tom', 'Joly', 'Tim', 'child6_2' ]
7 - ES6 中 extends 继承
class Parent7{
constructor(name) {
this.name = name
}
getName(){
return this.name
}
}
class Child7 extends Parent7{
constructor(name,age) {
super(name);
this.age = age;
}
}
const child7 = new Child7("tim",27);
console.log(child7.getName());
- extends 关键字的注意事项:
- 在子类的构造函数中必须调用 super()关键字来初始化父类的属性和方法
- 如果子类中没有显示声明constructor,则super会被默认添加
- 在子类的构造函数中,只有调用了super后才可以使用this指针。因为子类的构建基于父类的实例。