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

浏览器事件循环介绍

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

总结

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

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

相关推荐
万叶学编程2 小时前
Day02-JavaScript-Vue
前端·javascript·vue.js
前端李易安4 小时前
Web常见的攻击方式及防御方法
前端
PythonFun4 小时前
Python技巧:如何避免数据输入类型错误
前端·python
知否技术4 小时前
为什么nodejs成为后端开发者的新宠?
前端·后端·node.js
hakesashou4 小时前
python交互式命令时如何清除
java·前端·python
天涯学馆4 小时前
Next.js与NextAuth:身份验证实践
前端·javascript·next.js
HEX9CF5 小时前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
ConardLi5 小时前
Chrome:新的滚动捕捉事件助你实现更丝滑的动画效果!
前端·javascript·浏览器
ConardLi5 小时前
安全赋值运算符,新的 JavaScript 提案让你告别 trycatch !
前端·javascript