本文详细解析JavaScript中实现继承的五种主要方式。从基础的借用构造函数与组合继承,到原型式与寄生式继承,重点剖析了组合继承存在的构造函数调用两次的问题,并深入讲解如何通过寄生组合式继承优化性能,实现高效且标准的对象继承模式。
借用构造函数
该技术的核心逻辑在于子类型构造函数内部直接调用超类型的构造函数。由于函数本质是在特定环境中执行代码的对象,因此可以利用apply()和call()方法,在新创建的对象实例上执行超类型构造函数,从而实现属性的继承。
function Box(name){
this.name = name
}
Box.prototype.age = 18
function Desk(name){
Box.call(this, name) // 对象冒充,对象冒充只能继承构造里的信息
}
var desk = new Desk('ccc')
console.log(desk.name) // --> ccc
console.log(desk.age) // --> undefined
观察代码可知,这种模式下仅能继承实例属性,原型链上的属性无法被访问到。此模式成功解决了参数传递和实例属性继承两个问题,但缺点在于缺乏原型链支持,导致方法无法复用。
组合继承
function Box(name){
this.name = name
}
Box.prototype.run = function (){
console.log(this.name + '正在运行...')
}
function Desk(name){
Box.call(this, name) // 对象冒充
}
Desk.prototype = new Box() // 原型链
var desk = new Desk('ccc')
console.log(desk.name) // --> ccc
desk.run() // --> ccc正在运行...
组合继承的设计思路结合了前两种模式的优点:利用原型链机制来继承原型上的属性和方法,同时通过借用构造函数的方式来实现对实例属性的继承。
原型式继承
原型式继承允许基于已有对象直接创建新对象,而无需定义自定义类型。这一概念源自道格拉斯·克罗克福德(Douglas Crockford)在2006年发表的文章《Prototype inheritance in Javascript》中提出的方法。
function object(o) { //传递一个字面量函数
function F(){} //创建一个构造函数
F.prototype = o; //把字面量函数赋值给构造函数的原型
return new F() //最终返回出实例化的构造函数
}
参考以下示例代码进行理解:
function obj(o) {
function F (){}
F.prototype = o;
return new F()
}
var box = {
name: 'ccc',
age: 18,
family: ['哥哥','姐姐']
}
var box1 = obj(box)
console.log(box1.name) // --> ccc
box1.family.push('妹妹')
console.log(box1.family) // --> ["哥哥", "姐姐", "妹妹"]
var box2 = obj(box)
console.log(box2.family) // --> ["哥哥", "姐姐", "妹妹"]
由于上述实现逻辑与原型链继承相似,导致其中的引用类型属性(如family数组)被所有实例共享,修改一个实例会影响其他实例。
寄生式继承
function obj(o) {
function F (){}
F.prototype = o;
return new F()
}
function create(o){
var clone = obj(o) // 通过调用函数创建一个新对象
clone.sayName = function(){ // 以某种方式来增强这个对象
console.log('hi')
}
return clone // 返回这个对象
}
var person = {
name: 'ccc',
friends: ['aa','bb']
}
var anotherPerson = create(person)
anotherPerson.sayName() // --> hi
该示例代码基于person对象创建并返回一个新对象anotherPerson。新对象不仅拥有person的所有属性和方法,还额外添加了sayHi()方法。在主要关注对象而非自定义类型和构造函数的场景下,寄生式继承是一种有效模式。然而,通过寄生式继承为对象添加函数会导致无法实现函数复用,从而降低效率,这一点与构造函数模式类似。
寄生组合式继承
前文提到,组合继承是JavaScript中最常用的继承模式,但其存在明显缺陷:无论何种情况,超类型构造函数都会被调用两次——一次在创建子类型原型时,另一次在子类型构造函数内部执行时。这意味着子类型最终会包含超类型对象的全部实例属性,但在调用子类型构造函数时,这些属性会被重写。
function SuperType(name){
this.name = name;
this.colors = ['red','black']
}
SuperType.prototype.sayName = function (){
console.log(this.name)
}
function SubType(name, age){
SuperType.call(this, name) // 第二次调用SuperType
this.age = age
}
SubType.prototype = new SuperType() // 第一次调用SuperType
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function (){
console.log(this.age)
}
以SuperType为例,第一次调用时,SubType.prototype获得name和colors两个属性,它们作为SuperType的实例属性位于SubType原型中。当调用SubType构造函数时,第二次调用SuperType构造函数,在新对象上再次创建name和colors属性,从而屏蔽了原型中的同名属性。这导致存在两组name和colors属性:一组在实例上,一组在原型上。解决此问题的方案即为寄生组合式继承。 所谓寄生组合式继承,是指通过借用构造函数继承属性,通过原型链的混成形式继承方法。其核心思路是:无需为了制定子类型原型而调用超类型构造函数,只需获取超类型原型的一个副本即可。本质上,这是使用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型。寄生组合式继承的基本模式如下:
function object(o) {
function F (){}
F.prototype = o;
return new F()
}
function inheritPtototype(subType, superType){
var prototype = object(superType.prototype) // 创建对象
prototype.constructor = subType // 增强对象
subType.prototype = prototype // 指定对象
}
function SuperType(name){
this.name = name
this.colors = ['red', 'white']
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function SubType(name,age){
SuperType.call(this,name)
this.age = age
}
inheritPtototype(SubType, SuperType)
SubType.prototype.sayAge = function(){
console.log(this.age)
}
var instance = new SubType('ccc', 18)
instance.sayName() // --> ccc
instance.sayAge() // --> 18
console.log(instance)
控制台打印出的结构:

详细的图解:

该示例的高效性体现在仅调用了一次SuperType构造函数,从而避免了在SubType.prototype上创建不必要的、多余的属性。同时,原型链得以保持完整,instanceof和isPrototypeOf()方法仍可正常使用。这也是众多大型互联网公司采用的主流继承方式。
以上即为JavaScript中实现继承的五种方法的详细内容,更多关于js 实现继承的资料请关注本站其它相关文章!