详解JavaScript中8 种不同的继承实现方式

 更新时间:2025年05月03日 08:48:42   作者:北辰alk  
在 JavaScript 中,继承是实现代码复用和构建复杂对象关系的重要机制,虽然 JavaScript 是一门基于原型的语言,不像传统面向对象语言那样有类的概念,但它提供了多种实现继承的方式,本文将详细介绍 JavaScript 中 8 种不同的继承实现方式,需要的朋友可以参考下

前言

在 JavaScript 中,继承是实现代码复用和构建复杂对象关系的重要机制。虽然 JavaScript 是一门基于原型的语言,不像传统面向对象语言那样有类的概念,但它提供了多种实现继承的方式。本文将详细介绍 JavaScript 中 8 种不同的继承实现方式,每种方式都会配有代码示例和详细解释,最后还会通过流程图比较各种继承方式的特点。

1. 原型链继承

原型链继承是 JavaScript 中最基本的继承方式,它利用原型让一个引用类型继承另一个引用类型的属性和方法。

function Parent() {
  this.name = 'Parent';
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
  return this.name;
};

function Child() {
  this.childName = 'Child';
}

// 关键步骤:将Child的原型指向Parent的实例
Child.prototype = new Parent();

var child1 = new Child();
console.log(child1.getName()); // "Parent"
console.log(child1.childName); // "Child"

// 问题:引用类型的属性会被所有实例共享
child1.colors.push('black');
var child2 = new Child();
console.log(child2.colors); // ["red", "blue", "green", "black"]

特点:

  • 简单易实现
  • 父类新增原型方法/属性,子类都能访问到
  • 无法实现多继承
  • 来自原型对象的引用属性被所有实例共享
  • 创建子类实例时,无法向父类构造函数传参

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

通过在子类构造函数中调用父类构造函数实现继承。

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
  return this.name;
};

function Child(name, age) {
  // 关键步骤:在子类构造函数中调用父类构造函数
  Parent.call(this, name);
  this.age = age;
}

var child1 = new Child('Tom', 18);
child1.colors.push('black');
console.log(child1.name); // "Tom"
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

var child2 = new Child('Jerry', 20);
console.log(child2.colors); // ["red", "blue", "green"]

// 问题:无法继承父类原型上的方法
console.log(child1.getName); // undefined

特点:

  • 解决了原型链继承中引用类型共享的问题
  • 可以在子类构造函数中向父类构造函数传参
  • 可以实现多继承(call多个父类对象)
  • 只能继承父类实例属性和方法,不能继承原型属性和方法
  • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3. 组合继承(最常用)

组合继承结合了原型链继承和构造函数继承的优点。

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
  return this.name;
};

function Child(name, age) {
  // 构造函数继承 - 第二次调用Parent()
  Parent.call(this, name);
  this.age = age;
}

// 原型链继承 - 第一次调用Parent()
Child.prototype = new Parent();
// 修正constructor指向
Child.prototype.constructor = Child;

Child.prototype.getAge = function() {
  return this.age;
};

var child1 = new Child('Tom', 18);
child1.colors.push('black');
console.log(child1.name); // "Tom"
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
console.log(child1.getName()); // "Tom"
console.log(child1.getAge()); // 18

var child2 = new Child('Jerry', 20);
console.log(child2.colors); // ["red", "blue", "green"]
console.log(child2.getName()); // "Jerry"
console.log(child2.getAge()); // 20

特点:

  • 融合原型链继承和构造函数继承的优点
  • 既是子类的实例,也是父类的实例
  • 不存在引用属性共享问题
  • 可传参
  • 函数可复用
  • 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

4. 原型式继承

借助原型可以基于已有对象创建新对象。

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');

console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]

ES5 规范化了原型式继承,新增了 Object.create() 方法:

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = Object.create(person, {
  name: {
    value: 'Linda'
  }
});
yetAnotherPerson.friends.push('Barbie');

console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]

特点:

  • 不需要单独创建构造函数
  • 本质是对给定对象执行浅复制
  • 适用于不需要单独创建构造函数,但仍需要在对象间共享信息的场合
  • 同原型链继承一样,包含引用类型的属性会被共享

5. 寄生式继承

创建一个仅用于封装继承过程的函数,在函数内部增强对象。

function createAnother(original) {
  var clone = Object.create(original); // 通过调用函数创建一个新对象
  clone.sayHi = function() { // 以某种方式增强这个对象
    console.log('Hi');
  };
  return clone; // 返回这个对象
}

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // "Hi"

特点:

  • 基于原型式继承
  • 增强了对象
  • 无法实现函数复用
  • 同原型式继承一样,引用类型属性会被共享

6. 寄生组合式继承(最理想)

通过借用构造函数继承属性,通过原型链混成形式继承方法。

function inheritPrototype(child, parent) {
  var prototype = Object.create(parent.prototype); // 创建父类原型的副本
  prototype.constructor = child; // 修正constructor指向
  child.prototype = prototype; // 将副本赋值给子类原型
}

function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function() {
  return this.name;
};

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

// 关键步骤:避免调用Parent构造函数,直接使用父类原型
inheritPrototype(Child, Parent);

Child.prototype.getAge = function() {
  return this.age;
};

var child1 = new Child('Tom', 18);
var child2 = new Child('Jerry', 20);

console.log(child1.getName()); // "Tom"
console.log(child1.getAge()); // 18
console.log(child2.getName()); // "Jerry"
console.log(child2.getAge()); // 20

特点:

  • 只调用一次父类构造函数
  • 避免在子类原型上创建不必要的属性
  • 原型链保持不变
  • 能够正常使用 instanceof 和 isPrototypeOf
  • 是引用类型最理想的继承方式

7. ES6 Class 继承

ES6 引入了 class 语法糖,使得继承更加清晰易读。

class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
  }
  
  getName() {
    return this.name;
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 调用父类的constructor
    this.age = age;
  }
  
  getAge() {
    return this.age;
  }
}

const child1 = new Child('Tom', 18);
child1.colors.push('black');
console.log(child1.getName()); // "Tom"
console.log(child1.getAge()); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]

const child2 = new Child('Jerry', 20);
console.log(child2.colors); // ["red", "blue", "green"]

特点:

  • 语法更加清晰易读
  • 底层实现仍然是基于原型
  • 通过 extends 实现继承
  • 子类必须在 constructor 中调用 super(),否则新建实例时会报错
  • ES6 的继承机制完全不同,实质是先创造父类的实例对象 this(所以必须先调用 super 方法),然后再用子类的构造函数修改 this

8. 混入方式继承(多继承)

JavaScript 本身不支持多继承,但可以通过混入(Mixin)的方式实现类似功能。

function extend(target, ...sources) {
  sources.forEach(source => {
    for (let key in source) {
      if (source.hasOwnProperty(key)) {
        target[key] = source[key];
      }
    }
    
    // 支持Symbol属性
    const symbols = Object.getOwnPropertySymbols(source);
    symbols.forEach(symbol => {
      target[symbol] = source[symbol];
    });
  });
  return target;
}

const canEat = {
  eat() {
    console.log(`${this.name} is eating.`);
  }
};

const canWalk = {
  walk() {
    console.log(`${this.name} is walking.`);
  }
};

const canSwim = {
  swim() {
    console.log(`${this.name} is swimming.`);
  }
};

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

// 将多个mixin混入Person的原型
extend(Person.prototype, canEat, canWalk);

const person = new Person('John');
person.eat(); // "John is eating."
person.walk(); // "John is walking."
// person.swim(); // 报错,没有swim方法

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

extend(Fish.prototype, canEat, canSwim);

const fish = new Fish('Nemo');
fish.eat(); // "Nemo is eating."
fish.swim(); // "Nemo is swimming."
// fish.walk(); // 报错,没有walk方法

ES6 中可以使用 Object.assign 简化混入:

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

Object.assign(Person.prototype, canEat, canWalk);

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

Object.assign(Fish.prototype, canEat, canSwim);

特点:

  • 可以实现类似多继承的功能
  • 灵活性强,可以按需组合功能
  • 不是真正的继承,而是属性拷贝
  • 可能会导致命名冲突
  • 无法使用 instanceof 检查混入的功能

继承方式比较流程图

总结

JavaScript 提供了多种实现继承的方式,每种方式都有其适用场景和优缺点:

  1. 原型链继承:简单但引用类型属性会被共享
  2. 构造函数继承:可解决引用共享问题但无法继承原型方法
  3. 组合继承:最常用的继承方式,但会调用两次父类构造函数
  4. 原型式继承:适用于基于已有对象创建新对象
  5. 寄生式继承:增强对象但无法函数复用
  6. 寄生组合继承:最理想的继承方式,高效且完整
  7. ES6 Class继承:语法糖,底层仍是原型继承
  8. 混入方式:实现类似多继承的功能

在实际开发中,ES6 的 class 语法是最推荐的方式,它语法简洁,易于理解,且底层实现高效。对于需要兼容旧浏览器的项目,可以使用寄生组合式继承作为替代方案。

理解这些继承方式的原理和区别,有助于我们在不同场景下选择最合适的实现方式,写出更优雅、高效的 JavaScript 代码。

以上就是详解JavaScript中8 种不同的继承实现方式的详细内容,更多关于JavaScript实现继承的资料请关注脚本之家其它相关文章!

相关文章

  • 详解JavaScript的懒加载是如何实现的

    详解JavaScript的懒加载是如何实现的

    懒加载(Lazy Loading)是一种在软件开发中常用的优化技术,它主要用于延迟加载资源,直到真正需要使用的时候才进行加载,这样可以减少初始加载的时间和资源消耗,并提升用户体验,本文给大家详细介绍了JavaScript的懒加载是如何实现的,需要的朋友可以参考下
    2024-01-01
  • JavaScript初学者需要了解10个小技巧

    JavaScript初学者需要了解10个小技巧

    在之前的编程语言排行榜中,我们曾介绍过转正在即的JavaScript语言,正如文章中阐明的那样,JavaScript不仅是最具活力的脚本语言,还是是最有用的编程语言之一。
    2010-08-08
  • 前端直接导出excel文件的两种方式

    前端直接导出excel文件的两种方式

    这篇文章主要介绍了两种方法在前端实现本地表格导出功能,分别是插件方式和本地直接导出,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-01-01
  • JavaScript利用canvas实现炫酷的碎片切图效果

    JavaScript利用canvas实现炫酷的碎片切图效果

    这篇文章主要和大家分享一个炫酷的碎片式切图效果,本文主要利用canvas来实现,代码量不多,但有些地方还是需要花点时间去理解的,感兴趣的可以学习一下
    2022-10-10
  • javascript实现tab切换的两个实例

    javascript实现tab切换的两个实例

    这篇文章主要介绍了javascript实现tab切换的两个实例,是对之前方法原理的进一步延伸,需要深入了解的同学可以参考一下
    2015-11-11
  • 微信小程序实现弹出框提交信息

    微信小程序实现弹出框提交信息

    这篇文章主要为大家详细介绍了微信小程序实现弹出框提交信息,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • 一文带你彻底搞懂JavaScript正则表达式

    一文带你彻底搞懂JavaScript正则表达式

    正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式,这篇文章主要给大家介绍了关于彻底搞懂JavaScript正则表达式的相关资料,需要的朋友可以参考下
    2022-09-09
  • 你必须了解的JavaScript中的属性描述对象详解(下)

    你必须了解的JavaScript中的属性描述对象详解(下)

    JavaScript提供了一个内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可写、可遍历等等。这个内部数据结构称为“属性描述对象”。本文主要带大家了解一下JavaScript中你必须了解的属性描述对象,需要的可以参考一下
    2022-12-12
  • TypeScript新特性之using关键字的使用方法

    TypeScript新特性之using关键字的使用方法

    TypeScript 5.2版本中新添加了using关键字,目前该关键字的提案也进入了ECMAScript的Stage 3,也就是说很快就会进入JavaScript语言本身中,using和const, let和var一样都是用于变量声明的,那么它到底有什么与众不同的地方呢,本文给大家介绍的非常详细
    2023-11-11
  • amd、cmd、esmodule、commonjs区别详解

    amd、cmd、esmodule、commonjs区别详解

    本文主要介绍了amd、cmd、esmodule、commonjs区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04

最新评论