JavaScript 继承机制详解:原型链、构造函数与组合模式

作者:代码拾荒时间:2026-10-22 03:48:36浏览:364

本文深入解析 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() 方法,从而实现了继承效果。

尽管原型链继承直观易懂,但它存在以下显著问题:

  1. 当原型中包含引用类型值时,修改任一实例的原型引用类型属性,会导致所有实例受到影响。
  2. 在创建子类型实例时,无法在不影响所有对象实例的前提下,向超类型的构造函数传递特定参数。

示例代码如下:


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

可以看出,调用构造函数继承有效解决了向父类型传参的问题,但该方法自身也存在局限性:

  1. 所有方法都定义在构造函数内部,导致函数无法复用。
  2. 超类型原型中定义的方法,对子类型而言是不可见的。

第一个问题显而易见。针对第二个问题的解释是,由于仅执行了一次函数调用,并未通过 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();

寄生组合式继承仅在调用构造函数时执行了一遍超类型,从而解决了组合继承中调用两次构造函数的小问题。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

作者最新文章