谈一谈浏览器与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浏览器重绘前更新动画
postMessageiframe跨域通信
MessageChannel管道通信
setImmediate一次事件循环执行完毕调用

微任务

任务浏览器Node.js描述
Promise中resolve和reject回调
async函数中的await异步函数
MutationObserver监听DOM变动触发
process.nextTick当前任务结束后执行

事件循环

与上面进程与线程的介绍一样,在浏览器中与Node.js中实现循环的方式也并不相同。下面我们来分别简单介绍一下。注意,这仅仅是对执行逻辑的抽象和总结,实际上浏览器和Node.js中的实现要更复杂。

浏览器中的事件循环

浏览器中的事件循环可以分为两个队列,宏任务队列和微任务队列。具体的任务执行顺序如下:

  1. 解析HTML中遇到script标签,开始执行第一个宏任务。
  2. 在宏任务执行中遇到宏任务,执行其中的请求(例如网络请求,定时器),在请求完成后将回调放入宏任务队列中。
  3. 在宏任务执行中遇到微任务,暂不执行回调,而是放入微任务队列中。
  4. 宏任务执行完成。开始依次执行微任务队列中的任务。
  5. 微任务执行中遇到宏任务或者微任务,处理方式同上,分别放入各自的队列中。
  6. 微任务队列清空后,开始执行宏任务队列中的下一个任务。

在事件循环的流程中,微任务的优先级实际上更高,执行完一个宏任务之后,要执行微任务队列中的所有任务。

为什么要区分宏任务和宏任务,优先级也不同

因为不同任务的开销不同,有的任务需要调用不同的线程甚至进程,有的任务需要等待请求返回甚至定时。

  1. 如果将全部的任务同步执行,那些耗时较久的任务会阻塞,造成整个页面加载缓慢。假设有请求A耗时10秒,请求B耗时20秒,如果同步执行,需要耗费30秒。如果将请求由其它线程实现,回调放入宏任务,则执行流程变为:执行代码->碰到A请求,其他线程异步等待返回->继续执行代码->碰到b请求,其他线程异步等待返回。A和B就实现了异步请求,回调被分别放入宏任务,等待下次事件循环。耗时间为20秒。
  2. 为什么微任务的优先级更高?因为微任务大部分是耗时不太久,不需要等待其他线程/进程等待完成通知的。因此,微任务相当于在宏任务的基础上进行了“插队”,拥有更高的优先级,也提高了页面的响应速度。

为什么script标签是宏任务呢?

  1. script标签可能需要异步请求获取,例如<script src="myscripts.js"></script>
  2. script标签是嵌入在HTML中的,浏览器需要将HTML中的script标签解析出来供执行,这个步骤需要耗费一定的时间。

浏览器事件循环的更多说明

WHATWG(网页超文本应用技术工作小组)在官网对事件循环和任务队列做出了更详细的说明和解释,可以作为参考:说明文档。在新的说明中,任务的分类和事件循环已经有了部分区别,这里简要说一下,更多还请直接查看文档:

  1. 事件循环不一定对应于多线程。例如多个事件循环可以在单个线程中协作调度。
  2. 任务队列并不是一个严格的队列,而是一个集合。每次从队列中取出一个可以被执行的任务,而不是选取第一个任务(可能该任务还在阻塞中)。
  3. 宏任务队列有多个,不同类型的任务(任务源)放置在不同的任务队列中。具体的选取规则浏览器根据实际情况确定。

Node.js中的宏任务队列

Node.js的官网给出了事件循环的文档。它的事件循环要比浏览器的看起来复杂一些。下面是Node.js的宏任务队列。

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │└───────────────────────────┘

Node.js的宏任务队列并不是一整个队列,而是根据事件类型做出了区分,分为了六个队列,依次执行:

  1. timers 定时器队列,执行定时器的回调
  2. pending callbacks 挂起的回调函数,用于某些系统回调
  3. idle, prepare 仅在内部使用
  4. poll 执行I/O事件回调
  5. check setImmediate回调
  6. close callbacks close事件的回调,例如 socket.on('close', ...)

其中我们的大部分宏任务回调都会在poll阶段执行,除了timerscheckclose callbacks阶段的特殊回调。每个宏任务队列都有自己的微任务队列。

在这里插入图片描述

Node.js事件循环的流程

  1. 首先执行主线代码,遇到宏任务就分配到对应的宏任务队列中,微任务也划分到主线的微任务队列中,直到执行完毕。
  2. 执行主线代码的微任务队列中的所有任务。
  3. 没有宏任务则执行结束,有则开始事件循环。在事件循环中,按照上述的6个宏任务队列依次执行。下面的步骤是单个队列中的流程。
  4. 在单个宏任务队列中,选择一个宏任务执行。如果执行中遇到新的宏任务就分配到对应的宏任务队列中。遇到微任务就放到该宏任务的微任务队列中。
  5. 一个宏任务执行完毕后,执行process.nextTick中的回调(如果有)。
  6. 执行当前宏任务的微任务队列中的任务,直到微任务队列清空。
  7. 在上面的单个宏任务队列中,再选择一个宏任务执行。直到当前宏任务队列清空或者到达上限。
  8. 选择下一个宏任务队列执行。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/51693.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅谈视频汇聚平台EasyCVR视频平台在城市安全综合监测预警台风天气中的重要作用

夏日已至&#xff0c;台风和暴雨等极端天气频繁出现。在城市运行过程中&#xff0c;台风所带来的暴雨可能会导致城市内涝等次生灾害&#xff0c;引发交通瘫痪、地铁停运、管网泄漏爆管、路面塌陷、防洪排涝、燃气爆炸、供热安全、管廊安全、消防火灾等安全隐患&#xff0c;影响…

性能优化——分库分表

1、什么是分库分表 1.1、分表 将同一个库中的一张表&#xff08;比如SPU表&#xff09;按某种方式&#xff08;垂直拆分、水平拆分&#xff09;拆分成SPU1、SPU2、SPU3、SPU4…等若干张表&#xff0c;如下图所示&#xff1a; 1.2、分库 在表数据不变的情况下&#xff0c;对…

【Unity3D赛车游戏】【四】在Unity中添加阿克曼转向,下压力,质心会让汽车更稳定

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

C++实现字符串的逆置

目录 C和C的区别 【1】C对C的扩充 【2】C对C的兼容 第一个C程序 【1】hello world 【2】cout标准输出流对象 i&#xff09;介绍 ii&#xff09;运算 iii&#xff09;cout的使用 iv&#xff09;使用cout指定格式的输出 练习&#xff1a;1、输出斐波那契的前10项。 【3】…

《Linux从练气到飞升》No.17 进程创建

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

开学需要买哪些电容笔?ipad可以用的手写笔

因为iPad的功能亮眼&#xff0c;让iPad的用户越来越多&#xff0c;并且越来越受欢迎。用来画画、做笔记都很有用&#xff0c;但要是用来看电视、打游戏的话&#xff0c;使用价值就显得低了。如果你不想买一支价格不菲的苹果电容笔&#xff0c;或是只是想要日常用于书写记录&…

【云原生】Docker Cgroups资源控制管理

目录 一、cgroups简介 cgroups有四大功能&#xff1a; 二、cpu时间片的概念 三、对CPU使用的限制 3.1 设置CPU使用率上限 &#xff08;1&#xff09;查看容器的默认CPU使用限制 &#xff08;2&#xff09;进行压力测试 &#xff08;3&#xff09;创建容器时设置CPU使用时…

Linux下套接字TCP实现网络通信

Linux下套接字TCP实现网络通信 文章目录 Linux下套接字TCP实现网络通信1.引言2.具体实现2.1接口介绍1.socket()2.bind()3.listen()4.accept()5.connect() 2.2 服务器端server.hpp2.3服务端server.cc2.4客户端client.cc 1.引言 ​ 套接字(Socket)是计算机网络中实现网络通信的一…

浙大陈越何钦铭数据结构07-图6 旅游规划

题目: 有了一张自驾旅游路线图&#xff0c;你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序&#xff0c;帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的&#xff0c;那么需要输出最便宜的一条路径。 输入…

【3Ds Max】可编辑多边形“边界”层级的简单使用

目录 示例 &#xff08;1&#xff09;挤出 &#xff08;2&#xff09;插入顶点 &#xff08;3&#xff09;切角 &#xff08;4&#xff09;利用所选内容创建图形 &#xff08;5&#xff09;封口 &#xff08;6&#xff09;桥 示例 这里我们首先创建一个长方体&#xff…

Java基础之IO流File类创建及删除

1.File类概述及构造方法 2.File类创建功能 文件创建成功&#xff01; 如果文件不存在&#xff0c;就创建文件&#xff0c;并返回true 如果文件存在&#xff0c;就不创建文件&#xff0c;并返回false 如果文件夹不存在&#xff0c;就创建文件夹&#xff0c;并返回true 如果文件…

html学习第2篇---标签(1)

html学习第2篇---标签 1、标题标签h1---h62、段落标签p3、换行标签br4、文本格式化标签5、div标签和span标签6、图像标签img6.1、图像属性6.2、相对路径、绝对路径 7、超链接标签a7.1、属性7.2、分类 8、注释标签和特殊字符8.1、注释8.2、特殊字符 1、标题标签h1—h6 为了使网…

【Unity学习笔记】DOTween(2)官方案例

本文中大部分内容学习来自DOTween官方文档 此处无法展示动图&#xff08;懒得录GIF&#xff09;&#xff0c;请下载官方案例场景自行学习 文章目录 场景1 基本补间场景2 动态补间场景3 Shader修改场景4 路径拟合运动场景5 序列播放场景6 UGUI 场景1 基本补间 案例一展示了最基…

AMBA总线协议(0)——目录与传送门

一、AMBA总线协议 Arm高级微控制器总线架构&#xff08;Advanced Microcontroller Bus Architecture&#xff0c;AMBA&#xff09;是一种开放式标准片上互联规范&#xff0c;用于连接和管理片上系统&#xff08;System on Chip,Soc&#xff09;中的功能块。 AMBA是一种广泛用于…

【DevOps视频笔记】4.Build 阶段 - Maven安装配置

一、Build 阶段工具 二、Operate阶段工具 三、服务器中安装 四、修改网卡信息 五、安装 jdk 和 maven Stage1 : 安装 JDK Stage 2 : 安装 Maven 2-1 : 更换文件夹名称 2-2 : 替换配置文件 settings.xml- 2-3 : 修改settings.xml详情 A. 修改maven仓库地址 - 阿里云 B…

CentOS系统环境搭建(十七)——elasticsearch设置密码

centos系统环境搭建专栏&#x1f517;点击跳转 elasticsearch设置密码 没有密码是很不安全的一件事&#x1f62d; 文章目录 elasticsearch设置密码1.设置密码2.登录elasticsearch3.登录kibana4.登录elasticsearch-head 1.设置密码 关于Elasticsearch的安装请看CentOS系统环境搭…

适配小程序隐私保护指引设置

由于小程序发布了一个公告&#xff0c;那么接下来就是怎么改简单的问题了。毕竟不太想大的改动历史上的代码。尽量简单的适配隐私策略就可以了。 整体思路也是参考现在App普遍的启动就让用户同意隐私策略&#xff0c;不同意不让用&#xff0c;同意了之后才能够继续使用。 公告…

计算机网络 QA

DNS 的解析过程 浏览器缓存。当用户通过浏览器访问某域名时&#xff0c;浏览器首先会在自己的缓存中查找是否有该域名对应的 IP 地址&#xff08;曾经访问过该域名并且没有清空缓存&#xff09;系统缓存。当浏览器缓存中无域名对应的 IP 地址时&#xff0c;会自动检测用户计算机…

docker搭建owncloud,Harbor,构建镜像

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 拉取镜像 docker pull owncloud docker pull mysql:5.6 2、安装搭建私有仓库 Harbor 1.下载docker-compose 2.安装harbor 3.编辑 harbor.yml文件 使用./intall.sh安装 4.登录 3、编写Dockerfile制作Web应用系…

【学习FreeRTOS】第13章——FreeRTOS队列

1.队列简介 队列是任务到任务、任务到中断、中断到任务数据交流的一种机制&#xff08;消息传递&#xff09; FreeRTOS基于队列&#xff0c; 实现了多种功能&#xff0c;其中包括队列集、互斥信号量、计数型信号量、二值信号量、 递归互斥信号量&#xff0c;因此很有必要深入了…