mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1562 字
4 分钟
JavaScript的事件循环机制
2026-04-01
无标签

V8运行时环境中的事件循环机制#

事件循环(event loop)机制的背景#

由于JavaScript的单线程编程语言的机制, 所以在同一时间内只会执行一个任务 那如果JS允许多线程操作DOM的话, 会让多个线程同时修改DOM会导致不可预测的状态冲突(竟态条件)

所以,JS 就引入了异步编程模型, 事件循环机制就是该模型的核心

浏览器中是怎么运行事件循环机制的?#

在Goodle浏览器中, V8引擎提供的JavaScript运行时环境中, 包含以下关键模块:

调用栈 : V8引擎内部

Web API 线程池、任务队列、事件循环:浏览器(Chrome 渲染引擎)

V8运行时#

V8引擎/Nodejs 等JS 运行时环境,会负责

  1. 解析、编译、执行 JS 代码
  2. 维护调用栈,执行上下文(main()). 内存管理

V8引擎/Nodejs 等JS 运行时环境,本身不具备网络请求、定时器、DOM 操作的能力 这些都不是 JS 语法的一部分,而是浏览器提供的 Web API。 也就是说,JS代码唯一真正执行的地方其实是这些V8引擎和Nodejs提供的运行时环境中!!!

在V8引擎中,所有的JS代码都要通过调用栈(Callback Stack) 进行任务的出栈进栈的操作

调用栈的关键作用在于:

  1. 所有同步代码必须进栈才能执行
  2. 同一时间只能执行一个任务(JS 单线程的根源)
  3. 栈空 = JS 引擎空闲
  4. 栈不空 → 永远不会执行任何异步回调

浏览器中的 Web API 线程池#

Web API 线程池会在 Chrome 浏览器渲染进程中,由 Blink 引擎管理

Web API 包含很多线程:

  1. 网络线程(AJAX/fetch)
  2. 定时器线程(setTimeout/setInterval)
  3. DOM 事件线程(click/scroll)
  4. 文件 I/O 线程
  5. 渲染、合成、栅格线程…

由此可见它的核心职责就是:

  1. 处理所有异步、耗时操作
  2. 多线程并行执行,不阻塞 JS 主线程
  3. 任务完成后,把回调函数丢进任务队列

Web API线程池的存在让JS单线程不会被阻塞 用户发送的AJAX,定时器,点击事件, 全是浏览器在进行处理, 不是JS在进行处理

浏览器中的任务队列(Task Queue)#

任务队列在浏览器内部由Blink 渲染引擎维护

任务队列分为两种:

  1. 宏任务队列(Macrotask Queue) setTimeout、AJAX、DOM 事件、I/O、fetch
  2. 微任务队列(Microtask Queue) Promise.then、queueMicrotask、MutationObserver

他的核心职责就是:

  1. 存放异步回调函数,排队等待执行
  2. 先进先出 FIFO

关键作用就是缓存异步结果的回调函数, 不让它们打断同步代码

JS异步模型的主角----事件循环(Event Loop)#

同样的, 事件循环机制依旧是由浏览器(Blink)引擎进行调度

事件循环(Event Loop)的核心职责就是: 不断监听调用栈是否为空

执行流程为:

  1. 调用栈是否空
  2. 如果不空:什么都不做,等待
  3. 如果空: 先把微任务队列全部清空 再取一个宏任务放到调用栈执行
  4. 无限循环重复操作

它的作用不必多说,伟大,无需多言

连接 V8 调用栈浏览器异步系统 的桥梁。 没有事件循环(Event Loop), 异步回调永远都不会执行

事件循环中的实例#

判断输出顺序:

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

输出: 1 4 3 2 (从上到下)

详细分析一下这个过程:

  1. 在调用栈中推入执行上下文(main()),并依次执行同步代码:

console.log(1);

  1. 当调用栈执行宏任务定时器等 Web API 时, 浏览器将它们放入到Web API线程池中,等待操作产生回调函数:

    setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3));

  2. 产生的回调会根据任务类型(宏任务/微任务)缓存到各自的任务队列当中

  3. 关键一步, event loop会监听调用栈是否有未执行的同步代码,随后就会将任务队列的回调推入到调用栈中, 先清空微队列任务,后处理宏任务队列

当调用栈的所有代码都执行完毕后, 事件循环就会再去执行

事件循环机制带来的思考#

首先, 同步代码会带来阻塞,异步代码不会进行阻塞 同步函数执行完才能执行下一句

异步函数中通常接入一个回调作为参数, 在调用异步函数后,会立即继续执行下一行 并且回调函数仅在异步操纵完成且调用栈为空的时候调用.

例如服务器加载数据库和查询数据库应该异步完成, 以便在主线程中进行渲染等其他操作而不会出现一直阻塞的情况

还有一个场景就很好玩:

一般浏览器会在60帧每秒去渲染页面中 根据事件循环机制, 浏览器也不会在调用栈还有同步代码的时候去渲染你的页面

例如我们将它放入异步函数的回调中: delay()

为什么? 因为渲染调用的本身几乎就是一个回调过程 渲染相较于回调的区别就在于, 渲染的优先级比回调更高

假如我们在栈中执行了一个该死的循环delay()函数的话, 这个js代码在对堆栈中会执行,那么就会阻塞事件循环 就会导致页面ui渲染很慢很烂

总之, 我们应该避免去写这种 shity code 去阻塞我们的执行程序

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

JavaScript的事件循环机制
https://www.choria.top/posts/eventloop/
作者
乔瑞雅·克林威尔
发布于
2026-04-01
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录