一、原型链继承
// 父类
Animal.prototype.sleep = function() {
console.log(`${this.name} sleepping now`);
}
function Animal(name){
this.name = name || 'animal';
this.playList = ['ball','water']
}
// 子类
function Dog(){
}
// 继承
Dog.prototype = new Animal();
// 修正constructor
Dog.prototype.constructor = Dog;
var wangwang = new Dog();
var doudou = new Dog();
console.log(wangwang instanceof Dog) // true
console.log(doudou instanceof Animal) // true
console.log(wangwang instanceof Dog) // true
console.log(doudou instanceof Animal) // true
wangwang.sleep() // 'animal sleepping now'
doudou.sleep() // 'animal sleepping now'
console.log(wangwang.playList) // ['ball','water']
console.log(doudou.playList) // ['ball','water']
wangwang.playList.push('run')
console.log(wangwang.playList) // ["ball", "water", "run"]
console.log(doudou.playList) // ["ball", "water", "run"]
总结:
子类通过改变原型指向父类实例对象实现继承,能够继承父类属性和父类原型上的属性
优点:
1、实现容易,代码简洁易懂
2、子类实例既是子类的实例,也是父类的实例
缺点:
1、子类在创建实例的时,无法向父类传参
2、父类中属性为引用类型时,被子类实例共享,导致的结果是,子类的一个实例的改变,会影响其他子类实例
二、构造函数继承
// 父类
Animal.prototype.sleep = function(){
console.log(`${this.name} sleepping now`)
}
function Animal(name, sex){
this.name = name;
this.sex = sex;
this.playList = ['ball', 'water'];
}
// 子类
function Dog(name, sex, age){
Animal.call(this, name, sex);
this.age = age;
}
var wangwang = new Dog('wangwang', 'male', 19);
var doudou = new Dog('doudou', 'female', 11);
console.log(wangwang instanceof Dog) // true
console.log(doudou instanceof Animal) // false
console.log(wangwang instanceof Dog) // true
console.log(doudou instanceof Animal) // false
console.log(wangwang.playList) // ['ball','water']
console.log(doudou.playList) // ['ball','water']
wangwang.playList.push('run')
console.log(wangwang.playList) // ["ball", "water", "run"]
console.log(doudou.playList) // ["ball", "water"]
wangwang.sleep() // 报错
doudou.sleep() // 报错
总结:
当子类属性完全覆盖父类属性时,用call方式,借用父类构造函数,实现继承
优点:
解决了原型链继承中的两个缺点
1、子类可以传递参数给父类
2、父类属性中有引用类型时候,不会导致子类实例共享该属性
缺点:
1、子类实例只是子类的实例,不是父类的实例
2、父类原型的属性不会被子类继承
3、消耗内存,子类每次生成实例,都会执行一次父类函数,代码复用原则没有落实到位
三、组合继承(原型链和构造函数组合)
// 父类
Animal.prototype.sleep = function(){
console.log(`${this.name} sleepping now`);
}
function Animal(name, sex){
this.name = name;
this.sex = sex;
this.playList = ['ball', 'water'];
}
// 子类
function Dog(name, sex, age) {
Animal.call(this, name, sex);
this.age = age;
}
// 改变子类原型
Dog.prototype = new Animal();
// 修正constructor
Dog.prototype.constructor = Dog;
var wangwang = new Dog('wangwang', 'male', 19);
var doudou = new Dog('doudou', 'female', 11);
console.log(wangwang instanceof Dog) // true
console.log(doudou instanceof Animal) // true
console.log(wangwang instanceof Dog) // true
console.log(doudou instanceof Animal) // true
console.log(wangwang.playList) // ['ball','water']
console.log(doudou.playList) // ['ball','water']
wangwang.playList.push('run')
console.log(wangwang.playList) // ["ball", "water", "run"]
console.log(doudou.playList) // ["ball", "water"]
wangwang.sleep() // wangwang sleepping now
doudou.sleep() // doudou sleepping now
总结:
综合了原型链继承和构造函数继承两种方式的组合继承
优点:
解决了原型链继承和构造函数继承中的大部分问题
1、子类可以传递参数给父类
2、父类属性中有引用类型时候,不会导致子类实例共享该属性
3、子类实例既是子类的实例,也是父类的实例
4、父类原型的属性被子类继承
缺点:
1、消耗内存,子类每次生成实例,都会执行一次父类函数,代码复用原则没有落实到位
四、原型式继承
// 一点拓展(摘自一本想不起来的书籍)
// 借助原型prototype,可以根据已有的对象创建一个新对象,同时不必创建新的自定义对象类型。
function inheritObject(o){
// 声明一个过渡对象
function F(){}
// 过渡对象的原型指向父对象
F.prototype = o;
// 返回过渡对象的实例,该实例的原型继承了父对象
return new F();
}
总结:
这种方式实际上和类式继承十分相似,更多体现的是一种封装思想,我们把过渡函数 F 想象成子类,对象o想象成父类函数的实例,所以,这种方式的问题和类式继承存在同样的问题。
五、寄生式继承
// 原型式方法
function inheritObject(o){
function F(){}
F.prototype = o;
return new F();
}
// 寄生式继承
/**
* params: obj----父类函数实例
*/
function parasitic(obj) {
// 通过原型方式创建新的对象
var o = inheritObject(obj);
// 拓展新属性
o.fn = function() {}
// 返回拓展后的新对象
return o;
}
总结:
对原型式继承的方法再次进行封装,这样生成的新对象不仅继承了父类的属性和方法,自己也拓展了一些方法,
但是类式继承的问题仍然存在,没有得到解决
六、圣杯模式
// 核心方法
function inherit(Origin, Target){
function F(){}
F.prototype = Origin.prototype;
Target.prototype = new F();
// 修正constructor
Target.prototype.constructor = Target
}
// 父类
Animal.prototype.sleep = function(){
console.log(`${this.name} sleepping now`)
}
function Animal(name, sex){
this.name = name;
this.sex = sex;
this.playList = ['ball', 'water']
}
// 子类
function Dog(name, sex, age){
Animal.call(this, name, sex);
this.age = age;
}
// 实现继承
inherit(Animal, Dog);
var wangwang = new Dog('wangwang', 'male', 19);
var doudou = new Dog('doudou', 'female', 11);
console.log(wangwang.playList) // ["ball", "water"]
console.log(doudou.playList) // ["ball", "water"]
wangwang.playList.push('jump')
console.log(wangwang.playList) // ["ball", "water", "jump"]
console.log(doudou.playList) // ["ball", "water"]
wangwang.sleep() // wangwang sleepping now
总结:
1、这种模式网上还有其他叫法或者写法,但是仔细看下来,实现原理是一样的
2、这种方式实现的继承是结合了原型式继承和原型链继承的方式,在圣杯模式继承中,原型式继承中的参数对象不再是普通对象,而变成了原型对象;通过改变中介函数的原型指向父类函数原型,子类指向中介函数的实例,将子类与父类联系起来,同时,修正子类的constructor实现继承,也去除了上述几种方式大部分的缺陷,目前来看,这种继承方式相对是比较好的。