”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 事件循环

事件循环

发布于2024-08-23
浏览:615

Event Loop

介绍
JavaScript 主要在 Node.js 和浏览器中的单个线程上执行(有一些例外,例如工作线程,这超出了当前文章的范围)。在这篇文章中,我将尝试解释 Node.js 的并发机制,即事件循环。

在开始阅读本文之前,您应该熟悉堆栈及其工作原理,我过去写过这个想法,所以请查看堆栈和堆 — 不要在不了解它们的情况下开始编码 — Moshe Binieli |中等的

介绍图片
示例
我相信通过示例学习是最好的,因此我将从 4 个简单的代码示例开始。我将分析示例,然后深入探讨 Node.js 的架构。

示例1:
控制台.log(1);
控制台.log(2);
控制台.log(3);
// 输出:
// 1
// 2
// 3
这个例子很简单,第一步console.log(1)进入调用堆栈,被执行然后删除,第二步console.log(2)进入调用堆栈,被执行然后删除,以此类推 console.log(3).

示例 1 的调用堆栈可视化
示例2:
控制台.log(1);
setTimeout(函数 foo(){
控制台.log(2);
}, 0);
控制台.log(3);
// 输出:
// 1
// 3
// 2
在这个例子中我们可以看到我们立即运行setTimeout,所以我们期望console.log(2)在console.log(3)之前,但事实并非如此,让我们了解其背后的机制。

基本事件循环架构(稍后我们将深入探讨)

Stack & Heap:查看我关于此的文章(我在本文开头添加了链接)
Web API:它们内置于您的 Web 浏览器中,能够公开来自浏览器和周围计算机环境的数据,并用它执行有用的复杂操作。它们不是 JavaScript 语言本身的一部分,而是构建在核心 JavaScript 语言之上,为您提供在 JavaScript 代码中使用的额外超能力。例如,Geolocation API 提供了一些简单的 JavaScript 构造来检索位置数据,因此您可以说,在 Google 地图上绘制您的位置。在后台,浏览器实际上使用一些复杂的低级代码(例如 C )与设备的 GPS 硬件(或任何可用于确定位置数据的硬件)进行通信,检索位置数据,并将其返回到浏览器环境以使用在你的代码中。但同样,这种复杂性已被 API 抽象化。
事件循环和回调队列:完成Web API执行的函数将被移动到回调队列,这是一个常规队列数据结构,事件循环负责将下一个函数从回调队列中出队并将该函数发送到执行函数的调用堆栈。
执行顺序

当前位于调用堆栈中的所有函数都会被执行,然后它们会从调用堆栈中弹出。
当调用堆栈为空时,所有排队的任务都会一一弹出到调用堆栈中并执行,然后从调用堆栈中弹出。
让我们来理解示例 2

console.log(1)方法被调用并放置在调用堆栈上并被执行。

  1. setTimeout 方法被调用并放置在调用堆栈上并被执行,此执行创建一个对 setTimeout Web Api 的新调用,持续 0 毫秒,当它完成时(立即,或者更准确地说,那么它会最好说“尽快”)Web Api 将调用移至回调队列。

  2. console.log(3)方法被调用并放置在调用堆栈上并被执行。

  3. Event Loop发现Call Stack为空,从Callback Queue中取出“foo”方法放入Call Stack,然后执行console.log(2)。

示例 2 的过程可视化
所以setTimeout(function,delay)中的delay参数并不代表函数执行后的精确时间延迟。它代表等待的最短时间,之后在某个时间点将执行该函数。

示例3:
控制台.log(1);
setTimeout(函数 foo() {
console.log('foo');
}, 3500);
setTimeout(函数 boo() {
console.log(‘嘘’);
}, 1000);
控制台.log(2);
// 输出:
// 1
// 2
// '嘘'
// 'foo'

示例 3 的过程可视化
例4:
控制台.log(1);
setTimeout(函数 foo() {
console.log('foo');
}, 6500);
setTimeout(函数 boo() {
console.log(‘嘘’);
}, 2500);
setTimeout(函数 baz() {
console.log(‘baz’);
}, 0);
for ([‘A’, ‘B’] 的 const 值) {
console.log(值);
}
函数二() {
控制台.log(2);
}
二();
// 输出:
// 1
// 'A'
// 'B'
// 2
// '巴兹'
// '嘘'
// 'foo'

示例 4 的过程可视化
事件循环继续执行任务队列中等待的所有回调。任务队列内部,任务大致分为两类,即微任务和宏任务。

宏任务(任务队列)和微任务
更准确地说,实际上有两种类型的队列。

  1. 宏任务队列(或简称任务队列)。
  2. 微任务队列。

还有一些任务进入宏任务队列和微任务队列,但我将介绍常见的任务。

常见的宏任务有 setTimeout、setInterval 和 setImmediate。
常见的微任务有 process.nextTick 和 Promise 回调。
执行顺序
当前位于调用堆栈中的所有函数都会被执行,然后它们会从调用堆栈中弹出。
当调用堆栈为空时,所有排队的微任务都会一一弹出到调用堆栈中并执行,然后从调用堆栈中弹出。
当调用栈和微任务队列都为空时,所有排队的宏任务都会一一弹出到调用栈中并执行,然后从调用栈中弹出。
例5:
控制台.log(1);
setTimeout(函数 foo() {
console.log('foo');
}, 0);
Promise.resolve()
.then(函数 boo() {
console.log(‘嘘’);
});
控制台.log(2);
// 输出:
// 1
// 2
// '嘘'
// 'foo'
console.log(1) 方法被调用并放置在调用堆栈上并被执行。
SetTimeout 正在执行,console.log(‘foo’) 被移至 SetTimeout Web Api,0 毫秒后移至宏任务队列。
Promise.resolve() 正在被调用,正在被解析,然后 .then() 方法被移动到微任务队列。
console.log(2) 方法被调用并放置在调用堆栈上并被执行。
Event Loop 发现调用堆栈为空,它首先从 Micro-Task 队列中取出任务,即 Promise 任务,将 console.log('boo') 放在调用堆栈上并执行它。
事件循环看到调用堆栈为空,然后看到微任务为空,然后从宏任务队列中取出下一个任务,即 SetTimeout 任务,放入 console.log('foo')在调用堆栈上并执行它。

示例 5 的过程可视化
对事件循环的高级理解
我正在考虑写一篇关于事件循环机制如何工作的底层文章,它本身可以是一篇文章,所以我决定介绍该主题并附上深入解释该主题的良好链接。

事件循环底层解释
当 Node.js 启动时,它会初始化事件循环,处理提供的输入脚本(或放入 REPL),这可能会进行异步 API 调用、调度计时器或调用 process.nextTick(),然后开始处理事件循环。

下图显示了事件循环操作顺序的简化概述。 (每个框将被称为事件循环的一个“阶段”,请查看介绍图片以更好地理解循环。)

事件循环操作顺序的简化概述
每个阶段都有一个要执行的回调的 FIFO 队列(我在这里小心地说,因为根据实现的不同,可能会有其他数据结构)。虽然每个阶段都有其特殊之处,但通常,当事件循环进入给定阶段时,它将执行特定于该阶段的任何操作,然后执行该阶段队列中的回调,直到队列耗尽或达到最大回调数已执行。当队列耗尽或达到回调限制时,事件循环将进入下一阶段,依此类推。

阶段概述
计时器:此阶段执行由 setTimeout() 和 setInterval() 安排的回调。
挂起的回调:执行延迟到下一个循环迭代的 I/O 回调。
空闲,准备:仅内部使用。
Poll:检索新的I/O事件;执行 I/O 相关回调(几乎所有回调,关闭回调、计时器调度的回调和 setImmediate() 除外);节点会在适当的时候阻塞在这里。
检查:此处调用了 setImmediate() 回调。
关闭回调:一些关闭回调,例如socket.on('关闭', ...).
前面的步骤如何适合这里?
因此,前面仅使用“回调队列”,然后使用“宏和微队列”的步骤是关于事件循环如何工作的抽象解释。

还有一件重要的事情要提,事件循环应该在处理宏任务队列中的一个宏任务之后完全处理微任务队列。

第1步:事件循环将循环时间更新为当前执行的当前时间。
第2步:执行微队列。
步骤 3:执行计时器阶段的任务。
第四步:检查微队列中是否有东西,如果有则执行整个微队列。
步骤5:返回步骤3,直到Timers阶段为空。
步骤 6:执行 Pending Callbacks 阶段的任务。
步骤7:检查微队列中是否有东西,如果有则执行整个微队列。
步骤8:返回步骤6,直到Pending Callbacks阶段为空。
然后空闲…微队列…轮询…微队列…检查…微队列…关闭回调然后重新开始。
因此,我很好地概述了事件循环在幕后如何实际工作,有很多遗漏的部分我在这里没有提到,因为实际文档在解释它方面做得很好,我将提供很好的链接文档,我鼓励您花 10-20 分钟来理解它们。

版本声明 本文转载于:https://dev.to/atulnagose1499/event-loop-4fck?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 为什么不使用CSS`content'属性显示图像?
    为什么不使用CSS`content'属性显示图像?
    在Firefox extemers属性为某些图像很大,&& && && &&华倍华倍[华氏华倍华氏度]很少见,却是某些浏览属性很少,尤其是特定于Firefox的某些浏览器未能在使用内容属性引用时未能显示图像的情况。这可以在提供的CSS类中看到:。googlepic { 内容:url(&#...
    编程 发布于2025-07-15
  • 如何使用Depimal.parse()中的指数表示法中的数字?
    如何使用Depimal.parse()中的指数表示法中的数字?
    在尝试使用Decimal.parse(“ 1.2345e-02”中的指数符号表示法表示的字符串时,您可能会遇到错误。这是因为默认解析方法无法识别指数符号。 成功解析这样的字符串,您需要明确指定它代表浮点数。您可以使用numbersTyles.Float样式进行此操作,如下所示:[&& && && ...
    编程 发布于2025-07-15
  • 如何在无序集合中为元组实现通用哈希功能?
    如何在无序集合中为元组实现通用哈希功能?
    在未订购的集合中的元素要纠正此问题,一种方法是手动为特定元组类型定义哈希函数,例如: template template template 。 struct std :: hash { size_t operator()(std :: tuple const&tuple)const {...
    编程 发布于2025-07-15
  • 如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    使用http request 上传文件上传到http server,同时也提交其他参数,java.net.net.urlconnection and Multipart/form-data Encoding是普遍的。 Here's a breakdown of the process:Mu...
    编程 发布于2025-07-15
  • 如何干净地删除匿名JavaScript事件处理程序?
    如何干净地删除匿名JavaScript事件处理程序?
    删除匿名事件侦听器将匿名事件侦听器添加到元素中会提供灵活性和简单性,但是当要删除它们时,可以构成挑战,而无需替换元素本身就可以替换一个问题。 element? element.addeventlistener(event,function(){/在这里工作/},false); 要解决此问题,请考虑...
    编程 发布于2025-07-15
  • C++中如何将独占指针作为函数或构造函数参数传递?
    C++中如何将独占指针作为函数或构造函数参数传递?
    在构造函数和函数中将唯一的指数管理为参数 unique pointers( unique_ptr [2启示。通过值: base(std :: simelor_ptr n) :next(std :: move(n)){} 此方法将唯一指针的所有权转移到函数/对象。指针的内容被移至功能中,在操作...
    编程 发布于2025-07-15
  • 在Ubuntu/linux上安装mysql-python时,如何修复\“ mysql_config \”错误?
    在Ubuntu/linux上安装mysql-python时,如何修复\“ mysql_config \”错误?
    mysql-python安装错误:“ mysql_config找不到”“ 由于缺少MySQL开发库而出现此错误。解决此问题,建议在Ubuntu上使用该分发的存储库。使用以下命令安装Python-MysqldB: sudo apt-get安装python-mysqldb sudo pip in...
    编程 发布于2025-07-15
  • 找到最大计数时,如何解决mySQL中的“组函数\”错误的“无效使用”?
    找到最大计数时,如何解决mySQL中的“组函数\”错误的“无效使用”?
    如何在mySQL中使用mySql 检索最大计数,您可能会遇到一个问题,您可能会在尝试使用以下命令:理解错误正确找到由名称列分组的值的最大计数,请使用以下修改后的查询: 计数(*)为c 来自EMP1 按名称组 c desc订购 限制1 查询说明 select语句提取名称列和每个名称...
    编程 发布于2025-07-15
  • 如何使用Python有效地以相反顺序读取大型文件?
    如何使用Python有效地以相反顺序读取大型文件?
    在python 反向行读取器生成器 == ord('\ n'): 缓冲区=缓冲区[:-1] 剩余_size- = buf_size lines = buffer.split('\ n'....
    编程 发布于2025-07-15
  • \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    答案: 在大多数现代编译器中,while(1)和(1)和(;;)之间没有性能差异。编译器: perl: 1 输入 - > 2 2 NextState(Main 2 -E:1)V-> 3 9 Leaveloop VK/2-> A 3 toterloop(next-> 8 last-> 9 ...
    编程 发布于2025-07-15
  • 为什么我在Silverlight Linq查询中获得“无法找到查询模式的实现”错误?
    为什么我在Silverlight Linq查询中获得“无法找到查询模式的实现”错误?
    查询模式实现缺失:解决“无法找到”错误在银光应用程序中,尝试使用LINQ建立错误的数据库连接的尝试,无法找到以查询模式的实现。”当省略LINQ名称空间或查询类型缺少IEnumerable 实现时,通常会发生此错误。 解决问题来验证该类型的质量是至关重要的。在此特定实例中,tblpersoon可能需...
    编程 发布于2025-07-15
  • 如何在JavaScript对象中动态设置键?
    如何在JavaScript对象中动态设置键?
    在尝试为JavaScript对象创建动态键时,如何使用此Syntax jsObj['key' i] = 'example' 1;不工作。正确的方法采用方括号: jsobj ['key''i] ='example'1; 在JavaScript中,数组是一...
    编程 发布于2025-07-15
  • Python中嵌套函数与闭包的区别是什么
    Python中嵌套函数与闭包的区别是什么
    嵌套函数与python 在python中的嵌套函数不被考虑闭合,因为它们不符合以下要求:不访问局部范围scliables to incling scliables在封装范围外执行范围的局部范围。 make_printer(msg): DEF打印机(): 打印(味精) ...
    编程 发布于2025-07-15
  • MySQL中如何高效地根据两个条件INSERT或UPDATE行?
    MySQL中如何高效地根据两个条件INSERT或UPDATE行?
    在两个条件下插入或更新或更新 solution:的答案在于mysql的插入中...在重复键更新语法上。如果不存在匹配行或更新现有行,则此功能强大的功能可以通过插入新行来进行有效的数据操作。如果违反了唯一的密钥约束。实现所需的行为,该表必须具有唯一的键定义(在这种情况下为'名称'...
    编程 发布于2025-07-15
  • 为什么不````''{margin:0; }`始终删除CSS中的最高边距?
    为什么不````''{margin:0; }`始终删除CSS中的最高边距?
    在CSS 问题:不正确的代码: 全球范围将所有余量重置为零,如提供的代码所建议的,可能会导致意外的副作用。解决特定的保证金问题是更建议的。 例如,在提供的示例中,将以下代码添加到CSS中,将解决余量问题: body H1 { 保证金顶:-40px; } 此方法更精确,避免了由全局保证金重置引...
    编程 发布于2025-07-15

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3