JavaScript 中的这些继承方式,你弄懂了吗

本文深入探讨了JavaScript中的继承实现方式,包括原型链、盗用构造函数、组合继承、原型式继承、寄生式继承和寄生组合继承。通过各种示例,解释了每种方法的优缺点,如共享问题、传参问题等,并提出了最佳实践——寄生组合继承,以避免重复调用父类构造函数的问题。此外,文章还提到了ES6的类继承作为上述继承方式的语法糖。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面试中我们经常会被问到继承,希望通过此文,你能彻底搞懂 JavaScript 中的继承原理。

前言

ES6 以前,JavaScript 中的继承不像其它 oo 语言一样,用特定 class 去实现,它是由构造函数和原型去模拟,下面我们会介绍几种常见的继承方法以及对应的优点和不足。

原型链

什么是原型链?

比如我有一个构造函数,这个构造函数的实例有一个内部指针[[Prototype]]指向构造函数的原型,然后这个构造函数的原型又是另一个构造函数的实例,也就是说这个构造函数原型有一个内部指针[[Prototype]]指向另一个构造函数的原型,如此下去,就构成了一条原型链。那用原型链实现继承用代码表示出来就是这样:

function Parent () {
    this.name = 'Twittytop';
}
Parent.prototype.getName = function () {
    return this.name;
}
function Child () {
    this.age = 29;
}
// 继承
Child.prototype = new Parent();
var ins = new Child();
console.log(ins.getName()); // Twittytop

这样原来在 Parent 上的属性都变成了 Child.prototype 上的属性。

在这里插入图片描述

问题

第一:共享问题

当 Parent 上包含有引用属性时,就出出现问题,比如:

function Parent () {
    this.friends = ['Jack', 'Tom'];
}
function Child () {
    this.age = 29;
}
// 继承
Child.prototype = new Parent();
var ins1 = new Child();
ins1.friends.push('Bob');
var ins2 = new Child();
console.log(ins2.friends); // ["Jack", "Tom", "Bob"]

因为继承之后变成了 Child 的原型属性,所以所有 Child 的实例都指向的是同一个 friends,当其中一个实例修改了这个值之后,变化就会反映到所有实例上。

第二: 传参问题

Child 在实例化是没法向 Parent 传参,当 Parent 依赖外部传参时,就会导致问题。

盗用构造函数

function Parent (name) {
    this.name = name;
}
Parent.prototype.getName = function () {
    return this.name;
}
function Child (name, age) {
    // 继承
    Parent.call(this, name);
    this.age = age;
}
var ins = new Child('Twittytop', 29);
console.log(ins.name); // Twittytop
console.log(ins.getName); // undefined

可以看到,盗用构造函数的优点是能传递参数,问题是它只能继承实例属性,不能继承原型属性。

组合继承

既然原型链和盗用构造函数继承都有各自的缺点,那我们能不能把这两者结合起来呢?这就是组合继承。

function Parent (name) {
    this.name = name;
}
Parent.prototype.getName = function () {
    return this.name;
}
function Child (name, age) {
    // 继承实例属性
    Parent.call(this, name);
    this.age = age;
}
// 继承原型属性
Child.prototype = new Parent();
var ins = new Child('Twittytop', 29);
console.log(ins.name); // Twittytop
console.log(ins.getName()); // Twittytop

组合继承弥补了原型链和盗用构造函数的不足,能同时继承实例属性和原型属性,但它的缺点是会调用两次父类构造函数。一次是在 Child 构造函数中执行 Parent.call,一次是在实例化 Parent 时。这样就会导致 Child 的不仅自身实例上有 name 属性,原型上也有 name 属性,导致了不必要的多余继承。用图表示如下:

在这里插入图片描述

原型式继承

function Parent (name) {
    this.name = 'Twittytop';
}
Parent.prototype.getName = function () {
    return this.name;
}
function Child (age) {
    this.age = age;
}
// 继承原型属性
Child.prototype = Object.create(Parent.prototype);
var ins = new Child(29);
console.log(ins.getName);

原型式继承只继承了原型上的属性,没有继承实例属性,相比原型链继承更干净,它没有把父类的实例属性继承到自身的原型上面,当然,它和原型链一样,也会有引用属性的共享问题。

寄生式继承

寄生式继承是建立在原型式继承基础上的,寄生式继承用代码表达出来是这样:

function inherit (Parent) {
    let pro = Object.create(Parent.prototype);
    pro.myMethod = function () {};
    return pro;
}

它相比原型式继承多了添加一些自己的属性和方法。

寄生式组合继承

寄生式组合继承综合了盗用构造函数和寄生式继承,它使用盗用构造函数继承实例属性,使用寄生式继承继承原型属性。

function inherit (Child, Parent) {
    let pro = Object.create(Parent.prototype);
    pro.constructor = Child; // 将constructor重新指回Child
    Child.prototype = pro;
}
function Parent (name) {
    this.name = name;
}
Parent.prototype.getName = function () {
    return this.name;
}
function Child (name, age) {
    // 继承实例属性
    Parent.call(this, name);
    this.age = age;
}
// 继承原型属性
inherit(Child, Parent)
var ins = new Child('Twittytop', 29);
console.log(ins.name); // Twittytop
console.log(ins.getName()); // Twittytop

寄生式组合继承吸取了盗用构造函数和寄生式继承的优点,又没有组合继承中调用父类构造函数两次的不足,是ES5 实现继承的最佳模式。

关于 ES6 的继承,这里就不介绍了,它本质是上述继承的语法糖而已。

写在后面

JavaScript 继承独特的地方就是它的原型,如果这篇文章能让你对 JavaScript 继承有进一步的了解,那将是我最大的欣慰。如果你觉得能学到一点东西的话,还请动动你可爱的小指让更多人看到。如果有错误或者有疑问的地方,也欢迎交流讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值