1. 原型链继承
function Parent(name){
this.name = name;
this.hobby = ['吃饭','睡觉','打游戏'];
}
Parent.prototype.getInfo= function(){
console.log(this.name);
console.log(this.hobby);
}
function Children() {
}
Children.prototype = new Parent();
// 子类的实例对象:
let child1 = new Children();
child1.name = '张三';
child1.hobby.push('跑步');
child1.getInfo(); // 张三 ['吃饭','睡觉','打游戏', '跑步']
let child2 = new Children();
child1.name = '李四';
child1.getInfo(); // 李四 ['吃饭','睡觉','打游戏', '跑步']
优点:
- 写法方便简洁,容易理解。
缺点:
- 包含引用类型的原型属性会被所有实例属性共享,容易造成属性的修改混乱。
- 在创建子类型的实例时,不能向超类型的构造函数中传递参数。
2. 构造函数继承
function Parent(name){
this.name = name;
this.hobby = ['吃饭','睡觉','打游戏'];
}
function Children(){
// 利用call继承了 Parent
Parent.call(this);
}
// 子类的实例对象:
let child1 = new Children();
child1.hobby.push('跑步');
console.log(child1.hobby); // ['吃饭','睡觉','打游戏', '跑步']
let child2 = new Children();
console.log(child2.hobby); //['吃饭','睡觉','打游戏']
优点:
- 可以在子类型构造函数中向超类型构造函数添加参数
缺点:
- 和构造函数模式一样的问题,所有的方法都在构造函数中定义,因此就无法做到函数的复用。而且超类型的原型中定义的方法,对于子类型而言也是不可见的。
3. 组合继承
组合继承指的是将原型链继承和构造函数继承结合。这种方法主要是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
function Parent(name){
this.name = name;
this.hobby = ['吃饭','睡觉','打游戏'];
}
Parent.prototype.getInfo = function() {
console.log(this.name);
console.log(this.hobby);
}
function Children(name, age) {
// 利用call继承了 Parent
Parent.call(this, name);
this.age = age;
}
// 继承方法
Children.prototype = new Parent();
Children.prototype.constructor = Children;
Children.prototype.getAge = function() {
console.log(this.age)
}
let child1 = new Children('张三',18);
child1.hobby.push('跑步');
child1.getInfo(); // 张三 ['吃饭','睡觉','打游戏', '跑步']
child1.getAge(); // 18
let child2 = new Children('李四', 20);
child2.hobby.push('抽烟');
child2.getInfo(); // 李四 ['吃饭','睡觉','打游戏', '抽烟']
child2.getAge(); // 20
优点:
- 组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式
缺点:
- 调用了两次超类的构造函数,导致基类的原型对象中增添了不必要的超类的实例对象中的所有属性。
4. 原型式继承
let Parent = {
name: '王麻子',
hobby: ['吃饭','睡觉','打游戏'],
getInfo() {
console.log(this.name);
console.log(this.hobby);
}
}
let child1 = Object.create(Parent);
child1.name = '张三' ;
child1.hobby.push('唱歌');
child1.getInfo(); // 张三 ['吃饭','睡觉','打游戏', '唱歌']
let child2 = Object.create(Parent);
child2.getInfo(); // 王麻子 ['吃饭','睡觉','打游戏', '唱歌']
ES5中新增了 Object.create()
方法规范了原型式继承。这个方法接收两个参数,一个是将被用作新对象原型的对象,一个是为新对象定义额外属性的对象(可选)。
注意第二个参数的格式与 Object.defineProperties() 方法的第二个参数格式相同。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。在第二个参数为空的情况下,该方法与 object() 方法的行为相同。
优点:
- 可以实现基于一个对象的简单继承,不必创建构造函数
缺点:
- 与原型链中提到的缺点相同,一个是传参的问题,一个是属性共享的问题。
5. 寄生式继承
寄生式继承的思路是,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式增强对象,最后返回这个对象。
let Parent = {
name: "parent",
hobby: ['吃饭','睡觉','打游戏'],
getInfo() {
console.log(this.name);
}
};
function clone(original) {
let clone = Object.create(original);
clone.getHobby = function() {
console.log(this.hobby);
};
return clone;
}
let child1 = clone(Parent);
child1.getInfo(); // parent
child1.getHobby(); // ['吃饭','睡觉','打游戏']
优点:
- 在主要考虑对象而不是自定义类型和构造函数的情况下,实现简单的继承。
缺点:
- 使用该继承方式,在为对象添加函数的时候,没有办法做到函数的复用。
6. 寄生式组合继承
组合继承的缺点,由于调用了两次超类的构造函数,导致基类的原型对象中增添了不必要的超类的实例对象中的所有属性。
寄生式组合继承就是用来解决这个问题,它与组合继承不同的地方主要是,在继承原型时,我们继承的不是超类的实例对象,而是原型对象是超类原型对象的一个实例对象,这样就解决了基类的原型对象中增添了不必要的超类的实例对象中的所有属性d的问题。
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() {
console.log(this.name);
};
function Children(name, age) {
Parent.call(this, name);
this.age = age;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
Children.prototype.getAge = function() {
console.log(this.age);
};
优点:
- 效率高,避免了在 SubType.prototype 上创建不必要的属性。与此同时还能保持原型链不变,开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
7. ES6 extend class 关键字继承
ES6继承的结果和寄生组合继承相似,本质上,ES6继承是一种语法糖。但是,寄生组合继承是先创建子类实例this对象,然后再对其增强;而ES6先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
class Parent {
}
class Children extends Parent {
constructor() {
super();
}
}
ES6继承与ES5继承的异同:
相同点: 本质上ES6继承是ES5继承的语法糖。
不同点:
ES6继承中子类的构造函数的原型链指向父类的构造函数,ES5中使用的是构造函数复制,没有原型链指向。
ES6子类实例的构建,基于父类实例,ES5中不是。