likes
comments
collection
share

用第一性原理去看 `EventLoop`

作者站长头像
站长
· 阅读数 17

翻译翻译!什么 TMD第一性原理

第一性原理 是一种看待问题或者学习的方式。

我们在遇到未知的问题或者学习新的知识的时候,总是会用已知的知识去推导未知的东西,比如当我小时候第一次见到钢笔✒️的时候就不知道这是什么,这时别人告诉就会我这是钢笔,但我还是不理解,人家就说了,钢笔就是喝水的铅笔✏️,哦!那我就知道了。这种用已知推未知是最容易理解的思维方式,我们称之为类比思维。但是今天我们会用另一种思维 -- 第一性原理 去学习新知识。

那什么叫 第一性原理 呢?下面我们采访一下马斯克同学

  • 问:“小马同学,请解释下你理解的第一性原理。”
  • 小马:“ 第一性原理 是用物理学的角度看待世界,也就是说一层层拨开事物表象,看到里面的本质,再从本质一层层往上走。”
  • 问:“能举个简单的我能听懂的例子吗?”
  • 小马:“好的,我就以我的公司特斯拉为例,我们知道电池组是很贵的,需要 600 美金,如果让用户去花这么大的价钱去买一辆电车,那么没人会放弃他的油车。那么我们如果第一性原理的角度去看呢,锂电池是什么组成的,最核心的就是锂元素,还有些钴镍铝和碳。如果我们从伦敦交易所去购买这些材料只需要 80 美金。我们再通过物理化学等方法将它们组装成电池池,你就会发现价格便宜得多”

什么是 EventLoop 的本质?

既然我们已经知道了第一性原理,那么怎么利用它去理解 EventLoop 呢?我们就从最简单的角度去看,EventLoop 是用来做什么的?它就是用来实现事件调度的。 这就是 EventLoop 的作用,那么为什么会需要实现 事件调度 呢?我们知道 JavaScript 是单线程的,而且 JS 必须运行在一个 宿主环境 里。这个宿主环境可以是浏览器,也可以是 Node 。但是当 JS 需要和宿主环境进行通信的时候,因为这个事情是由宿主环境去做的,我们并不知道这个事件什么时候执行完成,比如我们去监听一个图片的加载事件的时候。我们并不知道用户的电脑什么时候能够加载完成,我们总不能让代码傻傻的等到图片加载完再去渲染剩余的 DOM 元素,或者为每个元素添加对应的事件吧。所以我们需要让 JS 先去做其他的事情,等到图片加载完成了再由 宿主环境去告知 JS 图片加载完成,这样 JS 就可以把手头的事情处理完成的时候,再去处理图片后续展示逻辑了。

EventLoop 是如何实现事件调度的?

既然我们知道了 EventLoop 的工作,那 EventLoop 又是如何实现事件调度的呢?继续以图片加载为例,首先 JS 会先拿到 <script></script> 里面一堆代码去执行。这一堆代码就是浏览器需要干的活儿,我们就把这堆活儿先叫做 宏任务。这时候 JS 就上场了,它开始执行我们的代码,当 JS 读取到需要去加载一张图片的时候,JS 发现图片加载这事儿不太简单,所以把图片加载扔给了叫浏览器的新人,告诉他,你把图片加载完的时候和我说一下,我干完手头的活就去处理图片的后续逻辑, 接着 JS 继续干手头的活,等着浏览器通知再去处理图片,给浏览器交代任务的过程其实又新建了一个宏任务。但是问题来了,现在只有两个宏任务,任务一多呢?那 JS 怎么知道有多少任务需要去做,以及哪个任务先做,所以为了帮 JS 记得任务的执行次序,我们就给 JS 新开了一个 回调线程,这个线程就是任务的备忘录。

所以这个地方有个问题,我们说 JS 是个单线程的语言是不严谨的,因为 JS 有代码执行逻辑的主线程和一个处理任务调度的回调线程。所以准确的说法应该是 JS 的代码执行逻辑是一个单线程的过程。

我们继续,虽然通过分配宏任务解决了浏览器与宿主环境的事件交互问题,但是我们知道 JS 自己在处理任务的时候,如果任务简单还好,但是任务一旦比较复杂呢?或者 JS 想自己去 异步处理任务呢?比如举个最简单的例子 -- 我们让 JS 解决自己的早餐问题,让 JS 先去煮个粥,再去 刷牙洗脸。但是 JS 完全可以按个煮粥键之后就去刷牙洗脸,洗漱完等粥🥣煮好直接开吃,从而节约时间⏰。所以为了解决 JS 能够处理异步任务的能力,我们需要让 JS 自己也去实现一套和 宿主环境 一样的事件调度机制,那怎么做呢?答案太简单了,我都已经有一套处理机制了,为什么还要再想其他方法,所以直接把和宿主环境处理的逻辑搬过来就行了,但是现在就有两套一模一样的事件调度机制了,我们怎么确定哪个任务先执行呢?答案也很简单 JS 自己的异步任务,那我们当然让他优先执行,毕竟自家人。那么这里就定义好了,JS 的异步任务一定是优先执行的,同时为了确保两套任务能够不影响,我们需要一个套娃机制,把 JS 的异步任务塞到宏任务里面去,同时规定好宏任务内只要有 JS 的异步任务存在,我们就一定优先执行 JS 的异步任务,直到这个宏任务没有 JS 的异步任务时,我们才能去处理下一个宏任务。但是 JS 的异步任务怎么命名呢?既然 JS 的任务优先执行,而且套在宿主环境的宏任务里面,那 JS 自己内部的这套就叫 微任务。这样就通过任务的套娃🪆机制,就能实现一整套任务的调度机制。完美!

补充

上面👆文字较多,如果想象力不够,可能🤔有点难理解,这里我们再结合一下类比思维,假设 JS 是一个文员,它的任务就是处理老板堆过来的一堆又一堆的文档,因为每一堆文档其实都是一个公司的,所以肯定一堆文档一堆文档的去处理,而这一堆文档就是 宏任务 。而这一堆文档又有 n 个文件夹📁,假设是这个公司的每月汇报好了,我们肯定一个文件夹📁一个文件夹📁的去处理,所以每个文件夹就是一个 微任务。这样我们每做完一个小任务都会汇报给老板,让他知道我们很忙很忙,真的很忙!

总结

这样就把整个 EventLoop 理清楚了,感觉怎么样,够简洁吗?换个角度看 EventLoop 是为了能让大家站在设计者的角度去考量、去设计。 核心就是让大家提出一个问题,为什么会有 EventLoop 这个东西?以及 EventLoop 底层的设计逻辑。 假设让你去处理这一大堆任务,你该怎么去做。一个语言的优秀,一定有它的独到之处,而能够做出这种优秀设计的,一定有一个底层思想的支撑。我们要做的不是一本技术的字典,一说一个技术点你就能从定义到使用说的很详细,但是这么设计的原因你有考虑过吗?这个就是最佳实践了吗? 我对技术的理解就是不要陷入技术陷阱,什么是技术陷阱,技术陷阱就是活成了一本技术字典。搜索引擎能做到的事情,为什么要你去死记硬背呢?那我们要做的是什么,我的建议是成为一本诗词歌赋。一个字典几万个字,但是一首唐诗呢,有多少个字?语言的简单精炼丝毫不影响它流传千古,汉字常用的也就 3k 字,如何把知识点用活用精,努力去做出创造性的工作,才是核心。行笔至此,大家共勉。