JavaScript 执行机制与事件循环详解(面试必备)

image.png

面试系列要不都用这个配图?

目录


1. 为什么要理解事件循环?

前端面试中,事件循环(Event Loop) 是必考点。来看一道经典题:

console.log('start')
setTimeout(() => console.log('timeout'))
Promise.resolve().then(() => console.log('promise'))
console.log('end')

👉 输出顺序是:

start
end
promise
timeout

✅ 面试官常考这个题,考察的就是你是否理解 JavaScript 的执行机制


2. JavaScript 单线程与任务模型

JavaScript 设计初衷是运行在浏览器中操作 DOM。由于 DOM 不能并发修改,所以 JavaScript 采用单线程模型

  • 同步任务:直接进入 调用栈(Call Stack) 执行。
  • 异步任务:完成后进入 任务队列(Task Queue),由事件循环调度。

3. 宏任务与微任务

异步任务分为两类:

宏任务(Macro Task)

  • 整体 script 代码
  • setTimeout / setInterval
  • I/O 操作
  • UI 渲染
  • (Node.js 中还包括 setImmediate,浏览器还可用 MessageChannel

微任务(Micro Task)

  • Promise.then / catch / finally
  • queueMicrotask
  • MutationObserver

执行顺序规则

  1. 执行一个宏任务(例如整个 script)。
  2. 清空当前产生的所有微任务。
  3. 浏览器可能渲染一次 UI(取决于刷新频率)。
  4. 执行下一个宏任务。

4. async/await 的本质

async/awaitPromise 的语法糖

示例:

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

输出结果:

1
3
2

✅ 解析:

  • await 会把后续语句(console.log(2))封装成 微任务
  • 当前宏任务执行到 console.log(3) 后,事件循环才会调度这个微任务。

5. 常见面试题解析

题目 1:Promise vs setTimeout

console.log('start')
setTimeout(() => console.log('timeout'))
Promise.resolve().then(() => console.log('promise'))
console.log('end')

输出:

start
end
promise
timeout

✅ 原因:

  • script 是宏任务 → 依次执行 start → 注册 setTimeout → 注册 Promise → end
  • 宏任务结束 → 清空微任务 → 输出 promise
  • 下一轮宏任务 → 输出 timeout

题目 2:async/await + setTimeout

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}

console.log('script start')
setTimeout(() => console.log('setTimeout'), 0)
async1()
new Promise(resolve => {
  console.log('promise1')
  resolve()
}).then(() => console.log('promise2'))
console.log('script end')

输出:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

✅ 解析:

  • 同步阶段:script start → async1 start → async2 → promise1 → script end
  • 微任务:async1 end, promise2
  • 宏任务:setTimeout

题目 3:多层 Promise.then

Promise.resolve()
  .then(() => {
    console.log('p1')
    return Promise.resolve()
  })
  .then(() => {
    console.log('p2')
  })

输出:

p1
p2

✅ 原因:

  • 第一个 then 注册微任务 → 输出 p1
  • 返回的 Promise.resolve() 立即 resolved → 再注册微任务 → 输出 p2

6. 浏览器 vs Node.js

浏览器事件循环:宏任务 ↔ 微任务交替。

Node.js 事件循环(基于 libuv):阶段顺序 → timers → pending callbacks → idle/prepare → poll → check → close callbacks且在 每个阶段结束后都会执行微任务

👉 面试加分点:明确指出浏览器与 Node.js 执行顺序的差异


7. 面试回答思路(拿满分)

如果被问到 “能解释一下 JS 事件循环吗?”,可以这样答:

  1. JavaScript 是单线程,需要事件循环来处理异步任务。
  2. 异步任务分为 宏任务微任务
  3. 执行顺序:宏任务 → 清空微任务 → (可能渲染) → 下一个宏任务。
  4. 举例说明:Promise vs setTimeout
  5. 补充:async/await 的本质,以及 Node.js 的执行差异。

✅ 这样回答既有逻辑,又有深度


8. 总结

  • JavaScript 是单线程语言。
  • 异步任务分为 宏任务微任务
  • 执行顺序:宏任务 → 清空微任务 → 渲染 → 下一个宏任务。
  • 面试高频考点:
    • Promise vs setTimeout
    • async/await
    • Node.js 与浏览器的差异

👉 一句话记忆:“一个宏任务结束后,会立刻清空所有微任务,再进入下一个宏任务。”

posted @ 2025-08-21 11:11  前端混子阿吉  阅读(14)  评论(0)    收藏  举报