JavaScript 继承机制详解:原型链、构造函数与组合模式
本文深入解析 JavaScript 中的四种主要继承方式。首先介绍基于原型链的继承及其共享引用类型的缺陷,随后讲解通过借用构造函数解决参数传递问题的方法。在此基础上,组合继承结合两者优点,但存在调用两次构造函数的开销。最后,重点阐述寄生组合式继承,通过原型副本实现高效且无冗余的方法与属性继承,是实际开发中的推荐方案。
JavaScript 继承概述
JavaScript 中的继承实例详解
阅读目录
- 原型链继承
- 借用构造函数
- 组合继承
- 寄生组合式继承
- 后记
继承机制在面向对象编程中主要分为两种形式:接口继承与实现继承。接口继承仅涉及方法签名的继承,而实现继承则关注具体方法逻辑的复用。
由于 ECMAScript 函数不具备签名概念,因此无法实现接口继承。该语言仅支持实现继承,其核心实现机制依赖于原型链。
接下来将详细介绍 JavaScript 中几种常见的继承实现方式。
原型链继承
原型链继承
原型链继承的核心机制是重写原型对象,将其替换为新类型的实例。具体实现代码如下:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
};
var instance = new SubType();
console.log(instance.getSuperValue()); // true
通过上述代码可见,instance 实例成功调用了父级的 getSuperValue() 方法,从而实现了继承效果。
尽管原型链继承直观易懂,但它存在以下显著问题:
- 当原型中包含引用类型值时,修改任一实例的原型引用类型属性,会导致所有实例受到影响。
- 在创建子类型实例时,无法在不影响所有对象实例的前提下,向超类型的构造函数传递特定参数。
示例代码如下:
function SuperType1() {
this.colors = ['red', 'blue', 'green'];
}
function SubType1() {
}
SubType1.prototype = new SuperType1();
var instance1 = new SubType1();
instance1.colors.push('black');
console.log(instance1.colors); // [ 'red', 'blue', 'green', 'black' ]
var instance2 = new SubType1();
console.log(instance2.colors); // [ 'red', 'blue', 'green', 'black' ]
运行结果发现,instance1 和 instance2 的 colors 属性是共享的,这导致了数据污染问题。同时可以看出,在 new 新实例时传入的值无法传递到父级构造函数中。
借用构造函数
借用构造函数
其原理是在子类型构造函数的内部调用超类型构造函数。由于函数是在特定环境中执行代码的对象,这种方式使得子类型能够获取父级的方法和属性。
代码如下:
function SuperType(name) {
this.name = name;
}
function SubType(name) {
// 继承了SuperType,同时还传递了参数
SuperType.call(this, name);
// 实例属性
this.age = 29;
}
var instance = new SubType('Bob');
console.log(instance.name); // Bob
console.log(instance.age); // 29
可以看出,调用构造函数继承有效解决了向父类型传参的问题,但该方法自身也存在局限性:
- 所有方法都定义在构造函数内部,导致函数无法复用。
- 超类型原型中定义的方法,对子类型而言是不可见的。
第一个问题显而易见。针对第二个问题的解释是,由于仅执行了一次函数调用,并未通过 new 创建新对象,因此父类 prototype 中的方法对子类不可见。
组合继承
组合继承
鉴于原型链继承和借用构造函数继承各自存在缺陷,实际开发中通常不会单独使用其中任何一种。
组合继承旨在结合两者的优点,从而产生一种更完善的继承方法。
其原理是利用原型链实现对原型属性和方法的继承,同时通过借用构造函数来实现对实例属性的继承。
代码如下:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
console.log(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() {
console.log(this.age);
};
var instance1 = new SubType('Nicholas', 29);
instance1.colors.push('black');
console.log(instance1.colors); // [ 'red', 'blue', 'green', 'black' ]
instance1.sayName(); // Nicholas
instance1.sayAge(); // 29
var instance2 = new SubType('Greg', 27);
console.log(instance2.colors); // [ 'red', 'blue', 'green' ]
instance2.sayName(); // Greg
instance2.sayAge(); // 27
组合继承能够解决上述两种继承方式带来的问题,但它自身存在一个小缺陷,即会调用两次超类型构造函数。分析可知,一次发生在创建子类型原型时,另一次发生在子类型构造函数内部执行时。
寄生组合式继承
寄生组合式继承
寄生组合式继承的原理是通过借用构造函数来继承属性,并通过原型链的混成形式来继承方法。其基本思路是不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。
代码如下:
function object(o) {
function F() {}
F.prototype = o;
return new F;
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
// 继承的关键
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
console.log(this.age);
};
var instance = new SubType('天涯', 23);
instance.sayName();
instance.sayAge();
寄生组合式继承仅在调用构造函数时执行了一遍超类型,从而解决了组合继承中调用两次构造函数的小问题。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
JavaScript函数定义的三种方式:声明、表达式与箭头函数
2026-11-05 13:50:55
