后台继承为我们提供了一种优雅可复用的编码方式。继承也是面试中经常被问到的问题。本文全面总结了JavaScript中的继承类型,每种继承类型的优缺点和使用场景等,深入了解JavaScript继承,收藏起来~原型继承。方法。实现原型链的基本模式);SubType.prototype.getSubValue=function(){returnthis.property;};varinstance=newSubType();console.log(instance.getSuperValue());//true;示例中实例与构造函数和原型之间的关系图:在示例代码中,定义了两个对象,subType和superType。继承是在两个对象之间实现的,这种继承方式是通过创建一个SuperType的实例并将该实例赋值给subType.prototype来实现的。实现的本质是重写原型对象。这样subType.prototype中就会有一个指向superType原型对象的指针。也就是说,存在于superType实例中的属性和方法现在存在于subType.prototype中。这样继承之后,就可以给subType添加新的方法和属性了。需要注意的是这个指针([[prototype]])默认不能再被外部访问了。估计会被一些内部方法用到,比如用for...in遍历原型链,可以枚举属性,需要通过this指针找到当前对象继承的对象。但是,Firefox、Safari和Chrome在每个对象上都支持属性__proto__。原型继承需要注意的一些问题1.不要忘记默认类型。我们知道所有的引用类型都继承Object,而这种继承也是通过原型链来实现的。所以所有的对象都有一些Object有的默认方法。如:hasOwnProperty()、propertyIsEnumerable()、toLocaleString()、toString()和valueOf()2、判断原型与实例的关系判断原型与实例的关系有两种方式。①使用instanceof操作符,只要使用这个操作符去测试实例链和原型链中已经出现过的构造函数,结果就会返回true。②第二种方式是使用isPrototypeOf()方法。同样,只要是在原型链中出现过的原型,就可以说是从原型链派生出来的实例的原型,所以isPrototypeOf()方法也会返回true。示例:alert(instanceinstanceofObject);//truealert(instanceinstanceofSuperType);//truealert(instanceinstanceofSubType);//truealert(Object.prototype.isPrototypeOf(instance));//truealert(SuperType.prototype.isPrototypeOf(instance));//truealert(SubType.prototype.isPrototypeOf(instance));//true③继承后子类必须定义新的方法,因为原型继承本质上是重写原型对象。因此,如果在继承前在子类的原型上定义一些方法和属性。那么在继承的时候,子类的这些属性和方法就会被覆盖。如图:④对象字面量不能用于创建原型方法。这个原理其实和第三点是一样的。当你使用对象字面量创建原型方法来重写原型时,就相当于重写了原型链,所以原来的原型链被切断了。如图:⑤注意父类包含引用类型如图:本例中的SuperType构造函数定义了一个colors属性,其中包含一个数组(引用类型值)。SuperType的每个实例都有自己的colors属性,其中包含自己的数组。当SubType通过原型链继承SuperType时,SubType.prototype成为SuperType的一个实例,因此它也有自己的colors属性——就像创建一个SubType.prototype.colors属性一样。但结果如何呢?结果是SubType的所有实例都将共享这个颜色属性。而我们对instance1.colors的修改可以通过instance2.colors体现出来。也就是说,此类修改会影响个别实例。原型继承的缺点(问题)就是上面最明显的点⑤。当存在引用类型时,每个实例对引用的操作都会影响到其他实例。没有办法在不影响所有对象实例的情况下将参数传递给超类型的构造函数。正因为如此,原型继承在实践中很少单独使用。借用构造函数继承为了解决原型中具有引用类型值的问题,开发人员开始使用一种称为构造函数窃取(有时称为假对象或经典继承)的技术。这个技巧的基本思想相当简单,在子类型构造函数内部调用超类型构造函数。基本模式functionSuperType(){this.colors=["red","blue","green"];}functionSubType(){//继承SuperTypeSuperType.call(this);}varinstance1=newSubType();instance1.colors。push("black");alert(instance1.colors);//"red,blue,green,black"varinstance2=newSubType();alert(instance2.colors);//"red,blue,green"基本思想借用构造函数的基本思想是使用call或apply将父类中this指定的属性和方法复制(借用)到子类创建的实例中。因为this对象是根据函数在运行时的执行环境绑定的。即在全局范围内,this等于window,当函数作为对象的方法调用时,this等于该对象。call和apply方法可用于调用一个方法而不是另一个对象。call、apply方法可以将函数的对象上下文从初始上下文更改为thisObj指定的新对象。所以这个借用构造函数是在new对象的时候(注意new操作符和直接调用是不一样的,直接以函数的形式调用的时候,this指向window,new的时候this指向创建的实例),新建一个实例对象,并执行SubType中的代码,调用SubType中的SuperType,也就是将this指向指向新的实例,所以SuperType中的this相关属性和该方法分配给新实例,而不是SupType。所有实例都具有由父类定义的这些属性和方法。优势与原型链相比,借用构造函数有一个很大的优势,就是可以在子类型构造函数中给超类型构造函数传递参数。因为属性是和this绑定的,调用的时候会赋值给对应的实例,各个实例的值不会互相影响。例如:functionSuperType(name){this.name=name;}functionSubType(){//继承SuperType,同时传递参数SuperType.call(this,"Nicholas");//实例属性this.age=29;}varinstance=newSubType();alert(instance.name);//"Nicholas";alert(instance.age);//29缺点如果只是借用构造函数,将无法避免构造函数的问题mode——方法都是在构造函数中定义的,所以函数重用是谈不上的。此外,超类型原型中定义的方法对于子类型是不可见的,因此所有类型都只能使用构造函数模式。鉴于这些问题,借用构造函数的技术也很少单独使用。组合继承组合继承有时被称为伪经典继承。是一种将原型链和借用构造函数技术相结合,充分发挥两者优势的继承模式。基本思想是利用原型链实现原型属性和方法的继承,通过借用构造函数实现实例属性的继承。这样通过在原型上定义方法来实现功能复用,并且可以保证每个实例都有自己的属性。基本模型函数SuperType(name){this.name=name;this.colors=["red","blue","green"];}SuperType.prototype.sayName=function(){alert(this.name);};functionSubType(name,age){//继承属性SuperType.call(this,name);this.age=age;}//继承方法SubType.prototype=newSuperType();SubTypeSubType.prototype.constructor=SubType;SubType.prototype.sayAge=function(){alert(this.age);};varinstance1=newSubType("Nicholas",29);instance1.colors.push("black");alert(instance1.colors);//"red,blue,green,black"instance1.sayName();//"Nicholas";instance1.sayAge();//29varinstance2=newSubType("Greg",27);alert(instance2.colors);//"red,blue,green"instance2.sayName();//"Greg";instance2.sayAge();//27优点组合继承避免了原型链和借用构造函数的缺陷,结合了它们的优点,成为JavaScript中最常用的继承模式。劣势组合继承的最大问题是无论在什么情况下,超类型构造函数都会被调用两次:一次是在创建子类型原型时,一次是在子类型构造函数内部。虽然子类型最终会包含超类型对象的所有实例属性,但是我们在调用子类型构造函数时不得不重写这些属性。寄生类继承原型继承的原理是借助原型,可以在已有对象的基础上创建新的对象。省去了创建自定义类型的步骤(虽然我觉得没有意义)。模型functionobject(o){functionW(){}W.prototype=o;returnnewW();}ES5增加了Object.create()方法来标准化原型继承。即调用方法为:Object.create(o);当适用场景只想建立一个对象与另一个对象的继承关系时,可以使用Object.create();如果此方法不兼容,您可以手动添加此方法兼容。寄生继承寄生继承是原型继承的增强版。模型functioncreateAnother(origin){varclone=object(origin);clone.say=function(){alert('hi')}returnclone;}就是在生成继承父类方法的对象后,对这个对象进行一些增强。寄生组合继承从本质上讲,寄生组合继承是寄生组合继承的增强版。这也是避免组合继承中不可避免的调用父类构造函数两次的最佳方案。因此,开发者普遍认为寄生组合继承是引用类型最理想的继承范式。基本模式函数inheritPrototype(SubType,SuperType){varprototype=object(SuperType.prototype);prototype.constructor=subType;subType.prototype=prototype;}这个对象是一个自定义函数,相当于ES5中的Object.create()方法。在兼容性方面,两者都可以写。兼容写法functionobject(o){functionW(){}W.prototype=o;returnnewW;}functioninheritPrototype(SubType,SuperType){varprototype;if(typeofObject.create==='function'){prototype=Object.create(SuperType.prototype);}else{prototype=object.create(SuperType.prototype);
prototype.constructor=SubType;SubType.prototype=prototype;}类继承类可以通过extends关键字继承。子类必须在构造方法中调用super方法,否则新建实例时会报错。这是因为子类的this对象必须先通过父类的构造函数进行整形,获得与父类相同的实例属性和方法,然后再处理添加子类自身的实例属性和方法。如果不调用super方法,子类就无法获取到这个对象。注意:ES5继承的本质是先创建子类的实例对象this,然后将父类的方法添加到this中(Parent.apply(this))。ES6的继承机制完全不同。本质就是将父类实例对象的属性和方法添加到this中(所以必须先调用super方法),然后使用子类的构造函数修改this。classColorPointextendsPoint{constructor(x,y,color){super(x,y);//调用父类constructor(x,y)this.color=color;}toString(){returnthis.color+''+super.toString();//调用父类的toString()}}类继承链在大多数浏览器的ES5实现中,每个对象都有一个__proto__属性,指向对应构造函数的prototype属性。Class作为构造函数的语法糖,同时具有prototype属性和__proto__属性,所以同时存在两条继承链。(1)子类的__proto__属性表示构造函数的继承,始终指向父类。(2)子类的prototype属性的__proto__属性表示方法的继承,始终指向父类的prototype属性。classA{}classBextendsA{}B.__proto__===A//trueB.prototype.__proto__===A.prototype//true上面代码中子类B的__proto__属性指向父类A和子类Bprototype属性的__proto__属性指向父类A的prototype属性。
