首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

JavaScript 事件循环:告别异步“玄学”,这一篇就够了!

大家好,我是小墨。本期我们来聊聊前端开发中一个非常重要,却又常常让初学者感到困惑的概念——JavaScript 事件循环(Event Loop)。

很多开发者在处理异步操作时,常常会遇到各种“灵异”现象:setTimeout延迟时间不准确、Promise链式调用顺序出错、页面莫名其妙卡顿…… 这些问题的根源,往往都指向了事件循环。

如果你对事件循环还一知半解,那么这篇文章就是为你量身定制的!小墨将用通俗易懂的语言,结合丰富的代码示例和精美图表,带你彻底揭开事件循环的神秘面纱。

先来看一张图,直观感受一下事件循环的运作流程:

这张图清晰地展示了事件循环的核心机制:

1. JavaScript 引擎执行同步任务(在主线程上)。

2. 遇到异步任务时,将其交给 Event Table 注册相应的回调函数。

3. 当异步事件触发(如定时器到期、网络请求完成),Event Table 会将对应的回调函数移入 Event Queue(任务队列)。

4. 当主线程上的同步任务全部执行完毕后,会去读取 Event Queue,将“准备就绪”的任务移入主线程执行。

5. Event Queue 又分为宏任务队列和微任务队列,微任务队列的优先级更高。

理解了这张图,就等于理解了事件循环的“骨架”。接下来,我们将深入探讨各个细节。

为什么 JavaScript 需要事件循环?

我们知道,JavaScript 引擎是单线程的。如果所有任务都同步执行,一旦遇到耗时操作(如网络请求),整个页面就会卡住,用户体验可想而知。

为了解决这个问题,JavaScript 引入了异步编程和事件循环机制。异步任务不会阻塞主线程,而是被“委托”给其他模块(如浏览器或 Node.js 环境)处理,并在合适的时机将其回调函数放入任务队列,等待主线程执行。

任务分类:宏任务 vs. 微任务

过去,人们习惯将任务简单分为“宏任务”和“微任务”。但随着 Web 技术的发展,这种简单的分类已经无法满足需求。现在,W3C 对事件循环有了更细致的规定:

• 每个任务都有自己的类型。

• 相同类型的任务会被放入同一个队列。

• 不同任务队列的执行优先级有所不同。

• 浏览器必须维护一个微任务队列,并且微任务队列的任务具有最高优先级。

这里重点强调:微任务总是优先于宏任务执行。

异步任务的归宿:Event Table 和 Event Queue

异步任务不会直接进入主线程,而是:

1. 首先被放入Event Table,并注册对应的回调函数。

2. 当异步事件触发时,Event Table 会将对应的回调函数移入Event Queue

3. 同步任务全部执行完成后,主线程开始执行Event Queue,依照优先级处理Event Queue里的任务,循环往复。

深入理解宏任务和微任务

微任务(Microtask)

•Promise.then、Promise.catch、Promise.finally:Promise 的回调函数。

•MutationObserver:用于监听 DOM 结构变化的 API。

•process.nextTick:Node.js 环境专属,用于将回调函数添加到当前事件循环的末尾。

以下提供MutationObserver使用例子。

宏任务(Macrotask)

Node.js 环境中,process.nextTick具有比setImmediate更高的优先级:

•script(整体代码):最外层的同步代码。

•setTimeout/setInterval:定时器。

• UI rendering:页面渲染。

• I/O 操作:Node.js 中的文件读写等。

•postMessage、MessageChannel:用于不同上下文间通信的 API。

•setImmediate:Node.js 环境专属(类似于setTimeout(fn, 0),但不建议在浏览器环境中使用)。

setTimeout真的准时吗?

setTimeout和setInterval这两个定时器并不保证精确的执行时间。setTimeout(() => {}, 2000)只是表示至少 2000 毫秒后,该任务才会被添加到任务队列。实际执行时间会受到以下因素影响:

• 队列中是否有其他任务。

• 浏览器执行计时器函数本身需要时间。

实战演练:代码执行顺序分析

代码分析:

1. 输出1。

2.setTimeout回调放入宏任务队列。

3.Promise.resolve().then回调放入微任务队列。

4. 输出6。

5. 执行微任务,输出4。遇到内部setTimeout的回调,放入宏任务队列

6. 执行宏任务, 输出2。遇到Promise.resolve().then,回调放入微任务队列

7. 当前宏任务完成,检查微任务队列。

8. 执行微任务队列输出3

9. 微任务队列完成,开始执行宏任务,输出5。

掌握事件循环,解决实际问题

掌握事件循环机制,对于解决以下问题至关重要:

避免页面卡顿:合理安排异步任务,避免长时间运行的同步任务阻塞主线程。

优化性能:可以将一些非关键任务延迟执行,提升页面响应速度。

处理复杂交互:更清晰地控制代码执行流程。

debug:帮助解决异步引起的bug

希望这篇文章能帮助你彻底理解 JavaScript 事件循环。如果你还有任何疑问,欢迎在评论区留言,小墨会尽力解答!

推荐阅读

•告别卡顿!Vue 项目瘦身终极指南,手把手教你消灭冗余代码!

•Vue3 自定义 Hook 杀手锏:告别代码屎山,进阶前端大神!

•Vue3 响应式数据太炸裂!Proxy + Reflect 源码级解析,看完我跪了!

•Vue3 组件通信天花板:作用域插槽「超详细」实战攻略(附图解 & 代码)

•Vue3 插槽还能这么玩?性能直接炸裂,快来抄作业!

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OmKS2-ix99-ZWGDPAQzVCOGA0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券