JavaScript 继承模式详解:借用构造函数与组合继承
本文深入解析 JavaScript 中的两种重要继承模式。首先介绍借用构造函数技术,通过 call 或 apply 在子类中调用父类构造函数,解决原型包含引用类型值的问题并支持参数传递,但存在方法无法复用的缺陷。随后探讨组合继承,结合原型链与借用构造函数,既实现方法复用又保证实例属性独立,成为最常用的继承方式,同时也指出了其调用两次父构造函数的性能损耗。
借用构造函数继承
借用构造函数继承
为了解决原型中包含引用类型值所带来的问题,开发人员引入了一种名为借用构造函数(constructor stealing,有时也称为伪造对象或经典继承)的技术。该技术的基本原理非常直观:在子类型的构造函数内部,直接调用超类型的构造函数。
基本模式
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
基本思想
借用构造函数的核心在于利用 call 或 apply 方法,将父类中通过 this 绑定的属性和方法“借用”到子类创建的实例中。由于 this 对象是依据函数的执行环境在运行时绑定的——在全局环境中 this 指向 window,而作为对象方法调用时指向该对象——因此 call 和 apply 能够改变函数的执行上下文,将其从初始上下文切换为由 thisObj 指定的新对象。
因此,借用构造函数的执行逻辑是:当使用 new 操作符创建对象时(注意 new 与直接函数调用的区别,直接调用时 this 指向 window,而 new 创建时 this 指向新实例),会执行 SubType 内部的代码。SubType 内部通过 call 调用了 SuperType,此时 this 指向被重新绑定到新创建的实例上。这使得 SuperType 中定义的所有 this 相关属性和方法都被赋值给了这个新实例,而非 SuperType 本身。最终,所有实例都拥有了父类定义的这些通过 this 绑定的属性和方法。
优势
相较于原型链,借用构造函数具有一个显著优势:可以在子类型构造函数中向超类型构造函数传递参数。由于属性是绑定在 this 上的,它们在调用时才被赋值给相应的实例,因此各个实例的值互不干扰。
例如:
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
劣势
如果仅使用借用构造函数,则无法避免构造函数模式的固有缺陷——所有方法都在构造函数内部定义,导致函数复用无从谈起。此外,超类型原型中定义的方法对子类型不可见,这意味着所有类型都只能依赖构造函数模式。鉴于这些局限性,借用构造函数很少被单独使用。
组合继承
组合继承
组合继承(combination inheritance),有时也被称为伪经典继承,是一种将原型链和借用构造函数技术结合起来的继承模式,旨在发挥两者的优势。
基本思想
其思路是利用原型链来继承原型上的属性和方法,同时通过借用构造函数来继承实例属性。这样既通过在原型上定义方法实现了函数复用,又确保了每个实例拥有自己独立的属性副本。
基本模型
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
优势
组合继承避免了原型链和借用构造函数各自的缺陷,融合了它们的优点,已成为 JavaScript 中最常用的继承模式。
劣势
组合继承最大的问题在于,无论何种情况,都会调用两次超类型构造函数:一次是在创建子类型原型时,另一次是在子类型构造函数内部执行时。虽然子类型最终包含超类型对象的全部实例属性,但在调用子类型构造函数时,必须重写这些属性。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18
