彻底了解js事件循环怎么处理宏任务、微任务

入坑互联网

共 2955字,需浏览 6分钟

 · 2022-01-26

关注 入坑互联网 ,回复“加群

加入我们一起学习,天天进步


Event Loop,事件循环,线程进程。这些概念对初识前端的同学来说可能会一头雾水。而且运行js代码的运行环境除了浏览器还有node。因此不同环境处理Event Loop又变得不同,十分容易混淆。

首先看一段代码:

console.log('script start');
setTimeout(function() { console.log('setTimeout');}, 0);
Promise.resolve().then(function() { console.log('promise1');}).then(function() { console.log('promise2');});
console.log('script end');

打印顺序是什么?

正确答案是

script start, script end, promise1, promise2, setTimeout

为什么会出现这样打印顺序呢?

要理解这些你首先需要对事件循环机制处理宏任务和微任务的方式有了解。

每个线程都会有它自己的event loop(事件循环),所以都能独立运行。然而所有同源窗口会共享一个event loop以同步通信。event loop会一直运行,来执行进入队列的宏任务。一个event loop有多种的宏任务源(译者注:event等等),这些宏任务源保证了在本任务源内的顺序。但是浏览器每次都会选择一个源中的一个宏任务去执行。这保证了浏览器给与一些宏任务(如用户输入)以更高的优先级。

宏任务(task)

浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)

鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTMl。还有下面这个setTimeout,setTimeout的作用是等待给定的时间后为它的回调产生一个新的宏任务。这就是为什么打印‘setTimeout’在‘script end’之后。因为打印‘script end’是第一个宏任务里面的事情,而‘setTimeout’是另一个独立的任务里面打印的。

微任务(Microtasks )

微任务通常来说就是需要在当前 task 执行结束后立即执行的任务,比如对一系列动作做出反馈,或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。微任务包括了mutation observe的回调还有接下来的例子promise的回调。

一旦一个pormise有了结果,或者早已有了结果(有了结果是指这个promise到了fulfilled或rejected状态),他就会为它的回调产生一个微任务,这就保证了回调异步的执行即使这个promise早已有了结果。所以对一个已经有了结果的promise调用.then(yey, nay)会立即产生一个微任务。这就是为什么‘promise1’,'promise2'会打印在‘script end’之后,因为所有微任务执行的时候,当前执行栈的代码必须已经执行完毕。‘promise1’,'promise2'会打印在‘setTimeout’之前是因为所有微任务总会在下一个宏任务之前全部执行完毕。

如何分辨宏任务和微任务?

现在先对前面那个例子进行解释:

console.log('script start');
setTimeout(function() { console.log('setTimeout');}, 0);
Promise.resolve().then(function() { console.log('promise1');}).then(function() { console.log('promise2');});
console.log('script end');

JS中分为两种任务类型:macrotask和microtask,在ECMAScript中,microtask称为jobs,macrotask可称为task

macrotask(又称之为宏任务)

1、每一个task会从头到尾将这个任务执行完毕,不会执行其它

2、浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)

microtask(又称为微任务),可以理解是在当前task 执行结束后立即执行的任务

1、也就是说,在当前task任务后,下一个task之前,在渲染之前

2、它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染

3、在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)

我们分步骤来进行这个例子过程解答

js执行引擎开始执行上述代码时,会先进一个main()方法加入执行栈。console.log方法是一个webkit内核支持的普通方法,而不是WebAPIs涉及的方法,所以这里当遇到console.log立即出栈被引擎执行。

引擎继续往下,将setTimeout添加到执行栈。setTimeout()方法属于事件循环模型中WebAPIs中的方法,引擎在将setTimeout()方法出栈执行时,将延时执行的函数交给了相应模块,即图右方的timer模块来处理。

然后主线程继续向下执行,遇到promise立即执行,then方法存入微任务队列,promise执行之后,即状态变更之后,执行微任务中的then方法。

最后执行setTimeOut;

总结下运行机制:

执行一个宏任务(栈中没有就从事件队列中获取)

执行过程中如果遇到微任务,就将它添加到微任务的任务队列中

宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)

当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染

渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)


❤️ 看完三件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓)。
  2. 关注公众号「入坑互联网」,不定期分享原创知识。
  3. 也看看其它文章

如何做到优秀,好难!

面试题联盟之CSS篇

年底前端面试及答案-html/css

年底面试之es6总结

年底面试之JavaScript总结(用心收集)

- END -


结伴同行前端路




浏览 38
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报