JavaScript 八种继承模式详解与对比

作者:纸飞机

时间:2026-10-31

浏览:646

本文系统梳理JavaScript中八种常见的继承实现方式,包括原型链、构造函数、组合、原型式、Object.create、寄生式、寄生组合式及ES6类继承。深入分析各模式的实现原理、优缺点及适用场景,重点解析组合继承的性能开销与寄生组合式优化的核心逻辑,帮助开发者根据实际需求选择最优继承方案。

文章目录

1. 原型链继承

原型链继承是JavaScript中最基础的继承机制。每个对象都拥有一个指向其原型对象的指针,通过这条原型链,属性和方法可以沿着对象链向下传递。在实现上,将子构造函数的原型对象(prototype)指向父构造函数的一个实例,从而使得子对象能够访问父对象原型链上的属性和方法。

function Parent() {
  this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
  console.log('Hello');
};

function Child() {
  this.name = 'Child';
}
Child.prototype = new Parent();

var child = new Child();
child.sayHello(); // Hello
function Parent() {
  this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
  console.log('Hello');
};

function Child() {
  this.name = 'Child';
}
Child.prototype = new Parent();

var child = new Child();
child.sayHello(); // Hello

在此示例中,Child的原型被设置为Parent的一个实例。当调用child.sayHello()时,JavaScript引擎会先在子对象自身查找sayHello方法,若未找到,则沿原型链向上查找并执行父对象原型上的该方法。需要注意的是,原型链继承存在一个显著缺陷:所有子实例共享同一个原型对象,因此对原型属性的修改会直接影响所有子对象。

2. 构造函数继承(经典继承)

构造函数继承的核心在于子构造函数内部调用父构造函数。具体实现时,利用**call()或apply()**方法将父构造函数的执行上下文(this)绑定到子对象上,从而完成属性的继承。

function Parent(name) {
  this.name = name;
}

function Child(name) {
  Parent.call(this, name);
}

var child = new Child('Child');
console.log(child.name); // Child
function Parent(name) {
  this.name = name;
}

function Child(name) {
  Parent.call(this, name);
}

var child = new Child('Child');
console.log(child.name); // Child

示例中,我们在Child构造函数内部调用Parent构造函数,并传入子对象所需的特定参数。这种方式的局限性在于,它仅能继承父构造函数的实例属性,而无法继承父构造函数原型对象(prototype)上定义的方法。

3. 组合继承

组合继承融合了原型链继承与构造函数继承的优势,既保留了父构造函数的实例属性,又复用了原型对象上的方法。其实现策略是:通过调用父构造函数来继承属性,同时将子构造函数的原型指向父构造函数的实例以继承方法。

function Parent(name) {
  this.name = name;
}
Parent.prototype.sayHello = function() {
  console.log('Hello');
};

function Child(name) {
  Parent.call(this, name);
}
Child.prototype = new Parent();

var child = new Child('Child');
child.sayHello(); // Hello
function Parent(name) {
  this.name = name;
}
Parent.prototype.sayHello = function() {
  console.log('Hello');
};

function Child(name) {
  Parent.call(this, name);
}
Child.prototype = new Parent();

var child = new Child('Child');
child.sayHello(); // Hello

具体代码中,通过**Parent.call(this, name)**继承父构造函数的属性,并通过**Child.prototype = new Parent()**将子原型指向父实例以实现方法继承。组合继承的优点在于属性与方法皆可继承;然而,其缺点是在创建子对象时会调用两次父构造函数:一次在设置原型时,另一次在创建子实例时,这会产生不必要的性能开销。

4. 原型式继承

原型式继承借助一个临时构造函数和**Object.create()**方法来实现对象的继承关系。

var parent = {
  name: 'Parent',
  sayHello: function() {
    console.log('Hello');
  }
};

var child = Object.create(parent);
console.log(child.name); // Parent
child.sayHello(); // Hello
var parent = {
  name: 'Parent',
  sayHello: function() {
    console.log('Hello');
  }
};

var child = Object.create(parent);
console.log(child.name); // Parent
child.sayHello(); // Hello

示例中,我们定义了一个parent对象,随后使用**Object.create()**创建新对象child,并将其原型指向parent,从而实现继承。该模式的本质是创建一个新对象,使其原型指向另一个已有对象。这种方式虽能实现属性和方法的继承,但无法像构造函数那样传递参数。

5. Object.create()方法

Object.create()的作用是将现有对象的属性挂载到新对象的原型上,新对象本身为空。

ECMAScript 5规范通过引入Object.create()方法规范化了原型式继承。该方法接收两个参数:第一个是作为新对象原型的对象,第二个是可选的、用于定义新对象额外属性的对象。当仅传入一个参数时,Object.create()的行为与前述临时构造函数方法A的效果一致。

let person = {
  name: 'mjy',
  age: 19,
  hoby: ['唱', '跳'],
  showName() {
    console.log('my name is: ', this.name)
  }
}
 
let child1 = Object.create(person)
child1.name = 'xxt'
child1.hoby.push('rap')
let child2 = Object.create(person)
 
console.log(child1)
console.log(child2)
console.log(person.hoby) // ['唱', '跳', 'rap']let person = {
  name: 'mjy',
  age: 19,
  hoby: ['唱', '跳'],
  showName() {
    console.log('my name is: ', this.name)
  }
}
 
let child1 = Object.create(person)
child1.name = 'xxt'
child1.hoby.push('rap')
let child2 = Object.create(person)
 
console.log(child1)
console.log(child2)
console.log(person.hoby) // ['唱', '跳', 'rap']

优点:无需单独创建构造函数。

缺点:若属性中包含引用值,这些引用会在相关对象间共享;此外,子类实例无法向父类传递参数。

6. 寄生式继承

寄生式继承的思路与原型式继承及工厂模式类似。其核心是创建一个仅用于封装继承过程的函数,该函数内部以某种方式增强对象,最后返回该对象,仿佛它完成了所有工作。

function objectCopy(obj) {
  function Fun() { };
  Fun.prototype = obj;
  return new Fun();
}
 
function createAnother(obj) {
  let clone = objectCopy(obj);
  clone.showName = function () {
    console.log('my name is:', this.name);
  };
  return clone;
}
 
let person = {
     name: "mjy",
     age: 18,
     hoby: ['唱', '跳']
}
 
let child1 = createAnother(person);
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
child1.showName(); // my name is: mjy
 
let child2 = createAnother(person);
console.log(child2.hoby); // ['唱', '跳', 'rap']function objectCopy(obj) {
  function Fun() { };
  Fun.prototype = obj;
  return new Fun();
}
 
function createAnother(obj) {
  let clone = objectCopy(obj);
  clone.showName = function () {
    console.log('my name is:', this.name);
  };
  return clone;
}
 
let person = {
     name: "mjy",
     age: 18,
     hoby: ['唱', '跳']
}
 
let child1 = createAnother(person);
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
child1.showName(); // my name is: mjy
 
let child2 = createAnother(person);
console.log(child2.hoby); // ['唱', '跳', 'rap']

优点:代码写法简单,无需单独创建构造函数。

缺点:通过寄生式继承为对象添加函数会导致函数难以重用。由于无法做到函数复用,其效率低于构造函数模式。

7. 寄生组合式继承

前文提到的组合继承是经典的常用模式,但其最大问题在于无论何种情况都会调用两次父类构造函数:一次在创建子类型原型时,另一次在子类型构造函数内部。寄生组合式继承旨在降低父类构造函数的调用开销。

该模式通过借用构造函数继承属性,并通过原型链的混成形式继承方法。其本质是利用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型。

function objectCopy(obj) {
  function Fun() { };
  Fun.prototype = obj;
  return new Fun();
}
 
function inheritPrototype(child, parent) {
  let prototype = objectCopy(parent.prototype);
  prototype.constructor = child;
  Child.prototype = prototype;
}
 
function Parent(name) {
  this.name = name;
  this.hoby = ['唱', '跳']
}
 
Parent.prototype.showName = function () {
  console.log('my name is:', this.name);
}
 
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
 
inheritPrototype(Child, Parent);
Child.prototype.showAge = function () {
  console.log('my age is:', this.age);
}
 
let child1 = new Child("mjy", 18);
child1.showAge(); // 18
child1.showName(); // mjy
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
 
let child2 = new Child("yl", 18);
child2.showAge(); // 18
child2.showName(); // yl
console.log(child2.hoby); // ['唱', '跳']function objectCopy(obj) {
  function Fun() { };
  Fun.prototype = obj;
  return new Fun();
}
 
function inheritPrototype(child, parent) {
  let prototype = objectCopy(parent.prototype);
  prototype.constructor = child;
  Child.prototype = prototype;
}
 
function Parent(name) {
  this.name = name;
  this.hoby = ['唱', '跳']
}
 
Parent.prototype.showName = function () {
  console.log('my name is:', this.name);
}
 
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
 
inheritPrototype(Child, Parent);
Child.prototype.showAge = function () {
  console.log('my age is:', this.age);
}
 
let child1 = new Child("mjy", 18);
child1.showAge(); // 18
child1.showName(); // mjy
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
 
let child2 = new Child("yl", 18);
child2.showAge(); // 18
child2.showName(); // yl
console.log(child2.hoby); // ['唱', '跳']

优点:仅调用一次父构造函数,从而避免了在子原型上创建不必要且多余的属性;同时保持了原型链的完整性。

缺点:代码实现相对复杂。

8. ES6类继承

ES6引入了类的概念,通过class关键字和extends关键字可以实现类的继承。

class Parent {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log('Hello');
  }
}

class Child extends Parent {
  constructor(name) {
    super(name);
  }
}

const child = new Child('Child');
console.log(child.name); // Child
child.sayHello(); // Hello
class Parent {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log('Hello');
  }
}

class Child extends Parent {
  constructor(name) {
    super(name);
  }
}

const child = new Child('Child');
console.log(child.name); // Child
child.sayHello(); // Hello

示例中,我们定义了一个Parent类,利用extends关键字实现子类Child对父类的继承。子类使用super关键字调用父类构造函数,并可访问父类的属性和方法。ES6类继承提供了更加语法简洁且符合面向对象编程习惯的继承方式。

以上总结了JavaScript中常见的八种继承方式,每种方式均有其特点与适用场景。开发者可根据具体需求选择最适合的继承模式来构建对象间的关系。

总结

本文介绍了JS中常见的8种继承方法。更多相关JS继承方法内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章