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

浏览器事件循环介绍

浏览器是单线程的,代码在执行栈(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);
  });

总结

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

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

相关推荐
LanceJiang1 分钟前
前端检测版本更新-Worker 项目实践
前端
月光冷青衫3 分钟前
uniapp-微信小程序-map组件-自定义marker
前端
jeff渣渣富4 分钟前
使用 AST 处理输入字段的关联计算问题
前端·javascript
百度地图开放平台14 分钟前
LBS 开发微课堂|智能调度API升级:解决循环取货场景下的调度难题
前端·javascript
cypking31 分钟前
vue实现一个pdf在线预览,pdf选择文本并提取复制文字触发弹窗效果
前端·vue.js·pdf
飘逸飘逸34 分钟前
若依前后端分离版使用Electron打包前端Vue为Exe文件
前端·vue.js·electron·vue·ruoyi
入门级前端开发34 分钟前
npm install 报错ERESOLVE
前端·npm·node.js
anyup1 小时前
最终!我还是抛弃了 VSCode 这个开发工具
前端·aigc·visual studio code
木亦Sam2 小时前
前端安全之 CSRF 攻击的防御策略
前端
光影少年2 小时前
es6+新增特性有哪些
前端·javascript·es6