直接上题,答对解释通算你赢,就不用看解析了。
点击页面后,下面代码的输出结果是什么?
document.addEventListener('click', function(){
Promise.resolve().then(()=> console.log(1));
console.log(2);
})
document.addEventListener('click', function(){
Promise.resolve().then(()=> console.log(3));
console.log(4);
})
输出结果
2, 1, 4, 3
JS异步执行原理: js执行引擎只有一个主线程执行代码逻辑,遇到需要异步执行的任务代码,会将其添加事件队列中。当主线程空闲时,轮询事件队列中可以执行的任务,将其放到主线程进行执行,以此类推,直到事件队列中无可执行的任务。如下图所示:
JS引擎只是执行事件队列中的异步代码,但事件队列中的信息来源并不是JS引擎,而是由浏览器中的其他相关线程产生的,如下图所示:
以 http 传输线程为例: 最常见的就是 js 代码发出 ajax 请求,然后就是交给浏览器的http线程去处理了,当后端有数据返回时,http 线程在事件队列中生成一个数据已ready好的事件,等待 JS 主线程空闲时执行。
再比如,我们常见的click,mouse事件,都是GUI 事件触发线程生成的。当用户点击页面时,GUI 事件触发线程就会在事件队列中生成一个click事件,等待 JS 主线程空闲时执行。
浏览器中的事件循环的任务队列被划分为宏任务和微任务两种类型:
macrotask:包含执行整体的js代码
script
,事件回调,XHR回调,定时器(setTimeout
、setInterval
、setImmediate
),IO操作,UI render microtask:更新应用程序状态的任务,包括promise回调,MutationObserver
,process.nextTick
,Object.observe
mactotask & microtask的执行顺序如下图所示:
总结起来,一次事件循环的步骤包括:
回顾上面的事件循环示意图,update rendering(视图渲染)发生在本轮事件循环的microtask队列被执行完之后,也就是说执行任务的耗时会影响视图渲染的时机。通常浏览器以每秒60帧(60fps)的速率刷新页面,据说这个帧率最适合人眼交互,大概16.7ms渲染一帧,所以如果要让用户觉得顺畅,单个macrotask及它相关的所有microtask最好能在16.7ms内完成。
但也不是每轮事件循环都会执行视图更新,浏览器有自己的优化策略,例如把几次的视图更新累积到一起重绘,重绘之前会通知requestAnimationFrame执行回调函数,也就是说requestAnimationFrame回调的执行时机是在一次或多次事件循环的UI render阶段。
示例如下:
setTimeout(function() {console.log('timer1')}, 0)
requestAnimationFrame(function(){
console.log('UI update')
})
setTimeout(function() {console.log('timer2')}, 0)
new Promise(function executor(resolve) {
console.log('promise 1')
resolve()
console.log('promise 2')
}).then(function() {
console.log('promise then')
})
console.log('end')
可能输出结果:
promise 1, promise 2, end, promise then, timer1, timer2, UI update promise 1, promise 2, end, promise then, UI update, timer1, timer2
a. 执行macrotask队列的一个任务
b. 执行完当前microtask队列的所有任务
c. UI render
3. 浏览器只保证requestAnimationFrame的回调在重绘之前执行,没有确定的 时间,何时重绘由浏览器决定。
浏览器环境下,microtask 的任务队列是每个 macrotask 执行完之后执行。而在 Node.js 中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 队列的任务。
image
接下我们通过一个例子来说明两者区别:
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
浏览器端运行结果:timer1=>promise1=>timer2=>promise2
Node 端运行结果:timer1=>timer2=>promise1=>promise2
浏览器和 Node 环境下,microtask 任务队列的执行时机不同
具体可参考:https://blog.csdn.net/Fundebug/article/details/86487117