图解 Event Loop

本片文章力求用最简单的语言,分析出:Event Loop、宏任务&微任务、同步任务&异步任务、消息队列&宏任务队列之间的关系。

JavaScript是一门单线程语言,这是这门语言的骨子里的性质。但是我们可以通过浏览器的多核性质,再加上Event Loop(事件循环队列),让单线程的JS也可以实现多线程和异步。

事件循环队列由三个部分组成:调用栈(call stack)、消息队列(Message Queue)、微任务队列(Microtask Queue)。

图解 Event Loop

  • 宏任务:script(主程序代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering
  • 微任务:process.nextTick、Promises.then、Object.observe、MutationObserver

  • 微任务队列:Promise.then、async/swait、process.nextTick创建的异步任务。
  • 消息队列:fectch、setTimeout、setInterval等的回调(这些任务并不一定是立即被推入消息队列的,就setTimeout来说,假如定时器设置的时间为5000ms,就会在5000ms之后被推入消息队列中)。

  • 同步任务:script中,同步执行的代码。包括如new Promise部分,函数体的同步任务部分。
  • 异步任务:消息队列、微任务队列中的任务。

执行栈执行顺序:同步任务微任务队列中的任务 → 消息队列中的任务

在调用栈中,将全局执行上下文中的同步任务依次压入、执行、弹出(期间如果遇到函数,在函数调用时,顺带将函数体所在上下文中的同步任务给执行了)。异步任务中的宏任务(除script)推到消息队列中,微任务推到微任务队列中。待同步任务全部执行完毕后,此时,调用栈为空。接下来,先执行微任务队列中的任务,在微任务队列中的任务执行完后,然后去执行消息队列中的宏任务。这时如果有新的微任务加入,执行完当前宏任务,会先去执行微任务,执行完后,再执行剩下的宏任务。如果加入的是一个宏任务,就排到消息队列的最后面。

常见任务优先级:process.nextTick > Promises.then > setTimeout > setInterval

我们来一起看三个例子:

// 例1:
setTimeout(() => {
  console.log(1);
}, 0);

new Promise(resolve => {
  console.log(2);
  resolve();
  console.log(3);
}).then(() => {
  console.log(4);
});

console.log(5);

// 2 3 5 4 1

new Promise属于同步任务,先执行。Promise.then、setTimeout都属于异步任务。其次Promise.then又属于微任务,宏任务中的setTimeout会被放到消息队列中,根据上面的执行顺序,就可以知道:

同步任务 → 异步任务 ( 微任务队列 → 消息队列 )

// 例2:

setTimeout(() => {
  console.log(1);
},0);

new Promise((resolve,reject) => {
  console.log(2);
  setTimeout(() => {
    resolve();
  },0)
}).then(() => {
  console.log(3);
}).then(() => {
  console.log(4);
});

process.nextTick(() => {
  console.log(5);
});

console.log(6);

// 2 6 5 1 3 4

这个例子和例1的区别在于:在promise的构造中,没有同步的resolve,因此promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法,而这个resolve是在一个setTimout时间中完成的,因此3,4最后输出。

// 例3

var p = new Promise(resolve => {
  console.log(4);
  resolve(5);
});

function fun1() {
  console.log(1);
}

function fun2() {
  setTimeout(()=>{
    console.log(2);
  });
  fun1();
  console.log(3);
  p.then(resolved => {
    console.log(resolved);
  })
  .then(() => {
    console.log(6);
  });
}

func2();

// 4 1 3 5 6 2

图解 Event Loop

参考文章:

GItHub_从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue

bilibili_2分钟了解 JavaScript Event Loop | 面试必备

上一篇:python 全栈开发,Day5(函数初识)


下一篇:setinterval和settimeout的区别