前言
你好,我是若川。这是面试官问系列的第五篇,旨在帮助读者提升
JS
基础知识,包含new、call、apply、this、继承
相关知识。
面试官问系列
文章如下:感兴趣的读者可以点击阅读。
用过React
的读者知道,经常用extends
继承React.Component
。
1 | scala复制代码// 部分源码 |
面试官可以顺着这个问JS
继承的相关问题,比如:ES6
的class
继承用ES5如何实现。据说很多人答得不好。
构造函数、原型对象和实例之间的关系
要弄懂extends继承之前,先来复习一下构造函数、原型对象和实例之间的关系。
代码表示:
1 | javascript复制代码function F(){} |
笔者画了一张图表示:
ES6 extends
继承做了什么操作
我们先看看这段包含静态方法的ES6
继承代码:
1 | javascript复制代码// ES6 |
其中这段代码里有两条原型链,不信看具体代码。
1 | javascript复制代码// 1、构造器原型链 |
一图胜千言,笔者也画了一张图表示,如图所示:
结合代码和图可以知道。ES6 extends
继承,主要就是:
- 把子类构造函数(
Child
)的原型(__proto__
)指向了父类构造函数(Parent
),
- 把子类构造函数(
- 把子类实例
child
的原型对象(Child.prototype
) 的原型(__proto__
)指向了父类parent
的原型对象(Parent.prototype
)。
- 把子类实例
这两点也就是图中用不同颜色标记的两条线。
- 子类构造函数
Child
继承了父类构造函数Parent
的里的属性。使用super
调用的(ES5
则用call
或者apply
调用传参)。
也就是图中用不同颜色标记的两条线。
- 子类构造函数
看过《JavaScript高级程序设计-第3版》 章节6.3继承
的读者应该知道,这2和3小点
,正是寄生组合式继承,书中例子没有第1小点
。1和2小点
都是相对于设置了__proto__
链接。那问题来了,什么可以设置了__proto__
链接呢。
new
、Object.create
和Object.setPrototypeOf
可以设置__proto__
说明一下,__proto__
这种写法是浏览器厂商自己的实现。
再结合一下图和代码看一下的new
,new
出来的实例的__proto__指向构造函数的prototype
,这就是new
做的事情。
摘抄一下之前写过文章的一段。面试官问:能否模拟实现JS的new操作符,有兴趣的读者可以点击查看。
new
做了什么:
- 创建了一个全新的对象。
- 这个对象会被执行
[[Prototype]]
(也就是__proto__
)链接。- 生成的新对象会绑定到函数调用的
this
。- 通过
new
创建的每个对象将最终被[[Prototype]]
链接到这个函数的prototype
对象上。- 如果函数没有返回对象类型
Object
(包含Functoin
,Array
,Date
,RegExg
,Error
),那么new
表达式中的函数调用会自动返回这个新的对象。
Object.create
ES5提供的
Object.create(proto, [propertiesObject])
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是undefined
)。对于不支持ES5
的浏览器,MDN
上提供了ployfill
方案。
MDN Object.create()
1 | javascript复制代码// 简版:也正是应用了new会设置__proto__链接的原理。 |
Object.setPrototypeOf
ES6提供的
Object.setPrototypeOf()
方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]
属性)到另一个对象或 null
。Object.setPrototypeOf(obj, prototype)
1 | javascript复制代码`ployfill` |
nodejs
源码就是利用这个实现继承的工具函数的。
nodejs utils inherits
1 | javascript复制代码function inherits(ctor, superCtor) { |
ES6
的extends
的ES5
版本实现
知道了ES6 extends
继承做了什么操作和设置__proto__
的知识点后,把上面ES6
例子的用ES5
就比较容易实现了,也就是说实现寄生组合式继承,简版代码就是:
1 | javascript复制代码// ES5 实现ES6 extends的例子 |
我们完全可以把上述ES6的例子
通过babeljs
转码成ES5
来查看,更严谨的实现。
1 | javascript复制代码// 对转换后的代码进行了简要的注释 |
如果对JS继承相关还是不太明白的读者,推荐阅读以下书籍的相关章节,可以自行找到相应的pdf
版本。
推荐阅读JS继承相关的书籍章节
《JavaScript高级程序设计第3版》-第6章 面向对象的程序设计,6种继承的方案,分别是原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承。图灵社区本书地址,后文放出github
链接,里面包含这几种继承的代码demo
。
《JavaScript面向对象编程第2版》-第6章 继承,12种继承的方案。1.原型链法(仿传统)、2.仅从原型继承法、3.临时构造器法、4.原型属性拷贝法、5.全属性拷贝法(即浅拷贝法)、6.深拷贝法、7.原型继承法、8.扩展与增强模式、9.多重继承法、10.寄生继承法、11.构造器借用法、12.构造器借用与属性拷贝法。
《深入理解ES6
》-第9章 JavaScript
中的类
《你不知道的JavaScript
-上卷》第6章 行为委托和附录A ES6中的class
总结
继承对于JS来说就是父类拥有的方法和属性、静态方法等,子类也要拥有。子类中可以利用原型链查找,也可以在子类调用父类,或者从父类拷贝一份到子类等方案。
继承方法可以有很多,重点在于必须理解并熟
悉这些对象、原型以及构造器的工作方式,剩下的就简单了。寄生组合式继承是开发者使用比较多的。
回顾寄生组合式继承。主要就是三点:
- 子类构造函数的
__proto__
指向父类构造器,继承父类的静态方法
- 子类构造函数的
- 子类构造函数的
prototype
的__proto__
指向父类构造器的prototype
,继承父类的方法。
- 子类构造函数的
- 子类构造器里调用父类构造器,继承父类的属性。
行文到此,文章就基本写完了。文章代码和图片等资源放在这里github inhert和demo
展示es6-extends
,结合console、source
面板查看更佳。
- 子类构造器里调用父类构造器,继承父类的属性。
读者发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,可以点赞、评论、转发,也是对笔者的一种支持。
笔者精选文章
学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK
学习 lodash 源码整体架构,打造属于自己的函数式编程类库
学习 underscore 源码整体架构,打造属于自己的函数式编程类库
学习 jQuery 源码整体架构,打造属于自己的 js 类库
前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并
关于
作者:常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。
segmentfault
前端视野专栏,开通了前端视野专栏,欢迎关注~
掘金专栏,欢迎关注~
知乎前端视野专栏,开通了前端视野专栏,欢迎关注~
github blog,求个star
^_^~
微信公众号 若川视野
可能比较有趣的微信公众号,长按扫码关注。也可以加微信 ruochuan12
,注明来源,拉您进【前端视野交流群】。
本文转载自: 掘金