node 的一些基础核心概念

发布时间:2022-04-15浏览次数:0

支持注册ChatGPT Plus的OneKey虚拟卡
绑定Apple Pay、Google Pay、支付宝和微信支付进行日常消费

注册和了解更多 ->

silver

node的模块化

在每一个模块文件中,都会存在一个 module 对象,即模块对象。

在模块对象中保存了和当前模块相关信息。

在模块对象中有一个属性 exports,它的值是一个对象,模块内部需要被导出的成员都应该存储在到这个对象中。

在其他文件中通过 require 方法引入模块,require 方法的返回值就是对应模块的 module.exports 对象。

在导入模块时,模块文件后缀.js可以省略,文件路径不可省略。

require 方法属于同步导入模块,模块导入后可以立即使用。

CPU

中央处理器,计算机核心部件,负责运算和指令调用。

开发者编写的 JavaScript 代码在被编译为机器码以后就是通过 CPU 执行的。

存储器

内存:用于临时存储数据,断电后数据丢失。由于数据读写速度快,计算机中的应用都是在内存中运行的。

磁盘:用于持久存储数据,断电后数据不丢失。内部有磁头依靠马达转动在盘片上读写数据,速度比内存慢。

计算机应用程序在没有运行时是存储在磁盘中的,当我们启动应用程序后,应用程序会被加载到内存中运行,应用程序中的指令会
被中央处理器CPU来执行。

什么是 I/O

就是 Input 表示输入,O就是 Output 表示输出, I/O操作就是输入输出操作。什么样的操作属于 V/0操作呢?
比如数据库的读写操作就是 I/O 操作,因为数据库文件是存储在磁盘中的。

将内存中的数据写入数据库对于内存来说就是输出,查询数据库中的数据就是将磁盘中的数据读取到内存中,对于内存来说就是输入。

再比如,在客户端的 HTML 代码中经常会外链文件,比如样式文件或者图片文件,因为这些外链文件都是存储在服务器端电脑的磁盘中的,所以客户端要向服务器端发送请求以获取这些文件。

当服务器端应用程序接收到请求以后,服务端 JavaScript 需要先将文件内容从磁盘读取到内存中,然后再将内容响应到客户端。将文件内容从磁盘中读取到内存中,对于内容来说就是输入操作,将内容响应到客户端,对于内存来说就是输出操作。

从数据库中查询数据(将磁盘中的文件内容读取到内存中),由于磁盘的读写速度比较慢,查询内容越多花费时间越多。

无论 I/O 操作要花费多少时间,在 I/O 操作执行完成后,CPU 都是需要获取到操作结果的,那么问题就来了,CPU 在发出 I/O 操作指令后是否要等待 VO 操作执行完成呢?这就涉及到 I/O操作模型了, I/O 操作的模型有两种。

第一种是 CPU 等待 I/O 操作执行完成获取到操作结果后再去执行其他指令,这是同步 I/O 操作(阻塞 I/O)。

第二种是 CPU 不等待 I/O 操作执行完成,CPU 在发出 I/O 指令后,内存和磁盘开始工作,CPU 继续执行其他指令。当 I/O操作完
成后再通知 CPU I/O 操作的结果是什么。这是异步 I/O 操作(非阻塞 I/O)。

同步 I/O 在代码中的表现就是代码暂停执行等待 I/O 操作, I/O 操作执行完成后再执行后续代码。

异步 I/O 在代码中的表现就是代码不暂停执行, I/O 操作后面的代码可以继续执行,当 I/O 操作执行完成后通过回调函数的方式通知
CPU,说 I/O操作已经完成了,基于 I/O 操作结果的其他操作可以执行了(通知 CPU 调用回调函数)。

同步 I/O 和 异步 I/O 区别就是是否等待 I/O 结果。

Node 采用的就是异步非阻塞 I/O 模型。

node 是单线程还是多线程

在 Node.js 代码运行环境中,它为 JavaScript 代码的执行提供了一个主线程,通常我们所说的单线程指的就是这个主线程,主线程用来执行所有的同步代码。

但是 Node.js 代码运行环境本身是由 C++ 开发的,在 Node.js 内部它依赖了一个叫做 libuv 的 c++库,在这个库中它维护了一个线程池,默认情况下在这个线程池中存储了 4 个线程,JavaScript 中的异步代码就是在这些线程中执行的,所以说 JavaScript 代码的运行依靠了不止一个线程,所以 JavaScript 本质上还是多线程的。

什么是回调函数

我们经常将回调函数写成 callback,实际上它是 call then back 的简写,含义是调用后返回,就是在主函数中调用参数函数,参数函数调用完成后返回主函数继续执行主函数中的代码。

为什么在B函数中不直接调用 A 函数而要通过参数的方式传递进去?

通常在编写应用程序时,B函数都是语言肉部或者其他开发者定义好的,我们看不到内部代码或者说不能直接在他内部代码中插入我们的代码,而我们又想介入程序的执行,此时就可以通过回调函数的方式将我们的逻辑传递给 B 函数,B函数在内部再来调用这个回调函数。

为什么要学习事件循环机制?

学习事件循环可以让开发者明白 JavaScript 的运行机制是怎么样的。

事件循环机制做的是什么事情?

事件循环机制用于管理异步 API 的回调函数什么时候回到主线程中执行。
Node.js 采用的是异步 V0 模型。

同步API在主线程中执行,异步AP1在底层的 C++ 维护的线程中执行,异步 API 的回调函数在主线程中执行。在 JavaScript 应用运行时,众多异步 API 的回调函数什么时候能回到主线程中调用呢?

这就是事件循环机制做的事情,管理异步 API 的回调函数什么时候回到主线程中执行。

为什么这种机制叫做事件循环?

因为 Nodejs 是事件驱动的。

事件驱动就是当什么时候做什么事情,做的事情就定义在回调函数中,可以将异步 AP1 的回调函数理解为事件处理函数,所以管理异步API回调函数什么时候回到主线程中调用的机制叫做事件循环机制。

事件循环阶段

  1. Timers:用于存储定时器的回调函数(setlnterval, setTimeout)。
  2. Pending calbacks:执行与操作系统相关的回调函数,比如启动服务器端应用时监听端口操作的回调西数就在这里调用。
  3. Idle, prepare:系统内部使用。
  4. IO Poll:存储 V/O 操作的回调函数队列,比如文件读写操作的回调函数。
    如果事件队列中有回调函数,执行它们直到清空队列。
    否则事件循环将在此阶段停留一段时间以等待新的回调函数进入,这个等待取决于以下两个条件:
  5. setlmmediate 队列(check 阶段)中存在要执行的回调函数.
  6. timers 队列中存在要执行的回调函数.在这种情况下,事件循环将移至 check 阶段,然后移至 Closing callbacks 阶段,并
    最终从 timers 阶段进入下一次循环。
  7. Check:存储 setlmmediate APl 的回调函数。
  8. Closing callbacks:执行与关闭事件相关的回调,例如关闭数据库连接的回调函数等。
    循环体会不断运行以检测是否存在没有调用的回调函数,事件循环机制会按照先进先出的方式执行他们直到队列为空。

宏任务与微任务

宏任务:setlnterval, setTimeout, setlmmediate, W/O
微任务:Promise.then Promise.catch Promise.finally, process.nextTick

微任务与宏任务的区别

微任务的回调函数被放置在微任务队列中,宏任务的回调函数被放置在宏任务队列中。

微任务优先级高于宏任务

当微任务事件队列中存在可以执行的回调函数时,事件循环在执行完当前阶段的回调函数后会暂停进入事件循环的下一个阶段,事件循环会立即进入微任务的事件队列中开始执行回调函数,当微任务队列中的回调函数执行完成后,事件循环再进入到下一个阶段开始执行回调函数。

nextTick 的优先级高于 microTask,在执行任务时,只有 nextTick 中的所有回调函数执行完成后才会开始执行 micro Task

不同阶段的宏任务的回调函数被放置在了不同的宏任务队列中

宏任务与宏任务之间没有优先级的概念,他们的执行顺序是按照事件循环的阶段顺序进行的。

在 Node 应用程序启动后,并不会立即进入事件循环,而是先执行输入代码,从上到下开始执行,同步 API 立即执行,异步 API交给 C++维护的线程执行,异步 API 的回调函数被注册到对应的事件队列中。当所有输入代码执行完成后,开始进入事件循环。

字节笔记本扫描二维码查看更多内容