我们在名为“Node Internals”的文章中讨论了为什么 Node JS 是单线程的,也是多线程的。它将为您提供 Node 架构的坚实基础,并为理解事件循环的魔力奠定基础!
由于事件循环,Node js 可以被认为是单线程的。但是,什么是事件循环?
我总是从餐厅的类比开始,因为我认为这样更容易理解技术细节。
所以,在餐厅里,主厨从订单列表中取出订单并将其交给助理团队。食物准备好后,厨师就会上菜。如果有VIP顾客来,厨师会优先处理这个订单。
如果我们考虑这个类比,那么我们可以说......
在 Node JS 事件循环的上下文中。
Chef 是管理任务和委派工作的事件循环。
协助团队是一个工作线程或操作系统,负责处理委托给他们的任务的执行。
订单列表是等待轮到的任务的任务队列。
VIP客户是一个微任务,优先级高,先于常规任务完成。
要了解事件循环,我们必须首先了解微任务和宏任务之间的区别。
微任务
微任务是指具有高优先级的任务,并且在当前执行的 Javascript 代码完成之后、进入事件循环的下一阶段之前执行。
示例:
- process.nextTick
- 承诺(.then、.catch、.finally)
- 队列微任务
宏任务
这些是优先级较低的任务,在事件循环的稍后阶段排队等待执行。
示例:
- 设置超时
- 设置间隔
- 立即设置
- I/O 操作
事件循环
当我们在 Node.js 中运行异步任务时,事件循环是一切的核心。
得益于事件循环,Node.js 可以高效地执行非阻塞 I/O 操作。它通过将耗时的任务委托给操作系统或工作线程来实现这一点。一旦任务完成,它们的回调就会以有组织的方式处理,确保顺利执行而不阻塞主线程。
这就是 Node.js 能够同时处理多个任务,同时仍然是单线程的神奇之处。
阶段
事件循环中有六个阶段,每个阶段都有自己的队列,其中保存特定类型的任务。
1.计时器阶段
在此阶段处理与计时器相关的回调,例如 setTimeout 和 setInterval。
Node js 检查计时器队列中是否有延迟已过期的回调。
如果满足计时器延迟,其回调将添加到此队列中执行。
console.log('Start'); setTimeout(() => { console.log('Timer 1 executed after 1 second'); }, 1000); setTimeout(() => { console.log('Timer 2 executed after 0.5 seconds'); }, 500); let count = 0; const intervalId = setInterval(() => { console.log('Interval callback executed'); count++; if (count === 3) { clearInterval(intervalId); console.log('Interval cleared'); } }, 1000); console.log('End');
输出:
Start End Timer 2 executed after 0.5 seconds Timer 1 executed after 1 second Interval callback executed Interval callback executed Interval callback executed Interval cleared
2.I/O回调阶段
此阶段的目的是为已完成的 I/O(输入/输出)操作执行回调,例如读取或写入文件、查询数据库、处理网络请求以及其他异步 I/O 任务。
当 Node.js 中发生任何异步 I/O 操作(例如使用 fs.readFile 读取文件)时,该操作将委托给操作系统或工作线程。这些 I/O 任务在主线程之外以非阻塞方式执行。任务完成后,会触发回调函数来处理结果。
I/O 回调阶段是操作完成后这些回调排队等待执行的阶段。
const fs = require('fs'); console.log('Start'); fs.readFile('example.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file:', err); return; } console.log('File contents:', data); }); console.log('Middle'); setTimeout(() => { console.log('Simulated network request completed'); }, 0); console.log('End');
输出
Start Middle End Simulated network request completed File contents: (contents of the example.txt file)
3.空闲阶段
在此阶段,不会执行任何用户定义的工作,而是在此阶段事件循环为下一阶段做好准备。此阶段仅进行内部调整。
4.投票阶段
轮询阶段检查是否有需要处理的待处理 I/O 事件(如网络活动或文件系统事件)。它将立即执行与这些事件相关的回调。
如果没有待处理的 I/O 事件,则轮询阶段可以进入阻塞状态。
在这种阻塞状态下,Node.js 将只是等待新的 I/O 事件到达。这种阻塞状态使 Node.js 成为非阻塞:它会等待,直到新的 I/O 事件触发回调执行,同时保持主线程空闲以执行其他任务。
已完成的 I/O 操作(例如 fs.readFile、HTTP 请求或数据库查询)的任何回调都会在此阶段执行。这些 I/O 操作可能已在之前的阶段(例如计时器阶段或 I/O 回调阶段)启动,现在已完成。
如果有使用 setTimeout 或 setInterval 设置的计时器,Node.js 将检查是否有计时器已过期以及是否需要执行其关联的回调。如果计时器已过期,它们的回调将移至回调队列,但直到下一阶段(即计时器阶段)才会处理它们。
const fs = require('fs'); const https = require('https'); console.log('Start'); fs.readFile('file1.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file1:', err); return; } console.log('File1 content:', data); }); fs.readFile('file2.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file2:', err); return; } console.log('File2 content:', data); }); https.get('https://jsonplaceholder.typicode.com/todos/1', (response) => { let data = ''; response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { console.log('HTTP Response:', data); }); }); console.log('End');
输出:
Start End File1 content: (contents of file1.txt) File2 content: (contents of file2.txt) HTTP Response: (JSON data from the HTTP request)
5.检查相位
投票阶段完成任务后。该阶段主要处理 setImmediate 回调的执行,这些回调被安排在轮询阶段处理完 I/O 事件后立即运行。
当您想要在当前事件循环周期之后执行某个操作时,通常会使用 setImmediate 回调,例如确保系统不忙于处理 I/O 事件后执行某些任务。
检查阶段的优先级高于定时器阶段(处理 setTimeout 和 setInterval)。这意味着 setImmediate 回调将始终在任何计时器之前执行,即使计时器已过期。
setImmediate 保证其回调将在当前 I/O 周期之后、下一个计时器周期之前运行。当您想要确保在运行其他任务之前先完成 I/O 相关任务时,这一点非常重要。
console.log('Start'); setTimeout(() => { console.log('Timer 1 executed after 1 second'); }, 1000); setTimeout(() => { console.log('Timer 2 executed after 0.5 seconds'); }, 500); let count = 0; const intervalId = setInterval(() => { console.log('Interval callback executed'); count++; if (count === 3) { clearInterval(intervalId); console.log('Interval cleared'); } }, 1000); console.log('End');
输出:
Start End Timer 2 executed after 0.5 seconds Timer 1 executed after 1 second Interval callback executed Interval callback executed Interval callback executed Interval cleared
6.结束阶段
关闭回调阶段通常在应用程序需要在退出或关闭之前进行清理时执行。
此阶段处理不再需要系统资源(例如网络套接字或文件句柄)时需要执行的事件和任务。
如果没有此阶段,应用程序可能会留下打开的文件句柄、网络连接或其他资源,从而可能导致内存泄漏、数据损坏或其他问题。
const fs = require('fs'); console.log('Start'); fs.readFile('example.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file:', err); return; } console.log('File contents:', data); }); console.log('Middle'); setTimeout(() => { console.log('Simulated network request completed'); }, 0); console.log('End');
输出:
Start Middle End Simulated network request completed File contents: (contents of the example.txt file)
Node JS 的事件循环中还有一个特殊的阶段。
微任务队列
process.nextTick() 并承诺在事件循环的特殊阶段执行回调。
process.nextTick() 安排回调在当前操作完成后立即执行,但在事件循环继续下一阶段之前。
process.nextTick() 不是事件循环中任何阶段的一部分。相反,它有自己的内部队列,该队列在当前执行的同步代码之后和进入事件循环中的任何阶段之前立即执行。
它在当前操作之后但在 I/O、setTimeout 或事件循环中安排的其他任务之前执行。
Promise 的优先级低于 process.nextTick(),并且在所有 process.nextTick() 回调之后处理。
const fs = require('fs'); const https = require('https'); console.log('Start'); fs.readFile('file1.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file1:', err); return; } console.log('File1 content:', data); }); fs.readFile('file2.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file2:', err); return; } console.log('File2 content:', data); }); https.get('https://jsonplaceholder.typicode.com/todos/1', (response) => { let data = ''; response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { console.log('HTTP Response:', data); }); }); console.log('End');
输出:
Start End File1 content: (contents of file1.txt) File2 content: (contents of file2.txt) HTTP Response: (JSON data from the HTTP request)
现在,您对事件循环的工作原理有了总体了解。
我给你一个问题,你可以在评论中给出答案。
const fs = require('fs'); console.log('Start'); fs.readFile('somefile.txt', 'utf8', (err, data) => { if (err) { console.error(err); return; } console.log('File content:', data); }); setImmediate(() => { console.log('Immediate callback executed'); }); setTimeout(() => { console.log('Timeout callback executed'); }, 0); console.log('End');
谢谢。
等待您的答复。
以上是Node JS - 事件循环的详细内容。更多信息请关注PHP中文网其他相关文章!

JavaScript可用于前端和后端开发。前端通过DOM操作增强用户体验,后端通过Node.js处理服务器任务。1.前端示例:改变网页文本内容。2.后端示例:创建Node.js服务器。

选择Python还是JavaScript应基于职业发展、学习曲线和生态系统:1)职业发展:Python适合数据科学和后端开发,JavaScript适合前端和全栈开发。2)学习曲线:Python语法简洁,适合初学者;JavaScript语法灵活。3)生态系统:Python有丰富的科学计算库,JavaScript有强大的前端框架。

JavaScript框架的强大之处在于简化开发、提升用户体验和应用性能。选择框架时应考虑:1.项目规模和复杂度,2.团队经验,3.生态系统和社区支持。

引言我知道你可能会觉得奇怪,JavaScript、C 和浏览器之间到底有什么关系?它们之间看似毫无关联,但实际上,它们在现代网络开发中扮演着非常重要的角色。今天我们就来深入探讨一下这三者之间的紧密联系。通过这篇文章,你将了解到JavaScript如何在浏览器中运行,C 在浏览器引擎中的作用,以及它们如何共同推动网页的渲染和交互。JavaScript与浏览器的关系我们都知道,JavaScript是前端开发的核心语言,它直接在浏览器中运行,让网页变得生动有趣。你是否曾经想过,为什么JavaScr

Node.js擅长于高效I/O,这在很大程度上要归功于流。 流媒体汇总处理数据,避免内存过载 - 大型文件,网络任务和实时应用程序的理想。将流与打字稿的类型安全结合起来创建POWE

Python和JavaScript在性能和效率方面的差异主要体现在:1)Python作为解释型语言,运行速度较慢,但开发效率高,适合快速原型开发;2)JavaScript在浏览器中受限于单线程,但在Node.js中可利用多线程和异步I/O提升性能,两者在实际项目中各有优势。

JavaScript起源于1995年,由布兰登·艾克创造,实现语言为C语言。1.C语言为JavaScript提供了高性能和系统级编程能力。2.JavaScript的内存管理和性能优化依赖于C语言。3.C语言的跨平台特性帮助JavaScript在不同操作系统上高效运行。

JavaScript在浏览器和Node.js环境中运行,依赖JavaScript引擎解析和执行代码。1)解析阶段生成抽象语法树(AST);2)编译阶段将AST转换为字节码或机器码;3)执行阶段执行编译后的代码。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver Mac版
视觉化网页开发工具

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

EditPlus 中文破解版
体积小,语法高亮,不支持代码提示功能

Atom编辑器mac版下载
最流行的的开源编辑器