1前面写过,我们知道在面向对象的编程语言中都会有继承,而且在各种语言中都起着至关重要的作用,但是什么是继承呢?有多少种继承方式??常见的有哪些?对js继承有了更深刻的理解,在开发中能够游刃有余。所谓继承,继承是面向对象的,使用这种方式可以更好的重用代码,缩短开发周期,提高开发效率。那么我们带着两个问题阅读文章,解决文章中的这些疑惑:JS继承有几种继承方式?ES6中的extends关键字是用哪种继承方式实现的?2继承的概念我们知道,一个人继承祖业,可以继承父母的所有物质基础,但作为主体,他还有其他的能力和特点。作为一个总的品类,同样的汽车生产轿车、跑车、厢式货车等,都具有四轮一机的特点,但又各有其独特的特点。比如五菱宏光,在秋明山上飙车就能成为车神。因此,继承可以使子类拥有父类的各种方法和属性。常见的继承方式有:原型链继承构造函数继承组合继承原型链继承寄生继承寄生组合继承3继承3.1原型链继承例子:每个构造函数都有一个原型对象原型对象包含一个指向构造函数的指针实例包含一个指向构造函数的指针原型对象functionPerson(){this.name="person";this.abilities=["eating","Sleep","BeatPeas"];}functionStudent(){this.study=["Chinese","Mathematics","英语"];}Student.prototype=newPerson();console.log(newStudent());我们可以看到,Student类继承了Person类的所有特性:但是,我们注意到,在使用同一个对象创建实例时,内存空间是共享的,当一个改变时,另一个也会随之改变。functionPerson(){this.name="person";this.abilities=["吃","睡","打豆豆"];}functionStudent(){this.study=["语文","数学","英文"];}Student.prototype=newPerson();conststu1=newStudent();conststu2=newStudent();stu1.abilities.push("walk");console.log(stu1.abilities,stu2.abilities);我们看到stu1和stu2是由Student对象创建的两个实例。当stu1的值改变时,stu2的值也会改变。图3.2构造函数继承(借助call)构造函数继承可以解决原型链继承共享内存的缺点,但是如果父类的原型对象中有父类定义的方法,那么子类就不会能够继承这些方法。这时候如果使用父类的方法,就会报错。构造函数继承只能继承父类的实例、属性和方法,不能继承原型的属性和方法。functionPerson(){this.name="person";}Person.prototype.getName=function(){returnthis.name;}functionStudent(){Person.call(this);this.study=["Chinese","数学","English"];}conststu=newStudent();console.log(stu);console.log(stu.getName())我们可以看到打印出来的stu实例包含了所有的Student对象和父类Person属性,但是如果要使用父类原型的方法,就会报错。getName是父类Person的引用方法,不会共享内存。3.3组合继承(原型链继承+构造函数继承)functionPerson(){this.name="person";this.abilities=["eating","sleeping","playbean"];}//第一次调用Person()Person.prototype.getName=function(){returnthis.name;}functionStudent(){//第二次调用Person()Person.call(this);this.study=["Chinese","Mathematics","English"];}Student.prototype=newPerson();//手动挂载构造函数,指向自己的构造函数Student.prototype.constructor=Student;conststu1=newStudent();conststu2=newStudent();stu1.abilities.push(“步行”);console.log(stu1.abilities,stu2.abilities);//不会互相影响console.log(stu1.getName());//正常输出“人”console.log(stu2.getName());//运行得到正常输出“person”:我们看到在stu1实例的abilities数组中添加元素不会影响stu2实例的值。Picture我们发现使用组合继承可以有效解决原型链继承和构造函数继承的不足。但是两次调用Person()会造成性能开销,那么我们还有优化的空间吗?3.4原型继承我们可以使用es5中的Object.create()方法进行原型继承,从而优化组合继承。Object.create()接收两个参数:用作新对象原型的对象为新对象定义附加属性的对象(可选参数)constperson={name:"person",abilities:["eating","sleeping","流行豆豆"],getName(){returnthis.name;}}constperson1=Object.create(person);person1.name="human";person1.abilities.push("walk");constperson2=Object.创建(人);person2.abilities.push(“运行”);console.log(person1.name);console.log(person1.name===person1.getName());console.log(person2.name);控制台日志(person1.abilities);console.log(person2.abilities);我们可以看到使用Object.create()可以实现普通的对象继承,不仅可以继承属性,还可以继承方法。但也有缺点:包括引用类型在内的属性值会一直共享对应的值,这和原型链继承是一样的。修改person1.name的值,person2.name的值没有变,不是因为person1和person2有独立的name值,而是因为person1.name='human',给person1增加了一个name值,并没有修改name值在原型上。3.5寄生继承寄生继承:首先利用原型继承获得目标对象的浅拷贝,然后利用这种浅拷贝的能力增强和添加一些方法。与原型继承相比,寄生继承还是在父类的基础上增加了更多的方法。functionclone(原版){constclone=Object.create(原版);clone.getAbilities=function(){returnthis.abilities;}returnclone;}constperson={name:"person",abilities:["eating","sleeping","豆豆"],getName(){returnthis.name;}}constperson1=clone(person);console.log(person1.getName());console.log(person1.getAbilities());运行得到:3.6寄生组合继承在前面的分析中,分析了五种常见的继承方式。现在,综合各种方法的优缺点,对它们进行优化改造,得到寄生组合继承方法,在所有继承方法中是相对最优的。functionclone(parent,child){//这里使用Object.create()可以减少组合继承多一个构造函数的过程child.prototype=Object.create(parent.prototype);child.prototype.constructor=child;}functionParent(){this.name="parent";this.abilities=["吃","睡","玩豌豆"];}Parent.prototype.getName=function(){returnthis.name;}functionChild(){Parent.call(this);this.study=["Chinese","Mathematics","English"];}clone(Parent,Child);Child.prototype.getStudy=function(){returnthis.study;}constchild=newChild();console.log(child);console.log(child.getName());console.log(child.getStudy());运行得到:extends关键字主要用于类声明或类表达式,创建一个类是另一个类的子类。其中,constructor代表一个构造函数。一个类中只能有一个构造函数。如果超过一个,就会报SyntaxError错误。如果没有显式指定构造函数,将添加一个默认的构造函数方法。使用示例如下。classRectangle{//constructor构造函数(height,width){this.height=height;this.width=width;}//Gettergetarea(){returnthis.calcArea()}//MethodcalcArea(){returnthis.height*this.width;}}constructangle=newRectangle(10,20);console.log(rectangle.area);//输出200--------------------------华丽的分割线------------------------------------//继承classSquareextendsRectangle{constructor(length){super(length,length);//如果子类中有构造函数,在使用“this”之前需要先调用super()。this.name='Square';}getarea(){returnthis.height*this.width;}}constsquare=newSquare(10);console.log(square.area);//输出100extends继承的核心代码如下如下,其实现与上述寄生组合继承相同。function_inherits(subType,superType){//创建对象,创建父类原型的副本//增强对象以弥补因重写原型而丢失的默认构造函数属性//指定对象并赋值新创建的对象到子类原型subType.prototype=Object.create(superType&&superType.prototype,{constructor:{value:subType,enumerable:false,writable:true,configurable:true}});if(superType){Object.setPrototypeOf?Object.setPrototypeOf(subType,superType):subType.__proto__=superType;}}4参考文章《JavaScript常用八种继承方案》《深入JavaScript继承原理》5最后写了很多继承方法,每个实现方法比较分散。有必要对常见的继承方式进行深入系统的分析和总结。图片
