当前位置: 首页 > 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;包名 实体类的定义…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...