认识Event Loop【1】
前言
这应该是一个系列文章,因为我觉得Event Loop(事件循环)是一件很抽象也很重要的一个机制。eventloop这个知识点处于非常杂糅的位置,和很多其他知识,如运行时、浏览器、渲染流程、数据结构、线程等等,也是正确理解JS异步编程的核心。所以很值得展开来娓娓道来。
⚠️强烈建议学完JS异步语法知识后再来学习EventLoop。
JS是单线程的编程语言
JS是单线程的编程语言,重要的事情说三遍,单线程,单线程,单线程。接下来请读者思考一下,单线程是什么意思?
单线程意味着JavaScript在同一时刻只能执行一个任务或操作。也就是说,它一次只能处理一个事件或指令,不会同时处理多个任务。
那我就不禁要问,单线程如何实现异步编程,这不是开玩笑吗,how,轮询?回调?这不麻烦死了。能实现基本操作还好,但有稍微复杂点的该如何呢。举一个例子,setTimeout的时候为什么还可以执行其他代码,谁在那边倒数时间?执行其他任务和进行记时任务,这俩件事情是如何在所谓单线程的JS中同时执行的。蒙了吗,我就不禁要怀疑JS是不是单线程了。
直到我去了解了EventLoop,EventLoop就是在保证JS是单线程的特点的同时,让其拥有高效实现复杂异步的能力。
Eventloop避免了阻塞和性能瓶颈。如果没有事件循环,虽然理论上可以通过轮询、回调等方式实现异步编程,但这些方式都存在效率低、开发复杂度高等问题,且无法像事件循环那样高效地协调和管理多个异步操作。
简单介绍完了Event Loop的重要性,那么我们要开始正式认识它了。
Runtime运行时
我先说结论,JavaScript 本身是单线程的,但它的 运行环境(runtime)(如浏览器或 Node.js)提供了 事件循环(Event Loop),并通过调用 底层的多线程 API(如浏览器的 Web APIs 或 Node.js 的 libuv 线程池)来实现异步操作。
那为什么浏览器能运行JS代码,就是因为浏览器内置了解析JS代码的工具,以Chromium浏览器内核为例,就是使用了大名鼎鼎的V8引擎。而eventloop实际上和runtime关系更密切的,本文就先以浏览器的Event Loop为中心,理解完浏览器后,下一章再介绍nodejs、deno、bun这些其他的“Runtime”
浏览器提供的runtime

看这幅图,大概就明白JS引擎和运行时的关系了吧,它们之间是包含关系,而EventLoop并不是在JS引擎里实现的。
我们先不关注EventLoop的具体细节,乘胜追击简单了解一下浏览器的架构。
浏览器是多进程+多线程架构的

现代Chorme浏览器是多进程架构,一个页面就是一个进程,而一个进程可以有多个线程。通过多个线程协作来完成不同的任务,包括 JavaScript 执行、页面渲染、I/O 操作等。通常,以下是常见的几个关键线程:
-
主线程:负责执行 JavaScript 代码、渲染页面、响应用户输入等。它与事件循环机制紧密配合。
-
渲染线程(Rendering Thread):专门负责页面渲染。它从 DOM 和 CSSOM 构建渲染树(Render Tree),然后进行页面布局、绘制和合成。渲染线程会独立于主线程运行,但也需要与主线程进行一定的交互(例如,当 DOM 更新时,渲染线程需要重新绘制)。
-
网络线程(Network Thread):负责处理网络请求,例如发起 HTTP 请求(如
fetch或XMLHttpRequest)以及接收服务器响应。它通常与主线程和渲染线程协作,但它运行在独立的线程中,不会阻塞 JavaScript 的执行。 -
I/O 线程(I/O Thread):处理与操作系统相关的 I/O 操作(如文件操作、网络操作等)。它的作用是将 I/O 操作的阻塞部分从主线程中分离出来,避免长时间的阻塞导致浏览器卡顿。
-
Web Worker:Web Worker 是一个额外的线程,通常用于执行耗时的计算任务,以防止阻塞主线程。Web Worker 线程之间通过消息传递与主线程或其他 Worker 进行通信。
有了宏观的视角,读者应该能猜出来了吧,JS就是通过调用WebAPIs,将I/O、Network、Render等极有可能消耗时间的任务甩锅给了浏览器的其他线程去执行,真坏啊,摸鱼技能+1。
理解为啥会堵塞

咱们先理解单核CPU,单核CPU其实就类似于在一个在单一轨道上行驶的超跑,在闲置的时间(EventQueue和RenderTask没任务,这俩都是我自己乱取名的嗷),就一直在黑色的轨道上一圈圈慢慢的跑。
当任务来了,需要CPU去进行处理的时候,这辆跑车就会进入蓝色或黄色轨道,去进行计算或读写操作。可能会对为什么CPU要去处理渲染任务有疑惑,这点和浏览器的渲染流程有关系,我直接简短告诉结论:渲染需要CPU先进行运算操作,后交由GPU去进行后续的处理,即需要二者配合,没有CPU的参与就会出现渲染堵塞的问题。
接下来我们在把注意力集中在EventQueue上,EventQueue,我自己这么叫它的,顾名思义,就是以类似于排队的感觉。各种事件和任务一个一个的进入队列中,然后等待CPU处理,处理完就滚蛋了,处理不完就会一直拖着CPU,不让它离开,像个事佬,其他任务处理不了,只能等着,这就是所谓的堵塞。
任务从哪来
让我们一窥全貌,call me灵魂画师🫡👍

接下来的重点就是左上角的那部分

两种数据结构
JS引擎维护一个CallStack回调栈,顾名思义,就一个栈结构,遵循先进后出(FILO)。而Runtime运行时维护一类TaskQueue,顾名思义,就是一个队列结构,遵循先进先出(FIFO)。
动画模拟感兴趣的可以看看这网站
CallStack
先关注CallStack,又是顾名思义啊,为什么叫Call,其实就是执行回调函数(CallBack)。这点其实不是我们这片文章的重点,简单的讲一下,就是应该是所有编程语言吧,同步代码都是以回调栈这种形式去执行代码。
其实我感觉,可以比较粗暴的讲一切操作理解为“同步”,因为以回调栈的视角去看,无非是有任务来了就按照Stack的形式(FILO)去执行操作回调操作。
WebAPIs

https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Client-side_APIs/Introduction#what_can_apis_do
https://developer.mozilla.org/zh-CN/docs/Web/API
WebAPIs有很多,感兴趣的可以自己去详细了解,我摘记了一段MDN的介绍
客户端 JavaScript 中有很多可用的 API。它们本身并不属于 JavaScript 语言,却建立在核心 JavaScript 语言之上,为使用 JavaScript 代码提供额外的超强能力。它们通常分为两类:
- 浏览器 API 内置于 Web 浏览器中,能从浏览器和电脑周边环境中提取数据,并用来做有用的复杂的事情。例如,Web 音频 API 为在浏览器中处理音频提供了 JavaScript 结构——获取音轨、改变音量、应用特效等。在后台,浏览器实际上使用了一些复杂的低级代码(如 C++ 或 Rust)来进行实际的音频处理。但同样,这种复杂性已被应用程序接口抽象化。
- 第三方 API 缺省情况下不会内置于浏览器中,通常必须在 Web 中的某个地方获取代码和信息。例如,Google Maps API 使你能够执行诸如在网站上显示办公室的交互式地图之类的操作。它提供一系列特殊的结构,可以用来查询 Google 地图服务并返回特定信息。
看看就好,这也提醒了我们,像是我们常用的DOM操作、fetch、setTimeout、setInterval都是通过WebAPIs来实现的,也就是说这些功能在其他Runtime中就不一定有,比如在NodeJS中就没有DOM操作。
TaskQueue
接下来就到了任务队列了

任务队列在浏览器环境中又区分微任务队列和宏任务队列,之所以区分,我猜测是为了对事件有更加精细的操作,就如NodeJS则甚至会有6个不同的阶段,当然NodeJS还不是本文的重点。
宏任务(Macro-task)
宏任务指的是宿主环境(如浏览器或 Node.js)提供的任务,通常与用户交互、I/O、定时器等相关。
通常是一些消耗时间比较多,计算量比较大的任务。
常见的宏任务
setTimeoutsetIntervalsetImmediate(Node.js 专有)- I/O 操作(如文件读取、数据库查询)
比较特殊的宏任务
requestAnimationFrame(浏览器专有)- UI 渲染任务(浏览器)
之所以说特殊,因为UI渲染的频率是和屏幕的刷新频率有直接关系的,也就是说屏幕如果说60hz的,在主线程不堵塞的情况下,就会每1/60秒去执行一次渲染任务,而如果个任务堵塞时间过长,就会将渲染任务延迟,甚至直接丢失(也就是会发生所谓的掉帧、丢帧)
特点
每次事件循环(Event Loop)都会从任务队列(Task Queue)中取出一个宏任务执行。
微任务(Micro-task)
常见的微任务
Promise.then()、catch()、finally()MutationObserver(浏览器专有)queueMicrotask()(现代浏览器和 Node.js)
特点
微任务的优先级高于宏任务,JS 引擎会立即清空所有的微任务队列(Micro-task Queue)中的任务。
执行顺序
已经了解了CallStack 和 TaskQueue,那么了解EventLoop就是易如反掌,接下来死记硬背一下规则就行了,没有为什么,就是一个人为规定的固定顺序
- 执行同步代码(包括变量声明、函数调用等)
- 执行所有微任务
- 取出并执行一个宏任务
- 重复步骤 2 和 3
这就是浏览器里的EventLoop
重新理解堵塞
微微的callback一下,从执行循序可以知道,
1.如果同步代码中执行的任务计算量太大,就会难以清空CallStack,堵塞TaskQueue里异步任务的执行。
2.如果微任务过多,就会堵塞宏任务的执行
3.如果单个任务执行时间超过两倍屏幕刷新率就会出现丢帧
后记
下次分享我应该会分享一下NodeJS的EventLoop,如果我时间的话,会顺手讲解一下Deno和Bun的EventLoop,当然这俩个目前没啥必要学,只是为了加深一下读者们“EventLoop由Runtime构建的而不是JS引擎”的这个重要观点。
相关文章:
认识Event Loop【1】
前言 这应该是一个系列文章,因为我觉得Event Loop(事件循环)是一件很抽象也很重要的一个机制。eventloop这个知识点处于非常杂糅的位置,和很多其他知识,如运行时、浏览器、渲染流程、数据结构、线程等等,也…...
《Linux栈破坏了,如何还原》
【栈破坏导读】栈破坏有了解过吗?何为栈破坏,栈破坏了,程序会立刻引发崩溃,我们通过gdb去调试coredump,栈被破坏的栈帧是没法被恢复的,这也给我们调试程序带来很大的困难,那如何还原栈破坏的第一…...
环形链表问题的探究与代码实现
在数据结构与算法的学习中,环形链表是一个经典的问题。它不仅考察对链表这种数据结构的理解,还涉及到指针操作和逻辑推理。本文将结合代码和图文,深入分析如何判断链表中是否有环以及如何找到环的入口点。 目录 一、判断链表中是否有环 …...
【CSS3】筑基篇
目录 复合选择器后代选择器子选择器并集选择器交集选择器伪类选择器 CSS 三大特性继承性层叠性优先级 背景属性背景色背景图背景图平铺方式背景图位置背景图缩放背景图固定背景复合属性 显示模式显示模式块级元素行内元素行内块元素 转换显示模式 结构伪类选择器结构伪类选择器…...
React:类组件(上)
kerwin老师我来了 类组件的创建 class组件,js里的类命名首字符大写,类里面包括构造函数,方法 组件类要继承React.Component才有效 必须包含render方法 import React from react class App extends React.Component{render() {return <…...
开启mysql远程登录
目录 前言开启步骤 前言 为了安全考虑,mysql默认不允许远程登录,需要我们自己开启。当然在远程登录之前mysql的端口也要开放。下面是mysql开启远程登录的步骤。 开启步骤 本地登录mysql mysql -u root -p然后输入登录密码 给登录账号授权 GRANT AL…...
Eclipse 查看 JAVA SE 23 官方API 源代码
第一步:下载 JAVA SE 23 官方API 源代码 JavaSE23API源代码资源-CSDN文库 (或者到open jdk网站JDK Builds from Oracle:)下载https://download.java.net/java/GA/jdk23.0.2/6da2a6609d6e406f85c491fcb119101b/7/GPL/openjdk-23.0.2_windows-…...
Spring Cloud之注册中心之Nacos的使用
目录 Naacos 服务注册/服务发现 引⼊Spring Cloud Alibaba依赖 引入Nacos依赖 引入Load Balance依赖 配置Nacos地址 服务端调用 启动服务 Naacos Nacos是Spring Cloud Alibaba的组件, Spring Cloud Alibaba遵循Spring Cloud中定义的服务注册, 服务发现规范. 因此使⽤Na…...
字符串相乘——力扣
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。 示例 1: 输入: num1 "2", num2 "3" …...
机试准备第13天
第一题是模拟出入栈游戏。 #include <stdio.h> #include <stack> #include <iostream> using namespace std; int main() {string str;while(getline(cin, str)){stack<char> stk;int j 0;//扫描出栈序列strfor(char i a;i<z;i){stk.push(i);//每…...
基于OpenCV的车牌识别系统(源码+论文+部署教程)
运行环境 基于OpenCV的车牌识别系统运行环境如下: • Python: ≥ 3.5 • OpenCV: ≥ 4.0 • IDE工具:Visual Studio Code(可自行选择) • 技术栈:Python OpenCV Tkinte 主要功能 基于OpenCV的车牌识别系统主要…...
MySQL:CRUD(增删查改)
目录 一、准备工作 二、Create 新增 1、语法 2、单行数据全列插入 3、单行数据指定列插入 4、多行数据指定列插入 5、多行数据全列插入 三、Retrieve 检索 1、语法 2、全列查询 3、指定列查询 4、查询字段为表达式 (1)常量表达式 &…...
德鲁伊连接池
德鲁伊连接池(Druid Connection Pool)是一个开源的Java数据库连接池项目,用于提高数据库连接的性能和可靠性。德鲁伊连接池通过复用数据库连接、定时验证连接的可用性、自动回收空闲连接等机制,有效减少了数据库连接的创建和销毁开…...
【git】【网络】【项目配置运行】HTTP 协议的微型简易 Web 服务器---tinyEasyMuduoWebServer
【git】【网络】【项目配置运行】HTTP 协议的微型简易 Web 服务器—tinyEasyMuduoWebServer csdn项目: 原文链接:https://blog.csdn.net/weixin_45178775/article/details/122257814 github链接:https://github.com/wyewyewye/tinyEasyMuduo…...
每周一个网络安全相关工具——MetaSpLoit
一、Metasploit简介 Metasploit(MSF)是一款开源渗透测试框架,集成了漏洞利用、Payload生成、后渗透模块等功能,支持多种操作系统和硬件平台。其模块化设计(如exploits、auxiliary、payloads等)使其成为全球…...
Python入门———条件、循环
目录 语句 顺序语句 条件语句 缩进和代码块 判断年份是否是闰年 空语句 pass 循环 while 循环 求5的阶乘: 求1!2!3!4!5! for循环 打印1-10 打印2,4,6,8&#x…...
InDraw6.2.3 | 甾体、核苷、黄酮类化合物实现简称命名
导语 当化学家对着屏幕输入"2-amino-1,9-dihydro-6H-purin-6-one"时,隔壁生物学家可能正在搜索"鸟嘌呤";这种命名差异如同"火星文"与"地球语"的碰撞。现在,鹰谷InDraw 6.2.3版带着53种多环化合物的…...
Linux中的TCP编程接口基本使用
TCP编程接口基本使用 本篇介绍 在UDP编程接口基本使用已经介绍过UDP编程相关的接口,本篇开始介绍TCP编程相关的接口。有了UDP编程的基础,理解TCP相关的接口会更加容易,下面将按照两个方向使用TCP编程接口: 基本使用TCP编程接口…...
系统部署【信创名录】及其查询地址
一、信创类型 (一)服务器: 1.华为云 2.腾讯云 3.阿里云 (二)中央处理器(CPU): 1.海思,鲲鹏920服务器 (三)中间件 1.人大金仓 ࿰…...
JavaWeb后端基础(7)AOP
AOP是Spring框架的核心之一,那什么是AOP?AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。AOP是一种思想,而在Spring框…...
Python 中多种方式获取屏幕的 DPI值
在 Python 中,可以通过多种方式获取屏幕的 DPI(每英寸点数)。以下是几种常见的方法: 方法 1:使用 tkinter 模块 tkinter 是 Python 的标准 GUI 库,可以通过它获取屏幕的 DPI。 import tkinter as tkdef …...
高效数据分析实战指南:Python零基础入门
高效数据分析实战指南 —— 以Python为基石,构建您的数据分析核心竞争力 大家好,我是kakaZhui,从事数据、人工智能算法多年,精通Python数据分析、挖掘以及各种深度学习算法。一直以来,我都发现身边有很多在传统行业从…...
Unity DOTS从入门到精通之EntityCommandBufferSystem
文章目录 前言安装 DOTS 包ECBECB可以执行的指令示例: 前言 DOTS(面向数据的技术堆栈)是一套由 Unity 提供支持的技术,用于提供高性能游戏开发解决方案,特别适合需要处理大量数据的游戏,例如大型开放世界游…...
开放充电点协议(OCPP)技术解析:架构演进与通信机制 - 慧知开源充电桩平台
开放充电点协议(OCPP)技术解析:架构演进与通信机制 引言 开放充电点协议(Open Charge Point Protocol, OCPP)作为电动汽车充电基础设施的核心通信标准,其技术架构与实现逻辑直接影响充电桩与中央管理系统&…...
MySQL 索引的数据结构(详细说明)
6. MySQL 索引的数据结构(详细说明) 文章目录 6. MySQL 索引的数据结构(详细说明)1. 为什么使用索引2. 索引及其优缺点2.1 索引概述 3. InnoDB中索引的推演3.1 索引之前的查找3.2 设计索引3.3 常见索引概念1. 聚簇索引2. 二级索引(辅助索引、非聚簇索引)…...
初学者快速入门Python爬虫 (无废话版)
全篇大概 5000 字(含代码),建议阅读时间 40min 一、Python爬虫简介 1.1 什么是网络爬虫? 定义: 网络爬虫(Web Crawler)是自动浏览互联网并采集数据的程序,就像电子蜘蛛在网页间"爬行"。 分类&…...
【git】ssh配置提交 gitcode-ssh提交
【git】ssh配置提交 gitcode-ssh提交 之前一直用的是gitee和阿里云的仓库,前两天想在gitcode上面备份一下我的打洞代码和一些资料 就直接使用http克隆了下来 。 在提交的时候他一直会让我输入账号和密码,但是我之前根本没有设置过这个,根本没…...
【二】JavaScript能力提升---this对象
目录 this的理解 this的原理 事件绑定中的this 行内绑定 动态绑定 window定时器中的this 相信小伙伴们看完这篇文章,对于this的对象可以有一个很大的提升! this的理解 对于this指针,可以先记住以下两点: this永远指向一个…...
C++————类和对象(一)
1.类定义格式 在C中,类(class)是封装数据和操作这些数据的函数的构造。类的定义包含成员变量和成员函数。 类的基本定义格式如下: class ClassName {// 访问修饰符public:// 公有成员DataType memberVariable; // 成员变量voi…...
SpringBoot参数校验:@Valid 与 @Validated 详解
SpringBoot参数校验:Valid 与 Validated 详解 一、案例(参数校验的必要性) 传统方式(无注解)的缺点: // 需要手动校验每个字段,代码冗余且易出错 public String register(User user) {// 手动…...
