0


【原理讲解+面试题分析】JS事件循环-微任务-宏任务你真的明白了吗

文章目录

前言

JS代码在运行时,有两种运行环境。一是在浏览器中,二是在node中。由于JS线程是单线程,在运行JS代码时,可能会遇到比较耗时的操作,比如setTimeout,或者是发送网络请求等,又由于JS线程是单线程,如果在解析耗时的代码时候,停在了这里,那执行代码的性能将是比较低的。为了解决此问题,在浏览器、node环境下,其实是有事件循环机制的。

一、浏览器的事件循环

1、浏览器的事件循环

JS线程执行代码时候,遇到比较耗时的操作时,将这些操作交给浏览器去处理,然后这些操作会根据不同的种类放进微任务队列或者宏任务队列,宏任务队列和微任务队列都不为空的时候,只有等微任务队列为空,即微任务队列里面的事件全部都执行完之后,才会再去让宏任务队列中的事件出栈,之后交由JS线程去处理,执行代码。
事件循环大概就是如图所示的流程:
在这里插入图片描述

2、浏览器的宏任务、微任务

其实,在浏览器拿到那些有些不能同步处理的事件的时候,有的会加入宏任务队列,有的会加入微任务队列,那么一般我们如何区分呢?
一般情况下:加入宏任务队列和微任务队列的事件如下:
宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
微任务队列(microtask queue):Promise的then回调、 Mutation Observer API、queueMicrotask()。
那么这些事件的执行顺序是怎么样子的呢?
首先,有一个原则,宏任务队列里面的事件,要执行的话,一定是在确保微任务队列为空的情况下,即微任务队列里面的事件全部执行完的情况。其次,main script里面的内容是最先执行的,由此,可以得到执行顺序为:main script > 微任务队列里面的事件 > 宏任务里面的事件。

3、面试题一

题目如下:

setTimeout(function(){
  console.log("setTimeout1");newPromise(function(resolve){resolve();}).then(function(){newPromise(function(resolve){resolve();}).then(function(){
      console.log("then4");});
    console.log("then2");});});newPromise(function(resolve){
  console.log("promise1");resolve();}).then(function(){
  console.log("then1");});setTimeout(function(){
  console.log("setTimeout2");});

console.log(2);queueMicrotask(()=>{
  console.log("queueMicrotask1")});newPromise(function(resolve){resolve();}).then(function(){
  console.log("then3");});// promise1// 2// then1// queueMicrotask1// then3// setTimeout1// then2// then4// setTimeout2

分析如下:
在第一个事件循环里面,main script、宏任务、微任务里面的事件如下:
在判断加入宏任务队列还是微任务队列时候,遵循如下原则:

宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
微任务队列(microtask queue):Promise的then回调、 Mutation Observer
API、queueMicrotask()。

按照这个原则,第一轮事件循环里面的事件如下:

在这里插入图片描述
先执行main script、然后微任务队列里面的,最后是宏任务队列里面的
// promise1
// 2
// then1
// queueMicrotask1
// then3
之后执行setTimeout1的宏任务,此时第二轮事件循环里面的内容如下:
在这里插入图片描述
第二轮事件循环执行内容如下:
// setTimeout1
// then2
// then4
// setTimeout2
综上:最后执行结果为:

// promise1// 2// then1// queueMicrotask1// then3// setTimeout1// then2// then4// setTimeout2

4、面试题二

题目如下:

// async function bar() {//   console.log("22222")//   return new Promise((resolve) => {//     resolve()//   })// }// async function foo() {//   console.log("111111")//   await bar()//   console.log("33333")// }// foo()// console.log("444444")asyncfunctionasync1(){
  console.log('async1 start')awaitasync2();
  console.log('async1 end')}asyncfunctionasync2(){
  console.log('async2')}

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

console.log('script end')// script start// async1 start// async2// promise1// script end// async1 end// promise2// setTimeout

第一轮事件循环里面的事件如下:
在这里插入图片描述
然后按照顺序执行,最后结果如下:

// script start// async1 start// async2// promise1// script end// async1 end// promise2// setTimeout

二、node的事件循环

1、node的事件循环

浏览器的事件循环是是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的。
首先我们看一下node的架构图:
我们可以从图中大致看出,事件循环是在libuv中实现的,libuv主要维护的是一个事件循环(Event Loop)和 线程池(worker threads)。libuv是一个多平台的专注于异步IO的库,它最初是为Node开发的,但是现在也被使用到Luvit、Julia、pyuv等其
他地方;
EventLoop负责调用系统的一些其他操作:文件的IO、Network、child-processes等在这里插入图片描述

由图可以看出,事件循环就像是一个桥梁,是连接着应用程序的JavaScript(左边部分)和系统调用(右边线程池部分)之间的通道:
无论是我们的文件IO、数据库、网络IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中;
事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行;
但是一次完整的事件循环Tick分成很多个阶段:

  1. 定时器(Timers):本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
  2. 待定回调(Pending Callback):对某些系统操作(如TCP错误类型)执行回调,比如TCP连接时接收到ECONNREFUSED。
  3. idle, prepare:仅系统内部使用。
  4. 轮询(Poll):检索新的 I/O 事件;执行与 I/O 相关的回调;
  5. 检测(check):setImmediate() 回调函数在这里执行。
  6. 关闭的回调函数:一些关闭的回调函数,如:socket.on(‘close’, …)

在这里插入图片描述

2、node的宏任务、微任务

node中也有微任务和宏任务,执行的原则和在浏览器中一样,是先执行微任务,然后再执行宏任务,但是对于宏任务来说,是按照上图从上到下的顺序执行的。
具体对应的常见事件的执行顺序如下;

在微任务队列中:

  • next tick queue:process.nextTick;
  • other queue:Promise的then回调、queueMicrotask;

(是按照从上往下的事件顺序执行)

在宏任务队列:

  • timer queue:setTimeout、setInterval;
  • poll queue:IO事件;
  • check queue:setImmediate;
  • close queue:close事件

(同样是按照从上往下的事件顺序执行)

所以,综上所述,在每一次事件循环的tick中,会按照如下顺序来执行代码:

next tick microtask queue;
other microtask queue;
timer queue;
poll queue;
check queue;
close queue

当然,main script 依旧是最先执行的,只有main script执行结束后,才会按照上述顺序来执行代码。

3、面试题一

asyncfunctionasync1(){
  console.log('async1 start')awaitasync2()
  console.log('async1 end')}asyncfunctionasync2(){
  console.log('async2')}

console.log('script start')setTimeout(function(){
  console.log('setTimeout0')},0)setTimeout(function(){
  console.log('setTimeout2')},300)setImmediate(()=> console.log('setImmediate'));

process.nextTick(()=> console.log('nextTick1'));async1();

process.nextTick(()=> console.log('nextTick2'));newPromise(function(resolve){
  console.log('promise1')resolve();
  console.log('promise2')}).then(function(){
  console.log('promise3')})

console.log('script end')// script start// async1 start// async2// promise1// promise2// script end// nexttick1// nexttick2// async1 end// promise3// settimetout0// setImmediate// setTimeout2

第一轮事件循环里面的事件如下:
在这里插入图片描述
按照顺序自左向右执行,3s后执行setTimeout2,
最后的结果是:

// script start// async1 start// async2// promise1// promise2// script end// nexttick1// nexttick2// async1 end// promise3// settimetout0// setImmediate// setTimeout2

本文转载自: https://blog.csdn.net/weixin_50926010/article/details/124759093
版权归原作者 既白biu 所有, 如有侵权,请联系我们删除。

“【原理讲解+面试题分析】JS事件循环-微任务-宏任务你真的明白了吗”的评论:

还没有评论