一文带你轻松了解浏览器的事件循环机制

53 阅读4分钟

Event Loop 事件循环

参考:浏览器事件循环机制的动态演示

Event Loop的作用是什么?

回答:Event Loop 规定了JavaScript中的事件的执行顺序,解决了JavaScript单线程运行阻塞问题。

什么是单线程?

回答:单线程指的是一个程序或进程在任何时刻只有一个线程在执行。


为什么JavaScript是单线程运行?

回答:因为JavaScript涉及的初衷是用来作为一种脚本语言执行,处理简单的业务逻辑和用户交互。单线程符合当时的设计需求和技术限制。

Event Loop的执行机制是什么?

在单线程的执行模型中,任务(或数据项)按照它们被提交到执行队列的时间顺序进行排队,并遵循“先进先出”(FIFO, First In First Out)的原则进行处理。具体结构如下:

img

回到本问题,事件循环的执行流程是:宏任务(macrotask) > 微任务(microtask) > 界面渲染(GUI)

  • 执行宏任务(Macrotask) :宏任务是较大的任务单元,通常表示一整块代码的执行,如setTimeoutsetIntervalI/O操作等。
  • 执行微任务(Microtask) :微任务是更小、更精细的任务单元,常用于处理细粒度的操作,如Promise.thenMutationObserver等。在宏任务执行完毕后,会立即执行所有已注册的微任务。
  • 渲染界面(GUI) :在所有宏任务和微任务执行完成后,浏览器会进行页面的渲染更新。

案例解析

下面我们开始使用案例来解析事件循环,有下面一段代码:

<script>
setTimeout(function () {
  console.log(1);
}, 0);
new Promise(function (resolve, reject) {
  console.log(2);
  for (var i = 0; i < 10; i++) {
    resolve();;
  }
  console.log(3);
  setTimeout(function () {
    console.log(4);
  }, 0);
}).then(function () {
  console.log(5);
});
console.log(6);
setTimeout(function () {
  console.log(7);
}, 0);
</script>

代码解析:

  1. 整个<script>标签内的代码块被视为一个宏任务,其中的同步任务会被马上执行,所以输出结果是2,3,6
  2. 当前宏任务中的所有同步代码执行完毕后,检查并执行所有微任务,比如:promise.then,所以接下来的输出结果是5
  3. 宏任务中的异步任务会开启新一轮的事件循环,异步任务内部的代码将作为宏任务执行。所以接下来会开启三轮新的事件循环,并在宏任务的流程中依次输出是1,4,7

具体的执行顺序如下: image-20240904144701621

使用浏览器的performance功能进一步验证我们的猜想是正确,具体结果如下:

image-20240904145248228

总结

  • 第一轮事件循环:

    • 宏任务:2, 3, 6
    • 微任务:5
  • 第二轮事件循环:

    • 宏任务:1
  • 第三轮事件循环:

    • 宏任务:4
  • 第四轮事件循环:

    • 宏任务:7

异步编程

什么是异步编程?

异步编程是一种编程模式,它允许程序继续执行而不必等待某个长时间运行的操作完成。常见于客户端与服务器的交互,客户端可以在等待服务器响应的同时继续处理其他任务。这种模式提高了程序的效率和响应性,特别是在处理I/O操作、网络请求等耗时操作时。

单线程如何实现异步编程?

JavaScript通过引入任务队列(Event Queue)和事件循环(Event Loop)来实现异步编程。异步任务被放入任务队列中,主线程在完成当前任务后,会检查并执行队列中的异步任务。任务队列的数据结构是先进先出(FIFO)的队列,用于管理待处理的任务。

任务队列的数据结构如下

img

什么是同步任务和异步任务?

  • 同步任务:立即执行的任务代码,主线程需要等待任务完成后才能继续执行后续代码。
  • 异步任务:延迟执行的任务代码,主线程可以继续执行其他代码,异步任务在完成后通过回调、Promise、async/await 等方式处理结果。

异步任务通常会被放入任务队列中,主线程在处理完当前同步任务后会检查并执行队列中的异步任务。