JavaScript 之 Event Loop

346 阅读3分钟

概述

JavaScript 属于单线程语言(除了 web work)。单线程,必然导致了代码需要一行行的去执行,但是一些耗时的操作(http 请求等),如果还是使用同步的方式,必然会导致页面阻塞,所以这个时候就需要异步操作。在 JavaScript 中,处理这种异步操作的机制就是 事件循环(Event Loop)

Event Loop

执行逻辑

JavaScript 从上到下开始执行代码,当遇到异步任务时,将异步任务放到任务队列中,当整个文件的代码都执行完成后(也可以理解成执行栈中的代码都执行成),则开始从任务队列(不一定是先入先出)中取任务执行,任务队列中的任务都执行完成后,这个过程(从任务队列中取任务)依然不停止,只是进行等待,直到下一个任务的到来,整个过程就叫 事件循环(Event Loop)

console.log(111);
setTimeout(() => {
  console.log(222);
}, 0)
console.log(333);

// 执行结果
// 111
// 333
// 222

从上面的代码就可以看到,哪怕 setTimeout 设置的延时时间为 0, 其中的方法也是先被放在任务队列中,等所有的代码都执行完成了,才从任务队列中取出并执行。

任务队列

任务对队列分为 宏任务微任务 两类,其中 微任务 执行的优先级高于 宏任务

  1. js 执行任务时,有个原则就是,不会停止执行当前任务,不管什么优先级的任务进来,也会将当前任务执行完;
  2. Event Loop 期间,当从任务队列取事件时,优先取 微任务 中的数据,当 微任务 中的数据执行完成后,再取 宏任务 中的数据;需要多说一点的是,早期的 js 没有 微任务,但是后来觉得需要有优先级,如 setTimeout 这种不紧要的异步,可以放在后面执行,这块就是说明了从任务队列取数据 不一定是先入先出

宏任务

  1. script (可以理解为外层同步代码);
  2. setTimeout/setInterval;
  3. UI rendering/UI事件;
  4. postMessage,MessageChannel;
  5. setImmediate,I/O(Node.js).

微任务

可以说,微任务都是比较新的一些功能(相对宏任务来说)。

  1. Promise;
  2. process.nextTick(Node.js);
  3. Object.observe(已废弃;Proxy 对象替代);
  4. MutaionObserver

示例

  1. 先执行了 console.log(111);
  2. setTimeout 属于异步操作,添加到任务队列(宏任务);
  3. 构造函数 Promise 中的代码属于同步任务,就算 Promise 中的代码没有异步操作,then 部分的代码,依然会被扔到任务队列(微任务)中;
  4. 执行 console.log(555);
  5. 由于 微任务 的优先级高于 宏任务,所以先执行 .then 部分的代码,打印 444;
  6. 微任务 中的代码都执行完成后,在执行 宏任务 中的代码,打印 console.log(222);
console.log(111);

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

new Promise((resolve) => {
  console.log(333);
  resolve();
})
  .then(() => {
    console.log(444);
  });

console.log(555);

// 执行结果
// 111
// 333
// 555
// 444
// 222