本文正在参加「金石计划」
简介
写这篇文章的目的,是希望大家能够透过现象看本质。借着 EventLoop ,让大家站在设计者的角度去考量、去设计。
核心是让大家提出一个问题,为什么会有 EventLoop 这个东西?以及 EventLoop 底层的设计逻辑。
假设让你去处理一大堆任务复杂的任务,你该怎么去处理。一个语言的优秀,一定有它的独到之处,而能够做出这种优秀设计的,一定有一个底层思想的支撑。
我们要学的是它的设计思路与核心思想而不是简简单单局限在实现方式上。
如何去学习和理解新的知识
最容易理解问题的思维方式叫类比思维,这是一种使用已知的知识去推导未知的思维方式,比如当我小时候第一次见到钢笔✒️的时候就不知道这是什么,这时别人告诉就会我这是钢笔,但我还是不理解,人家就说了,钢笔就是喝水的铅笔✏️,哦!那我就知道了。类比思维很好用,但是类比思维有比较大的局限性,它会把我们禁锢在问题的表象。比如在没有火车的时候,交通工具就是马,我们很容易就会把马和交通工具打上等号。所以为了能拉更多货物,我们的做法是增加马匹,为了能更快到达目的地,我们的做法是选一匹快马,我们的想法永远是通过马实现,这时候我们自己就把自己局限在了马上面,而马的本质是什么,它只是一个交通工具。所以你看今天,想拉货?有火车!想要快?有高铁🚄!我们需要的是更好的交通工具,而不是一匹马🐎。
换个思维方式 -- 第一性原理
之所以选用 第一性原理 就是因为它是一种透过现象看本质的思维方式,大家不用刻意关注第一性原理,而下面我就会带着大家使用 第一性原理 去思考,在解决 EventLoop 的同时大家跟着我的思路其实就能理解 第一性原理 了,任督二脉自然就会打通。
EventLoop 是个啥?
怎么去理解 EventLoop ?用人话说 EventLoop 就是个累死累活的员工。假设你是一个文员,你每天的任务就是处理老板堆过来的一堆又一堆的文档,一堆又一堆的文档里面还夹着一个又一个的文件夹。
而且问题是你手头的活还没干完,老板又时不时的塞过来一堆文档,看着👀这一堆堆的文档能不窒息吗?但是人只有一个啊,所以我们只能一点点🤏的慢慢干。
而 JS 其实就是这个员工,不过它的效率会很高,而且不会累,但是我们需要告诉它任务怎么做。而这个给他分配任务的过程就是 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自己内部的这套就叫微任务。这样就通过任务的套娃🪆机制,就能实现一整套任务的调度机制。完美!
补充:第一性原理详述
马斯克在一次采访中介绍了第一性原理,我觉得这个解释是最通俗易懂的。下面带大家看看这段采访:
- 问:“小马同学,请解释下你理解的第一性原理。”
- 小马:“ 第一性原理 是用物理学的角度看待世界,也就是说一层层拨开事物表象,看到里面的本质,再从本质一层层往上走。”
- 问:“能举个简单的我能听懂的例子吗?”
- 小马:“好的,我就以我的公司特斯拉为例,我们知道电池组是很贵的,需要
600美金,如果让用户去花这么大的价钱去买一辆电车,那么没人会放弃他的油车。如果我们用第一性原理的角度去看呢,锂电池是什么组成的,最核心的就是锂元素,还有些钴镍铝和碳。如果我们从伦敦交易所去购买这些材料只需要80美金。我们再通过物理化学等方法将它们组装成电池组,你就会发现价格便宜得多”
结尾
这样就把整个 EventLoop 理清楚了,感觉怎么样,够简洁吗?当我们选择了技术这条路,会发现这条路是一个个的十字路口,无论向前还是两边,总有无数的路口。如何选择就成了一个难题。
这里提出我的小建议 不要陷入技术陷阱,什么是技术陷阱,技术陷阱就是活成了一本技术字典。搜索引擎能做到的事情,为什么要你去死记硬背呢?那我们要做的是什么,我的建议是成为一本诗词歌赋。一个字典几万个字,但是一首唐诗呢,有多少个字?语言的简单精炼丝毫不影响它流传千古,汉字常用的也就 3k 字,如何把知识点用活用精,努力去做出创造性的工作,才是核心。行笔至此,大家共勉。
附上 github, 欢迎star :github.com/coveychen95…