从V8到事件循环:Node.js的内部解剖

什么是Node.js?
许多人认为Node.js是一个框架,而其他初学者则将其理解为库。但是等等,朋友们,Node既不是框架也不是库。
Node.js是一个跨平台、开源的服务器环境,可以在Windows、Linux、macOS和其他操作系统上运行。Node.js是一个后端JavaScript运行时环境,它利用V8 JavaScript引擎在Web浏览器之外执行JavaScript代码。Node是使用C、C++和JavaScript等编程语言构建的。
_您可以从官方文档或维基百科了解更多关于Node.js的信息。_
V8引擎——Node应用程序的支柱
V8是由Chromium项目为Chromium和Google Chrome Web浏览器开发的免费开源JavaScript和WebAssembly引擎。项目的创建者是Lars Bak。V8引擎使用JavaScript、C++、ECMAScript和汇编语言构建。
V8是为Google Chrome提供动力的JavaScript引擎的名称。它是在使用Chrome浏览时获取我们的JavaScript并执行它的东西。V8提供了JavaScript执行的运行时环境。V8是用C++编写的,并且不断改进。它是可移植的,可以在Mac、Windows、Linux和其他几个系统上运行。
_您可以从官方文档或维基百科了解更多关于V8引擎的信息。_
性能神话——Node.js的内部工作原理
许多人仍然无法理解Node.js的内部工作原理,因为它的非阻塞和异步程序执行使其非常复杂。特别是Node.js的内部工作原理,即事件循环。
现在,许多人质疑什么是异步编程。所以,让我们理解什么是同步和异步编程。
什么是同步和异步编程?
同步编程意味着代码按照定义的顺序运行。当在同步程序中调用函数并返回某个值时,只有在那时才会执行下一行。
简单来说,同步编程意味着程序的执行是逐行进行的。如果一行需要很长时间才能执行,那么程序的执行就不会转到下一行。它等待该行执行,然后才会转到下一行。如果一行需要很长时间才能执行,那么UI将会冻结。
另一方面,异步编程指的是不按顺序执行的代码。这些函数的执行不是按照它们在程序中定义的顺序,而是只有在满足某些条件时才执行。
简单来说,异步意味着环境不会等待特定行执行。它将开始执行该行并将其放入后台,当该行的输出出现时,它将打印到控制台。这样UI就不会冻结,只有从实际应用程序服务器获取数据的部分才会冻结。
_JavaScript代码_
  1. console.log("Hello world!")

  2. setTimeout(()=>{
  3. console.log('set timeout function')
  4. }, 2000)

  5. console.log('I am Parmesh Bhatt.')
javascript
_输出_
  1. Hello world!
  2. I am Parmesh Bhatt.
  3. set timeout function
现在,让我们了解Node.js中的事件循环。
事件循环
事件循环是一个无限循环,它等待任务,执行它们,然后休眠直到收到更多任务。Node在NodeJS环境内部使用事件循环处理请求。
Node.js在后端主要有三个部分:调用栈(Call Stack)、回调队列(Call Back Queue)和Node API。这三者的组合称为事件循环。
让我们通过一个例子来简单理解:
假设我们有如下代码:
  1. let a = 2;
  2. let b = 3;
  3. const sum = a + b;
  4. console.log(sum);
javascript
_对于这个,输出将是:_
  1. 5
当我们编写如上所示的简单代码时,第一次执行将开始,主函数进入调用栈。
现在你们在JavaScript中都有一个问题,我们没有像在C++和Java中那样编写任何主函数。那么,这个主函数在调用栈中是从哪里来的呢?
朋友们,JavaScript是内部构建的。当它开始执行时,它会自动将所有代码放在主函数中,然后开始其实际执行。
主函数进入调用栈后,console.log函数进入调用栈,调用栈执行它,输出打印在屏幕上。
调用栈有一个遵循LIFO属性的内部构建。LIFO意味着后进先出。这意味着无论哪个函数最后进来都会首先执行,这就是为什么主函数首先进来最后离开栈的原因。
另一个例子:
  1. console.log('one');

  2. setTimeout(()=>{
  3. console.log("Settimeout 1")
  4. }, 2000)

  5. setTimeout(()=>{
  6. console.log("Settimeout 2")
  7. }, 0)

  8. console.log('Two');
javascript
_上述代码的输出是:_
  1. one
  2. Two
  3. Settimeout 2
  4. Settimeout 1
这真的很有趣。让我们理解它...
我们现在都知道在NodeJS中,有三件事:调用栈,第二是Node API,第三是回调队列。
当我们开始执行上述代码时,像往常一样,主函数进入调用栈,console.log函数也将进入调用栈。
但是从setTimeout函数开始,setTimeout函数不会直接进入调用栈,而是进入Node API。
现在你们都有一个问题:为什么它直接进入node API?为什么不进入调用栈?
让我们理解它,
setTimeout函数直接进入Node API,因为setTimeout函数是从C++继承的,每个从C++继承的函数总是直接进入Node API,在进入调用栈之前不会被执行。
在C++中还有很多像setTimeout这样的函数。您可以在互联网上探索它。
所以setTimeout第一个函数首先进入Node API,然后setTimeout第二个函数进入Node API。
然后最后一个console.log将进入调用栈,调用栈将执行它并打印输出。现在输出是one和two。在执行最后一个console之后,调用栈中存在的每个函数都将从调用栈中出来,包括main,现在调用栈是空的。
当调用栈为空时,Node API中存在的函数将一个一个地进入回调队列。
优先级较低的函数将首先进入回调队列。当Node API中存在多个函数时,每个函数将一个一个地进入回调队列,然后进入调用栈。
回调队列中存在的每个函数只有在调用栈为空时才会进入调用栈。如果调用栈不为空,函数就不会从回调队列进入调用栈。
进入调用栈后,调用栈一个一个地执行函数并在控制台上打印输出。这就是为什么输出是这样的:
  1. one
  2. Two
  3. Settimeout 2
  4. Settimeout 1
这就是Node内部的工作方式,调用栈、Node API和回调队列的组合被称为事件循环。
事件循环的特性
事件循环是一个无限循环,它等待任务,执行它们,然后休眠直到收到更多任务。
事件循环只有在调用栈为空时(即没有正在进行的任务)才从事件队列执行任务。
事件循环允许我们使用回调和Promise。
事件循环从最旧的开始执行任务。
结论
事件循环允许Node.js执行非阻塞I/O操作,尽管JavaScript是单线程的。
由于大多数现代内核都是多线程的,它们可以处理在后台执行的多个操作。当这些操作之一完成时,内核告诉Node.js,以便可以将适当的回调添加到轮询队列中,最终被执行。
现在您应该理解为什么两秒的延迟函数不会阻止程序的其余部分执行...
所以,我在这里结束我的对话,但如果有人想与我联系,那么链接在下面...
LinkedIn
Twitter或X
GitHub
技术要点总结
1. Node.js的本质
不是框架或库,而是JavaScript运行时环境
基于V8引擎构建
支持跨平台运行
2. V8引擎的作用
为Chrome浏览器提供JavaScript执行能力
用C++编写,持续优化
提供JavaScript的运行时环境
3. 同步vs异步编程
同步:按顺序执行,会阻塞UI
异步:不按顺序执行,不会阻塞UI
4. 事件循环的组成
调用栈(Call Stack):遵循LIFO原则
Node API:处理C++继承的函数
回调队列(Callback Queue):存储待执行的回调
5. 执行流程
主函数进入调用栈
同步代码直接执行
异步代码进入Node API
调用栈为空时,回调进入调用栈执行