面试中,继承是一大类问题。本文总结了个人对继承的几种方式的理解,并分析了一些优缺点。
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()方法,否则实例化时会报错
