更像是笔记,本文介绍多种继承方式的优缺点,推荐《JavaScript高级程序设计》
原型链继承
// 父类构造函数
function Parent () {
this.name = 'parent';
}
Parent.prototype.sayName = function () {
console.log(this.name);
};
function Child () {}
// 原型链继承
Child.prototype = new Parent();
const pan = new Child();
pan.sayName(); // 'parent'
原型链继承主要有两个问题:
- 原型链属性被所有实例共享,如果是引用类型属性,一个实例修改该属性后,所有实例上获取到的都是修改后的属性
- 创建子类
Child对应的实例时,无法向Parent传参
// 父类构造函数
function Parent () {
this.name = 'parent';
this.wealth = ['car', 'money'];
}
Parent.prototype.sayName = function () {
console.log(this.name);
};
function Child () {}
// 原型链继承
Child.prototype = new Parent();
const pan = new Child();
console.log(pan.wealth); // ['car', 'money']
// 修改原型链属性
pan.wealth.push('house');
console.log(pan.wealth); // ['car', 'money', 'house']
const duan = new Child();
console.log(duan.wealth); // ['car', 'money', 'house']
借用构造函数
// 父类构造函数
function Parent (name) {
this.name = name;
this.wealth = ['car', 'money'];
}
function Child (name) {
Parent.call(this, name);
}
const pan = new Child('panpan');
console.log(pan.name); // 'panpan'
console.log(pan.wealth); // ['car', 'money']
pan.wealth.push('house');
console.log(pan.wealth); // ['car', 'money', 'house']
const duan = new Child('duanduan');
console.log(duan.name); // 'duanduan'
console.log(duan.wealth); // ['car', 'money']
借用构造函数实现继承优点如下:
- 可以在创建子类实例时向
Parent传参 - 避免了引用类型属性被所有子类实例共享 缺点:
- 方法都在构造函数中定义,每次创建实例都会创建一遍方法
组合继承
原型链继承和构造函数继承的组合版
// 父类构造函数
function Parent (name) {
this.name = name;
this.wealth = ['car', 'money'];
}
Parent.prototype.sayName = function () {
console.log(this.name);
};
function Child (name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
// 修正constructor
Child.prototype.constructor = Child;
const pan = new Child('panpan');
console.log(pan.name); // 'panpan'
console.log(pan.wealth); // ['car', 'money']
pan.wealth.push('house');
console.log(pan.wealth); // ['car', 'money', 'house']
const duan = new Child('duanduan');
console.log(duan.name); // 'duanduan'
console.log(duan.wealth); // ['car', 'money']
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。
原型式继承
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
实际就是Object.create()的模拟实现
const person = {
name: 'panent',
wealth: ['car', 'money']
};
function createObj (o) {
function F () {}
F.prototype = o;
return new F();
}
const pan = createObj(person);
console.log(pan.name); // 'panent'
pan.name = 'pan';
console.log(pan.name); // 'pan'
console.log(pan.wealth); // ['car', 'money']
pan.wealth.push('house');
console.log(pan.wealth); // ['car', 'money', 'house']
const duan = createObj(person);
console.log(duan.name); // 'panent'
console.log(duan.wealth); // ['car', 'money', 'house']
缺点:
- 引用类型的值还是会被所有实例共享
寄生式继承
function createObj (o) {
var clone = object.create(o);
clone.sayName = function () {
console.log('hello');
}
return clone;
}
缺点:
- 跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
寄生组合式继承
组合继承是 JavaScript 最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。下面来看一下寄生组合式继承
function Person(name){
this.category = 'human';
this.legNum = 2;
this.name = name;
}
Person.prototype.sayHello = function(){
console.log('Hi,i am ' + this.name);
}
// 定义集成方法
function inherit(subType,superType){
//在new inheritFn 的时候将构造函数指向子类
function inheritFn(){this.constructor = subType}
inheritFn.prototype = superType.prototype;
//将子类的原型指向父类原型的一个副本
subType.prototype = new inheritFn();
}
//定义子类构造函数Pan
function Pan(name,age){
Person.call(this,name); //借用构造函数
this.age = age;
}
//将子类Pan的原型指向父类Person原型的一个副本
//注意:要执行该动作后才能在Pan的prototype上定义方法,否则没用
inherit(Pan,Person);
Pan.prototype.sayAge = function(){
console.log(this.age);
}
//定义子类构造函数Duan
function Duan(name,hairColor){
Person.call(this,name);
this.hairColor = hairColor;
}
inherit(Duan,Person);
Duan.prototype.showHairColor = function(){
console.log(this.hairColor);
}
//Pan的实例
var pan = new Pan('panfengshan',27);
console.log(pan.name); //panfengshan
console.log(pan.age); //27
console.log(pan.category); //human
console.log(pan.legNum); //2
pan.sayHello(); //Hi,i am panfengshan
pan.sayAge(); //27
//Duan的实例
var duan = new Duan('duanyanan','black');
console.log(duan.name); //duanyanan
console.log(duan.hairColor); //black
console.log(pan.category); //human
console.log(pan.legNum); //2
duan.sayHello(); //Hi,i am duanyanan
duan.showHairColor(); //black
寄生组合式继承的高效体现在它只调用了一次
Person构造函数,并且因此避免了在Pan.prototype上面创建不必要的、多余属性。与此同时,原型链还能保持不变;因此,还能正常使用instanceof和isPropertyOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。