以下是本人在过往一直总结的一些对于 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队列