本文转载自微信公众号「前端Sharing」,作者前端Sharing 。转载本文请联系前端Sharing公众号。
一 前言
说到源码,大家脑海里可能浮现出四个字 我太难了。读源码貌似和我们遥不可及,因为在日常工作中,我们基本掌握在熟练的程度上,就能够满足工作需求,即便是想看源码,也会被源码复杂的逻辑拒之门外,成为了我们心中挥之不去的阴影。那么我们真的有必要阅读源码吗?我以一个过来人的角度看,答案是肯定的,阅读源码不只是停留在源码层面,它还会带来一些附加的价值 。
笔者读过很多源码,比如 主流前端框架 vue2.0,vue3.0 ,react,node框架 express , koa,和它们衍生生态 react-router,react-redux , dva 等等。要说在阅读源码的过程,痛苦么?我感觉过程是痛苦的,但是读完之后,就会感觉收获颇丰,感觉付出都是值得的。接下来我们一起探讨一下,阅读源码那些事。
二 为什么读源码?
1 为了面试
一场面试题的思考?
假设这是一场面试。
面试官:说一下vue2.0响应式原理 ?
第一位应聘者:object.defineproperty()拦截器属性,拦截set, get。
打分:4-5分 这样的答案似乎很难说服我,只能证明面试者对这个知识点有备而来。
第二位应聘者:在第一位基础上,说出了收集依赖的dep对象,负责渲染更新的渲染watcher,递归响应式等等,并能够介绍它们的原理和作用。
打分:6-7分 这样的答案,已经很符合标准了,至少说了vue响应式的核心,说明应聘者至少深入了解过。
第三位应聘者:一方面从初始化 data 开始,到解析 template 模版,进行依赖收集。另一方面能够从 data 改变,通知渲染 watcher 更新,到页面变化,把整个流程说明白。
打分:8-10分 这样的答案,是非常完美的,能够从源码角度入手,说明应聘者很深入原理,读过源码。
从一道面试题,就能看出一个应聘者的对于框架的认知程度。而阅读源码就是从底层开始全方面认识框架的最佳方式。而且如果把源码搞得明明白白。可以让面试官刮目相看。甚至能够‘吊打’面试官????。
2 更清晰的运用框架
阅读源码的过程中,能够了解底层是怎么运作的。如果在工作中遇到某些问题,如果读过源码,就会找到办法,问题也就会迎刃而解。
一个bug案例引发的思考 之前见有同事遇到过这么一个问题。
- <el-select v-model="value" >
- <el-option
- v-for="(item,index) in list"
- :key="index"
- :value="item.value"
- :label="item.label"
- />
- </el-select>
可能业务场景要比这个复杂,大致是如上这么样的。出现一个问题就是,每次改变 list ,然后重新选择 option 的时候,会发现绑定的 value 数据改变了,但是视图没有发生变化。
如果没有对 vue 中 diff 算法有一定了解,肯定会对这个现象一脸蒙蔽,明明数据已经改变了,但是视图为什么没有变呢?what?
如果看过 diff 算法,和子节点 patch 过程的同学,就会发现,这个问题主要来源于,用 index 作为 key ,在一次更新中,虽然数据改变了,但是根据 index,复用了错误的元素节点,导致了视图和数据不对应的情况。
对于框架或者开源库,如果我们在使用中遇到了问题,与其在 GitHub 提 issue 等待解决,不如亲自去看看源码,也许答案就在其中。正所谓蓦然回首,那人却在灯火阑珊处。
3 提高编程能力,拓展知识盲区
我个人觉得,阅读源码绝对是提高编程能力,拓展知识点的捷径。为什么这么说。我们先看两短经典的代码片段:
no 1 redux compose
- export default function compose(...funcs) {
- if (funcs.length === 0) {
- return arg => arg
- }
- if (funcs.length === 1) {
- return funcs[0]
- }
- return funcs.reduce((a, b) => (...args) => a(b(...args)))
- }
这是前端领域经典的中间件案例,代码精简,却堪称神来之笔。我们可以学习源码中的,编程手法,即使写不出如上这么经典函数,也能明白什么时候使用继承,什么时候用闭包。
在阅读源码过程中,会有很多高级用法和我们很少用到 api , 我们可以有效对知识点进行扫盲。
- vue3.0 /reactivity/src/reactive.ts
- const rawToReactive = new WeakMap<any, any>()
- const reactiveToRaw = new WeakMap<any, any>()
- const rawToReadonly = new WeakMap<any, any>()
- const readonlyToRaw = new WeakMap<any, any>()
vue3.0 中做保存依赖收集关系的几个 WeakMap ,引发了我对 WeakMap 以及垃圾回收机制的思考? WeakMaps 保持了对键名所引用的对象的弱引用,即垃圾回收机制不将该引用考虑在内。只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要, WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
阅读源码,一方面有助于我们写出诗一样的代码,另一方面,扩充了我们的知识面。总之,真香!
4 培养设计思维和架构能力
优秀的源码有着纵览大局,运筹帷幄的思维,和中流砥柱的架构能力,这对一个正在进阶或者正打算进阶的工程师来说,是最缺少的。
也许你疯狂的补习这知识点,疯狂看这博客,疯狂刷着编程题,但是接手一个大的工程项目的时候,还是会手足无措,最后搞得一塌糊涂。这是为什么呢,也许就真的是缺少那么一丢丢设计思维和架构能力。
人生的三种境界和阅读源码的三种境界是一样的。慢慢的自己编程能力会受到潜移默化的影响。
当你刚开始看源码的时候,看自己的代码还是自己的代码。但是慢慢的,你会发现自己写的代码,受到了源码的影响,已经不像是自己最初的样子,当你日复一日的坚持,你就会明白源码真正架构设计,并能够自己设计架构,代码中有了自己的灵魂,你会发现自己的代码还是自己的代码,原因是自己进步了,能够有能力去把控全局。
三 怎么样读源码
上面讲述了阅读源码的好处,接下来我们讲一讲怎么有效阅读源码。
1 化整为零
冰冻三尺,非一日之寒,阅读源码也不是一朝一夕的事情,我们要有计划的去阅读源码。一天抽出时间看一点,然后重点记笔记,可以是 md,可以是手写的笔记,总之要把核心内容记录下来,因为一是好记性不如烂笔头,可以加深我们的印象。二是在每次阅读之前,都把上一次的笔记拿出来看看,做到完美的衔接。把整个源码分割成多个模块,一点点去消化,不要想着一口气把源码看完,这个是不现实的。
这是笔者在做vue3.0源码阅读解析过程中记录的笔记。
在react 源码阅读解析过程中,记录的笔记:
2 三思而后行
这个是笔者阅读源码的精髓所在。三思而后行,在阅读源码的时候先问几个为什么?带着问题去看源码会起到事半功倍的效果,为什么这么说呢?如果不带着问题阅读,就会处于一种无目标,盲目的状态,在这种状态下,尤其看无聊和繁琐的源码,就会精力不集中,长时间就会犯困,无法坚持下去。
在阅读源码之前,首先想几个问题,带着这几个问题去源码中找答案,
例子一:
vue3.0响应式原理之前,先提几个问题:
- 1 vue3.0怎么构建的响应式,reactive API到底做了什么?
- 2 effect 和 reactive 是什么关系?effect 如何取代 watcher ?
- 3 如何收集的依赖?
- 4 通过 this.a 改变,是怎么做到视图对应更新的。
例子二
在阅读 react-redux 的时候,我会先提这么几个问题:
- 1 为什么要在 root 根组件上使用 react-redux 的 Provider 组件包裹?
- 2 react-redux 是怎么和 redux 契合,做到 state 改变更新视图的呢?
- 3 provide 用什么方式存放当前的 redux 的 store, 又是怎么传递给每一个需要订阅 state 的组件的?
- 4 connect 是怎么样连接我们的业务组件,然后更新已经订阅组件的呢?
- 5 connect 是怎么通过第一个参数mapStateToProps,来订阅与之对应的 state 的呢?
- 6 connect 怎么样将 props,和 redux的 state 合并的?
带着这些问题去阅读源码,就会在源码中仔细去寻找这些问题的答案,如果找到了答案,并解释了原理,也会有不错的成就感。
3 提炼精髓
这一步对于阅读源码也是非常重要的,我们要学会提炼源码的精髓,以 react 为例子,react 个别函数,可能几百行,甚至上千行,但是除去服务端渲染,开发环境的警告 __dev__ ,context 上下文的处理,和一些判断等等,真正的核心逻辑代码,也许就那么几行和十几行,所以我们不需要去扣源码中的每一行代码,只需要搞清楚核心逻辑就好。
我们拿react源码为例子:
- react/react-reconciler/ReactFiberClassComponent.js
这个文件下,有一个 constructClassInstance 方法,用于 new 我们的组件实例。这个方法大约有 200 行左右,但是我给它进行提炼之后,代码如下
- function constructClassInstance(
- workInProgress, //
- ctor, // 我们的 component
- props, // 组件的 props
- ){
- /* 这里实例化 我们的component */
- const instance = new ctor(props, context);
- /* 给当前 组件实例 ,挂上 updater 对象,用于组件渲染更新 */
- adoptClassInstance(workInProgress, instance);
- }
核心的代码只有这区区几行,所以在阅读源码的流程中,提炼精髓也是十分重要的。
4 真枪实弹
实践是检验真理的唯一标准。如果想搞清楚源码,不要单独停留在看的层面,也要真正去跑一遍源码。这样一来我们可以在 github ,克隆下来源码。然后在关键的上下文,可以 debugger 或者 console 。
步骤如下:
从 github 下载文件。
然后进行debugger或者 console。
接下来把源码单独抽出来,打包。
放入我们的demo项目进行验证。
此时我们要改变一下路径。因为原来我们的 package 是放在 node_modules 中的,现在路径改了,所以注意路径问题。
5 因材施教
并不是所有的框架源码都需要一个固定的模式去解析的。这一点笔者就吃了苦头。我们先来说一下背景。
笔者在阅读 vue2.0,采用集中式阅读,就是从new Vue为入口,然后逐步向代码深层去挖掘。最后将各个模块串联起来。
在阅读完vue2.0的核心原理后, 想要以同样的模式去阅读 react,发现此方案根本行不通,因为react 有很多模块,比如 react, react-reconciler ,react-dom,scheduler,有好几千个函数方法,看着看着就蒙蔽了,即使 debugger ,效果也是甚微,无法把各模块功能串联起来,形成体系。
后来,开始阅读一些大佬的文章,先明白每一块干了些什么,有什么作用,然后一块块的串起来。最后再去阅读源码,发现效果甚佳。
案例: vue 和 react
vue 集中式阅读源码
vue 源码适合集中式阅读,就是从 new vue() 开始,到初始化 data ,建立响应式 ,patch 元素节点,解析 template 模版,注入依赖,挂载真实 dom ,一气呵成,一条线串起来。
react 发散式阅读源码
而 react 需要一种发散式的阅读方法,就是你需要先明白,reconciler,scheduler,expiration time,请求关键帧等等,具体是干什么的,有什么作用,然后像搭积木一样,把它们搭起来。
6 水滴石穿
把值得做的事坚持下去,再把坚持做的事努力做好。 既然选择阅读源码,就要坚持下去,笔者刚开始看源码的时候也是很痛苦,曾经几度想放弃,但是后来按照上面方法,坚持下去,终于养成了好习惯,现在完全能够注意力集中的阅读源码,而且过程感觉也不像当初那么无趣。
听说过21天效应,如果一天一天坚持下去,用不了多久就能养成一种阅读源码的好习惯,相信那个时候,我们比如尝试用一个新的 package 的时候,忍不住先去 github 上拉下源码瞧瞧。
四 收获与总结
关于收获
看源码的习惯坚持了差不多二年了,收获感觉还是蛮多的,首先无论是从知识储备还是编程写法或者设计架构上,都有很大的进步,也尝试了写了自己的开源项目,并下定决心好好维护下去。
rux 一款redux和react-redux状态管理工具
react-keepalive-router缓存页面路由
总结
以上就是我在阅读源码过程中的所感所悟,路漫漫其修远兮吾将上下而求索,在阅读源码的路上,能坚持下来,将会有一片美丽的风景。