【前端基础】JavaScript 的事件循环机制(Event loop)

1,182 阅读2分钟

前言

我们知道 JavaScript 是一门单线程的非阻塞脚本语言,单线程意味着 js 在执行任何任务时,都只有一个主线程来处理所有的任务。

那么 JavaScript 引擎是如何实现这一点的呢?这就要说到 JavaScript 的事件循环机制(Event Loop)了:

基本流程

我们可以用个流程图来描述一下这个过程:

详细解释如下~~

  1. 同步任务和异步任务分别进入不同的执行环境,同步任务进入主线程,异步任务进入 Event Table 并注册函数
  2. 当指定的事情完成后,Event Table 会将这个函数移入到 Event Queue 中
  3. 主线程内的任务执行完毕,就会去 Event Queue 中读取对应的函数,进入主线程执行
  4. 上述过程不断重复,形成 Event Loop

从上面的解释我们可以初步得出一个结论:

同步任务比异步任务先执行

用代码来表现如下:

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

// 输出 1 3 2

我们接下来可以了解一些更复杂点的例子,了解之前我们需要知道 宏任务微任务

宏任务和微任务

宏任务和微任务均属于异步任务,同属于队列,他们主要区别在于:** 微任务比宏任务更先执行 **

宏任务有:setTimeout, setInterval, setImmediate,I/O,UI rendering

微任务有:process.nextTick,promise.then,MutationObserver

到这里我们可以来看个小🌰:

console.log('1');
setTimeout(() => {
  console.log('2')
}, 1000);
new Promise((resolve, reject) => {
  setTimeout(() => {
      console.log('3');
  }, 0);
  console.log('4');
  resolve();
  console.log('5');
}).then(() => {
  console.log('6');
});
console.log('7');

输出:
1 4 5 7 6 3 2

首先执行到第一个 setTimeout 的时候,js 会把这个任务放进队列等待执行,接下去执行同步函数,在执行到 new Promise 的时候会立马新建一个 promise 对象并立即执行。所以会输出 1,4,5,而 then 会放在微任务队列中,两个 setTimeout 会放到宏任务队列中。

所以执行顺序是:同步 - promise 同步 - 微任务 then - 宏任务 setTimeout

所以输出结果: 1 - 4 - 5 - 7 - 6 - 3 - 2

再来一个复杂一点的🌰:

console.log(1)
process.nextTick(() => {
  console.log(8)
  setTimeout(() => {
    console.log(9)
  })
})
setTimeout(() => {
  console.log(2)
  new Promise(() => {
    console.log(11)
  })
})
let promise = new Promise((resolve,reject) => {
  setTimeout(() => {
    console.log(10)
  })
  resolve()
  console.log(4)
})
fn()
console.log(3)
promise.then(() => {
  console.log(12)
})
function fn(){
  console.log(6)
}

1 - 4 - 6 - 3 - 8 - 12 - 2 - 11 - 10 - 9

执行顺序:先从头到尾找出第一层级的同步任务 - 找出第一层级的微任务(包括里面的同步任务) - 找出第一层级的宏任务(包括里面的同步任务)

如果第一层的宏任务微任务中有深层次的宏任务微任务,push 到 queue 中。