Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
rodson opened this issue Apr 20, 2024 · 0 comments
Open

Comments

@rodson
Copy link
Owner

rodson commented Apr 20, 2024

浏览器事件循环介绍

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

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

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

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

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

代码示例分析

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

示例1:

// 宏任务
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在接下来的事件循环中依次执行。
image

示例2:

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
image

为什么需要微任务

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

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

  fetchData().then((data) => {
    updateUI(data);
  });

总结

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant