说到源码,大家脑海里可能浮现出四个字 我太难了。读源码貌似和我们遥不可及,因为在日常工作中,我们基本掌握在熟练的程度上,就能够满足工作需求,即便是想看源码,也会被源码复杂的逻辑拒之门外,成为了我们心中挥之不去的阴影。那么我们真的有必要阅读源码吗? 我以一个过来人的角度看,答案是肯定的,阅读源码不只是停留在源码层面,它还会带来一些附加的价值 。
笔者读过很多源码,比如 主流前端框架 vue2.0
,vue3.0
,react
,node
框架 express
, koa
,和它们衍生生态 react-router
,react-redux
, dva
等等。 要说在阅读源码的过程,痛苦么?我感觉过程是痛苦的,但是读完之后,就会感觉收获颇丰,感觉付出都是值得的。接下来我们一起探讨一下,阅读源码那些事。
一场面试题的思考?
假设这是一场面试。
面试官:说一下vue2.0
响应式原理 ?
第一位应聘者:object.defineproperty()
拦截器属性,拦截set, get
。
打分:4-5分 这样的答案似乎很难说服我,只能证明面试者对这个知识点有备而来。
第二位应聘者:在第一位基础上,说出了收集依赖的dep
对象,负责渲染更新的渲染watcher
,递归响应式等等,并能够介绍它们的原理和作用。
打分:6-7分 这样的答案,已经很符合标准了,至少说了vue
响应式的核心,说明应聘者至少深入了解过。
第三位应聘者:一方面从初始化 data
开始,到解析 template
模版,进行依赖收集。另一方面能够从 data
改变,通知渲染 watcher
更新,到页面变化,把整个流程说明白。
打分:8-10分 这样的答案,是非常完美的,能够从源码角度入手,说明应聘者很深入原理,读过源码。
从一道面试题,就能看出一个应聘者的对于框架的认知程度。而阅读源码就是从底层开始全方面认识框架的最佳方式。而且如果把源码搞得明明白白。可以让面试官刮目相看。甚至能够‘吊打’面试官。
阅读源码的过程中,能够了解底层是怎么运作的。如果在工作中遇到某些问题,如果读过源码,就会找到办法,问题也就会迎刃而解。
一个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
等待解决,不如亲自去看看源码,也许答案就在其中。正所谓蓦然回首,那人却在灯火阑珊处。
我个人觉得,阅读源码绝对是提高变成能力,拓展知识点的捷径。为什么这么说。我们先看两短经典的代码片段:
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
, 我们可以有效对知识点进行扫盲。
no2 vue3.0
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
里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
阅读源码,一方面有助于我们写出诗一样的代码,另一方面,扩充了我们的知识面。总之,真香!
优秀的源码有着纵览大局,运筹帷幄的思维,和中流砥柱的架构能力,这对一个正在进阶或者正打算进阶的工程师来说,是最缺少的。
也许你疯狂的补习这知识点,疯狂看这博客,疯狂刷着编程题,但是接手一个大的工程项目的时候,还是会手足无措,最后搞得一塌糊涂。这是为什么呢,也许就真的是缺少那么一丢丢设计思维和架构能力。
人生的三种境界和阅读源码的三种境界是一样的。慢慢的自己编程能力会受到潜移默化的影响。
当你刚开始看源码的时候,看自己的代码还是自己的代码。但是慢慢的,你会发现自己写的代码,受到了源码的影响,已经不像是自己最初的样子,当你日复一日的坚持,你就会明白源码真正架构设计,并能够自己设计架构,代码中有了自己的灵魂,你会发现自己的代码还是自己的代码,原因是自己进步了,能够有能力去把控全局。
上面讲述了阅读源码的好处,接下来我们讲一讲怎么有效阅读源码。
冰冻三尺,非一日之寒,阅读源码也不是一朝一夕的事情,我们要有计划的去阅读源码。一天抽出时间看一点,然后重点记笔记,可以是 md
,可以是手写的笔记,总之要把核心内容记录下来,因为一是好记性不如烂笔头,可以加深我们的印象。二是在每次阅读之前,都把上一次的笔记拿出来看看,做到完美的衔接。把整个源码分割成多个模块,一点点去消化,不要想着一口气把源码看完,这个是不现实的。
这是笔者在做vue3.0
源码阅读解析过程中记录的笔记。
在react
源码阅读解析过程中,记录的笔记:
这个是笔者阅读源码的精髓所在。三思而后行,在阅读源码的时候先问几个为什么?带着问题去看源码会起到事半功倍的效果,为什么这么说呢?如果不带着问题阅读,就会处于一种无目标,盲目的状态,在这种状态下,尤其看无聊和繁琐的源码,就会精力不集中,长时间就会犯困,无法坚持下去。
在阅读源码之前,首先想几个问题,带着这几个问题去源码中找答案,
例子一:
vue3.0
响应式原理之前,先提几个问题:
vue3.0
怎么构建的响应式,reactive API
到底做了什么?effect
和 reactive
是什么关系?effect
如何取代 watcher
?this.a
改变,是怎么做到视图对应更新的。例子二
在阅读 react-redux
的时候,我会先提这么几个问题:
root
根组件上使用 react-redux
的 Provider
组件包裹?react-redux
是怎么和 redux
契合,做到 state
改变更新视图的呢?provide
用什么方式存放当前的 redux
的 store
, 又是怎么传递给每一个需要订阅 state
的组件的?connect
是怎么样连接我们的业务组件,然后更新已经订阅组件的呢?connect
是怎么通过第一个参数mapStateToProps,来订阅与之对应的 state 的呢?connect
怎么样将 props,和 redux的 state 合并的?带着这些问题去阅读源码,就会在源码中仔细去寻找这些问题的答案,如果找到了答案,并解释了原理,也会有不错的成就感。
这一步对于阅读源码也是非常重要的,我们要学会提炼源码的精髓,以 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);
}
核心的代码只有这区区几行,所以在阅读源码的流程中,提炼精髓也是十分重要的。
实践是检验真理的唯一标准。如果想搞清楚源码,不要单独停留在看的层面,也要真正去跑一遍源码。 这样一来我们可以在 github
,克隆下来源码。然后在关键的上下文,可以 debugger
或者 console
。
步骤如下:
从 github
下载文件。
然后进行debugger或者 console。
接下来把源码单独抽出来,打包。
放入我们的demo项目进行验证。
此时我们要改变一下路径。因为原来我们的 package
是放在 node_modules
中的,现在路径改了,所以注意路径问题。
并不是所有的框架源码都需要一个固定的模式去解析的。这一点笔者就吃了苦头。我们先来说一下背景。
笔者在阅读 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
,请求关键帧等等,具体是干什么的,有什么作用,然后像搭积木一样,把它们搭起来。
把值得做的事坚持下去,再把坚持做的事努力做好。 既然选择阅读源码,就要坚持下去,笔者刚开始看源码的时候也是很痛苦,曾经几度想放弃,但是后来按照上面方法,坚持下去,终于养成了好习惯,现在完全能够注意力集中的阅读源码,而且过程感觉也不像当初那么无趣。
听说过21天效应,如果一天一天坚持下去,用不了多久就能养成一种阅读源码的好习惯,相信那个时候,我们比如尝试用一个新的 package
的时候,忍不住先去 github
上拉下源码瞧瞧。
关于收获
看源码的习惯坚持了差不多二年了,收获感觉还是蛮多的,首先无论是从知识储备还是编程写法或者设计架构上,都有很大的进步,也尝试了写了自己的开源项目,并下定决心好好维护下去。
rux 一款redux和react-redux状态管理工具
react-keepalive-router缓存页面路由
总结
以上就是我在阅读源码过程中的所感所悟,路漫漫其修远兮吾将上下而求索,在阅读源码的路上,能坚持下来,将会有一片美丽的风景。
手留余香,阅读的朋友可以给笔者点赞,关注一波 ,陆续更新前端超硬核文章。接下来将写一篇react-redux
源码文章。
微信扫码关注公众号,定期分享技术文章