通过原型继承理解ES6 extends 如何实现继承
前言
第一次接触到 ES6 中的 class 和 extends 时,就听人说这两个关键字不过是语法糖而已。它们的本质还是 ES3 的构造函数,原型链那些东西,没有什么新鲜的,只要理解了原型链等这些概念自然就明白了。这话说的没错,但是这些继承的实现是否是我们想的那样呢,今天让我们来用原型链解释下 ES6 extends 如何实现的继承。
结论
这里先上结论,如果有理解不对的地方,欢迎在留言指出;如果有不理解的地方可以看完结论后继续阅读,如果阅读完后有难以理解指出也欢迎留言讨论。
- extends 的继承通过两种方式完成了三类值的继承
- 构造函数设置的属性通过复制完成继承
- 实例方法通过实例原型之间的原型链完成继承
- 构造函数的静态方法通过构造函数之间的原型链完成继承
属性通过复制完成继承
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 属性。原始类型的值的复制好理解,直接拷贝值就好,那么引用类型的复制是深拷贝,还是浅拷贝,或者说仅仅是对象引用的拷贝呢?
构造函数对象值的继承,比想象中要复杂一点,根据代码实践(暂未查看标准),得出结论,引用类型的继承主要分为两种情况:
- 字面量定义的对象属性是深拷贝
- 变量赋值对象属性是引用复制
字面量定义的对象属性是深拷贝
这里的字面量定义的对象属性指的是,指的是直接在构造函数中通过 {} 的形式定义的对象直接赋值给 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 的构造函数定义的属性值的继承是通过复制继承的。第二点,副职的方式主要分为以下几重情形:
- 原始类型直接复制值到子类对象
- 引用类型的值如果值是直接在构造函数中定义的(包括字面量直接赋值给属性和在构造函数内定义的变量然后变量赋值给属性),那么其会被深拷贝到子类对象上
- 在构造函数外定义的变量,其值是引用类型,构造函数中将该变量赋值给对象的某个属性,该属性会被通过引用复制的方式拷贝到子类对象上
实例方法的继承
实例方法的继承比较好理解,通过原型链原型链继承的,只不过这个链的形式是一个比较直接的链。这条链的大概就像下面这个图
没错,图上那条红色的线就是 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 如何实现继承的更多相关文章
- 前端知识体系:JavaScript基础-原型和原型链-理解 es6 中class构造以及继承的底层实现原理
理解 es6 中class构造以及继承的底层实现原理 原文链接:https://blog.csdn.net/qq_34149805/article/details/86105123 1.ES6 cla ...
- 【ES6】更易于继承的类语法
和其它面向对象编程语言一样,ES6 正式定义了 class 类以及 extend 继承语法糖,并且支持静态.派生.抽象.迭代.单例等,而且根据 ES6 的新特性衍生出很多有趣的用法. 一.类的基本定义 ...
- 深入理解es6(下)
一.symbol javascript基本数据类型: null.undefined.number.boolean.string.symbol ES6 引入了一种新的原始数据类型Symbol,表示独一无 ...
- extends:类似于java中的继承特征,extends="struts-default"
extends:类似于java中的继承特征,extends="struts-default"就是继承struts-default.xml,它里面定义了许多跳转类型.拦截器等一些常用 ...
- 关于JS对象原型prototype与继承,ES6的class和extends · kesheng's personal blog
传统方式:通过function关键字来定义一个对象类型 1234567891011 function People(name) { this.name = name}People.prototype. ...
- ES6 extends继承及super使用读书笔记
extends 继承 extends 实现子类的继承 super() 表示父类的构造函数, 子类必须在 constructor中调用父类的方法,负责会报错. 子类的 this 是父类构造出来的, 再在 ...
- javascript实现继承3种方式: 原型继承、借用构造函数继承、组合继承,模拟extends方法继承
javascript中实现继承的三种方式:原型继承.借用构造函数继承.混合继承: /* js当中的继承 js中 构造函数 原型对象 实力对象的关系: 1 构造函数.prototype = 原型对象 2 ...
- js原型链理解(2)--原型链继承
1.原型链继承 2.constructor stealing(构造借用) 3.组合继承 js中的原型链继承,运用的js原型链中的__proto__. function Super(){ this.se ...
- 通过一段代码理解es6继承;
class animal{ constructor(props){ this.name = 'xiaoniao' || props.name } eat(){ console.log(this.nam ...
随机推荐
- 利用ImageAI库只需几行python代码超简实现目标检测
目录 什么是目标检测 目标检测算法 Two Stages One Stage python实现 依赖 安装 使用 附录 什么是目标检测 目标检测关注图像中特定的物体目标,需要同时解决解决定位(loca ...
- IOS7.0唯一“设备ID”的获取方法
ios7.0 以后通过sysctl获得的mac地址已经失效,所有设备均为020000000000. 可以通过苹果的keychain机制,实现设备的唯一ID标示. 具体过程:在app第一次安装时,生成一 ...
- ccf 201903-3 损坏的RAID5
9月份考ccf,暑假打算做一些往年的真题... 这个题,一开始真是把我给看晕了 传说中的大模拟,果然不简单QAQ 首先读懂题目就是一个大难点,特别是对于我这种题目一长就看不进去的人来说 读懂题目之后, ...
- 变量Variable
变量Variable 内存 #conding:utf-8 a = 1 #conding:utf-8 a = 1 b = a #conding:utf-8 a = 1 b = a a = 2 命名规则 ...
- 在Linux和Windows系统中输出目录结构
前言 一直以来就想在写文章时,能以文本形式(而不是截图)附上项目的目录结构,今天终于知道怎么操作了,在这分享一下. Linux 首先说下Linux上输出目录结构的方法. yum安装tree 需要支持t ...
- java学习笔记(中级篇)—JDK动态代理
一.什么是代理模式 相信大家都知道代理商这个概念,在商业中,代理商无处不在.假设你要去买东西,你不可能去找真正的厂家去买,也不可能直接跟厂家提出需求,代理商就是这中间的一桥梁,连接买家和厂商.你要买或 ...
- 洛谷 P2157 [SDOI2009]学校食堂
题意简述 每个人有一个口味,食堂每次只能为一个人做菜 做每道菜所需的时间是和前一道菜有关的,若前一道菜的对应的口味是a,这一道为b,则做这道菜所需的时间为a 异或 b 每个人都有一个容忍度,最多允许紧 ...
- 洛谷 P1120 小木棍
题意简述 给出n个数,求最小的l,使n个数可分成若干组,每组和都为l. 题解思路 暴力搜索+剪枝 代码 #include <cstdio> #include <cstdlib> ...
- ASP.NET CORE 2.* 利用集成测试框架覆盖HttpClient相关代码
ASP.NET CORE 集成测试官方介绍 我的asp.net core 项目里面大部分功能都是去调用别人的API ,大量使用HttpClient,公司单元测试覆盖率要求95%以上,很难做到不mock ...
- 【KakaJSON手册】04_JSON转Model_04_值过滤
在KakaJSON手册的第2篇文章中提过:由于JSON格式能表达的数据类型是比较有限的,所以服务器返回的JSON数据有时无法自动转换成客户端想要的数据类型 比如客户端想要的是Date类型,服务器返回的 ...