前言面向对象编程最重要的方面之一就是对象继承。通过继承B对象,A对象可以直接拥有B对象的所有属性和方法。这对于代码重用非常有用。大多数面向对象的编程语言都是通过“类”来实现对象继承的。传统上,JavaScript语言的继承不是通过类(ES6引入类语法)实现的,而是通过“原型对象”实现的。那么JS中常见的继承方式有哪些呢?想要本文源码请戳六种常见的继承方式。如果觉得文章对你有帮助,请点赞关注我的GitHub博客。非常感谢!方法一、原型链继承这种方法的关键是子类型的原型是父类型的实例对象。//父类型functionPerson(name,age){this.name=name,this.age=age,this.play=[1,2,3]this.setName=function(){}}Person.prototype.setAge=function(){}//subtypefunctionStudent(price){this.price=pricethis.setScore=function(){}}Student.prototype=newPerson()//子类型的原型是父类型之一typeInstanceobjectvars1=newStudent(15000)vars2=newStudent(14000)console.log(s1,s2)但是这个方法的本质是将子类的原型指向父类的实例,所以子类的Student.prototype的实例,也就是Person的实例,可以通过__proto__来访问,这样就可以访问到父类的私有方法,然后通过指向通过__proto__方法获取父类的原型。所以可以把父类的private和public方法和属性当作子类的public属性。子类通过将父类的私有属性和公共方法作为自己的公共属性和方法来继承父类的属性和方法。知道操作基本数据类型时操作的是值,操作引用数据类型时操作的是地址。如果父类的私有属性中有引用类型的属性,那么在子类继承的时候会作为公有属性使用,这样子类1操作这个属性的时候,就会影响到子类2。s1.play.push(4)console.log(s1.play,s2.play)console.log(s1.__proto__===s2.__proto__)//trueconsole.log(s1.__proto__.__proto__===s2.__proto__.__proto__)//trues1中的play属性发生变化,同时s2中的play属性也会发生变化。另外需要注意的是,当我们需要在子类中增加一个新的方法或者重写父类的一个方法时,记得放在替换原型函数Person(name,age){this.name=name的语句之后,this.age=age}Person.prototype.setAge=function(){console.log("111")}functionStudent(price){this.price=pricethis.setScore=function(){}}//学生.prototype.sayHello=function(){}//这里写子类的原型方法和属性是无效的,//因为会改变原型的指针,应该放在重新设计之后Student.prototype=newPerson()Student.prototype.sayHello=function(){}vars1=newStudent(15000)console.log(s1)特点:父类增加了一个新的原型方法/原型属性,可以被子类访问。简单,容易实现缺点:不可实现多重继承原型对象的所有属性为所有实例共享在创建子类实例时,不能将参数传递给父类构造函数要为子类添加属性和方法,必须在之后执行Student.prototype=newPerson(),不能放在构造函数中方法二:借用构造函数继承该方法的关键是:子类型构造函数中的通用call()调用父类型构造函数functionPerson(name,age){this.name=name,this.age=age,this.setName=function(){}}Person.prototype.setAge=function(){}functionStudent(name,age,price){Person.call(this,name,age)//相当于:this.Person(name,age)/*this.name=namethis.age=age*/this.price=price}vars1=newStudent('Tom',20,15000)该方法只实现了部分继承。如果父类的原型还有方法和属性,子类无法获取这些方法和属性实例在原型链继承中共享父类引用属性在创建子类实例时,可以向父类传递参数,可以实现多重继承(调用多个父类对象)缺点:实例不是父类的实例,而是实例子类只能继承父类的实例属性和方法,不能继承原型的属性和方法。为了实现函数重用,每个子类都有一份父类实例函数,影响性能。第三种方法:原型链+借用构造函数继承的组合。该方法的关键在于:通过调用父类构造,继承父类的属性并保留传递参数的优点,进而以父类实例为子类原型实现功能复用。functionPerson(name,age){this.name=name,this.age=age,this.setAge=function(){}}Person.prototype.setAge=function(){console.log("111")}函数Student(姓名,年龄,价格){Person.call(this,name,age)this.price=pricethis.setScore=function(){}}Student.prototype=newPerson()Student.prototype.constructor=Student//复合继承也需要修复指向Student.prototype.sayHello=function(){}vars1=newStudent('Tom',20,15000)vars2=newStudent('Jack',22,14000)console.log(s1)console.log(s1.constructor)//学生console.log(p1.constructor)//Person结合了原型链继承和构造函数的优点,是JavaScript中最常用的继承模式。但是,也有一个缺点,无论在什么情况下,构造函数都会被调用两次:一次是在创建子类型原型时,一次是在子类型构造函数内部,而子类型最终会包含父类型对象属性的所有实例,但是我们必须在调用子类构造函数时重写这些属性。优点:可以继承实例属性/方法,也可以继承原型属性/方法。共享引用属性没有问题。可以传递参数,函数可以重用。缺点:调用了两次父类构造函数,生成了两个实例。方法四:组合继承优化一这样父类的原型和子类的原型指向同一个对象,子类可以继承父类的公共方法作为自己的公共方法,不会对实例方法/属性进行两次初始化,避免了组合继承的缺点。functionPerson(name,age){this.name=name,this.age=age,this.setAge=function(){}}Person.prototype.setAge=function(){console.log("111")}函数学生(姓名、年龄、价格){人。call(this,name,age)this.price=pricethis.setScore=function(){}}Student.prototype=Person.prototypeStudent.prototype.sayHello=function(){}vars1=newStudent('汤姆',20,15000)console.log(s1)但是这个方法没有办法区分对象是从子类还是父类实例化的console.log(s1instanceofStudent,s1instanceofPerson)//truetrueconsole.log(s1.constructor)//个人优点:不会对实例方法/属性进行两次初始化,避免了组合继承的弊端缺点:无法区分实例是子类还是父类创建的,子类的构造函数类的和父类指向同一个。方法五:组合继承优化2借助原型,可以在已有对象的基础上创建对象。varB=Object.create(A)以对象A为原型生成对象B,B继承了A的所有属性和方法。functionPerson(name,age){this.name=name,this.age=age}Person.prototype.setAge=function(){console.log("111")}functionStudent(name,age,price){Person.call(this,name,age)this.price=pricethis.setScore=function(){}}Student.prototype=Object.create(Person.prototype)//核心代码Student.prototype.constructor=Student//核心代码vars1=newStudent('Tom',20,15000)console.log(s1instanceofStudent,s1instanceofPerson)//truetrueconsole.log(s1.constructor)//Studentconsole.log(s1)同样,Student继承了Person原型对象的所有属性和方法。目前来说,最完美的继承方式!方法六:ES6中类的继承ES6中引入了class关键字。一个类可以通过extends关键字来继承,类的静态方法也可以通过static关键字来定义。这比ES5通过修改原型链的方式继承更清晰,也方便很多。ES5继承的本质是先创建子类的实例对象this,然后将父类的方法添加到this中(Parent.apply(this))。ES6的继承机制完全不同。本质就是将父类实例对象的属性和方法添加到this中(所以必须先调用super方法),然后使用子类的构造函数修改this。需要注意的是,class关键字只是原型的语法糖,JavaScript的继承仍然是基于原型来实现的。classPerson{//调用类的构造函数constructor(name,age){this.name=namethis.age=age}//定义通用方法showName(){console.log("调用parent的方法class")console.log(this.name,this.age);}}letp1=newPerson('kobe',39)console.log(p1)//定义一个子类classStudentextendsPerson{constructor(name,age,salary){super(name,age)//调用构造函数父类的通过superthis.salary=salary}showName(){//在子类自己定义方法console.log("调用子类的方法")console.log(this.name,this.age,这个.薪水);}}lets1=newStudent('wade',38,1000000000)console.log(s1)s1.showName()优点:语法简单易懂,操作更方便缺点:并非所有浏览器都支持class关键字文章于2018.10.11改版,希望对您有所帮助!参考文章JS实现继承的几种方式JavaScript深度继承的多种方式和优点缺点JavaScript常用的继承方式阮一峰ES6入门类继承熊