JavaScript EventLoop详解

393 阅读2分钟

定义

在计算机科学中,事件循环是一种编程构造或设计模式,用于等待和调度程序中的事件或消息。事件循环的工作原理是向某个内部或外部“事件提供程序”(通常会阻止请求,直到事件到达),然后调用相关的事件处理程序(“调度事件”)。事件循环有时也称为消息调度程序、消息循环、消息泵或运行循环 (维基百科对Event loop的定义)

JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其他语言中的模型截然不同,比如 C 和 Java (MDN对Javascript Event loop的介绍)

前置知识

  1. JS引擎 单线程执行机制
  2. 宏任务 & 微任务

进入正题

JS引擎执行任务是基于EventLoop的,大致流程为:等待任务->依次执行任务->等待任务-> ...往复循环,因此称为loop,如下图:

eventloop0.jpg 图片来源: javascript.info/event-loop

任务队列遵循“先进先出”的原则,如上图所示,JS引擎依次执行script,然后是mousemovesetTimeout。执行完毕后,进入下一个循环。

需要注意的是:DOM更新只会发生在一个任务结束后,无论该任务有多耗时。

event-loop-1.jpg 图片来源: javascript.info/event-loop

使用场景

  1. 大任务分解,提升用户体验

前端执行比较耗时的任务,比如请求到地图数据后计算组装等。在任务执行期间,JS引擎无法响应用户操作,页面会陷入暂时“卡死”状态。

let i = 0;

let start = Date.now();

function count() {

  // do a heavy job
  for (let j = 0; j < 1e9; j++) {
    i++;
  }

  alert("Done in " + (Date.now() - start) + 'ms');
}

count();

通过将大任务拆成若干个小任务,在小任务执行间隙,JS引擎可以响应用户操作,DOM更新等,带来更好的体验,并且不会额外消耗太多执行时间。

let i = 0;

let start = Date.now();

function count() {

  // move the scheduling to the beginning
  if (i < 1e9 - 1e6) {
    setTimeout(count); // schedule the new call
  }

  do {
    i++;
  } while (i % 1e6 != 0);

  if (i == 1e9) {
    alert("Done in " + (Date.now() - start) + 'ms');
  }

}

count();

  1. 进度条展示

DOM更新发生在多个任务之间,所以正常来说在一个任务内多次改变一个值,用户看不到其变化过程,展现在用户面前的只有该值的初始状态和最终状态。

<div id="progress"></div>

<script>

  function count() {
    for (let i = 0; i < 1e6; i++) {
      i++;
      progress.innerHTML = i;
    }
  }

  count();
</script>

如果我们想要展现其变化过程,如计数器、进度条等,可以使用setTimeout来将大任务拆成多个小任务,DOM会在小任务之间更新,因此用户便可以看到DOM的变化过程了。

<div id="progress"></div>

<script>
  let i = 0;

  function count() {

    // do a piece of the heavy job (*)
    do {
      i++;
      progress.innerHTML = i;
    } while (i % 1e3 != 0);

    if (i < 1e7) {
      setTimeout(count);
    }

  }

  count();
</script>

考察题目

请写出以下代码执行后,打印的顺序

题目一

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
console.log('script start')
setTimeout(function () {
  console.log('settimeout')
})
async1()
new Promise(function (resolve) {
  console.log('promise1')
  resolve()
}).then(function () {
  console.log('promise2')
})
console.log('script end')

题目二

setTimeout(() => {
    console.log('setTimeout start');
    new Promise((resolve) => {
      console.log('promise1 start');
      resolve();
    }).then(() => {
      console.log('promise1 end');
    })
    console.log('setTimeout end');
  }, 0);
  function promise2() {
    return new Promise((resolve) => {
      console.log('promise2');
      resolve();
    })
  }
  async function async1() {
    console.log('async1 start');
    await promise2();
    console.log('async1 end');
  }
  async1();
  console.log('script end');

总结与思考

  1. 理解EventLoop有助于开发者排查问题、写出更优雅的代码,带来更好的体验
  2. 可以将JavascriptNode.jsEventLoop进行对比,分析其异同点
  3. 分析某个知识点时,可以顺着主线,抓住重点,参考大量文章材料后,可以先画一个思维导图再下笔

参考文章

javascript.info/event-loop

developer.mozilla.org/en-US/docs/…

jakearchibald.com/2015/tasks-…

html.spec.whatwg.org/multipage/w…