js中【微任务】和【宏任务】长篇解读

在 JavaScript 中,理解微任务(microtasks)宏任务 (macrotasks)是掌握异步编程和事件循环(Event Loop)机制的关键。这两个概念影响了代码的执行顺序,特别是在涉及异步操作(如 setTimeoutPromiseasync/await 等)时。为了深刻理解它们的差异及其在事件循环中的表现,我们将从最基础的概念出发,逐步解释这些任务的执行机制。

1. JavaScript 的单线程与异步模型

首先,JavaScript 是一门单线程语言,意味着它在同一时刻只能执行一个任务。这一特性使得 JavaScript 在执行同步代码时很容易理解:代码按顺序一行一行地执行,直到所有代码执行完毕。然而,现代应用往往需要处理异步任务,例如网络请求、定时器回调、用户输入等。为了解决这一问题,JavaScript 引入了异步机制,通过事件循环(Event Loop)调度和管理异步任务。

2. 事件循环(Event Loop)的基本概念

JavaScript 的事件循环是处理异步操作的核心机制。事件循环的工作流程如下:

  1. 执行同步任务:JavaScript 引擎首先执行所有的同步任务,这些任务会立即进入调用栈(Call Stack)并被逐个执行。
  2. 处理异步任务:当遇到异步任务时(例如 setTimeout、Promise、I/O 操作等),这些任务不会立即执行,而是被放入相应的任务队列中(宏任务队列或微任务队列)。
  3. 检查微任务队列:当所有同步任务执行完毕,调用栈为空时,事件循环会优先检查并执行微任务队列中的所有任务。
  4. 执行宏任务:在微任务队列清空后,事件循环从宏任务队列中取出下一个宏任务并执行。每次执行完一个宏任务后,事件循环会再次检查微任务队列,依此反复进行。

3. 宏任务(Macrotasks)与微任务(Microtasks)

在 JavaScript 中,任务分为宏任务(macrotask)微任务(microtask)。它们的区别主要体现在任务调度和执行顺序上。

宏任务(Macrotasks)

宏任务是事件循环的主任务,包括所有涉及异步操作的主要任务。宏任务由浏览器或 Node.js 环境调度,并在每轮事件循环中处理一个宏任务。常见的宏任务包括:

  • setTimeoutsetInterval
  • I/O 操作(例如网络请求回调)
  • DOM 事件(如 clickkeydown 等事件)
  • postMessage(在多个窗口或 iframe 之间通信)
  • MessageChannel
  • requestAnimationFrame

这些任务通常需要一定的等待时间(即使是 setTimeout(fn, 0)),并会在事件循环的某个阶段被处理。

微任务(Microtasks)

微任务的执行优先级比宏任务高。每当一个宏任务完成后,事件循环会立即执行微任务队列中的所有任务,只有在微任务队列清空后,才会继续执行下一个宏任务。常见的微任务包括:

  • Promise.then()Promise.catch()Promise.finally()
  • MutationObserver(观察 DOM 变化)
  • queueMicrotask()(用于将函数显式添加到微任务队列)

微任务的优势在于它们可以在当前事件循环周期内尽快执行,确保任务能够快速完成,尤其适合异步操作的回调处理。

4. 事件循环中的任务调度机制

同步任务执行流程

事件循环的第一步是执行所有的同步任务,即将同步代码依次压入调用栈(Call Stack),并按照顺序执行。当调用栈为空时,事件循环将开始处理异步任务。此时,事件循环会检查两个任务队列:

  1. 微任务队列(Microtask Queue)
  2. 宏任务队列(Macrotask Queue)
微任务优先原则

当同步任务执行完毕后,事件循环会首先检查微任务队列。如果微任务队列中有任务,事件循环会一次性执行所有的微任务(即清空微任务队列)。只有当微任务队列为空时,事件循环才会执行宏任务队列中的第一个任务。

宏任务执行流程

每次从宏任务队列中取出一个任务执行后,事件循环会再次检查微任务队列。如果微任务队列不为空,则会立即执行所有的微任务。在微任务队列为空后,才会回到宏任务队列继续处理下一个宏任务。

因此,微任务的执行优先级高于宏任务。即使一个宏任务已经准备好执行,微任务队列中的任务仍会先被执行。

5. 例子:理解微任务与宏任务的执行顺序

console.log('start'); // 同步任务

setTimeout(() => {
    console.log('setTimeout'); // 宏任务
}, 0);

Promise.resolve().then(() => {
    console.log('promise1'); // 微任务
}).then(() => {
    console.log('promise2'); // 微任务
});

console.log('end'); // 同步任务

执行顺序解析:

  1. 同步任务:首先执行 console.log('start'),输出 start
  2. 同步任务:继续执行 console.log('end'),输出 end
  3. 微任务队列Promise.resolve() 的回调被放入微任务队列。事件循环执行完同步任务后,开始执行微任务队列中的任务,先输出 promise1,再输出 promise2
  4. 宏任务队列setTimeout() 的回调被放入宏任务队列。所有微任务完成后,事件循环执行宏任务,输出 setTimeout

最终输出顺序为:

start
end
promise1
promise2
setTimeout

6. queueMicrotask()Promise.then() 的区别

queueMicrotask()Promise.then() 都会将任务加入微任务队列,但它们有一些细微的差别:

  • queueMicrotask():这是一个直接将任务添加到微任务队列的函数,适用于需要在当前事件循环周期内尽快执行的任务。
  • Promise.then():当 Promise 被 resolve 时,其 .then() 回调会被添加到微任务队列。

两者的执行时机基本一致,但 queueMicrotask() 提供了显式控制微任务的能力。

queueMicrotask(() => {
    console.log('microtask');
});

setTimeout(() => {
    console.log('timeout');
}, 0);

Promise.resolve().then(() => {
    console.log('promise');
});

输出顺序为:

microtask
promise
timeout

尽管 setTimeout 的延迟是 0,但微任务 microtaskpromise 仍会优先于宏任务 timeout 执行。

7. 在 Node.js 环境中的区别

在浏览器环境中,微任务和宏任务的执行顺序如上所述。然而,在 Node.js 中,事件循环的机制略有不同。Node.js 的事件循环包括多个阶段,每个阶段处理特定类型的任务。

Node.js 中有一个特殊的微任务机制:process.nextTick(),它的优先级甚至高于微任务。process.nextTick() 的回调会在微任务之前执行,因此它可以用于需要更高优先级的任务。

process.nextTick(() => {
    console.log('nextTick');
});

Promise.resolve().then(() => {
    console.log('promise');
});

setTimeout(() => {
    console.log('timeout');
}, 0);

输出顺序为:

nextTick
promise
timeout

process.nextTick() 的回调会在 Promise.then() 回调之前执行。

8. 常见的微任务和宏任务陷阱

1. setTimeout(fn, 0) 并不会立即执行

setTimeout(fn, 0) 并不会在 0 毫秒后立即执行,而是会将回调放入宏任务队列中。由于微任务具有更高优先级,Promise.then()queueMicrotask() 等微任务会先于 setTimeout 执行。

2. 微任务过多可能导致性能问题

由于微任务总是会在每个事件循环周期内被优先执行,因此如果有过多的微任务被不断添加,它们可能会阻塞宏任务的执行,导致浏览器无法响应用户的交互。

总结

  1. 同步任务:优先执行。
  2. 微任务:每当同步任务执行完毕或宏任务结束后,立即执行所有微任务。
  3. 宏任务:微任务执行完后,才会执行下一个宏任务。
  4. 微任务优先于宏任务:在每次事件循环中,微任务总是先于宏任务执行。
  5. Node.js 中的 process.nextTick():其回调优先级比微任务更高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值