JavaScript六种继承方式总结大全

 更新时间:2025年10月22日 10:15:43   作者:懒羊羊小可爱  
JavaScript中最基本的继承方式,其核心思想是利用原型让一个引用类型继承另一个引用类型的属性和方法,下面这篇文章主要介绍了JavaScript六种继承方式的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

1. 原型链继承

是什么?

这是最基础的继承方式。其核心是:让一个构造函数的 prototype 对象指向另一个构造函数的实例。这样,子类就能通过原型链访问到父类的属性和方法。

javascript

// 父类
function Parent() {
    this.name = 'Parent';
    this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
    console.log(this.name);
};

// 子类
function Child() {
    this.type = 'Child';
}

// 关键!将Child的原型指向Parent的实例,建立原型链
Child.prototype = new Parent();

var child1 = new Child();
child1.sayName(); // 'Parent' (来自原型链)
console.log(child1.colors); // ['red', 'blue'] (来自原型链)

var child2 = new Child();
child1.colors.push('green'); // 修改child1的colors
console.log(child2.colors); // ['red', 'blue', 'green'] (问题出现了!)

有什么作用?

实现属性和方法的继承。

实际开发运用场景?

在现代前端开发中,几乎不会单独使用原型链继承,因为它有致命缺陷。但它是一切其他继承方式的理论基础。

优点:

  • 简单易懂,是理解JS继承的基础。

  • 能够继承父类原型上的方法。

缺点:

  1. 引用类型属性被所有实例共享(如上例中的 colors 数组)。一个实例修改了引用类型属性,所有实例都会受到影响。这是最大的问题。

  2. 创建子类实例时,无法向父类构造函数传参(因为 new Parent() 在初始化原型时就执行了)。

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

是什么?

为了解决原型链继承的缺点,这种方法的核心是:在子类构造函数的内部调用父类构造函数。这利用了 call() 或 apply() 方法,使父类的 this 指向子类的实例。

javascript

// 父类
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
}
// 注意:父类原型上的方法子类访问不到
Parent.prototype.sayName = function() {
    console.log(this.name);
};

// 子类
function Child(name, type) {
    // 关键!“借用”父类的构造函数来初始化属性
    Parent.call(this, name); // 相当于执行了 this.name = name; this.colors = ['red', 'blue'];
    this.type = type;
}

var child1 = new Child('小明', 'Child');
var child2 = new Child('小红', 'Child');

child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
console.log(child2.colors); // ['red', 'blue'] (互不影响了!)
console.log(child1.sayName); // undefined (无法继承父类原型的方法)

有什么作用?

解决原型链继承中“引用属性共享”和“无法传参”的问题。

实际开发运用场景?

通常不会单独使用,但它是组合继承的重要组成部分。

优点:

  1. 避免了引用属性共享的问题。

  2. 可以在子类中向父类传递参数

缺点:

  1. 方法都在构造函数中定义,每次创建实例都会创建一遍方法,无法实现函数复用效率低

  2. 无法继承父类原型(prototype)上的方法(如上例中的 sayName)。

3. 组合继承

是什么?

组合继承结合了原型链继承借用构造函数继承的优点,是 JavaScript 中最常用的继承模式。其核心是:

  1. 使用借用构造函数来继承属性(解决共享和传参问题)。

  2. 使用原型链来继承方法(实现方法复用)。

javascript

// 父类
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
    console.log(this.name);
};

// 子类
function Child(name, type) {
    // 1. 继承属性
    Parent.call(this, name); // 第二次调用 Parent()
    this.type = type;
}

// 2. 继承方法
Child.prototype = new Parent(); // 第一次调用 Parent()
// 修复构造函数指向,否则Child实例的constructor会指向Parent
Child.prototype.constructor = Child;
// 子类自己的方法
Child.prototype.sayType = function() {
    console.log(this.type);
};

var child1 = new Child('小明', 'Child1');
child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
child1.sayName(); // '小明'
child1.sayType(); // 'Child1'

var child2 = new Child('小红', 'Child2');
console.log(child2.colors); // ['red', 'blue'] (属性不共享)
child2.sayName(); // '小红' (方法可复用)

有什么作用?

综合了两种模式的优点,成为 JavaScript 中一种非常实用的继承模式。

实际开发运用场景?

在 ES6 的 class 出现之前,这是最主流、最可靠的继承方式。常用于构建复杂的对象系统,如UI组件库、游戏引擎中的实体继承等。

优点:

  1. 实例拥有独立的属性,不会共享。

  2. 实例可以复用父类原型上的方法。

  3. 可以向父类构造函数传参。

缺点:

  • 最大的缺点:会两次调用父类构造函数

    • 一次在 Parent.call(this)

    • 一次在 new Parent()
      这导致子类实例和原型上存在两份相同的属性(一份在实例自身,一份在 __proto__ 里),造成了一些不必要的浪费(虽然实例自身的属性会屏蔽原型上的属性,没有功能问题)。

4. 原型式继承

是什么?

道格拉斯·克罗克福德提出的方法。其核心是:创建一个临时的构造函数,将其原型指向某个对象,然后返回这个临时构造函数的实例。本质上是对传入的对象进行了一次浅复制

javascript

// object() 就是 ES5 中 Object.create() 的模拟实现
function object(o) {
    function F() {} // 创建一个临时构造函数
    F.prototype = o; // 将其原型指向传入的对象o
    return new F(); // 返回这个临时构造函数的实例
}

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

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

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

console.log(person.friends); // ['Shelby', 'Court', 'Rob', 'Barbie'] (共享问题依然存在)

有什么作用?

在不必兴师动众地创建构造函数的情况下,基于一个已有对象创建新对象

实际开发运用场景?

  • 适用于简单对象的浅拷贝继承。

  • ES5 的 Object.create() 方法规范化了原型式继承。现在直接使用 Object.create() 即可。

优点:

  • 无需创建构造函数,代码简洁。

缺点:

  • 和原型链继承一样,存在引用属性共享的问题

5. 寄生式继承

是什么?

寄生式继承的思路与寄生构造函数工厂模式类似。其核心是:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式(如原型式继承)增强对象,最后返回这个对象

javascript

function createAnother(original) {
    var clone = object(original); // 1. 通过调用函数(如object)创建一个新对象(原型式继承)
    clone.sayHi = function() {    // 2. 以某种方式来增强这个对象
        console.log('hi');
    };
    return clone; // 3. 返回这个对象
}

var person = {
    name: 'Nicholas',
    friends: ['Shelby', 'Court']
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'hi'

有什么作用?

主要关注对象而不是自定义类型和构造函数,在主要考虑对象而非自定义类型和构造函数的情况下,实现简单的继承和扩展。

实际开发运用场景?

  • 适用于为对象添加一些额外功能的场景,但不广泛使用。

优点:

  • 可以在不创建构造函数的情况下,为对象添加函数。

缺点:

  • 函数难以复用,效率低(跟借用构造函数模式一样)。

  • 存在引用属性共享的问题(跟原型式继承一样)。

6. 寄生组合式继承

是什么?

这是组合继承的优化版本,也是目前公认的最理想的继承范式。它解决了组合继承调用两次父类构造函数的问题。

其核心是:

  1. 使用借用构造函数来继承属性

  2. 使用寄生式继承继承父类原型,并将其赋值给子类原型。

javascript

function inheritPrototype(child, parent) {
    // 1. 创建父类原型的一个副本(原型式继承)
    var prototype = Object.create(parent.prototype);
    // 2. 修复副本的constructor指针
    prototype.constructor = child;
    // 3. 将子类的原型指向这个副本
    child.prototype = prototype;
}

// 父类
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
    console.log(this.name);
};

// 子类
function Child(name, type) {
    // 只调用一次父类构造函数(继承属性)
    Parent.call(this, name);
    this.type = type;
}

// 关键!替换掉组合继承中的 `Child.prototype = new Parent()`
inheritPrototype(Child, Parent);

// 添加子类自己的方法
Child.prototype.sayType = function() {
    console.log(this.type);
};

var child1 = new Child('小明', 'Child1');
// 实例的 __proto__ 指向 Child.prototype
// Child.prototype 的 __proto__ 指向 Parent.prototype
// 完美!

有什么作用?

只调用一次父类构造函数,并且避免了在子类原型上创建不必要的、多余的属性。同时,原型链还能保持不变。

寄生组合式继承核心目标:修复“组合继承”的缺陷

要理解“寄生组合”,我们必须先回顾“组合继承”的问题。

组合继承的做法:

  1. Parent.call(this):在子类构造函数里调用父类构造函数。这会在子类实例自身上创建一份父类的属性。

  2. Child.prototype = new Parent():将子类的原型指向一个父类的实例。这会在子类的原型对象上创建第二份父类的属性。

这就导致了:

  • 子类实例上有 name 和 colors 属性。

  • 子类实例的 __proto__(也就是 Child.prototype)上也有 name 和 colors 属性。

虽然实例自身的属性会屏蔽掉原型上的同名属性,功能上没问题,但多创建了一份多余的属性,造成了内存浪费和不优雅。

寄生组合式继承的解决方案

它的核心思路非常巧妙:我们真的需要那个 new Parent() 实例来充当原型吗?不,我们只需要父类原型上的方法。

我们不需要 Parent 实例上的属性(因为我们已经通过 Parent.call(this) 在子类实例上得到了一份),我们只需要能通过原型链找到 Parent.prototype 上的方法。

所以,新的方案是:

  1. 继承属性:保持不变,依然在子类构造函数里用 Parent.call(this)子类构造函数里调用父类构造函数,这会在子类实例自身上创建一份父类的属性。这保证了每个实例都有自己独立的属性。

  2. 继承方法不再用 new Parent(),而是直接创建一个纯净的、指向父类原型的对象,用它来作为子类的原型。

这个“纯净的、指向父类原型的对象”就是 Object.create(Parent.prototype) 所做的事情。

一步步拆解(超级详细版)

让我们把 inheritPrototype(Child, Parent) 这个函数里的三步拆开来看:

javascript

function inheritPrototype(child, parent) {
    // 第一步:创建原型副本(核心)
    var prototype = Object.create(parent.prototype);
    // 第二步:修复constructor指向
    prototype.constructor = child;
    // 第三步:将子类的原型指向这个新创建的对象
    child.prototype = prototype;
}

第一步:var prototype = Object.create(parent.prototype);

  • Object.create() 方法会创建一个新对象。

  • 这个新对象的 __proto__ 会指向你传入的参数,也就是 parent.prototype

  • 想象一下:这就好比我们凭空造了一个空对象 {},并且让这个空对象“认” Parent.prototype 为它的爸爸(原型)。这个空对象自己没有任何属性(解决了属性重复的问题),但它可以顺着原型链找到 Parent.prototype 上的所有方法(sayName)

第二步:prototype.constructor = child;

  • 任何一个 prototype 对象都有一个 constructor 属性,默认指向它关联的构造函数。

  • 因为我们用 Object.create() 创建的新对象,它的 constructor 指向的是 parent(因为它继承自 parent.prototype,而 parent.prototype.constructor 指向 parent)。

  • 这显然不对,我们希望子类原型的 constructor 指向子类自己 child

  • 所以我们需要手动纠正一下,让 prototype.constructor = child;

第三步:child.prototype = prototype;

  • 最后,我们把这个我们精心制作好的、纯净的、链接到了父类原型的、constructor指正确的 prototype 对象,赋值给子类的 prototype

  • 从此,所有 new Child() 出来的实例,它们的 __proto__ 都指向我们这个 prototype 对象,从而可以顺利地通过原型链调用父类的方法。

终极比喻:“继承家产”的故事

  • 父类 (Parent):一个富豪老爹,他有金库(实例属性 namecolors)和一本生意经(原型方法 sayName)。

  • 组合继承:老爹先给你复制了一本完整的生意经(包括金库的地图),然后你又自己去金库里拿了一次金子。你手里有金子,书上也有金子的地图,地图是多余的。

  • 寄生组合继承:一个聪明的律师(Object.create)出现了。他没有复制整本生意经,而是只做了一张神奇的索引卡。这张索引卡本身是空白的(没有多余的属性),但它直接指向了老爹那本生意经的原始内容(Parent.prototype)。然后你又自己去金库里拿了一次金子。你手里有金子,也通过索引卡学会了老爹的生意经,完美!

总结:为什么它是最佳的?

特性组合继承寄生组合继承优势
父类属性副本2份 (实例上1份,原型上1份)1份 (仅在实例上)更高效,无浪费
父类方法继承通过原型链继承通过原型链继承同样有效
父类构造函数调用2次1次性能更优
原型链保持正确保持正确同样正确

所以,寄生组合式继承的核心贡献就是:
它用一种极其巧妙的方式(Object.create)建立了子类和父类原型的直接联系,完全跳过了创建父类实例这个不必要的步骤,从而避免了创建多余的属性,完美地实现了继承。这也是为什么ES6的 class 和 extends 语法糖其底层实现原理是寄生组合继承的原因,因为它确实是理论上最完美的方案

实际开发运用场景?

这是实现基于构造函数的继承的最佳模式。在需要高度优化和避免不必要的内存开销的库或框架中可能会看到。但在日常开发中,我们更倾向于使用 ES6 的 class 和 extends,其底层原理就是寄生组合式继承。

优点:

  • 只调用一次父类构造函数,效率高

  • 避免了在子类原型上创建不必要的属性。

  • 原型链保持不变,能正常使用 instanceof 和 isPrototypeOf

缺点:

  • 实现起来相对复杂。但通常可以封装成一个函数(如上面的 inheritPrototype)来复用。

总结与建议

继承方式核心思想优点缺点适用场景
原型链继承子类原型 = 父类实例简单,方法可复用引用共享,无法传参基础学习,几乎不用
借用构造函数在子类中 Parent.call(this)属性独立,可传参方法不能复用组合继承的一部分
组合继承借用构造 + 原型链属性独立,方法可复用,可传参调用两次父类构造函数ES6前的主流方式
原型式继承Object.create()无需构造函数,简单引用共享对象浅拷贝
寄生式继承工厂模式+增强对象无需构造函数,可增强对象方法不能复用,引用共享为对象添加功能
寄生组合继承借用构造 + 寄生式继承父类原型近乎完美,只调用一次父类构造函数实现稍复杂理想的继承范式

现代开发建议:

直接使用 ES6 的 class 和 extends 关键字。它们的语法更清晰、更易于理解,并且其底层实现的就是寄生组合式继承这种最理想的方式。你不再需要手动处理原型链,避免了出错的可能。

javascript

// ES6 的写法,底层是寄生组合式继承
class Parent {
    constructor(name) {
        this.name = name;
        this.colors = ['red', 'blue'];
    }
    sayName() {
        console.log(this.name);
    }
}

class Child extends Parent {
    constructor(name, type) {
        super(name); // 相当于 Parent.call(this, name)
        this.type = type;
    }
    sayType() {
        console.log(this.type);
    }
}

到此这篇关于JavaScript六种继承方式的文章就介绍到这了,更多相关JS六种继承方式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • js parseInt("08")未指定进位制问题

    js parseInt("08")未指定进位制问题

    今天在做JS关于月份的判断,对于parseInt("01")到parseInt("07");都能得到正确的结果,但如果是parseInt("08")或parseInt("09")则返回0,首先看parseInt语法:parseInt(string, radix);
    2010-06-06
  • Bootstrap 响应式实用工具实例详解

    Bootstrap 响应式实用工具实例详解

    Bootstrap 提供了一些辅助类,以便更快地实现对移动设备友好的开发。这些可以通过媒体查询结合大型、小型和中型设备,实现内容对设备的显示和隐藏。下面通过本文给大家分享Bootstrap 响应式实用工具,一起看看吧
    2017-03-03
  • Javascript之面向对象--接口

    Javascript之面向对象--接口

    本篇文章实例演示了Javascript的面向对象--接口的用法。希望对大家有所帮助,下面就随小编一起来看看吧
    2016-12-12
  • 如何写出一个惊艳面试官的JavaScript深拷贝

    如何写出一个惊艳面试官的JavaScript深拷贝

    浅拷贝是面试中经常会被问到的问题,这篇文章主要给大家介绍了关于如何写出一个惊艳面试官的JavaScript深拷贝的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • JS 连接MQTT的使用方法

    JS 连接MQTT的使用方法

    这篇文章主要介绍了JS 连接MQTT的使用方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-01-01
  • javascript天然的迭代器

    javascript天然的迭代器

    有一个数n=5,不用for循环,怎么返回[1,2,3,4,5]这样一个数组
    2010-10-10
  • javascript特殊日历控件分享

    javascript特殊日历控件分享

    这篇文章主要为大家详细介绍了javascript特殊日历控件的使用方法,展示了javascript日历控件实现效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-03-03
  • footer定位页面底部(代码分享)

    footer定位页面底部(代码分享)

    本文主要分享了footer定位页面底部的实例代码,具有很好的参考价值。下面跟着小编一起来看下吧
    2017-03-03
  • 将input框中输入内容显示在相应的div中【三种方法可选】

    将input框中输入内容显示在相应的div中【三种方法可选】

    本篇文章主要介绍了在input框中输入内容,会相应的显示在下面的div中的不同做法:js方法;jQuery方法;AngularJs方法,具有很好的参考价值。下面跟着小编一起来看下吧
    2017-05-05
  • Js数组排序函数sort()介绍

    Js数组排序函数sort()介绍

    本文给大家简单探讨下Js数组排序函数sort()的用法和示例,有需要的小伙伴可以参考下。
    2015-06-06

最新评论