JavaScript 任务执行机制

75 阅读3分钟

JavaScript 任务执行机制(Event Loop)

JavaScript 是单线程的,意味着同一时间只能执行一个任务。为了避免长时间运行的任务阻塞主线程,JavaScript 采用了异步任务队列和**事件循环(Event Loop)**来调度任务。


📌 1. 任务类型

JavaScript 任务分为两大类:

  1. 同步任务(Synchronous)

    • 立即执行,不需要等待
    • 例如:普通函数、变量赋值、for 循环等
  2. 异步任务(Asynchronous)

    • 需要等待,稍后执行
    • 例如:setTimeoutsetIntervalPromisefetchasync/await

📌 2. 执行流程

JavaScript 执行任务的流程如下:

  1. 同步任务 直接进入 主线程(调用栈,Call Stack)。

  2. 异步任务 进入 任务队列,等待执行。

  3. 任务队列 又分为:

    • 宏任务队列(Macro Task Queue)
    • 微任务队列(Micro Task Queue)
  4. 主线程执行完当前任务后,会先清空微任务队列,然后再取出一个宏任务执行。

  5. 重复以上步骤,形成 Event Loop(事件循环)


📌 3. 宏任务 vs 微任务

JavaScript 将异步任务分为 宏任务(Macro Task)微任务(Micro Task) ,微任务优先级更高。

任务类型分类例子
宏任务(Macro Task)setTimeoutsetInterval定时器回调
setImmediate(Node.js)立即执行的回调
I/O文件、网络请求
UI 渲染浏览器的渲染任务
微任务(Micro Task)Promise.then/catch/finallyPromise 回调
MutationObserver监听 DOM 变化
process.nextTick(Node.js)Node.js 专有

💡 规则:

  • 微任务优先级高于宏任务,会先执行微任务队列,再执行宏任务队列中的任务。

📌 4. 事件循环(Event Loop)

事件循环负责管理任务的执行顺序,基本流程如下:

1. 执行同步任务(主线程执行)
2. 清空微任务队列(所有 Promise 回调)
3. 执行一个宏任务(如 setTimeout 回调)
4. 清空微任务队列
5. 执行下一个宏任务...
6. 重复以上步骤

📌 5. 代码执行顺序示例

🌟 示例 1

console.log('start');

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

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

console.log('end');

🔹 执行过程

  1. 同步任务console.log('start')执行
  2. 同步任务console.log('end')执行
  3. 微任务Promise.then → 加入 微任务队列
  4. 宏任务setTimeout → 加入 宏任务队列
  5. 执行微任务console.log('promise')
  6. 执行宏任务console.log('setTimeout')

🔹 最终输出

start
end
promise
setTimeout

🌟 示例 2

console.log('A');

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

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

console.log('E');

🔹 执行顺序

  1. 同步任务:输出 A
  2. 同步任务:输出 E
  3. 微任务Promise.then(C → D)加入 微任务队列
  4. 宏任务setTimeout 加入 宏任务队列
  5. 执行微任务:输出 C
  6. 执行微任务:输出 D
  7. 执行宏任务:输出 B

🔹 最终输出

A
E
C
D
B

📌 6. 常见问题

❓ 为什么 setTimeout(fn, 0) 不是立即执行?

setTimeout(fn, 0) 并不会立即执行,而是:

  1. setTimeout(fn, 0) 进入 宏任务队列
  2. 等当前同步任务和所有微任务执行完毕后,才会执行 宏任务

示例:

setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('Sync');

输出:

Sync
Promise
Timeout

因为 Promise微任务,先执行。


async/await 的本质是什么?

async/await 只是 Promise 的语法糖,await 相当于 Promise.then(),是一个微任务

示例:

async function test() {
  console.log(1);
  await Promise.resolve();
  console.log(2);
}
test();
console.log(3);

输出:

1
3
2
  • console.log(1) 直接执行
  • await 之后的代码 变成微任务
  • console.log(3) 先执行
  • console.log(2) 作为微任务最后执行

📌 7. 重点总结

  1. JavaScript 是单线程,通过 Event Loop 管理异步任务执行顺序。

  2. 任务分为 同步任务(立即执行)和 异步任务(放入任务队列)。

  3. 异步任务分为

    • 宏任务(Macro Task)setTimeoutsetInterval、I/O 任务
    • 微任务(Micro Task)Promise.thenasync/await
  4. 微任务优先级更高,在每个宏任务执行后,先清空微任务队列,再执行下一个宏任务。

  5. setTimeout(fn, 0) 不会立即执行,因为它是宏任务,必须等所有微任务执行完再执行。

理解 Event Loop 的执行机制,是写好 JavaScript 异步代码的关键! 🚀