Skip to main content

Event Loop

2022/12/11

latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D

听完大佬几年前的JSConf上的分享,感觉收益很多。

image-20221211132016368

首先,JS是一个单线程的语言。这也就意味着,它没有真正的像C/C++那样的Thread,而是只能一次做一件事情。

对于Call Stack,这个对各种语言都差不多,调用栈都有入栈、出栈的操作。但是如果遇到Blocking的事件,比如Alert弹窗,比如Xhr request,比如超级大的数组遍历循环,都会占用Call Stack,导致后续的事件无法进行。

这里还有个概念,就是Chrome等浏览器默认都是16ms 刷新一次页面,每秒60帧。如果js的Call Stack里单个任务调用链的调用时长(即清空Call Stack所需花费的时间)超过16ms,那么必然会block住浏览器请求绘制页面的任务。

对于Node来说,它的backend是C++,对JS代码只开放Web API。这套Web API是由一些支持多线程的语言编写的,比如Node的timer:

node/timers.cc at main · nodejs/node (github.com)

然后其对JavaScript暴露SetTimeout接口。

也就是说,JavaScript里的SetTimeout无非是一个宏定义,其真正做的事情,就是把任务丢给Web API的实现者去做(无论是Node 还是Chrome V8),然后后端引擎做完任务后再把回调任务放到Callback Queue里。

JS的Runtime会在清空Call Stack后,去不断检查Callback Queue,然后把Callback Queue里的任务挪到Callback Stack里执行。

这也是React等框架分块渲染的逻辑:尽量把任务分割得足够小,让浏览器能够频繁地清空Callback Stack,这样每16ms执行的渲染任务才能正常地被执行。