当前位置: 首页 > 科技观察

JavaScript六大继承方式

时间:2023-03-18 12:12:50 科技观察

继承是面向对象编程中另一个非常重要的概念。JavaScript支持实现继承,但不支持接口继承。继承的实现主要依赖于原型链。原型链首先要了解什么是原型链。在一篇了解proto和prototype的关系和区别的文章中,讲的很详细。原型链继承的基本思想是让一个原型对象指向另一个类型的实例functionSuperType(){this.property=true}SuperType.prototype.getSuperValue=function(){returnthis.property}functionSubType(){this.subproperty=false}SubType.prototype=newSuperType()SubType.prototype.getSubValue=function(){returnthis.subproperty}variation=newSubType()console.log(instance.getSuperValue())//true代码定义了两种类型SuperType而SubType,每个类型都有一个属性和一个方法,SubType继承SuperType,继承是通过创建SuperType实例,并将这个实例赋值给SubType.prototype的实现。实现的本质是重写原型对象,将其替换为新类型的实例,那么SuperType实例中存在的所有属性和方法现在也存在于SubType.prototype中。我们知道,当一个实例被创建时,实例对象中会有一个内部指针指向创建它的原型,它们会被关联起来。这里,代码SubType.prototype=newSuperType()也会在SubType.prototype中创建一个内部指针,将SubType.prototype与SuperType相关联。所以instance指向SubType的原型,SubType的原型指向SuperType的原型。然后,当实例调用getSuperValue()方法时,它会向上搜索这条链。Add方法当给SubType原型添加一个方法时,如果父类也有同名的方法,SubType会覆盖这个方法来达到新的目的。但是这个方法在父类中还是存在的。记住不能以字面量的形式添加,因为上面说了通过实例继承本质上是重写,然后使用字面量又是另一种重写,但是这个重写和父类没有关系。所以会导致原型链被截断。functionSuperType(){this.property=true}SuperType.prototype.getSuperValue=function(){returnthis.property}functionSubType(){this.subproperty=false}SubType.prototype=newSuperType()SubType.prototype={getSubValue:function(){returnthis.subproperty}}varinstance=newSubType()console.log(instance.getSuperValue())//错误问题简单地使用了原型链继承,主要问题来自于包含引用类型值的原型。functionSuperType(){this.colors=['red','blue','green']}functionSubType(){}SubType.prototype=newSuperType()varinstance1=newSubType()varinstance2=newSubType()instance1.colors.push('black')console.log(instance1.colors)//["red","blue","green","black"]console.log(instance2.colors)//["red","blue","green","black"]在SuperType构造函数中定义了一个颜色属性。当通过原型链继承SubType时,这个属性会出现在SubType.prototype中,就像创建SubType.prototype.colors一样,所以会导致SubType的所有实例都会共享这个属性,所以引用的修改instance1的颜色类型值也将反映在instance2中。借用构造函数这种方法是为了解决原型中包含引用类型值所引起的问题。该方法的思路是在子类构造函数内部调用父类构造函数,可以使用apply()和call()方法改变对象的执行上下文functionSuperType(){this.colors=['red','blue','green']}functionSubType(){//继承SuperTypeSuperType.call(this)}varinstance1=newSubType()varinstance2=newSubType()instance1.colors.push('black')console.log(instance1.colors)//["red","blue","green","black"]console.log(instance2.colors)//["red","blue","green"]调用SuperType结构在创建新的SubType实例函数时,以便在SuperType函数中定义的所有对象初始化代码都在新的SubType对象上执行。因此,每个SubType实例都将拥有自己的colors属性副本。用构造函数传参还有一个好处就是可以传参functionSuperType(name){this.name=name}functionSubType(){//InheritSuperTypeSuperType.call(this,'Jiang')this.job='student'}varinstance=newSubType()console.log(instance.name)//jiangconsole.log(instance.job)//学生问题如果只使用构造函数,方法都定义在构造函数中,所以函数无法实现复用组合继承(原型链+构造函数)组合继承是将原型链继承和构造函数结合起来,充分发挥两者优势的一种模式。其思想是利用原型链实现原型属性和方法的继承,通过借用构造函数实现实例属性的继承。这样通过在原型上定义方法来实现功能复用,并且可以保证每个实例都有自己的属性。functionSuperType(name){this.name=namethis.colors=['red','blue','green']}SuperType.prototype.sayName=function(){console.log(this.name)}functionSubType(name,job){//继承属性SuperType.call(this,name)this.job=job}//继承方法SubType.prototype=newSuperType()SubType.prototype.constructor=SuperTypeSubType.prototype.sayJob=function(){console.log(this.job)}varinstance1=newSubType('Jiang','student')instance1.colors.push('black')console.log(instance1.colors)//["red","blue","green"","black"]instance1.sayName()//'江'instance1.sayJob()//'student'varinstance2=newSubType('J','doctor')console.log(instance2.colors)////["red","blue","green"]instance2.sayName()//'J'instance2.sayJob()//'doctor'这种模式避免了原型链和构造函数继承的缺陷,整合了它们的优点在于它是最常用的继承模式。原型继承借助原型,可以在现有对象的基础上创建新对象,而无需创建自定义类型。functionobject(o){functionF(){}F.prototype=oreturnnewF()}对象函数内部,先创建一个临时构造函数,然后将传入的对象作为构造函数的原型,***返回这个新实例临时类型的。本质上,对象执行传递给它的对象的浅拷贝。varperson={name:'Jiang',friends:['Shelby','Court']}varanotherPerson=object(person)console.log(anotherPerson.friends)//['Shelby','Court']这个模式应该去你必须有一个对象作为另一个对象的基础。在这个例子中,person作为另一个对象的基础,将person传给object,函数返回一个新的对象。这个新对象以person为原型,所以它的原型包含一个原始类型和一个引用类型。所以意味着如果person关联了另外一个对象,那么当anotherPerson修改friends数组时,也会在这个对象中体现出来。Object.create()方法ES5通过Object.create()方法规范了原型继承,它可以接受两个参数,一个是用作新对象原型的对象和一个可选对象,为新对象定义附加属性,behavior一样,基本用法和上面的对象一样,只是对象不能接受第二个参数。varperson={name:'Jiang',friends:['Shelby','Court']}varanotherPerson=Object.create(person)console.log(anotherPerson.friends)//['Shelby','Court']寄生继承寄生继承的思想类似于寄生构造函数和工厂模式,即创建一个只用于封装继承过程的函数。functioncreateAnother(o){varclone=Object.create(o)//创建一个新对象clone.sayHi=function(){//添加方法console.log('hi')}returnclone//返回这个对象}varperson={name:'Jiang'}varanotherPeson=createAnother(person)anotherPeson.sayHi()返回一个基于人的新对象anotherPeson。新对象不仅有person的属性和方法,还有自己的sayHi方法。这是一种有用的模式,其中主要考虑对象而不是自定义类型和构造函数。寄生组合继承在上面提到的组合模式(原型链+构造函数)中,继承时需要调用两次父类构造函数。父类functionSuperType(name){this.name=namethis.colors=['red','blue','green']}functionSubType(name,job){//第一次继承子类构造函数中的属性SuperType.call(this,name)this.job=job}第二次将子类的原型指向父类的实例//继承方法SubType.prototype=newSuperType()当使用varinstance=newSubType()时,二会生成多组name和color属性,一组在SubType实例上,另一组在SubType原型上,但是实例上的屏蔽了原型上的。这个问题可以通过使用寄生组合模式来规避。这种模式通过借用构造函数来继承属性,通过原型链的混合形式来继承方法。基本思想:不需要调用父类的构造函数来指定子类型的原型,我们需要的只是父类原型的副本。本质上就是利用寄生继承来继承父类的原型,并将结果赋值给子类的原型。functioninheritPrototype(subType,superType){varprototype=Object.create(superType.prototype)prototype.constructor=subTypesubType.prototype=prototype}这个函数实现了最简单的寄生组合继承形式。这个函数有两个参数,一个子类和一个父类。第一步创建父类原型的副本,第二步将constructor属性添加到创建的副本中,第三步将子类原型指向这个副本。functionSuperType(name){this.name=namethis.colors=['red','blue','green']}SuperType.prototype.sayName=function(){console.log(this.name)}functionSubType(name,job){//继承属性SuperType.call(this,name)this.job=job}//继承inheritPrototype(SubType,SuperType)varinstance=newSubType('Jiang','student')instance.sayName()补充:direct使用Object.create实现其实就是将上面封装的功能拆解,这样演示更容易理解。functionSuperType(name){this.name=namethis.colors=['red','blue','green']}SuperType.prototype.sayName=function(){console.log(this.name)}functionSubType(name,job){//继承属性SuperType.call(this,name)this.job=job}//继承SubType.prototype=Object.create(SuperType.prototype)//修复构造函数SubType.prototype.constructor=SubTypevarinstance=newSubType('Jiang','student')instance.sayName()ES6新增了一个方法Object.setPrototypeOf,可以直接创建关联,无需手动添加constructor属性。//继承Object.setPrototypeOf(SubType.prototype,SuperType.prototype)console.log(SubType.prototype.constructor===SubType)//true