浏览器事件循环机制Event Loop
前言
学习概念之前让我们来看几行简单的代码:
console.log(1) setTimeout(() => { console.log(2); }, 0); const promise = new Promise((resolve, reject) => { console.log(3); resolve(); }); promise.then(() => { console.log(4); }); console.log(5); 复制代码
输出结果如下:
1 3 5 4 2 复制代码
和你预想的结果一样吗?
我经常在工作中遇到这种类似的问题,我明明想让它先执行,他就是后执行,导致页面渲染的各种问题。
想要弄清楚为什么输出结果是这样的,我们就需要了解浏览器的事件循环机制。
事件循环机制
JavaScript 事件循环机制分为: 浏览器事件循环机制和 Node 事件循环机制,两者的实现技术不一样,浏览器 Event Loop 是 HTML 中定义的规范,Node Event Loop 是由 libuv 库实现。 我们只讲浏览器事件循环机制。 浏览器执行 js 代码大致可以分为三个步骤,而这三个步骤的循环构成了 js 的事件循环机制(如上图所示)。
主线程(js引擎线程)中执行宏任务(JS整体代码或回调函数),执行过程中会将对象存储到堆(heap)中,将函数的参数和局部变量加入到栈(stack)中,执行完毕后会释放堆或退出栈。
执行完这个宏任务后,会判断微任务队列是否为空,如果不为空,则会将所有的微任务依次取出并执行
。如果在这个过程中触发了任何 Web APIs 将进行第二步操作。调用 Web API,并在合适的时候将回调函数加入到事件回调队列(event queue)中。比如执行了
setTimeout(callback1, 1000)
,会创建一个计时器,并且在另一个线程(浏览器定时触发线程)里面监听计时器是否过期,等到计时器过期后,会将对应回调callback1
加入事件回调队列中。等到
第一步中的微任务执行完毕之后,会判断事件回调队列是否为空。如果不为空,则会取出并执行最先进入队列的回调函数,执行过程如同第一步
。如果为空,则会视情况进行等待或挂起主线程。
一句话总结:先执行一个宏任务,再执行这个宏任务产生的对应微任务,执行完毕后,再执行后面的宏任务,以此往复。
宏任务、微任务
宏任务macro-task
Script整体代码、setTimeout、setInterval、I/O操作、UI rendering
微任务micro-task
new Promise().then中的内容、MutationObserve(前端的回溯)
了解完了宏任务与微任务的分类和js执行宏任务与微任务的顺序,我们再来看开头的那个例子
console.log(1) setTimeout(() => { console.log(2); }, 0); const promise = new Promise((resolve, reject) => { console.log(3); resolve(); }); promise.then(() => { console.log(4); }); console.log(5); 复制代码
第一次宏任务(整体代码):输出1,遇到setTimeout加入到宏任务队列(等待执行),遇到promise正常输出3,遇到.then()加入到微任务队列,输出5,本次宏任务执行完毕,共输出1 3 5
第一次宏任务执行完毕,清空这个宏任务产生的微任务队列,输出4
检查宏任务队列,发现还有一个宏任务setTimeout,执行该任务,输出2
最终结果是1 3 5 4 2
趁热打铁
js 执行顺序:先执行一个宏任务,再执行这个宏任务产生的对应微任务,执行完毕后,再执行后面的宏任务,以此往复。
function func1() { console.log('func1'); Promise.resolve().then(() => { console.log('microtask.promise1'); }); } function func2() { console.log('func2'); Promise.resolve().then(() => { console.log('microtask.promise2'); }); } function main() { func1(); func2(); setTimeout(func1, 0); setTimeout(func2, 0); } main() 复制代码
输出结果如下:
// 第一次宏任务(整体代码)执行 func1 func2 microtask.promise1 microtask.promise2 // 第二次宏任务(setTimeout(func1, 0)) func1 microtask.promise1 // 第三次宏任务(setTimeout(func2, 0)) func2 microtask.promise2 复制代码
是不是已经完全懂了,你要相信你是最棒的。
作者:摸鱼吹泡泡
链接:https://juejin.cn/post/7023640113632509983