「小议」 我从读源码中收获到了什么?阅读源码那点小事

2021-06-01 10:39:31 浏览数 (1)

一 前言

说到源码,大家脑海里可能浮现出四个字 我太难了???。读源码貌似和我们遥不可及,因为在日常工作中,我们基本掌握在熟练的程度上,就能够满足工作需求,即便是想看源码,也会被源码复杂的逻辑拒之门外,成为了我们心中挥之不去的阴影。那么我们真的有必要阅读源码吗?我以一个过来人的角度看,答案是肯定的,阅读源码不只是停留在源码层面,它还会带来一些附加的价值 。

笔者读过很多源码,比如 主流前端框架 vue2.0,vue3.0 ,reactnode框架 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案例引发的思考 之前见有同事遇到过这么一个问题。

代码语言:javascript复制
<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 数据改变了,但是视图没有发生变化。

如果没有对 vuediff 算法有一定了解,肯定会对这个现象一脸蒙蔽,明明数据已经改变了,但是视图为什么没有变呢?what?

如果看过 diff 算法,和子节点 patch 过程的同学,就会发现,这个问题主要来源于,用 index 作为 key ,在一次更新中,虽然数据改变了,但是根据 index,复用了错误的元素节点,导致了视图和数据不对应的情况。

对于框架或者开源库,如果我们在使用中遇到了问题,与其在 GitHubissue 等待解决,不如亲自去看看源码,也许答案就在其中。正所谓蓦然回首,那人却在灯火阑珊处。

3 提高编程能力,拓展知识盲区

我个人觉得,阅读源码绝对是提高编程能力,拓展知识点的捷径。为什么这么说。我们先看两短经典的代码片段:

no 1 redux compose

代码语言:javascript复制
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

代码语言:javascript复制
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 effectreactive 是什么关系?effect 如何取代 watcher ?
  • 3 如何收集的依赖?
  • 4 通过 this.a 改变,是怎么做到视图对应更新的。

例子?二

在阅读 react-redux 的时候,我会先提这么几个问题:

  • 1 为什么要在 root 根组件上使用 react-reduxProvider 组件包裹?
  • 2 react-redux 是怎么和 redux 契合,做到 state 改变更新视图的呢?
  • 3 provide 用什么方式存放当前的 reduxstore, 又是怎么传递给每一个需要订阅 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 行左右,但是我给它进行提炼之后,代码如下

代码语言:javascript复制
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 ,效果也是甚微,无法把各模块功能串联起来,形成体系。

后来,开始阅读一些大佬的文章,先明白每一块干了些什么,有什么作用,然后一块块的串起来。最后再去阅读源码,发现效果甚佳。

案例: vuereact

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缓存页面路由

0 人点赞