携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 29 天,点击查看活动详情

问题描述

继承 是基于面向对象的,使用 继承 可以让我们更好的复用以前开发的代码,缩短开发的周期、提升开发的效率。 继承 在各种语言中都充当着至关重要的角色,尤其是在 JavaScript 中,它天生的灵活性,使运用场景更加的丰富。 JavaScript 中的继承也经常用于一些组件库底层的搭建,在 JavaScript 的学习中也尤为重要。今天我们就一起来盘点一下 JavaScript 中常见的继承方式吧!

首先我们先来思考几个问题,带着问题来学习,这样可以加深我们学习的记忆。

问题1:JS 中的继承到底有多少种实现方式呢?

问题2:ES6 中的 extends 关键字是用哪种继承方式实现的呢?

关于继承的探究

继承 简单来说,就是有一个父类,生活中常见的案例就是汽车,它包含颜色、轮胎、品牌、速度、排气量等等,然后基于汽车这个父类,它又可以生产出轿车、货车、大巴车,这些车型都是基于汽车这个父类生成的。总的来说, 继承 可以使子类具有父类的各种属性和方法,下面我们就一起来学习一下 JavaScript 中的几种继承方式。

原型链继承

原型链继承 是比较常见的继承方式之一,其中涉及的构造函数、原型和实例三者之间存在着紧密的联系,也就是每个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针。下面我们一起通过代码来了解一下,如下:

function Parent() {
this.name = 'parent';
this.list = [1, 2, 3];
} function Child() {
this.type = 'child';
} Child.prototype = new Parent(); console.log(new Child());

上述的代码中,看起来没有什么问题,都可以正常的执行,但是父类和子类都有一个潜在的问题,那就是它们都指向同一个原型对象,内存空间是共享的,当一个对象发生改变时,另外一个对象也会随之改变,这就是使用原型链继承的一个缺点。为了解决这个问题,我们就需要继续学习其它的继承方式。

构造函数继承

在前面的 原型链继承 中,我们知道了它的缺陷,那么有什么办法可以规避这个缺陷呢?答案是使用 构造函数继承,我们还是使用之前的代码,通过修改相关的内容来解决这个问题,代码如下:

function Parent() {
this.name = 'parent';
this.list = [1, 2, 3];
} Parent.prototype.getName = function () {
return this.name;
}; function Child() {
Parent.call(this);
this.type = 'child';
} const child = new Child();
console.log(child); // 正常运行
console.log(child.getName()); // 会报错,因为子类中不能直接访问父类原型中的方法

上述的代码中,虽然子类可以继承父类的属性,但是当父类原型中一旦存在相关的原型方法,子类就无法继承这些方法,因此在子类中就无法使用父类的getName构造函数继承 能使父类的引用属性不会被共享,优化了 原型链继承 中的弊端,但随之而来的缺点也比较明显,就是子类不能继承父类原型中的属性和方法,针对上面这两种继承的方式,又产生了第三种继承方式,这种继承方式结合了前两种继承方式的优缺点。

组合继承

原型链继承 和 构造函数继承 它们的优缺点都非常明显,因此基于这两种继承就出现了第三种继承 -- 组合继承。它主要是为了解决父类引用属性被共享,以及子类无法继承父类原型中属性和方法的问题,下面我们一起来看代码,如下:

function Parent() {
this.name = 'parent';
this.list = [1, 2, 3];
} Parent.prototype.getName = function () {
return this.name;
}; function Child() {
Parent.call(this);
this.type = 'child';
} Child.prototype = new Parent();
// 原型对象指向自己的构造函数
Child.prototype.constructor = Child; const child = new Child();
const child2 = new Child();
child.list.push(4);
console.log(child.list, child2.list); // 互不影响
console.log(child.getName()); // 正常输出 parent
console.log(child2.getName()); // 正常输出 parent

执行上述代码后,可以在控制台中看到它们的执行结果,我们发现在 原型链继承 和 构造函数继承 中存在的问题都已经解决了,但是这里又产生了一个新的问题,通过上述代码,我们可以明显发现 Parent 执行了两次,第一次是将子类原型指向父类的的时候,第二次这是在子类中通过 call 调用父类。当父类每多执行一次的时候,就会多产生一份性能的开销。那么是否有更好的办法来解决这个问题呢?让我们接着来看下一个继承方式。

原型式继承

说到 原型式继承 就不得不提 ES5 中的 Object.create 方法,这个方法接收两个参数。第一个参数是用作于新对象原型的对象,第二个参数是可选参数,主要是给新对象定义额外属性的对象,让我们一起来看一段代码,看一下普通对象是如何实现继承的,代码如下:

const parent = {
namn: 'parent',
children: ['zhangsan', 'lisi'],
getName: function () {
return this.name;
}
}; let person = Object.create(parent);
person.name = 'son';
person.children.push('wangwu'); let person2 = Object.create(parent);
person2.children.push('zhaoliu'); console.log(person.name);
console.log(person.name === person.getName()); console.log(person2.name); console.log(person.children);
console.log(person2.children);

在上述代码中,我们可以看到普通对象中子类通过 Object.create 实现继承,不仅能够继承父类的属性,也能继承父类的方法,但是这种实现方式也有缺点,那就是多个实例对象的引用,类型属性指向相同的内存地址,存在数据被篡改的可能,因此基于这种实现方式,我们可以得到一个新的继承方法 -- 寄生式继承 。

寄生式继承

使用 原型式继承 可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些相关的方法,这样的继承方法叫做 寄生式继承。虽然优缺点跟 原型式继承 一样,但对于普通对象来说, 寄生式继承 相比于 原型式继承 还是在父类的基础上添加了更多的方法,下面我们一起看一下代码是怎么实现的,如下:

const parent = {
name: 'parent',
children: ['zhangsan', 'lisi'],
getName: function () {
return this.name;
}
}; function clone (targetObj) {
let clone = Object.create(targetObj);
clone.getChildren = function () {
return this.children;
};
return clone;
} const person = clone(parent); console.log(person.getName());
console.log(person.getChildren());

通过上述的代码,我们可以看到 person 是通过 寄生式继承 实现的,生成的实例不仅有 getName 方法,我们自己添加的 getChildren 方法也被继承到子类,并能够正确执行。在前面第三种继承中的实现方式造成父类被两次调用造成浪费,通过 寄生式继承 就能很好的解决这个问题。结合前面这几种继承方法,我们继续学习最后一种最优的继承方式 -- 寄生组合式继承 。

寄生组合式继承

基于前面几种继承方式的优缺点,我们得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式,让我们一起通过代码来学习一下,代码如下:

function clone (target, source) {
source.prototype = Object.create(target.prototype);
source.prototype.constructor = source;
} function Parent () {
this.name = 'parent';
this.children = ['zhangsan', 'lisi'];
}
Parent.prototype.getName = function () {
return this.name;
}; function Child () {
Parent.call(this);
this.children = 'wangwu';
} clone(Parent, Child); Child.prototype.getChildren = function () {
return this.children;
}; const person = new Child();
console.log(person);
console.log(person.getName());
console.log(person.getChildren());

通过上述代码,我们发现 寄生组合式继承 能够解决前几种继承方式的缺点,较好的实现了继承想要的结果,同时也减少了构造函数的次数,并减少了性能的开销。

ES6 - extends

我们使用 es6 中的 extends 关键词很容易就能实现 JavaScript 的继承,但是如果想要深入了解 extends 语法糖是怎么实现的,就得深入研究 extends 的底层逻辑,我们先看一下使用 extends 是怎么实现继承的,代码如下:

class Person {
constructor (name) {
this.name = name;
} getName() {
return this.name;
}
} class Child extends Person {
constructor (name, age) {
super(name);
this.age = age;
}
} const child = new Child('zhangsan', 18);
console.log(child.getName());

上述的代码通过 babel 转换后,最终的实现方法还是基于我们前面的 寄生组合式继承,因此也证明了 寄生组合式继承 是目前最优的解决方式。

最后

我们可以回答一下前面的两个问题了。

第一个问题:JS 中的继承到底有多少种实现方式呢?答:总共是6种,分别是 原型链继承 、构造函数继承 、组合继承 、原型式继承 、寄生式继承 、寄生组合式继承

第二个问题:ES6 中的 extends 关键字是用哪种继承方式实现的呢?答:extends 是通过 寄生组合式继承 实现的,它只是一种 es6 中的语法糖。

通过对 JavaScript 继承方式的探讨,不仅能够让我们加深对 js 的理解,也能为我们后续去学习其它内容打下坚实的基础。

总结给大家推荐一个实用面试题库

 1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

2、前端技术导航大全      推荐:★★★★★

地址:前端技术导航大全

3、开发者颜色值转换工具   推荐:★★★★★

地址 :开发者颜色值转换工具

【面试题】JS 中这些继承方式你知道吗?的更多相关文章

  1. JS中对象继承方式

    JS对象继承方式 摘自<JavaScript的对象继承方式,有几种写法>,作者:peakedness 链接:https://my.oschina.net/u/3970421/blog/28 ...

  2. JS中的继承方式总结

    1. 原型链继承(又称类继承) Child.prototype = new Parent(); function Parent (name, age) { this.name = name; this ...

  3. js中常见继承方式

    1.原型模式 function Father(){ this.property = true; } Father.prototype.getValue = function(){ return thi ...

  4. js中的继承和重载

      js中有三种继承方式:一.通过原型(prototype)实现继承 二.借用构造函数式继承,可分为通过call()方法实现继承和通过apply()方法实现继承 仅仅通过原型继承我们可以发现在实例化子 ...

  5. js中实现继承的几种方式

    首先我们了解,js中的继承是主要是由原型链实现的.那么什么是原型链呢? 由于每个实例中都有一个指向原型对象的指针,如果一个对象的原型对象,是另一个构造函数的实例,这个对象的原型对象就会指向另一个对象的 ...

  6. 【学习笔记】六:面向对象的程序设计——理解JS中的对象属性、创建对象、JS中的继承

    ES中没有类的概念,这也使其对象和其他语言中的对象有所不同,ES中定义对象为:“无序属性的集合,其属性包含基本值.对象或者函数”.现在常用的创建单个对象的方法为对象字面量形式.在常见多个对象时,使用工 ...

  7. JS中的继承(上)

    JS中的继承(上) 学过java或者c#之类语言的同学,应该会对js的继承感到很困惑--不要问我怎么知道的,js的继承主要是基于原型(prototype)的,对js的原型感兴趣的同学,可以了解一下我之 ...

  8. JS中的继承(下)

    JS中的继承(下) 在上一篇 JS中的继承(上) 我们介绍了3种比较常用的js继承方法,如果你没看过,那么建议你先看一下,因为接下来要写的内容, 是建立在此基础上的.另外本文作为我个人的读书笔记,才疏 ...

  9. JS中写继承的方式

    有父子两个函数,代表两个类: var parent = function(){} var child = function(){} 一.直接继承 child.prototype = new paren ...

  10. JS中的继承实现方式

    第一种:通过prototype来实现 prototype.html <!DOCTYPE html><html lang="en"><head> ...

随机推荐

  1. VSCode运行C/C++配置

    将MinGw安装目录下的 1.安装 VSCode 2.安装 MinGW 链接:点击跳转 3.MinGW 内安装两个模块 1.右键 Mark for Installation 勾选 (此处已安装好,所以 ...

  2. 《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(3)-再识Charles

    1.简介 上一篇通过宏哥的介绍想必各位小伙伴或者童鞋们对Charles已经有了一个理性地认识,今天宏哥在从Charles的外貌介绍和分享一下,让小伙伴们或者童鞋们再对Charles有一个感性的认识,今 ...

  3. 算法学习笔记(9): 中国剩余定理(CRT)以及其扩展(EXCRT)

    扩展中国剩余定理 讲解扩展之前,我们先叙述一下普通的中国剩余定理 中国剩余定理 中国剩余定理通过一种非常精巧的构造求出了一个可行解 但是毕竟是构造,所以相对较复杂 \[\begin{cases} x ...

  4. Redefinition of 'y1' as different kind of symbol

    Redefinition of 'y1' as different kind of symbol 原因 解释:此次定义的y1变量与函数库中定义的y1重名了,所以编译错误,重定义了y1变量. 解决方法: ...

  5. echarts系列-带图教你调整左右位置x轴样式网格虚线刻度居中双轴Y轴滚动上下移动文字旋转改分割线颜色部分字体改色折注混合,X轴的颜色,X轴字体颜色,调整柱子颜色,调整小图标图例的大小和位置,鼠标hover时候的样式,用纵向阴影

    上面先说注意事项 1.如果使用show hidden控制图表显示隐藏,某些切换效果很奇怪,比如饼图,会从左上角开始放大,很丑,这个时候我们可以设置其宽高来解决问题,给其设置宽高后,切换的奇怪效果即可消 ...

  6. 在Spring Boot中整合Katharsis,来快速开发JSON API的Web应用

    1 简介 我们进行Web API开发的时候,经常会使用Json格式的消息体,而Json格式非常灵活,不同的人会有不同的设计风格和实现,而JSON API提供了一套标准.但它并不提供直接实现. Kath ...

  7. 基于NOSTR协议的“公有制”版本的Twitter,去中心化社交软件Damus用后感,一个极端走向另一个极端

    最近,一个幽灵,Web3的幽灵,在网络游荡,它叫Damus,这玩意诠释了什么叫做病毒式营销,滑稽的是,一个Web3产品却在Web2的产品链上疯狂传销,各方大佬纷纷为其背书,到底发生了什么?Damus的 ...

  8. 【Oculus Interaction SDK】(二)抓取释放效果的物理优化

    前言 这篇文章是[Oculus Interaction SDK]系列的一部分,如果发现有对不上的对方,可以回去翻看我之前发布的文章,或在评论区留言.如果文章的内容已经不适用于新版本了,也可以直接联系我 ...

  9. Grafana 系列文章(十二):如何使用Loki创建一个用于搜索日志的Grafana仪表板

    概述 创建一个简单的 Grafana 仪表板, 以实现对日志的快速搜索. 有经验的直接用 Grafana 的 Explore 功能就可以了. 但是对于没有经验的人, 他们如何能有一个已经预设了简单的标 ...

  10. Linux安装KingbaseES数据库 Unsupported major.minor version错误处理

    Linux系统安装V008R006C007B0012版本KingbaseES数据库报错:Unsupported major.minor version 52.0 系统版本: [root@vm-10-3 ...