阅读 107

浏览器事件循环机制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 库实现。 我们只讲浏览器事件循环机制。 event-loop.png 浏览器执行 js 代码大致可以分为三个步骤,而这三个步骤的循环构成了 js 的事件循环机制(如上图所示)。

  1. 主线程(js引擎线程)中执行宏任务(JS整体代码或回调函数),执行过程中会将对象存储到堆(heap)中,将函数的参数和局部变量加入到栈(stack)中,执行完毕后会释放堆或退出栈。执行完这个宏任务后,会判断微任务队列是否为空,如果不为空,则会将所有的微任务依次取出并执行。如果在这个过程中触发了任何 Web APIs 将进行第二步操作。

  2. 调用 Web API,并在合适的时候将回调函数加入到事件回调队列(event queue)中。比如执行了setTimeout(callback1, 1000),会创建一个计时器,并且在另一个线程(浏览器定时触发线程)里面监听计时器是否过期,等到计时器过期后,会将对应回调 callback1加入事件回调队列中。

  3. 等到第一步中的微任务执行完毕之后,会判断事件回调队列是否为空。如果不为空,则会取出并执行最先进入队列的回调函数,执行过程如同第一步。如果为空,则会视情况进行等待或挂起主线程。

一句话总结:先执行一个宏任务,再执行这个宏任务产生的对应微任务,执行完毕后,再执行后面的宏任务,以此往复。

宏任务、微任务

  • 宏任务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


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐