当前位置: 首页 > news >正文

JavaScript 事件循环(EventLoop) —— 浏览器 Node

一、事件循环的本质

本质:运行时对 JS 脚本的调度方式就叫做事件循环.

  • 对于 浏览器 而言,需要考虑用户交互、UI渲染、脚本运行、网络请求等操作,这些操作必然都依赖于事件去执行,因此,为了协调事件必须要使用事件循环.
  • 对于 Node 而言,尽管 JavaScript 是单线程的,但系统内核是多线程的,它们可以在后台处理多种操作,当其中一个完成操作的时候,内核将通知 Node 将适合的回调添加到队列中,并等待时机执行. 而事件循环是 Node 处理非阻塞 I/O 操作的机制.

二、浏览器的事件循环

JS 为什么是单线程?

JavaScript 的用途决定了它的单线程。

作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

任务队列

产生任务队列的原因?

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

如果排队是因为计算量大,CPU 处理不过来,这时候也算合理,但很多时候 CPU 是空闲的,是因为 IO 设备(输入输出设备)很慢(比如 Ajax 操作从网络读取数据),CPU 不得不等着结果返回,才能继续往下执行。

JavaScript 语言的设计者意识到,这时主线程完全可以不管 IO 设备,挂起处于等待中的任务,先运行排在后面的任务。等到 IO 设备返回了结果,再回过头,把挂起的任务继续执行下去。

任务队列类型

所有任务可以分成两种,一种是 同步任务(synchronous),另一种是 异步任务(asynchronous)。

同步任务 指的是 在主线程上排队执行的任务 ,只有前一个任务执行完毕,才能执行后一个任务;

异步任务 指的是 不进入主线程,而进入"任务队列"(task queue)的任务,只有 “任务队列” 通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

异步执行的运行机制(同步执行也是如此,因为它可以被视为没有异步任务的异步执行)

(1)所有同步任务都在 主线程 上执行,形成一个 执行栈(execution context stack)。

(2)主线程之外,还存在一个 “任务队列”(task queue)。只要 异步任务 有了运行结果,就在 “任务队列” 之中放置一个事件。

(3)一旦 “执行栈” 中的 所有同步任务执行完毕,系统就会 读取 “任务队列” ,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

主线程 “任务队列” 中读取事件,这个过程是循环不断的,所以整个的运行机制又被称为 事件循环(Event Loop)

在这里插入图片描述
在这里插入图片描述

宏任务和微任务

除了广义的定义 同步任务异步任务,JavaScript 单线程中的任务还可以细分为 宏任务(macrotask)微任务(microtask)

宏任务包含:

  • script (整体代码)
  • setTimeout
  • setInterval
  • I/O
  • UI 交互事件
  • postMessage
  • MessageChannel
  • setImmediate(Node.js)

注意: 很多人可能认为 requestAnimationFrame 是宏任务,但确切的说它的执行时机和宏任务还是有些不同的,具体可见

微任务包含:

  • Promise.then、catch 、finally
  • queueMicrotask()
  • MutationObserver
  • process.nextTick (Node.js)

在这里插入图片描述

通过题目进行分析

  • 一轮事件循环,会执行一次宏任务,以及本轮循环产生的所有微任务.
  • 任务队列保持先进先出的顺序去执行.
// 下面的输出顺序是什么?
setTimeout(() => {console.log('setTimeout');
}, 0);Promise.resolve().then(()=>{console.log('promise');
});console.log('main');

相信上面的题目对大家来说都不困难,下面通过图片来展示相应的流程:

在这里插入图片描述

到现在你应该对整个浏览器事件循环有所理解,可以尝试分析下面这道题目:

  • PS:如果你仍旧不是很理解这个流程,建议按照本文中画流程图的形式去进行分析
setTimeout(() => {console.log('setTimeout_outer');Promise.resolve().then(() => {console.log('promise in setTimeout_outer');});
}, 0);Promise.resolve().then(() => {console.log('promise_outer');setTimeout(() => {console.log('setTimeout in promise_outer');}, 0);
});console.log('main');

三、Node 中的事件循环

事件循环机制解析

当 Node.js 启动后,它会初始化事件循环,处理已提供的输入脚本,它可能会调用一些异步的 API、调度定时器,或者调用 process.nextTick(),然后开始处理事件循环。

每一个 Event Loop 都会包含如下顺序的六个阶段,它和浏览器的事件循环是完全不同的。

在这里插入图片描述

六个阶段

  • timers (定时器):本阶段执行已经被 setTimeout()setInterval() 的调度回调函数。
  • pending callbacks (待定回调):执行延迟到下一个循环迭代的 I/O 回调。
  • idle, prepare:仅系统内部使用。
  • poll (轮询):检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
  • check (检测)setImmediate() 回调函数在这里执行。
  • close callback (关闭的回调函数):一些关闭的回调函数,如:socket.on('close', ...)

其中 poll (轮询) 阶段有两个重要的功能:

  1. 计算应该阻塞和轮询 I/O 的时间。
  2. 然后,处理 轮询 队列里的事件。

当事件循环进入 轮询 阶段且 没有被调度的计时器时 ,将发生以下 两种情况之一

  • 如果 轮询 队列 不是空的 ,事件循环将循环访问回调队列并同步执行它们,直到队列已用尽,或者达到了与系统相关的硬性限制。

  • 如果 轮询 队列 是空的 ,还有两件事发生:

    • 如果脚本被 setImmediate() 调度,则事件循环将结束 轮询 阶段,并继续 检查 阶段以执行那些被调度的脚本。
    • 如果脚本 未被 setImmediate()调度,则事件循环将等待回调被添加到队列中,然后立即执行。

一旦 poll(轮询) 队列为空,事件循环将检查 已达到时间阈值的计时器。如果一个或多个计时器已准备就绪,则事件循环将绕回timers(计时器) 阶段以执行这些计时器的回调。

setImmediate() 对比 setTimeout()

setImmediate()setTimeout() 很类似,但它们被调用的时机不同。

  • setImmediate() 设计为一旦在当前 轮询 阶段完成, 就执行脚本.
  • setTimeout() 在最小阈值(ms 单位)过后运行脚本.
  • setImmediate() 相对于 setTimeout() 优势在于,如果 setImmediate() 是在 I/O 周期内 被调度的,那它将会在其中 任何的定时器之前执行,跟存在多少个定时器无关.

执行计时器的顺序将根据调用它们的上下文而异

如果二者都从主模块内调用,则计时器将受进程性能的约束(这可能会受到计算机上其他正在运行应用程序的影响)。

例如,如果运行以下 不在 I/O 周期(即主模块)内的脚本 ,则执行两个计时器的顺序是 非确定性 的,因为它受进程性能的约束,或者说受 event loop 启动时间的影响:

// timeout_vs_immediate.jssetTimeout(() => {console.log('timeout');
}, 0);setImmediate(() => {console.log('immediate');
});输出的结果可能为:timeout  immediate 或者 immediate  timeout

下面给出了存在上述输出结果的两种情况 图解

在这里插入图片描述

如果把这两个函数放入一个 I/O 循环 内调用,setImmediate 总是被 优先调用

// timeout_vs_immediate.js
const fs = require('fs');fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');}, 0);setImmediate(() => {console.log('immediate');});
});输出结果恒为:immediate  timeout

同一个 I/O 循环中更有确定性,因此,你也可以自己尝试画流程图的方式去理解它.

process.nextTick()

  • process.nextTick() 是异步 API 的一部分,它并不是事件循环的一部分.
  • process.nextTick() 始终在当前操作完成后处理 nextTickQueue,而不管事件循环的当前阶段如何.
  • 任何时候在给定的阶段中调用的 process.nextTick(),不在六个阶段中的任一阶段内执行,而是在 一个阶段切换到下一个阶段之前执行.

process.nextTick() 对比 setImmediate()

  • process.nextTick() 在同一个阶段立即执行.
  • setImmediate() 在事件循环的接下来的迭代或 ‘tick’ 上触发.
  • process.nextTick()setImmediate() 触发得更快.
    下面通过具体的例子,来验证三者的关系:
const fs = require('fs');
const path = require('path');fs.readFile(path.resolve(__dirname, '/index.html'), () => {setTimeout(() => {console.log('setTimeout');process.nextTick(() => {console.log('nextTick in setTimeout');});}, 1);setImmediate(() => {console.log('setImmediate');process.nextTick(() => {console.log('nextTick in setImmediate');});});process.nextTick(() => {console.log('nextTick outer'); });
});

下面给出流程分析图解:

在这里插入图片描述

结束语

事件循环的相关内容相对来说还是迂回环绕的,并不是任何一篇文章就能够了解透彻,里面的很多执行流程,最好自己画图去辅助理解,不要凭空想象.

相关文章:

JavaScript 事件循环(EventLoop) —— 浏览器 Node

一、事件循环的本质 本质:运行时对 JS 脚本的调度方式就叫做事件循环. 对于 浏览器 而言,需要考虑用户交互、UI渲染、脚本运行、网络请求等操作,这些操作必然都依赖于事件去执行,因此,为了协调事件必须要使用事件循环…...

【ROS2】订阅手柄数据,发布运动命令

1、相关消息 sensor_msgs::msg::Joy:用来描述手柄控制器数据 geometry_msgs::msg::Twist :用来描述物体运动时的线速度和角速度 参见博客: 【ROS2】geometry_msgs::msg::Twist和sensor_msgs::msg::Joy 2、订阅和发布 2.1 定义、创建订阅者和发布者 订阅手柄的按键、摇杆…...

WinX86内核02-驱动程序

把昨天的程序改用 c++ 编译,改成 .cpp ,发现编译报错 原因是名称粉碎,因此可以直接 extern “C”声明一下这个函数 或者用 头文件(推荐) 因为 在头文件中 可以把 头文件一起包含进去 #pragma once extern "C" { #include <Ntddk.h> ​ /*驱动入口函…...

基于SpringBoot+Vue的体育馆场地预约系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

【WebGIS】Cesium:天地图加载

天地图是中国国家基础地理信息系统&#xff0c;由中国测绘地理信息局和国家地理信息公共服务平台共同开发和运营。它提供多项地理信息服务&#xff0c;包括地图数据、地理编码、路径规划以及地理搜索等。天地图的目标是为各行业提供高质量、全面的地理信息数据和解决方案。 天…...

[产品管理-46]:产品组合管理中的项目平衡与管道平衡的区别

目录 一、项目平衡 1.1 概述 1.2 项目的类型 1、根据创新程度和开发方式分类 2、根据产品开发和市场周期分类 3、根据风险程度分类 4、根据市场特征分类 5、根据产品生命周期分类 1.3 产品类型的其他分类 1、按物理形态分类 2、按功能或用途分类 3、按技术或创新程…...

【MySQL】MySQL的简单了解详解SQL分类数据库的操纵方法

一、mysql定义 mysql是数据库服务的客户端&#xff0c;mysqld是数据库服务的服务器端。mysql的本质就是基于CS模式下的一种网络服务。数据库一般指的是在磁盘中或内存中存储的特定结构组织的数据&#xff0c;将来就是在磁盘上存储的一套数据库方案。 创建数据库&#xff0c;本质…...

【Python爬虫实战】正则:从基础字符匹配到复杂文本处理的全面指南

&#x1f308;个人主页&#xff1a;https://blog.csdn.net/2401_86688088?typeblog &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html 目录 前言 一、正则表达式 &#xff08;一&#xff09;正则表达式的基本作用 &#xf…...

10.18Python基础迭代器生成器_函数式编程

Python迭代器与生成器 1. 迭代器 Iterator 什么是迭代器 迭代器是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器可以重复使用&#xff0c;而不会像列表那样在迭代时被修改。 迭代器函数iter和next 函数说明iter(iterable)从可迭代对象中返回一个迭…...

HttpPost 类(构建 HTTP POST 请求)

HttpPost 类是 Apache HttpClient 库中的一个类&#xff0c;用于构建 HTTP POST 请求。以下是 HttpPost 类的一些常用方法和代码案例&#xff1a; 常用方法 构造方法&#xff1a; HttpPost(String uri)&#xff1a;创建一个 HttpPost 对象&#xff0c;并将请求的 URI 作为参数…...

xtu oj 原根

文章目录 回顾杂思路c 语言代码 回顾 AB III问题 H: 三角数问题 G: 3个数等式 数组下标查询&#xff0c;降低时间复杂度1405 问题 E: 世界杯xtu 数码串xtu oj 神经网络xtu oj 1167 逆序数&#xff08;大数据&#xff09; 杂 有一些题可能是往年的程设的题&#xff0c;现在搬到…...

Java Spring 中常用的 @PostConstruct 注解使用总结

引言 在最近的学习中&#xff0c;我发现了一个非常实用的注解 —— PostConstruct。通过深入学习&#xff0c;逐步发现这个注解在实际开发中可以帮助我们更轻松地解决不少原本复杂的问题&#xff0c;特别是在项目启动时自动执行一些必要的初始化操作。相比于手动调用&#xff…...

Visual Studio--VS安装配置使用教程

Visual Studio Visual Studio 是一款功能强大的开发人员工具&#xff0c;可用于在一个位置完成整个开发周期。 它是一种全面的集成开发环境 (IDE)。对新手特别友好&#xff0c;使用方便&#xff0c;不需要复杂的去配置环境。用它学习很方便。 Studio安装教程 Visual Studio官…...

什么叫CMS?如何使用CMS来制作网站?

CMS是什么&#xff1f; 内容管理系统&#xff08;Content Management System&#xff0c;CMS&#xff09;&#xff0c;是一种位于WEB前端&#xff08;Web 服务器&#xff09;和后端办公系统或流程&#xff08;内容创作、编辑&#xff09;之间的软件系统。内容的创作人员、编辑人…...

如何获取谷歌浏览器窗口句柄并将其设置为Qt的父窗口

1、首先&#xff0c;确保你在项目的 .pro 文件中加入对WinAPI的支持&#xff1a; win32: LIBS -luser322、步骤概述&#xff1a; 使用WinAPI获取谷歌浏览器窗口的句柄。获取Qt窗口的句柄。使用SetParent函数&#xff0c;将Qt窗口设置为谷歌浏览器窗口的子窗口。调整Qt窗口的…...

牛客小白月赛102:最短?路径(分层bfs)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 给定一个 nnn 个点 mmm 条边的无向图&#xff0c;LH 打算从点 111 出发去点 nnn。 假如 LH 到达了一个点 iii&#xff0c;那么他可以选择在这个点花费 aia_iai​ 的时间休息后继续赶…...

JSON字符串转成java的Map对象

要将这个JSON字符串转换成Java对象&#xff0c;你可以定义一个Element类来表示每个要素&#xff0c;然后使用一个Map来存储这些要素。以下是具体的实现步骤&#xff1a; 步骤 1: 定义 Element 类 首先&#xff0c;定义一个Element类来表示每个要素的结构&#xff1a; public…...

重读《人月神话》(8)-为什么巴比伦塔会失败?(Why Did the Tower of Babel Fail?)

据《创世纪》记载&#xff0c;巴比伦塔是人类继诺亚方舟之后的第二大工程壮举&#xff0c;但巴比伦塔同时也是第一个彻底失败的工程。 巴比伦塔的管理教训 这个项目具备了几乎所有成功的先决条件&#xff1a; 有清晰的目标&#xff0c;尽管目标理想化到了近乎不可实现的地步&…...

STL源码剖析:Hashtable

hashtable 概述 哈希表是一种数据结构&#xff0c;它提供了快速的数据插入、删除和查找功能。它通过使用哈希函数将键&#xff08;key&#xff09;映射到表中的一个位置来实现这一点&#xff0c;这个位置称为哈希值或索引。哈希表使得这些操作的平均时间复杂度为常数时间&…...

spring-boot学习(2)

上次学习截止到拦截器 1.构建RESfun服务 PathVariable通过url路径获取url传递过来的信息 2.MyBatisPlus 第三行的mydb要改为自己的数据库名 第四&#xff0c;五行的账号密码改成自己的 MaooerScan告诉项目自己的这个MyBatisPlus是使用在哪里的&#xff0c;包名 实体类的定义…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

2023赣州旅游投资集团

单选题 1.“不登高山&#xff0c;不知天之高也&#xff1b;不临深溪&#xff0c;不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

Yolov8 目标检测蒸馏学习记录

yolov8系列模型蒸馏基本流程&#xff0c;代码下载&#xff1a;这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中&#xff0c;**知识蒸馏&#xff08;Knowledge Distillation&#xff09;**被广泛应用&#xff0c;作为提升模型…...

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

Vue3 PC端 UI组件库我更推荐Naive UI

一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用&#xff0c;前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率&#xff0c;还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库&#xff08;Naive UI、Element …...

写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里

写一个shell脚本&#xff0c;把局域网内&#xff0c;把能ping通的IP和不能ping通的IP分类&#xff0c;并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...

初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)

零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...