前言

第一次接触到 ES6 中的 class 和 extends 时,就听人说这两个关键字不过是语法糖而已。它们的本质还是 ES3 的构造函数,原型链那些东西,没有什么新鲜的,只要理解了原型链等这些概念自然就明白了。这话说的没错,但是这些继承的实现是否是我们想的那样呢,今天让我们来用原型链解释下 ES6 extends 如何实现的继承。

结论

这里先上结论,如果有理解不对的地方,欢迎在留言指出;如果有不理解的地方可以看完结论后继续阅读,如果阅读完后有难以理解指出也欢迎留言讨论。

  1. extends 的继承通过两种方式完成了三类值的继承
  2. 构造函数设置的属性通过复制完成继承
  3. 实例方法通过实例原型之间的原型链完成继承
  4. 构造函数的静态方法通过构造函数之间的原型链完成继承

属性通过复制完成继承

class 实例对象属性的继承是通过复制达到继承效果的,这里的属性指的是通过构造函数的 this 定义的属性。

class Person {
constructor(name) {
this.maxage = 100;
this.name = name
}
}
class Programmer extends Person {
constructor(name) {
super(name);
this.job = 'coding'
}
}
const personA = new Person('xiaoming')
const programmerB = new Programmer('xiaozhi') console.log(personA.hasOwnProperty('maxage'));
console.log(programmerB.hasOwnProperty('maxage'));

以上代码的打印结果都是true,这个结果就证明了对象的 extends 继承的属性是通过复制继承的,而不是通过原型链完成的属性继承。

我们将以上代码中得到的两个实例对象打印出来,可以得到如下图结果

根据打印结果可以更直观的看到两个实例对象上均有 maxage 属性。原始类型的值的复制好理解,直接拷贝值就好,那么引用类型的复制是深拷贝,还是浅拷贝,或者说仅仅是对象引用的拷贝呢?

构造函数对象值的继承,比想象中要复杂一点,根据代码实践(暂未查看标准),得出结论,引用类型的继承主要分为两种情况:

  1. 字面量定义的对象属性是深拷贝
  2. 变量赋值对象属性是引用复制

字面量定义的对象属性是深拷贝

这里的字面量定义的对象属性指的是,指的是直接在构造函数中通过 {} 的形式定义的对象直接赋值给 this 的某个属性。代码形如

class A {
constructor() {
this.obj = {
name: 'obj',
secondObj: {
name: 'secondObj'
}
}
}
}

示例代码中,obj 属性是直接通过 {} 定义的一个对象。

class Person {
constructor(name) {
this.maxage = 100;
this.name = name;
this.obj = {
name: 'obj',
secondObj: {
name: 'secondObj'
}
};
}
eat() {
console.log('eat food')
}
}
class Programmer extends Person {
constructor(name) {
super(name);
this.job = 'coding'
}
coding() {
console.log('coding world')
}
} const personA = new Person('xiaoming')
const programmerB = new Programmer('xiaohei')
console.log(personA.obj === programmerB.obj)
console.log(personA.obj.secondObj === programmerB.obj.secondObj)

上述代码的运行结果如下

Person 的实例对象上定义了一个 obj 属性,该属性被 Programmer 的实例对象继承,通过对比这两个属性的值,我们知道他们并不相等,这首先排除了是引用复制的可能(如果是引用复制,这里两个属性应该指向同一个对象,也就是其存储的内存地址应该是致的,但是这里得到的结果应该是等式不成立)。通过实例对象属性 obj 中的 secondObj 属性的比较,排除了这是浅拷贝,由此我们可以得出在代码示例的场景中引用类型的继承是通过深拷贝完成的。

变量赋值对象属性是引用复制

按理来说我们得出上一小节的结论应该基本就可以确定 extends 继承是如何处理引用类型的值的继承了,但是事实是到这里并没有结束。

考虑如下代码,这段代码和上一小节的代码区别不大,有变化的地方是,这是在外部定义了一个变量,变量的值是对象,然后将变量赋值给了了 obj 属性。

let obj = {
name: 'obj'
}
class Person {
constructor(name) {
this.maxage = 100;
this.name = name;
this.obj = obj;
}
eat() {
console.log('eat food')
}
}
class Programmer extends Person {
constructor(name) {
super(name);
this.job = 'coding'
}
coding() {
console.log('coding world')
}
} const personA = new Person('xiaoming')
const programmerB = new Programmer('xiaohei')
console.log(personA.obj === programmerB.obj);

运行结果如下

从代码运行结果中不难看出,这里出现了变化,通过变量赋值定义的对象属性,是通过引用复制完成继承的。下面我们来看看对象变量被定义在构造函数中然后再赋值给对象的属性是否还是这样的结果。

class Person {
constructor(name) {
const innerObj = {
name: 'obj'
}
this.maxage = 100;
this.name = name;
this.obj = innerObj;
}
eat() {
console.log('eat food')
}
}
class Programmer extends Person {
constructor(name) {
super(name);
this.job = 'coding'
}
coding() {
console.log('coding world')
}
} const personA = new Person('xiaoming')
const programmerB = new Programmer('xiaohei')
console.log(personA.obj === programmerB.obj);

运行结果如下

没错当你把变量定义在构造函数中,然后来赋给 this 的属性的时候,是通过深拷贝来继承的。神奇不,同样是变量,只是变量定义的作用域不一样,连继承方式都变了,具体为什么要这么做,我现在还不太清楚,改日查下标准,有知道的同学还望评论区不吝赐教。

小结

这节有点长,需要个小结总结下我们得到的结论。首先,extends 的构造函数定义的属性值的继承是通过复制继承的。第二点,副职的方式主要分为以下几重情形:

  1. 原始类型直接复制值到子类对象
  2. 引用类型的值如果值是直接在构造函数中定义的(包括字面量直接赋值给属性和在构造函数内定义的变量然后变量赋值给属性),那么其会被深拷贝到子类对象上
  3. 在构造函数外定义的变量,其值是引用类型,构造函数中将该变量赋值给对象的某个属性,该属性会被通过引用复制的方式拷贝到子类对象上

实例方法的继承

实例方法的继承比较好理解,通过原型链原型链继承的,只不过这个链的形式是一个比较直接的链。这条链的大概就像下面这个图

没错,图上那条红色的线就是 programmerB 这个实例对象继承 eat 方法的方式,是不是和想的不一样。这条链还是比较好理解的,具有继承关系的构造函数的 prototype 之间有一条原型链,而每个实例对象的原型又是其构造函数的 prototype,这样一来就产生了图中红色线条实例方法的原型链。两个 class 的实例对象之间没有什么关系。

如果对上面的图存在疑问运行下面这段代码,运行结果会证明图是没有问题的。

class Person {
constructor(name) {
const innerObj = {
name: 'obj'
}
this.maxage = 100;
this.name = name;
this.obj = innerObj;
}
eat() {
console.log('eat food')
}
}
class Programmer extends Person {
constructor(name) {
super(name);
this.job = 'coding'
}
coding() {
console.log('coding world')
}
} const personA = new Person('xiaoming')
const programmerB = new Programmer('xiaohei')
console.log(personA.__proto__ === Person.prototype);
console.log(programmerB.__proto__ === Programmer.prototype);
console.log(Programmer.prototype.__proto__ === Person.prototype);
console.log(programmerB.__proto__.__proto__ === Person.prototype);

静态方法的继承

相比实例方法的继承,静态方法的继承要简单的多,就是一条简单的原型链,具有继承关系的两个 class 之间存在一条原型链。如下图这样

这个关系图就没什么多说了,有疑问的同学可以随便写段验证下。

继承关系图

这里假定 class B extends A,那么关于原型 class 之间的原型继承可得出如下等式。

B.proto === A

b.proto === B.prototype

a.proto === A.prototype

B.prototype.proto === A.prototype

b.proto.proto === A.prototype

用关系图来表达上面的这些等式会更容易理解

转载请注明出处!

通过原型继承理解ES6 extends 如何实现继承的更多相关文章

  1. 前端知识体系:JavaScript基础-原型和原型链-理解 es6 中class构造以及继承的底层实现原理

    理解 es6 中class构造以及继承的底层实现原理 原文链接:https://blog.csdn.net/qq_34149805/article/details/86105123 1.ES6 cla ...

  2. 【ES6】更易于继承的类语法

    和其它面向对象编程语言一样,ES6 正式定义了 class 类以及 extend 继承语法糖,并且支持静态.派生.抽象.迭代.单例等,而且根据 ES6 的新特性衍生出很多有趣的用法. 一.类的基本定义 ...

  3. 深入理解es6(下)

    一.symbol javascript基本数据类型: null.undefined.number.boolean.string.symbol ES6 引入了一种新的原始数据类型Symbol,表示独一无 ...

  4. extends:类似于java中的继承特征,extends="struts-default"

    extends:类似于java中的继承特征,extends="struts-default"就是继承struts-default.xml,它里面定义了许多跳转类型.拦截器等一些常用 ...

  5. 关于JS对象原型prototype与继承,ES6的class和extends · kesheng's personal blog

    传统方式:通过function关键字来定义一个对象类型 1234567891011 function People(name) { this.name = name}People.prototype. ...

  6. ES6 extends继承及super使用读书笔记

    extends 继承 extends 实现子类的继承 super() 表示父类的构造函数, 子类必须在 constructor中调用父类的方法,负责会报错. 子类的 this 是父类构造出来的, 再在 ...

  7. javascript实现继承3种方式: 原型继承、借用构造函数继承、组合继承,模拟extends方法继承

    javascript中实现继承的三种方式:原型继承.借用构造函数继承.混合继承: /* js当中的继承 js中 构造函数 原型对象 实力对象的关系: 1 构造函数.prototype = 原型对象 2 ...

  8. js原型链理解(2)--原型链继承

    1.原型链继承 2.constructor stealing(构造借用) 3.组合继承 js中的原型链继承,运用的js原型链中的__proto__. function Super(){ this.se ...

  9. 通过一段代码理解es6继承;

    class animal{ constructor(props){ this.name = 'xiaoniao' || props.name } eat(){ console.log(this.nam ...

随机推荐

  1. Appium+python自动化(二十九)- 模拟手指在手机上多线多点作战 - 多点触控(超详解)

    简介 在网页中我们经常使用缩放操作来便利的查看具体的信息,在appium中使用MultiAction多点触控的类来实现.MultiAction是多点触控的类,可以模拟用户多点操作.主要包含加载add( ...

  2. axios配置请求头content-type

    现在前端开发中需要通过Ajax发送请求获取后端数据是很普遍的一件事情了,鉴于我平时在撸码中用的是vue技术栈,今天这里来谈谈我们常用的发Ajax请求的一个插件—axios.> 现在网上可能发送A ...

  3. node 删除和复制文件或文件夹

    [toc] 创建时间:2019-08-12 注意:在win10,v10.16.1 环境运行无问题 首先引入相关包(会在使用处具体说明): const fs = require('fs') const ...

  4. Kafka集群配置---Windows版

    Kafka是一种高吞吐量的分布式发布订阅的消息队列系统,Kafka对消息进行保存时是通过tipic进行分组的.今天我们仅实现Kafka集群的配置.理论的抽空在聊 前言 最近研究kafka,发现网上很多 ...

  5. 谈谈我对Ext的认识,元芳,你怎么看

    实用Ext第一步当然是引用jar包啦. 下载地址 在页面上加上div用于显示这也是必须的 <div id='loginpanel' ></div> 在js中我们肯定需要将Ext ...

  6. 《深入理解Java虚拟机》- Java虚拟机是如何加载Java类的?

    Java虚拟机是如何加载Java类的?  这个问题也就是面试常问到的Java类加载机制.在年初面试百战之后,菜鸟喜鹊也是能把这流程倒背如流啊!但是,也只是字面上的背诵,根本就是像上学时背书考试一样. ...

  7. ZooKeeper系列(三)—— Zookeeper 常用 Shell 命令

    一.节点增删改查 1.1 启动服务和连接服务 # 启动服务 bin/zkServer.sh start #连接服务 不指定服务地址则默认连接到localhost:2181 zkCli.sh -serv ...

  8. 解决Springboot整合ActiveMQ发送和接收topic消息的问题

    环境搭建 1.创建maven项目(jar) 2.pom.xml添加依赖 <parent> <groupId>org.springframework.boot</group ...

  9. 【JS档案揭秘】第二集 Event loop与执行栈

    我时常在思考关于JS的很多知识在工作中有什么用?是否只能存在于面试这种理论性的东西中,对于我们的业务和工作,它们又能扮演怎样的角色.以后在JS档案揭秘的每一期里,都会加入我对于业务的思考,让这些知识不 ...

  10. 图解Java数据结构之队列

    本篇文章,将对队列进行一个深入的解析. 使用场景 队列在日常生活中十分常见,例如:银行排队办理业务.食堂排队打饭等等,这些都是队列的应用.那么队列有什么特点呢? 我们知道排队的原则就是先来后到,排在前 ...