前言
本篇谈一下关于JavaScript 继承相关的知识。包括继承的方式,已经一些优缺点等。本篇知识点获取来源红宝书。
继承方式
关于JavaScript 继承的方式,有大概的如下六种:
原型链继承;
借用构造函数继承;
组合继承;
原型式继承;
寄生式继承;
寄生组合式继承;
这里我们分别展开来讲解。
原型链继承
主要思想:
利用原型让一个引用类型继承另一个引用类型的属性和方法。
实现代码:
functionParent(){this.name='cook';}Parent.prototype.getName=function(){console.log(this.name)}functionChild(){this.age=1}constchild=newChild();console.log(child.getName())//'cook'
缺点:
引用类型的原型属性会被所有实例共享;
创建子类型的实例时(new Child()
),不能向超类型的构造函数(Parent
)中传递参数;
借用构造函数继承
借用构造函数也叫伪造对象或经典继承。
主要思想:
在子类型的构造函数的内部,调用超类型构造函数。
实现代码:
functionParent(name){this.colors=['red','blue','green'];this.name=name;}functionChild(name){//继承了Parent,并传递了参数Parent.call(this,name)}constchild1=newChild('james');child1.colors.push('pink');console.log(child1.colors);//['red','blue','green','pink']console.log(child1.name);//'james'constchild2=newChild('henry');console.log(child2.colors);//['red','blue','green']console.log(child2.name);//'henry'
优点:
避免引用类型的原型属性会被所有实例共享;
子类型的构造函数(Child
)能向超类型构造函数(Parent
)传递参数。
缺点:
方法都在构造函数中定义,无法函数复用(无法继承超类型的原型Parent.prototype
定义的方法);
每次创建实例都会创建一遍方法(每new Child()
一次,就会执行Parent.call(this, name)
);
组合继承
组合继承也叫伪经典继承。
主要思路:
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。
实现代码:
functionParent(name){this.colors=['red','blue','green'];this.name=name;}Parent.prototype.getName=function(){console.log(this.name)}functionChild(name,age){//继承了Parent,并传递了参数//通过借用构造函数来实现对实例属性的继承Parent.call(this,name)this.age=age;}//使用原型链实现对原型属性和方法的继承Child.prototype=newParent();Child.prototype.constructor=Child;constchild1=newChild('james',12);child1.colors.push('pink');console.log(child1.colors);//['red','blue','green','pink']console.log(child1.name);//'james'console.log(child1.age);//12constchild2=newChild('henry',15);console.log(child2.colors);//['red','blue','green']console.log(child2.name);//'henry'console.log(child2.age);//15
优点:
融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
避免引用类型的原型属性会被所有实例共享;
子类型的构造函数(Child
)能向超类型构造函数(Parent
)传递参数;
继承超类型的原型Parent.prototype
定义的方法,实现函数复用;
缺点:
会调用两次父构造函数Parent
。
constchild1=newChild('james',12);//内部调用了Parent.call(this,name)
一次是设置子类型实例的原型的时候(Child.prototype = new Parent()
);
一次在创建子类型实例的时候;
存在实例属性过度赋值。使用 Child.prototype = new Parent()
,导致Child.prototype
和 child1
都拥有构造函数实例的属性。
原型式继承
主要思路:
借助原型,基于已有的对象创建新对象。简单来说就是Object.create
的模拟实现。
实现代码:
functioncreate(o){functionF(){}F.prototype=o;returnnewF();}
缺点:
引用类型的原型属性会被所有实例共享,这点跟原型链继承一样。
constparent={colors:['red','blue','green'],age:1,}constchild1=create(parent);constchild2=create(parent);child1.age=18;console.log(child2.age);//1child1.colors.push('pink');console.log(child2.colors);//['red','blue','green','pink']
寄生式继承
主要思路:
创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后返回对象。
实现代码:
functioncreateAnother(o){varclone=Object.create(o);clone.sayName=function(){console.log('hi');}returnclone;}
缺点:
跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
寄生组合式继承
主要思路:
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
实现代码:
functioncreate(o){functionF(){}F.prototype=o;returnnewF();}functioninheritPrototype(child,parent){//使用寄生式继承来继承超类型的原型constprototype=create(parent.prototype);prototype.constructor=child;//将结果指定给子类型的原型child.prototype=prototype;}functionParent(name){this.colors=['red','blue','green'];this.name=name;}Parent.prototype.getName=function(){console.log(this.name)}functionChild(name,age){//通过借用构造函数来继承属性Parent.call(this,name)this.age=age;}//使用的时候:inheritPrototype(Child,Parent);
优点:
只调用了一次构造函数,避免了在Child.prototype上创建不必要的、多余的属性;
与此同时,原型链还能保持不变;
还能够正常使用 instanceof 和 isPrototypeOf。