【JavaScript】事件循环 Event Loop

JavaScript 单线程却不阻塞,靠的就是事件循环机制。理解事件循环是搞懂 setTimeout「不准时」、Promise 执行顺序等问题的前提。

1. 运行模型

JS 引擎只有一个调用栈。异步任务不进入调用栈,而是由浏览器(或 Node)的其他线程处理,完成后把回调放入任务队列,等调用栈清空后事件循环再取出来执行。任务队列分两类:

宏任务(macrotask):setTimeout、setInterval、I/O、UI 渲染、script 整体代码。微任务(microtask):Promise.then/catch/finally、queueMicrotask、MutationObserver。

2. 执行顺序

每一轮事件循环:执行一个宏任务 → 清空全部微任务 → (浏览器环境)可能渲染 → 取下一个宏任务。微任务永远先于下一个宏任务执行:

console.log("1 同步");

setTimeout(() => console.log("2 宏任务"), 0);

Promise.resolve().then(() => console.log("3 微任务"));

console.log("4 同步");

// 输出:1 同步 → 4 同步 → 3 微任务 → 2 宏任务

即使 setTimeout 延迟 0,它的回调也要等同步代码和全部微任务执行完才会运行。

3. 嵌套场景

async function demo() {
    console.log("a");

    await Promise.resolve();
    // await 之后的代码相当于放进微任务
    console.log("b");
}

demo();
console.log("c");

// 输出:a → c → b
// demo 同步执行到 await 后让出,先走完外层同步代码

4. setTimeout 的最小延迟

HTML 标准规定嵌套超过 5 层的 setTimeout 最小延迟 4ms,浏览器对后台标签页还会把间隔放宽到 1 秒以上。所以 setTimeout(fn, 0) 的真实含义是「尽快,但不早于当前微任务清空后」。

5. 实用结论

需要「当前同步代码全部执行完再运行」的逻辑放微任务(Promise.then);定时、轮询放宏任务;动画和 UI 更新用 requestAnimationFrame,它在渲染前执行,比 setTimeout 更适合视觉任务。

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容