共计 2698 个字符,预计需要花费 7 分钟才能阅读完成。
以下是本人在过往一直总结的一些对于 JavaScript 在浏览器和 Node 环境下事件循环的了解。如有舛误欢送斧正。
你是否说出这个例子输入什么?
这里的后果其实是:不确定的输入。为什么呢?这里就要给大家廓清几个误会
上面这些都是误会,你有吗?
误会 1:在 JavaScript 平台上有一个用户态的主线程,用来执行 JavaScript 代码;除此之外,还有个 EventLoop 线程
用来做事件循环的查看,查看到有事件工作时,再交给 JavaScript 执行线程来执行
误会 2:所有的异步操作(无论文件读写还是 database 操作)都交给 libuv 提供的线程池来解决。
误会 3:EventLoop 的事件队列就是一个相似 queue 的先进先出数据结构的队列
解释
误会 1:EventLoop 和执行 JavaScript 就在一个线程内。
误会 2:libuv 默认创立 4 个线程来进行异步工作;但当初 OS 个别都有提供异步接口例如 linux 的 AIO,个别都是优先应用异步接口。
误会 3:EventLoop 作为过程,它有一组阶段,他会以循环的形式去解决各个阶段的事件,每个阶段是一种相似队列的构造。
https://www.dynatrace.com/news/blog/all-you-need-to-know-to-really-understand-the-node-js-event-loop-and-its-metrics/
前面咱们来持续分享 EventLoop 的机制。
浏览器内核会在其它线程中执行异步操作,当操作实现后,将操作后果以及当时定义的回调函数放入 JavaScript 主线程的工作队列中。
JavaScript 主线程会在执行栈清空后,读取工作队列,读取到工作队列中的函数后,将该函数入栈,始终运行直到执行栈清空,再次去读取工作队列,一直循环。
当主线程阻塞时,工作队列依然是可能被推入工作的。这也就是为什么当页面的 JavaScript 过程阻塞时,咱们触发的点击等事件,会在过程复原后顺次执行。
图解浏览器中的 JavaScript 运行时线程
Promise.then 的回调是异步执行的
之所以 promise 无论有没有真的走异步,但 then 始终要异步,是因为标准要求的。onFulfilled 必须在执行上下文栈(Execution Context Stack) 只蕴含 平台代码(platform code) 后能力执行。平台代码指引擎,环境,Promise 实现代码等。实际上来说,这个要求保障了 onFulfilled 的异步执行(以全新的栈),在 then 被调用的这个事件循环之后
也就是说,必须等到所有业务代码执行完(相当于 resolve 之前存在的所有 macro 和 micro 都做完,再执行)。其实对咱们关注业务代码来说,相当于 then 外面的代码就是异步执行。
macroTask 和 microtask
JavaScript 中的工作又分为 MacroTask 与 MicroTask 两种,在 ES2015 中 MacroTask 即指 Task,而 MicroTask 则是指代 Job
(macro)task 次要蕴含:script(整体代码)、setTimeout、setInterval、I/O(包含网络)、UI 交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境),
自测发现: macrotask 队列也不止一个
(macro)task 次要蕴含:script(整体代码)、setTimeout、setInterval、I/O(包含网络)、UI 交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境),requestAnimationFrame(这个比拟非凡,他跟 ui render 相干)
能够看到:如果做一个试验(先让页面产生一个 setTimeout3 秒,而后让页面 sleep6 秒,sleep 第 4 秒去点击页面 click),最终鼠标点击事件要在 setTimeout 之前触发。哪怕 setTimeout 事件放入队列要早于 click.
为了验证本人的论断,我写了这样一个试验:
发现,dom 事件会比 setTimeout 更早触发。
UI render 在事件循环中什么地位?
能够看到,其实,实际上浏览器渲染的触发并不是每次事件循环都会触发,他会以大概 60 帧每秒的频率来触发。
如果一次循环内没有触发 render,那你的 requestAnimation 回调也不会执行
温习 UI 渲染的根本流程:
Promise 的 microtask 何时放入队列?
Promise 退出 microtask 的程序 是以 then 触发时为根据的。且对于曾经 resolve 的 promise,then 注册时也会立即放入 microtask 队列。
例子:
网上常说的 Tick 是什么意思。何为一个 Tick?
Tick 是否蕴含 microTask 队列清空的这一部分工夫?待定,我感觉看你怎么了解了。
vue 官网文档认为把 dom 更新放到 microTask 里就称作“when in the next tick”, 而且 vue 把 nextTick 这个函数名的实现细节实现为 microTask,看似都是把 nexttick 这个概念认为是“执行完以后一个 macroTask 工作之后,下一个 macroTask 工作开始之前”
而 node 中的 nextTick,也是放在 microTask 队列。其 microTask 是在 macroTask 执行过程的两个阶段之间执行。它的粒度是“清空一个阶段的 macroTask 工作之后,执行下一个阶段的 macroTask 工作之前”
总之,他们 nextTick 都有点像两个大粒度之间插入的工作。
但咱们不要被 nextTick 名字误导,我感觉能够了解为下一个 Tick 之前要执行的货色,所以函数名叫 nextTick。
对于这个问题,在 Stack Overflow 下面有个问题:
https://stackoverflow.com/questions/47508649/when-does-the-event-loop-turn
对于 microtask 执行 到底是在一个 tick(loop turn 或 loop iterator)外面,不是很重要。然而为了能有个规范,我偏向于把它归类在一个 tick 外面。
HTML 标准中对 EventLoop 的形容:
浏览器与 Node.js 的 EventLoop 机制的区别
- 浏览器中是 “ 执行一个 macroTask+ 清空 microtask 队列”,顺次循环
- Node.js 中是 “ 执行一个 macroTask 队列的所有工作 + 清空 microtask 队列 ”; 再取下一种 macroTask 队列