面试中,继承是一大类问题。本文总结了个人对继承的几种方式的理解,并分析了一些优缺点。
1.原型继承
//父类:Animal
function Animal(name){
this.name=name || "Animal";
this.master="铲屎官";
this.sleep=function(){
console.log(this.name+ "正在睡觉");
}
}
//原型属性
Animal.prototype.age=1;
//原型方法
Animal.prototype.eat=function(food){
console.log(this.name +"吃的食物是:"+food);
}
//子类:Cat
function Cat(){}
Cat.prototype=new Animal();
Cat.prototype.name="猫猫";
//可以添加子类自己的属性
Cat.prototype.leg=4;
//可以添加子类自己的方法
Cat.prototype.cry=function(){
console.log("喵喵");
};
var cat=new Cat();
cat.sleep(); //猫猫正在睡觉
cat.age; //1
cat.eat("鱼"); //猫猫吃的食物是:鱼
cat.master; //"铲屎官"
cat.cry(); //喵喵
//实例测试
cat instanceof Cat; //true
cat instanceof Animal; //true
Cat.prototype instanceof Animal; //true
优点:是最纯粹的继承,实例既是父类Animal的实例,也是子类Cat/Dog的实例。
缺点:
(1)子类的属性和方法,需要在new Animal()之后执行,且不能放进子类的构造函数中去(可以看到子类都是一个空的构造函数)。
(2)子类实例共享父类引用属性,会造成相互影响。
(3)无法实现多继承。
2.构造继承
//构造继承
//父类:Animal
function Animal(name){
this.name=name || "Animal";
this.master="铲屎官";
this.sleep=function(){
console.log(this.name+"正在睡觉");
}
}
//原型属性
Animal.prototype.age=1;
//原型方法
Animal.prototype.eat=function(food){
console.log(this.name+"吃的食物是:"+food);
}
//子类:Cat
function Cat(){
Animal.call(this);
this.name="猫猫";
this.master="猫奴";
//子类自己的属性
this.leg=4;
//子类自己的方法
this.cry=function(){
console.log("喵喵");
}
}
var cat=new Cat();
cat.name; //"猫猫"
cat.master; //"猫奴"
cat.sleep(); //猫猫正在睡觉
cat.leg; //4
cat.cry(); //喵喵
//无法继承父类的原型属性和方法
cat.age; //undefined
cat.eat("鱼"); //Uncaught TypeError: cat.eat is not a function
//实例测试
cat instanceof Cat; //true
cat instanceof Animal; //false
Cat.prototype instanceof Animal; //false
核心:在子类构造函数中调用Animal.call(this)
优点:可以把子类的属性和方法写在子类的构造函数中,也解决了共享父类引用属性的问题,并且实现了多继承
缺点:
(1)实例并不是父类的实例,只是子类的实例
(2)只能继承父类的实例属性和方法,无法继承其原型属性和方法
(3)父类的方法没有被共享,即有多少个子类,就有多少个父类实例函数的副本,影响性能。
3.实例继承
//实例继承
//父类:Animal
function Animal(name){
this.name=name || "Animal";
this.master="铲屎官";
this.sleep=function(){
console.log(this.name+"正在睡觉");
}
}
Animal.prototype.age=1;
Animal.prototype.eat=function(food){
console.log(this.name+"吃的食物是:"+food);
}
//子类:Cat
function Cat(){
var instance=new Animal();
instance.name="猫猫";
instance.master="猫奴";
instance.leg=4;
instance.cry=function(){
console.log("喵喵");
}
return instance;
}
var cat=new Cat();
cat.name; //"猫猫"
cat.master; //"猫奴"
cat.sleep(); //猫猫正在睡觉
cat.leg; //4
cat.cry(); //喵喵
cat.age; //1
cat.eat("鱼"); //猫猫吃的食物是:鱼
//实例测试
cat instanceof Cat; //false
cat instanceof Animal; //true
Cat.prototype instanceof Animal; //false
核心:在子类构造函数中实例化父类var instance=new Animal(),并返回这个实例。
优点:不限调用方式,既可以实例化子类new Cat(),也可以直接调用子类Cat(),其返回结果相同。并且继承了父类的原型属性和方法。
缺点:
(1)实例是父类的实例,不是子类的实例
(2)不支持多继承
4.拷贝继承
//父类:Animal
function Animal(name){
this.name=name || "Animal";
this.master="铲屎官";
this.sleep=function(){
console.log(this.name+"正在睡觉");
}
}
Animal.prototype.age=1;
Animal.prototype.eat=function(food){
console.log(this.name+"吃的食物是:"+food);
}
//子类:Cat
function Cat(){
var animal=new Animal();
for(var p in animal){
Cat.prototype[p]=animal[p];
}
Cat.prototype["name"]="猫猫";
this.leg=4;
this.cry=function(){
console.log("喵喵");
}
}
var cat=new Cat();
cat.name; //"猫猫"
cat.master; //"铲屎官"
cat.age; //1
cat.sleep(); //猫猫正在睡觉
cat.eat("鱼"); //猫猫吃的食物是:鱼
cat.leg; //4
cat.cry(); //喵喵
//实例测试
cat instanceof Cat; //true
cat instanceof Animal; //false
Cat.prototype instanceof Animal; //false
优点:支持多继承
缺点:
(1)无法获取父类不可枚举的方法
(2)因为要拷贝父类的属性,效率较低
5.组合继承
//父类:Animal
function Animal(name){
this.name=name || "Animal";
this.master="铲屎官";
this.sleep=function(){
console.log(this.name+"正在睡觉");
}
}
Animal.prototype.age=1;
Animal.prototype.eat=function(food){
console.log(this.name+"吃的食物是:"+food);
}
//子类:Cat
function Cat(){
Animal.call(this);
this.name="猫猫";
this.master="猫奴";
this.leg=4;
this.cry=function(){
console.log("喵喵");
}
}
Cat.prototype=new Animal();
Cat.prototype.constructor=Cat; //只要修改了prototype属性,就需要重置constructor属性
var cat=new Cat();
cat.name; //"猫猫"
cat.master; //"猫奴"
cat.age; //1
cat.sleep(); //猫猫正在睡觉
cat.eat("鱼"); //猫猫吃的食物是:鱼
cat.leg; //4
cat.cry(); //喵喵
//实例测试
cat instanceof Cat; //true
cat instanceof Animal; //true
Cat.prototype instanceof Animal; //true
核心:同时采用构造继承+原型继承
优点:可以继承父类原型方法和属性;实例既是子类的实例,也是父类的实例;实现了父类函数的共享
缺点:调用了两次父类构造函数,生成了两份实例
6.寄生组合继承
function Animal(name){
this.name=name || "Animal";
this.master="铲屎官";
this.sleep=function(){
console.log(this.name+"正在睡觉");
}
}
Animal.prototype.age=1;
Animal.prototype.eat=function(food){
console.log(this.name+"吃的食物是:"+food);
}
function Cat(){
Animal.call(this); //构造继承,无法继承父类原型的方法和属性
this.name="猫猫";
this.leg=4;
this.cry=function(){
console.log("喵喵");
}
}
//创建一个没有实例方法的类
function Super(){};
Super.prototype=Animal.prototype; //只继承父类原型的方法和属性
Cat.prototype=new Super();
var cat=new Cat();
cat.name; //"猫猫"
cat.master; //"铲屎官"
cat.leg; //4
cat.cry(); //喵喵
cat.sleep(); //猫猫正在睡觉
cat.age; //1
cat.eat("鱼"); //猫猫吃的食物是:鱼
//实例测试
cat instanceof Cat; //true
cat instanceof Animal; //true
Cat.prototype instanceof Animal; //true
核心:创建一个没有实例方法的类Super,将父类的原型赋予Super的原型
优点:堪称完美,共享了父类的方法,不会发生重复实例化父类的情况
缺点:实现有点复杂
7.ES6 class
class Animal{
constructor(name,master,age){
this.name=name || "Animal";
this.master="铲屎官";
this.age=1;
}
sleep(){
console.log(this.name+"正在睡觉");
}
eat(food){
console.log(this.name+"吃的食物是:"+food);
}
}
class Cat extends Animal{
constructor(){
super();
this.name="猫猫";
this.master="猫奴";
this.leg=4;
}
cry(){
console.log("喵喵");
}
}
let cat=new Cat();
cat.name; //"猫猫"
cat.master; //"猫奴"
cat.age; //1
cat.leg; //4
cat.sleep(); //猫猫正在睡觉
cat.eat("鱼"); //猫猫吃的食物是:鱼
cat.cry(); //喵喵
//实例测试
cat instanceof Cat; //true
cat instanceof Animal; //true
特点:和寄生组合继承的效果一致,但是简洁得多,且可读性好
需要注意的是:父类中的所有方法都是prototype上的方法,且不可枚举;子类必须在constructor中调用super()方法,否则实例化时会报错