谈一谈浏览器与Node.js中的JavaScript事件循环,宏任务与微任务机制
JavaScript中的异步代码
JavaScript是一个单线程非阻塞的脚本语言。这代表代码是执行在一个主线程上面的。但是JavaScript中有很多耗时的异步操作,例如AJAX,setTimeout等等;也有很多事件,例如用户触发的点击事件,鼠标事件等等。这些异步操作并不会阻塞我们代码的执行。例如:
let a = 1;
setTimeout(() => {console.log('->', a)
}, 10);
a = 2;
// 输出 -> 2
可以看到,上述代码在浏览器中执行时,遇到setTimeout操作,并没有阻塞等待异步操作的结束再继续执行代码,而是先继续执行后面的代码。等异步操作结束后,浏览器再回来执行异步回调中的代码。因此,上述代码的console.log输出时,a的值已经变为了2。
这些异步非阻塞的实现,就是靠Javascript中的事件循环机制。
JavaScript中的线程
上面说到JavaScript是一个单线程的语言,这句话并不完全对。单线程指的是代码在一个主线程中运行,但是代码所触发的任务不一定在主线程运行。除了执行代码的线程之外,执行JavaScript的环境中还包含其他很多线程。其中浏览器的线程与Node.js中的线程也不相同。
浏览器中的线程
注意,这里对于浏览器线程进行了抽象和总结。实际上浏览器的线程和进程要更复杂,而且有时候会根据浏览器版本的不同而变化,因此仅供参考。
- JS主线程
负责运行JavaScript代码,解析HTML,CSS,构建DOM树,布局和绘制页面等等。 - 事件监听线程
负责监听触发的各种事件,放入事件循环中。 - HTTP请求线程
负责处理各类网络请求。 - 定时触发器线程
为setInterval,setTimeout定时触发操作等操作进行定时计数的线程。
浏览器中的进程
上面的线程实际上都在浏览器中的渲染进程中包含。一个浏览器要想正常运行,只做上述的操作是不够的。我们以Chrome为例,列举一个浏览器运行所需要的进程。
- 浏览器进程
负责网页外的界面功能,例如地址栏,书签等等。 - GPU进程
负责使用GPU渲染界面。 - 网络进程
负责网络相关的请求处理。 - 插件进程
负责浏览器插件运行。 - 渲染进程
负责网页内页面展示相关的操作,即上一节浏览器中的线程包含的所有线程都在这个进程中执行。
一个浏览器可以拥有多个标签页,在不同的标签页中,除了渲染进行之外,都是共享的。即我们打开一个新的标签页时,会产生一个新的渲染进程。(当在原标签页中打开新标签页,且属于同一个域则共享一个渲染进程)
进程与线程的关系
上面我们了解了浏览器中的进程和线程,有些同学就会有疑问,为什么要设立这么多的进程和线程?
进程是操作系统分配资源的基本单位,而线程是CPU任务调度和执行的基本单位。
简单理解下就是一个完整的应用程序是以进程为单位的,即至少有一个进程。而一段程序/代码在CPU的独立执行则至少以线程为单位。不同的进程和不同的线程都可以并行运行。
一个进程可以包含很多个线程,多个线程共享一个进程的资源(比如内存)。当一个进程崩溃后不会影响其他进程,但是当一个线程崩溃,它所在的整个进程都会崩溃掉,这个进程内的其他线程也会崩溃。
因此,为了同时并行执行代码和异步请求,浏览器中的渲染进程包含很多线程来并行运行任务。而为了让不同标签页的网页不互相影响,不同标签页拥有独立的渲染进程。这样即使某个网页崩溃,也不会影响其他标签页。
Node.js中的线程
- JS主线程
负责运行JavaScript代码。 - libuv的异步I/O线程池
负责实现事件循环和异步IO等操作,在不同操作系统的具体实现方式不同。 - 用户创建的线程
上述这些进程和线程的说明也仅仅是进行了抽象和简化,事实上浏览器和Node.js中的进程和线程数要更多,处理也更复杂。
宏任务与微任务
Javascript中的异步任务大致可以分为两种:宏任务和微任务。宏任务和微任务的执行顺序和优先级是不同的,具体的执行顺序问题我们在事件循环中描述,这里先来看一下,哪些操作属于宏任务,哪些属于微任务。这里仅仅是简单介绍,更详细的要在了解事件循环之后说明。
宏任务
任务 | 浏览器 | Node.js | 描述 |
---|---|---|---|
setTimeout | ✓ | ✓ | 在指定的毫秒数后调用函数 |
setInterval | ✓ | ✓ | 定时调用函数 |
script标签 | ✓ | 整体代码块 | |
I/O请求 | ✓ | ✓ | 例如文件请求,网络请求等 |
DOM事件 | ✓ | 例如点击事件,hover事件等 | |
requestAnimationFrame | ✓ | 浏览器重绘前更新动画 | |
postMessage | ✓ | iframe跨域通信 | |
MessageChannel | ✓ | ✓ | 管道通信 |
setImmediate | ✓ | 一次事件循环执行完毕调用 |
微任务
任务 | 浏览器 | Node.js | 描述 |
---|---|---|---|
Promise中resolve和reject回调 | ✓ | ✓ | |
async函数中的await异步函数 | ✓ | ✓ | |
MutationObserver | ✓ | 监听DOM变动触发 | |
process.nextTick | ✓ | 当前任务结束后执行 |
事件循环
与上面进程与线程的介绍一样,在浏览器中与Node.js中实现循环的方式也并不相同。下面我们来分别简单介绍一下。注意,这仅仅是对执行逻辑的抽象和总结,实际上浏览器和Node.js中的实现要更复杂。
浏览器中的事件循环
浏览器中的事件循环可以分为两个队列,宏任务队列和微任务队列。具体的任务执行顺序如下:
- 解析HTML中遇到script标签,开始执行第一个宏任务。
- 在宏任务执行中遇到宏任务,执行其中的请求(例如网络请求,定时器),在请求完成后将回调放入宏任务队列中。
- 在宏任务执行中遇到微任务,暂不执行回调,而是放入微任务队列中。
- 宏任务执行完成。开始依次执行微任务队列中的任务。
- 微任务执行中遇到宏任务或者微任务,处理方式同上,分别放入各自的队列中。
- 微任务队列清空后,开始执行宏任务队列中的下一个任务。
在事件循环的流程中,微任务的优先级实际上更高,执行完一个宏任务之后,要执行微任务队列中的所有任务。
为什么要区分宏任务和宏任务,优先级也不同
因为不同任务的开销不同,有的任务需要调用不同的线程甚至进程,有的任务需要等待请求返回甚至定时。
- 如果将全部的任务同步执行,那些耗时较久的任务会阻塞,造成整个页面加载缓慢。假设有请求A耗时10秒,请求B耗时20秒,如果同步执行,需要耗费30秒。如果将请求由其它线程实现,回调放入宏任务,则执行流程变为:执行代码->碰到A请求,其他线程异步等待返回->继续执行代码->碰到b请求,其他线程异步等待返回。A和B就实现了异步请求,回调被分别放入宏任务,等待下次事件循环。耗时间为20秒。
- 为什么微任务的优先级更高?因为微任务大部分是耗时不太久,不需要等待其他线程/进程等待完成通知的。因此,微任务相当于在宏任务的基础上进行了“插队”,拥有更高的优先级,也提高了页面的响应速度。
为什么script标签是宏任务呢?
- script标签可能需要异步请求获取,例如
<script src="myscripts.js"></script>
。 - script标签是嵌入在HTML中的,浏览器需要将HTML中的script标签解析出来供执行,这个步骤需要耗费一定的时间。
浏览器事件循环的更多说明
WHATWG(网页超文本应用技术工作小组)在官网对事件循环和任务队列做出了更详细的说明和解释,可以作为参考:说明文档。在新的说明中,任务的分类和事件循环已经有了部分区别,这里简要说一下,更多还请直接查看文档:
- 事件循环不一定对应于多线程。例如多个事件循环可以在单个线程中协作调度。
- 任务队列并不是一个严格的队列,而是一个集合。每次从队列中取出一个可以被执行的任务,而不是选取第一个任务(可能该任务还在阻塞中)。
- 宏任务队列有多个,不同类型的任务(任务源)放置在不同的任务队列中。具体的选取规则浏览器根据实际情况确定。
Node.js中的宏任务队列
Node.js的官网给出了事件循环的文档。它的事件循环要比浏览器的看起来复杂一些。下面是Node.js的宏任务队列。
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │└───────────────────────────┘
Node.js的宏任务队列并不是一整个队列,而是根据事件类型做出了区分,分为了六个队列,依次执行:
timers
定时器队列,执行定时器的回调pending callbacks
挂起的回调函数,用于某些系统回调idle, prepare
仅在内部使用poll
执行I/O事件回调check
setImmediate回调close callbacks
close事件的回调,例如socket.on('close', ...)
其中我们的大部分宏任务回调都会在poll
阶段执行,除了timers
、check
和close callbacks
阶段的特殊回调。每个宏任务队列都有自己的微任务队列。
Node.js事件循环的流程
- 首先执行主线代码,遇到宏任务就分配到对应的宏任务队列中,微任务也划分到主线的微任务队列中,直到执行完毕。
- 执行主线代码的微任务队列中的所有任务。
- 没有宏任务则执行结束,有则开始事件循环。在事件循环中,按照上述的6个宏任务队列依次执行。下面的步骤是单个队列中的流程。
- 在单个宏任务队列中,选择一个宏任务执行。如果执行中遇到新的宏任务就分配到对应的宏任务队列中。遇到微任务就放到该宏任务的微任务队列中。
- 一个宏任务执行完毕后,执行
process.nextTick
中的回调(如果有)。 - 执行当前宏任务的微任务队列中的任务,直到微任务队列清空。
- 在上面的单个宏任务队列中,再选择一个宏任务执行。直到当前宏任务队列清空或者到达上限。
- 选择下一个宏任务队列执行。
6个宏任务队列都执行完毕,才叫做一次事件循环执行完毕。
Node.js的11版本之前的区别
其中,在Node.js的11版本之前,宏任务和微任务的执行关系与上述流程不同:
每个宏任务队列有一个微任务队列。在单个宏任务队列中,首先执行完所有的宏任务,如果遇到微任务就放到微任务队列中。当单个宏任务队列中的所有宏任务执行完毕后,再执行该宏任务队列的微任务队列。
对比执行流程的区别,可以看到Node.js的11版本提高了微任务队列中的优先级,让Node.js中微任务队列的优先级和浏览器中的表现类似。而process.nextTick
可以看做是一个比微任务更高优先级的钩子。
注意
- setTimeout的时间即使设置为0,也会有一个最小时间,因此它与setImmediate谁更早执行不一定。
- 并不是所有回调函数都是异步的。例如
new Promise(fun)
中的回调是同步执行,在回调中遇到resolve(), reject()
等才是微任务异步执行的。
参考
- JavaScript 之事件循环 (Event Loop)
https://xie.infoq.cn/article/921841837025748baac847030 - The Node.js Event Loop, Timers, and process.nextTick()
https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick
https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick - 深入理解浏览器中的进程与线程
https://juejin.cn/post/6991849728493256741 - 这一篇浏览器事件循环,可能会颠覆部分人的对宏任务和微任务的理解
https://juejin.cn/post/7259927532249710653 - HTML Living Standard (event-loops)
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops - 阿里一面:熟悉事件循环?那谈谈为什么会分为宏任务和微任务
https://juejin.cn/post/7073099307510923295 - node.js事件循环简单理解——定时器,process.nextTick()等
https://blog.csdn.net/qq_46561394/article/details/123172336 - 手摸手带你彻底掌握,任务队列、事件循环、宏任务、微任务
https://juejin.cn/post/6979876135182008357 - 浏览器UI线程和JS线程是同一个线程吗?
https://www.zhihu.com/question/264253488 - 微信小程序的双线程设计有何创新之处?浏览器的渲染线程和 JS 线程本来不就是两个独立线程吗?
https://www.zhihu.com/question/446103629
相关文章:

谈一谈浏览器与Node.js中的JavaScript事件循环,宏任务与微任务机制
JavaScript中的异步代码 JavaScript是一个单线程非阻塞的脚本语言。这代表代码是执行在一个主线程上面的。但是JavaScript中有很多耗时的异步操作,例如AJAX,setTimeout等等;也有很多事件,例如用户触发的点击事件,鼠标…...

User Java bean的命名规范
Java Bean 是一种用于表示简单的、可重用的组件的规范。它是一个符合特定命名和约定的 Java 类,通常用于封装数据和提供访问方法。以下是关于 Java Bean 命名规范的一些准则: 类名: 类名应该使用驼峰命名法(Camel Case)…...

ajax和fetch的区别
ajax 和 fetch的相同点和区别是什么? 以前我们都用ajax去做请求, 但是原生的ajax不好用,我们会用$.ajax或者axios插件去请求,他们都是ajax的封装 最近出来个fetch是什么? 问到这里的时候,你就已经入坑了&am…...

java+springboot+mysql村务档案管理系统
项目介绍: 使用javaspringbootmysql开发的村务档案管理系统,系统包含超级管理员、工作人员角色,功能如下: 超级管理员:系统用户管理(工作人员管理);公开资料;会议记录&…...

windows查看/删除DNS缓存
一、查看DNS缓存 打开CMD,输入ipconfig/displaydns 二、删除DNS缓存 打开CMD,输入ipconfig/flushdns...

自动化测试之Junit
Junit引入注解参数化单参数多参数方法传参 测试用例执行顺序断言测试套件 Junit引入 Junit来编写和组织自动化测试用例,使用Selenium来实际模拟用户与Web应用程序的交互。也就是使用JUnit的测试功能来管理和运行Selenium测试。常见的做法是,使用JUnit作…...

Spring Boot 整合MyBatis-Plus
😀前言 本篇博文是关于Spring Boot 整合MyBatis-Plus的,希望你能够喜欢😊 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的…...

CC++ 常用技巧
C 中的C C 是面向过程的是把整个大程序分为一个个的子函数;C 是面向对象的是把整个程序划分为一个个的类。C 是完全兼容C 的,C 是C 的子集,C 是C 的超集。C 又对C 做了很多补充和提升,因此使用C 会比使用纯C 更方便。混用C和C&am…...

【AndroidStudio】屏蔽小米打印
使用小米手机调试时,会一直有notifyQueue load error的打印 在过滤器重添加过滤条件即可 -message:notifyQueue...

Tomcat的安装与介绍
首先我们先了解一下什么是服务器?什么是服务器软件? 什么是服务器?安装了服务器软件的计算机。 什么是服务器软件? 服务器软件是一种运行在服务器操作系统上,用于接收和处理客户端请求,并提供相应服务和资…...

说点大实话丨知名技术博主 Kirito 测评云原生网关
作者:徐靖峰 关注了阿里云云原生公众号,经常能看到 MSE-Higress 相关的推文,恰逢这次阿里云产品举办了一个 MSE-Higress 云原生网关的测评活动,借此机会体验了一把云原生网关的功能。 购买流程体验 购买网关时,页面明…...

时序预测 | MATLAB实现SO-CNN-BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测
时序预测 | MATLAB实现SO-CNN-BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现SO-CNN-BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | MATLAB实现SO-CNN-BiL…...

简述docker的网络模式
Docker 提供了多种网络模式,用于控制容器之间以及容器与主机之间的网络通信。以下是 Docker 的一些常见网络模式 briage模式: docker容器启动时默认就是该模式,在该模式下,docker容器会连接到一个名为docker0的虚拟以太网桥上,通…...

MySql-8.0.34 CentOS 安装命令记录
1、执行以下命令获取 glibc 版本,根据版本下载相应的MySQL安装包。 ldd --version 2、下载MySQL。 wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.34-linux-glibc2.17-x86_64.tar.gz 3、解压 tar -xzvf mysql-8.0.34-linux-glibc2.17-x86_64.t…...

开发电子木鱼功德+1需要多少钱
冥想木鱼小程序是一种结合了冥想和科技的应用形式,为用户提供了随时随地进行冥想的便捷方式。开发一款高质量的冥想木鱼小程序需要综合考虑技术实现、冥想专业性和用户体验等多个方面。本文将详细介绍冥想木鱼小程序的开发过程,并探讨其中的专业性与思考…...

批处理中扩展解释%~的相关知识和用法,并给出示例和实际运行结果展示
批处理中扩展解释%~的相关知识和用法,并给出示例和实际运行结果展示 在批处理脚本中,%~是一个特殊的前缀,用于对参数和变量进行字符串处理。这个前缀后面可以跟着不同的字符,用于执行不同的操作。下面是一些常见的用法:…...

LA@向量组间的表示关系
文章目录 2个向量组间的表示关系向量组的相互表出向量组用另一个向量组表示👺线性表示的系数矩阵矩阵乘法与线性表出列向量组线性表示行向量组线性表示 向量组等价👺向量组等价的性质推论 等价矩阵与向量组等价的关系行等价矩阵的行向量组等价列等价矩阵…...

Mybatis与Spring集成
目录 一.Spring整合Mybatis 1.什么是Spring整合Mybatis 新建一个ssm 编辑 导入pom依赖 导入generatorConfig.xml 导入Spring-context.xml文件 导入Spring-mybatis.xml文件 自动生成Bookmapper.xml和Bookmapper文件 编写接口类:Bookbiz 编写接口实现类 …...

AMBA总线协议(0)——目录与传送门
一、AMBA总线协议 Arm高级微控制器总线架构(Advanced Microcontroller Bus Architecture,AMBA)是一种开放式标准片上互联规范,用于连接和管理片上系统(System on Chip,Soc)中的功能块。 AMBA是一种广泛用于…...

R语言快速生成三线表(1)
R语言的优势在于批量处理,常使用到循环和函数,三线表是科研文章中必备的内容。利用函数实现自动判断数据类型和计算。使用R包(table1)。 # 创建连续性变量 continuous_var1 <- c(1.2, 2.5, 3.7, 4.8, 5.9) continuous_var2 &l…...

【动手学深度学习】--20.目标检测和边界框
文章目录 目标检测和边界框1.目标检测2.边界框 目标检测和边界框 学习视频:物体检测和数据集【动手学深度学习v2】 官方笔记:目标检测和边界框 在图像分类任务中,我们假设图像中只有一个主要物体对象,我们只关注如何识别其类别…...

实验八 网卡驱动移植
【实验目的】 掌握 Linux 内核配置的基本方法,完成对网卡驱动、NFS 等相关功能的配置 【实验环境】 ubuntu 14.04 发行版FS4412 实验平台交叉编译工具:arm-none-linux-gnueabi- 【注意事项】 实验步骤中以“$”开头的命令表示在 ubuntu 环境下执行&…...

Linux的Man Page知识记录
Man(short for manual) Page是Unix和Linux操作系统中的一个重要文档,提供命令、函数、系统调用等的详细介绍和使用说明。它是以纯文本的形式出现,通常在终端(terminal)中使用man命令访问。Man Page按照章节…...

RTSP/Onvif视频服务器EasyNVR安防视频云服务调用接口录像会被自动删除的问题解决方案
EasyNVR安防视频云服务是基于RTSP/Onvif协议接入的视频平台,可支持将接入的视频流进行全平台、全终端的分发,分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等。平台丰富灵活的视频能力,可应用在智慧校园、智慧工厂、智慧水利等…...

几个nlp的小项目(文本分类)
几个nlp的小项目(文本分类) 导入加载数据类、评测类查看数据集精确展示数据测评方法设置参数tokenizer,token化的解释对数据集进行预处理加载预训练模型进行训练设置训练模型的参数一个根据任务名获取,测评方法的函数创建预训练模型开始训练本项目的工作完成了什么任务?导…...

MFC——base编码和json数据
目录 1. JSON是什么 2. base64是什么 Base64是一种编解码算法 1. JSON是什么 JSON 是一种数据格式。采用完全独立于语言的文本格式, 因为易读, 易写, 易解析的特性成为理想的数据交换语言。主要有三种类型的值:简单值(字符串, 数字, 布尔, null), 对象, 数组。 长这样的数…...

SQL Server、MySQL和Oracle数据库分页查询的区别与联系
摘要:本文将通过一个现实例子,详细解释SQL Server、MySQL和Oracle这三种常见关系型数据库在分页查询方面的区别与联系。我们将提供具体场景下的SQL语句示例,并解释每个数据库的分页查询用法以及优化方法,帮助读者更好地选择适合自…...

Qt跨平台无边框窗口探索记录
一、前言 实现的效果为:通过黑色矩形框预操作,鼠标释放时更新窗口。效果图如下: 1.功能 1.1 已实现功能 8个方向的缩放标题栏拖动标题栏双击最大化/正常窗口窗口最小尺寸预操作框颜色与背景色互补多屏幕默认标题栏 1.2 待开发功能 拖动到…...

概念解析 | 电磁计算的新篇章:智能电磁计算
注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:智能电磁计算。 电磁计算的新篇章:智能电磁计算 随着人工智能的飞速发展,我们正处在一个信息爆炸的时代。各个领域&a…...

SpringBoot-1-Spring Boot实战:快速搭建你的第一个应用,以及了解原理
SpringBoot-1-Spring Boot实战:快速搭建你的第一个应用,以及了解原理 今日内容 SpringBootWeb入门 前言 我们在之前介绍Spring的时候,已经说过Spring官方(Spring官方)提供很多开源项目,点击projects,看到spring家族…...