JavaScript事件循环(Event Loop)的完整指南

 更新时间:2026年07月13日 10:52:41   作者:环球旅行的蜗牛  
在JavaScript的世界中事件循环是一个核心概念,它支撑着JavaScript处理异步操作的能力,让JavaScript能够在单线程环境中优雅地应对复杂的异步编程需求,这篇文章主要介绍了JavaScript事件循环(Event Loop)的相关资料,需要的朋友可以参考下

JavaScript最核心知识点:事件循环、调用栈、宏任务、微任务、异步执行顺序的权威完整指南,包含所有面试必考内容。

一、开篇经典代码(你必须理解)

console.log('Start')
setTimeout(() => console.log('Timeout'), 0)
Promise.resolve().then(() => console.log('Promise'))
console.log('End')

// 输出:
// Start
// End
// Promise
// Timeout

为什么延时 0ms 反而最后执行?答案:事件循环 + 微任务优先机制

二、核心概览

你将学到:

  • JS 单线程如何实现异步非阻塞
  • 调用栈、Web API、任务队列、微任务队列分工
  • 为什么 Promise 比 setTimeout 先执行
  • setTimeout、setInterval、requestAnimationFrame 区别
  • 所有面试题逐行解析

三、事件循环是什么?

事件循环是 JavaScript 实现非阻塞异步的核心机制。即使 JS 是单线程,也能处理网络、定时器、事件,不卡死页面。

餐厅厨房类比(最易懂)

  • 唯一厨师 = JS 引擎(单线程)
  • 出餐排队 = 任务队列(宏任务)
  • VIP 加急通道 = 微任务队列(Promise 等)
  • 厨房经理 = 事件循环

规则:同步先跑完 → 清空所有微任务 → 再执行一个宏任务 → 渲染 → 循环

四、JS 运行时三大组成

1. 调用栈(Call Stack)

  • 执行同步代码
  • 后进先出 LIFO
  • 栈空了,事件循环才会取任务

2. Web API / Node API(环境提供)

不属于 JS 语法,由浏览器 / Node 实现:

  • setTimeout
  • fetch/ajax
  • DOM 事件
  • 定时器、文件、网络等

3. 两大队列

宏任务队列(Task Queue)

  • setTimeout
  • setInterval
  • I/O
  • UI 渲染
  • DOM 事件回调

微任务队列(Microtask Queue)优先级更高!

  • Promise.then/catch/finally
  • async/await 后面的代码
  • queueMicrotask
  • MutationObserver

五、事件循环黄金规则(背下来)

  • 先把同步代码全部执行完
  • 清空微任务队列(全部执行)
  • 执行一个宏任务
  • 浏览器渲染
  • 回到第一步,无限循环

一句话口诀:同步 > 微任务 > 宏任务

六、逐行解析经典面试题

console.log('1')

setTimeout(() => console.log('2'), 0)

Promise.resolve().then(() => console.log('3'))

console.log('4')

执行顺序:

  • 同步:1
  • 同步:4
  • 清空微任务:3
  • 执行一个宏任务:2

结果:1 → 4 → 3 → 2

七、微任务 vs 宏任务 完整清单

宏任务(Macrotask)

  • setTimeout
  • setInterval
  • I/O(文件、网络)
  • DOM 事件(click、load)
  • setImmediate(Node)
  • requestAnimationFrame(渲染阶段)

微任务(Microtask)

  • Promise.then / catch / finally
  • async/await 后续代码
  • queueMicrotask()
  • MutationObserver

八、微任务饥饿效应(重要坑)

如果微任务里不断加微任务:宏任务和渲染永远执行不了!页面直接卡死

function loop() {
  Promise.resolve().then(loop)
}
loop() // 页面卡死

九、定时器深度原理

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

必须等:

  • 同步代码跑完
  • 微任务清空
  • 轮到它执行

2. 最小延时 4ms 规则

嵌套 5 层以上定时器,浏览器强制加 4ms 延迟。

3. 延时不保证!

主线程阻塞时,定时器会超时执行

4. setInterval 会漂移

执行时间会被吞掉,不准。高精度轮询请用 嵌套 setTimeout

十、requestAnimationFrame(rAF)

专门用于动画,和浏览器刷新同步(60fps)。

执行时机:微任务之后 → 渲染之前

优点:

  • 流畅不卡顿
  • 后台标签自动暂停
  • 省电

十一、async/await 本质

await 前面的代码同步执行;await 后面的代码 = 微任务

async function foo() {
  console.log(1)
  await Promise.resolve()
  console.log(2) // 微任务
}

十二、最经典 6 道面试题(答案全解析)

题 1:输出顺序

(前面讲过:1 4 3 2)

题 2:嵌套 Promise

setTimeout(()=>console.log(1),0)
Promise.resolve().then(()=>{
  console.log(2)
  Promise.resolve().then(()=>console.log(3))
})
setTimeout(()=>console.log(4),0)
console.log(5)

答案:5 → 2 → 3 → 1 → 4

题 3:await 顺序

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

答案:1 → 2 → 4 → 3

题 4:var 循环陷阱

for(var i=0;i<3;i++){
  setTimeout(()=>console.log(i),0)
}

答案:3 3 3修复:let / 闭包 / 传参

题 5:阻塞事件循环

长时间同步代码会让定时器超时执行

题 6:微任务饥饿

无限微任务 → 宏任务永远不执行 → 页面卡死。

十三、常见误区(90% 人错)

  • ❌ setTimeout(fn,0) 立刻执行
  • ❌ 延时到秒就一定秒到
  • ❌ JS 是异步语言✅ JS 是单线程同步,异步靠环境
  • ❌ 事件循环属于 ES 标准✅ 属于 HTML 标准
  • ❌ setInterval 精准✅ 会漂移,不精准

十四、总结(最简版)

  • JS 单线程,靠事件循环实现异步
  • 执行顺序:同步 → 微任务 → 宏任务
  • Promise 是微任务,比 setTimeout 先跑
  • 微任务会清空再跑宏任务
  • 定时器延时不保证,主线程阻塞就延迟
  • 动画用 requestAnimationFrame
  • 永远别阻塞主线程

十五、渲染与事件循环的关系

浏览器的渲染流水线严格遵循事件循环的固定节奏:

  • 执行完当前调用栈所有同步代码
  • 清空微任务队列
  • 执行渲染阶段(重要)
    • 执行 requestAnimationFrame
    • 计算样式(Recalculate Style)
    • 布局(Layout / Reflow)
    • 绘制(Paint)
    • 合成(Composite)

渲染 每 16ms 一次(60fps),不是每次循环都渲染。如果主线程阻塞,页面就会掉帧、卡顿。

十六、阻塞事件循环的危害

任何耗时超过 16ms 的同步代码都会导致:

  • 按钮点击无响应
  • 输入框卡顿
  • 动画掉帧
  • 定时器严重延迟
  • 页面卡死无法交互

典型阻塞代码:

  • 超大循环 for (let i=0; i<1000000000; i++)
  • 复杂计算、加密、解密
  • 大量 DOM 操作

解决方案:

  • Web Workers(真正多线程)
  • 任务分块(Chunking)
  • requestIdleCallback(空闲时执行)
  • 异步拆分(setTimeout yielding)

十七、Web Workers(真正并行)

Web Workers 可以创建独立线程,不阻塞主线程。

  • 有独立的调用栈、事件循环、堆
  • 不能访问 DOM
  • 不能共享内存,只能通过 postMessage 通信

适合:

  • 大量计算
  • 数据处理
  • 加密解密
  • 图片处理

十八、任务分拆(Chunking)

把大任务切成小片,每次让出事件循环:

function process(items, processFn) {
  let index = 0
  function next() {
    const chunk = items.slice(index, index + 100)
    index += 100
    chunk.forEach(processFn)
    if (index &lt; items.length) {
      setTimeout(next)
    }
  }
  next()
}

十九、requestIdleCallback

在浏览器空闲时间执行低优先级任务:

requestIdleCallback((deadline) =&gt; {
  while (deadline.timeRemaining() &gt; 0) {
    doWork()
  }
})

适合:

  • 埋点上报
  • 预加载
  • 日志
  • 非紧急计算

二十、事件循环在 Node.js 与浏览器的区别

浏览器

  • 宏任务每次取一个
  • 微任务全部清空
  • 渲染每 16ms 一次

Node.js(libuv)

更复杂,分为多个阶段:

  • timers(setTimeout、setInterval)
  • pending callbacks
  • idle/prepare
  • poll(最重要,I/O)
  • check(setImmediate)
  • close callbacks

每个阶段跑完后都会清空微任务

二十一、setImmediate 与 setTimeout

在 Node.js 中:

setTimeout(() =&gt; console.log('timeout'), 0)
setImmediate(() =&gt; console.log('immediate'))

结果不固定。但在 I/O 回调里:setImmediate 一定先执行

二十二、事件循环总结(最终极版)

浏览器完整流程:

  • 执行同步代码(调用栈)
  • 清空微任务队列(全部)
  • 判断是否需要渲染
    • 需要 → 执行 requestAnimationFrame
    • 执行样式、布局、绘制、合成
  • 取一个宏任务执行
  • 回到第 1 步

终极口诀:

同步优先,微任务插队,宏任务排队,渲染靠边,循环不息。

二十三、必须掌握的 8 个核心结论

  • JS 是单线程,但环境是多线程
  • 异步不是并行,是调度交替
  • 微任务优先级 > 宏任务
  • await 后面的代码是微任务
  • setTimeout (0) 不立即执行
  • 渲染在微任务之后、宏任务之间
  • 长同步代码阻塞一切
  • 微任务无限递归会饿死渲染与宏任务"""

总结 

到此这篇关于JavaScript事件循环(Event Loop)的文章就介绍到这了,更多相关JS事件循环内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • js加减乘除精度丢失问题解决

    js加减乘除精度丢失问题解决

    计算机的二进制实现和位数限制有些数无法有限表示,就会产生精度丢失问题,本文主要介绍了js加减乘除精度丢失问题解决,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • JavaScript实现通过滑块改变网页颜色

    JavaScript实现通过滑块改变网页颜色

    这篇文章主要为大家详细介绍了JavaScript实现通过滑块改变网页颜色,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • 简单聊聊JavaScript中的事件循环

    简单聊聊JavaScript中的事件循环

    js的事件循环(event-loop)是我们前端学习中非常重要的一部分,也是面试中经常被问到的点,这篇文章我们就来给大家着重讲解一下吧
    2023-02-02
  • JS实现让网页背景图片斜向移动的方法

    JS实现让网页背景图片斜向移动的方法

    这篇文章主要介绍了JS实现让网页背景图片斜向移动的方法,涉及javascript操作背景图片特效的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • 魔鬼字典 JavaScript 笔记 代码比较多乱

    魔鬼字典 JavaScript 笔记 代码比较多乱

    魔鬼字典 JavaScript 笔记 代码比较多乱,对于有经验的看容易点。
    2010-03-03
  • 用客户端js实现带省略号的分页

    用客户端js实现带省略号的分页

    带省略号的分页只有在服务器端才可以实现,下面为大家介绍的是用js实现的带省略号的分页,感兴趣的朋友可以参考下哈,希望对你写出好的分页有所帮助
    2013-04-04
  • 一步一步教你写淡入淡出带注释的图片轮播插件(二)

    一步一步教你写淡入淡出带注释的图片轮播插件(二)

    开始之前,还是说说前文提到的关于把所有函数都写在一个闭包内的优化问题。前文也提到了,因为我们在初始化的时候要调用的只是init,所以可以只把init写入闭包,其他功能函数作为init的原型继承方法来调用。
    2010-10-10
  • Echart结合圆形实现仪表盘的绘制详解

    Echart结合圆形实现仪表盘的绘制详解

    EChart开源来自百度商业前端数据可视化团队,基于html5 Canvas,是一个纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。本文将利用EChart实现仪表盘的绘制,感兴趣的可以学习一下
    2022-03-03
  • javascript实现前端分页功能

    javascript实现前端分页功能

    这篇文章主要为大家详细介绍了javascript实现前端分页功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • 如何用JS WebSocket实现简单聊天

    如何用JS WebSocket实现简单聊天

    这篇文章主要介绍了如何用JS WebSocket实现简单聊天,对websocket感兴趣的同学,可以参考下
    2021-05-05

最新评论