直接上题,答对解释通算你赢,就不用看解析了。前端
点击页面后,下面代码的输出结果是什么?面试
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);
})
复制代码
输出结果ajax
2, 1, 4, 3后端
JS异步执行原理: js执行引擎只有一个主线程执行代码逻辑,遇到须要异步执行的任务代码,会将其添加事件队列中。当主线程空闲时,轮询事件队列中能够执行的任务,将其放到主线程进行执行,以此类推,直到事件队列中无可执行的任务。以下图所示:promise
JS引擎只是执行事件队列中的异步代码,但事件队列中的信息来源并非JS引擎,而是由浏览器中的其余相关线程产生的,以下图所示:浏览器
以 http 传输线程为例: 最多见的就是 js 代码发出 ajax 请求,而后就是交给浏览器的http线程去处理了,当后端有数据返回时,http 线程在事件队列中生成一个数据已ready好的事件,等待 JS 主线程空闲时执行。bash
再好比,咱们常见的click,mouse事件,都是GUI 事件触发线程生成的。当用户点击页面时,GUI 事件触发线程就会在事件队列中生成一个click事件,等待 JS 主线程空闲时执行。异步
浏览器中的事件循环的任务队列被划分为宏任务和微任务两种类型:函数
macrotask:包含执行总体的js代码
script
,事件回调,XHR回调,定时器(setTimeout
、setInterval
、setImmediate
),IO操做,UI renderoop
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
浏览器环境下,microtask 的任务队列是每一个 macrotask 执行完以后执行。而在 Node.js 中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 队列的任务。
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 任务队列的执行时机不一样
Node 端,microtask 在事件循环的各个阶段之间执行
浏览器端,microtask 在事件循环的 macrotask 执行完以后执行
具体可参考:blog.csdn.net/Fundebug/ar…
扫一扫 关注个人公众号【前端名狮】,更多精彩内容陪伴你!