理解浏览器的异步秘密:事件循环、宏任务和微任务

浏览器事件循环介绍

浏览器是单线程的,代码在执行栈(Call Stack)中执行,这意味着它在任何时候只能执行一个任务。然而,这并不意味着浏览器无法处理多个任务。为了实现这一点,浏览器引入了事件循环(Event Loop)机制。浏览器中有很多操作是异步的,比如网络请求、定时器、用户交互等。浏览器异步处理完这些任务,将执行结果放到回调任务队列(Queue),事件循环机制会不断地检查任务队列,当执行栈空闲时,如果队列中有任务,就取出任务并执行,然后再检查下一个任务,如此循环往复,直到任务队列为空。

宏任务(macrotask)和微任务(microtask)

在浏览器事件循环中,任务被分为两种:宏任务(macrotask)和微任务(microtask),相应的有两个任务队列。宏任务包括像setTimeout、setInterval、setImmediate、I/O操作等,这些任务会被放入宏任务队列。而微任务包括Promise、MutationObserver、queueMicrotask等,这些任务会被放入微任务队列。

在每一次事件循环中,浏览器首先会执行一个宏任务,然后执行所有的微任务。也就是说,微任务的优先级高于宏任务。如果在执行宏任务的过程中产生了微任务,那么这些微任务会在当前宏任务结束后,下一个宏任务开始前被执行。这样可以保证更快的响应,因为微任务通常用于处理一些更紧急的、需要快速响应的操作。

javascript 复制代码
// 宏任务、微任务执行流程
while (true) {
  macrotask = macroqueue.pop();
  execute(macrotask);
  // 需要执行完微任务队列的任务再进入下个循环
  while (microtask.hasTask()) {
    doMicrotask()
  }
}

代码示例分析

下面通过代码示例来验证宏任务和微任务的执行流程

示例1:

javascript 复制代码
// 宏任务
setTimeout(function macrotask1() {
  console.log("宏任务1");
}, 0);

// 微任务
Promise.resolve().then(function microtask1() {
  console.log("微任务1");
});

// 微任务
Promise.resolve().then(function microtask2() {
  console.log("微任务2");
});

// 宏任务
setTimeout(function macrotask2() {
  console.log("宏任务2");
}, 0);

// 输出结果
console.log("同步任务");

// 输出顺序:
// 同步任务
// 微任务1
// 微任务2
// 宏任务1
// 宏任务2

通过Chrome的Performance可以看到,在执行下一个宏任务之前,会先执行微任务队列。两个timer宏任务1和宏任务2在接下来的事件循环中依次执行。

示例2:

javascript 复制代码
console.log('开始');

setTimeout(function macrotask1() {
    console.log('宏任务1');
    Promise.resolve().then(function microtask1() {
        console.log('微任务1');
    });
}, 0);

Promise.resolve().then(function microtask2() {
    console.log('微任务2');
    setTimeout(function macrotask2() {
        console.log('宏任务2');
    }, 0);
}).then(function microtask3() {
    console.log('微任务3');
});

console.log('结束');

// 输出顺序:
// 开始
// 结束
// 微任务2
// 微任务3
// 宏任务1
// 微任务1
// 宏任务2

由图可见,在第一个任务循环中执行了微任务2和3,在第二个循环中执行宏任务1,因为宏任务1中创建了微任务1,所以会在当前循环中执行微任务1,最后再到下一个循环执行宏任务2

为什么需要微任务

微任务是更高优先级的任务队列,提供了一种事件插队的机制,在一次事件循环中,会先执行当前的所有微任务,然后再执行下一个宏任务。这样可以确保微任务在宏任务之前执行,避免了因为队列中有很多任务时导致执行阻塞,数据没有及时更新的问题。

例如,我们通过Promise实现异步数据获取,在then方法中更新UI,这样在保证异步编程的同时,UI也得到了及时的更新。

javascript 复制代码
  fetchData().then((data) => {
    updateUI(data);
  });

总结

本文介绍了浏览器的事件循环机制以及宏任务和微任务的区别。总的来说,宏任务和微任务是前端开发中处理异步操作的基础,浏览器的事件循环机制通过宏任务和微任务,实现了同步和异步操作的统一调度。

了解宏任务和微任务的执行顺序,可以帮助我们更好地理解和使用异步编程模式,并且更好地理解和预测你的代码行为。通过合理地使用宏任务和微任务,我们可以优化代码的性能和响应速度,提高应用程序的稳定性和可维护性。

相关推荐
汪汪队长1 小时前
谷歌浏览器自定义油猴插件
前端
ZFSS1 小时前
SeeDance Tasks API 的对接和使用
前端·人工智能
睿智的仓鼠1 小时前
🦞OpenClaw 快速部署及使用指南
前端·人工智能
前端付豪1 小时前
Nest 项目小实践之图书增删改查
前端·node.js·nestjs
比特鹰1 小时前
手把手带你用Flutter手搓人生K线
前端·javascript·flutter
奔跑路上的Me1 小时前
前端导出 Word/Excel/PDF 文件
前端·javascript
bluceli1 小时前
JavaScript异步编程深度解析:从回调到Async Await的演进之路
前端·javascript
青青家的小灰灰1 小时前
Vue 3 新标准:<script setup> 核心特性、宏命令与避坑指南
前端·vue.js·面试
SuperEugene1 小时前
路由与布局骨架篇:布局系统 | 头部、侧边栏、内容区、面包屑的拆分与复用
前端·javascript·vue.js
大金乄1 小时前
用canvans画一个流程图
前端