一文读懂NodeJs知识体系和原理浅析

node.js 初探

Node.js 是一个 JS 的服务端运行环境,简单的来说,它是在 JS 语言规范的基础上,封装了一些服务端的运行时对象,让我们能够简单实现非常多的业务功能。

如果我们只使用 JS 的话,实际上只是能进行一些简单的逻辑运算。node.js 就是基于 JS 语法增加与操作系统之间的交互。

node.js 的安装

我们可以使用多种方式来安装 node.js,node.js 本质上也是一种软件,我们可以使用直接下载二进制安装文件安装,通过系统包管理进行安装或者通过源码自行编译均可。

一般来讲,对于个人开发的电脑,我们推荐直接通过 node.js 官网的二进制安装文件来安装。对于打包上线的一些 node.js 环境,也可以通过二进制编译的形式来安装。

安装成功之后,我们的 node 命令就会自动加入我们的系统环境变量 path 中,我们就能直接在全局使用 node 命令访问到我们刚才安装的 node 可执行命令行工具。

node.js 版本切换在个人电脑上,我们可以安装一些工具,对 node.js 版本进行切换,例如 nvmn

nvm 的全称就是 node version manager,意思就是能够管理 node 版本的一个工具,它提供了一种直接通过 shell 执行的方式来进行安装。简单来说,就是通过将多个 node 版本安装在指定路径,然后通过 nvm 命令切换时,就会切换我们环境变量中 node 命令指定的实际执行的软件路径。

安装成功之后,我们就能在当前的操作系统中使用多个 node.js 版本。

包管理工具 npm

curl -o- https://raw.githubusercontent.com/nvm- sh/nvm/v0.35.3/install.sh | bash

我们对 npm 应该都比较熟悉了,它是 node.js 内置的一款工具,目的在于安装和发布符合 node.js 标准的模块,从而实现社区共建的目的繁荣整个社区。

npx 是 npm@5 之后新增的一个命令,它使得我们可以在不安装模块到当前环境的前提下,使用一些 cli 功能。

例如 npx create-react-app some-repo

node.js 的底层依赖

node.js 的主要依赖子模块有以下内容:

V8 引擎

主要是 JS 语法的解析,有了它才能识别 JS 语法\

libuv

c 语言实现的一个高性能异步非阻塞 IO 库,用来实现 node.js 的事件循环

http-parser/llhttp

底层处理 http 请求,处理报文, 解析请求包等内容

openssl

处理加密算法,各种框架运用广泛

zlib

处理压缩等内容 node.js 常⻅内置模块

主要模块

node.js 中最主要的内容,就是实现了一套 CommonJS 的模块化规范,以及内置了一些常⻅的模块。

fs:

文件系统,能够读取写入当前安装系统环境中硬 盘的数据\

path:

路径系统,能够处理路径之间的问题

crypto:

加密相关模块,能够以标准的加密方式对我 们的内容进行加解密

dns:

处理 dns 相关内容,例如我们可以设置 dns 服 务器等等\

http:

设置一个 http 服务器,发送 http 请求,监听 响应等等

readline:

读取 stdin 的一行内容,可以读取、增加、 删除我们命令行中的内容\

os:

操作系统层面的一些 api,例如告诉你当前系统类 型及一些参数

vm:

一个专⻔处理沙箱的虚拟机模块,底层主要来调 用 v8 相关 api 进行代码解析。

V8 引擎:

引擎只是解析层面,具体的上层还有许多具体环境的封装。

Debug & 内存泄漏

对于浏览器的 JS 代码来说,我们可以通过断点进行分步调试,每一步打印当前上下文中的变量结果,来定位具体问题出现在哪一步。

我们可以借助 VSCode 或者自行打断点的形式,来进行分步 node.js 调试。

对于 JS 内存泄漏,我们也可以使用同样的道理,借助工具,打印每次的内存快照,对比得出代码中的问题。

另一种 JS 解析引擎 quickjs

quickjs 是一个 JS 的解析引擎,轻量代码量也不大,与之功能类似的就是 V8 引擎。

他最大的特点就是,非常非常轻量,这点从源码中也能体现,事实上并没有太多的代码,它的主要特点和优势:

  1. 轻量而且易于嵌入: 只需几个C文件,没有外部依赖,一个x86下的简单的“hello world”程序只要180 KiB

  2. 具有极低启动时间的快速解释器: 在一台单核的台式PC上,大约在100秒内运行ECMAScript 测试套件156000次的运行时实例完整生命周期在不到300微秒的时间内完成。

  3. 几乎完整实现ES2019支持,包括: 模块,异步生成器和和完整Annex B(MPEG-2 transport stream format格式)支持 (传统的Web兼容性)。许多ES2020中带来的特性也依然会被支持。 通过100%的ECMAScript Test Suite测试。 可以将Javascript源编译为没有外部依赖的可执行文件。

另一类 JS 运行时服务端环境 deno

deno 是一类类似于 node.js 的 JS 运行时环境,同时它也是由 node.js 之父一手打造出来的,他和 node.js 比有什么区别呢?

相同点:
  • deno 也是基于 V8 ,上层封装一些系统级别的调用我们的 deno 应用也可以使用 JS 开发
不同点:
  • deno 基于 rust 和 typescript 开发一些上层模块,所以我们可以直接在 deno 应用中书写 ts

  • deno 支持从 url 加载模块,同时支持 top level await 等特性

全局对象解析

JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。

在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。

全局对象和全局变量

global 最根本的作用是作为全局变量的宿主。按照 ECMAScript 的定义,满足以下条 件的变量是全局变量:

在最外层定义的变量;
全局对象的属性;
隐式定义的变量(未定义直接赋值的变量)。
当你定义一个全局变量时,这个变量同时也会成为全局对象的属性,反之亦然。需要注 意的是,在 Node.js 中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的, 而模块本身不是最外层上下文。

注意: 永远使用 var 定义变量以避免引入全局变量,因为全局变量会污染 命名空间,提高代码的耦合风险。

__filename

__filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。

console.log( __filename );

__dirname

__dirname 表示当前执行脚本所在的目录。

console.log( __dirname );

setTimeout(cb, ms)

setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。:setTimeout() 只执行一次指定函数。

返回一个代表定时器的句柄值。

function printHello(){
   
   console.log( "Hello, World!");
}
// 两秒后执行以上函数
setTimeout(printHello, 2000);


clearTimeout、setInterval、clearInterval、console 在js中比较常见,故不做展开。

process

process 是一个全局变量,即 global 对象的属性。

它用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口。通常在你写本地命令行程序的时候,少不了要 和它打交道。下面将会介绍 process 对象的一些最常用的成员方法。

  1. exit
    当进程准备退出时触发。

  2. beforeExit
    当 node 清空事件循环,并且没有其他安排时触发这个事件。通常来说,当没有进程安排时 node 退出,但是 ‘beforeExit’ 的监听器可以异步调用,这样 node 就会继续执行。

  3. uncaughtException
    当一个异常冒泡回到事件循环,触发这个事件。如果给异常添加了监视器,默认的操作(打印堆栈跟踪信息并退出)就不会发生。

  4. Signal 事件
    当进程接收到信号时就触发。信号列表详见标准的 POSIX 信号名,如 SIGINT、SIGUSR1 等。参考nodejs进阶视频讲解:进入学习

process.on('exit', function(code) {
   
  // 以下代码永远不会执行
  setTimeout(function() {
   
    console.log("该代码不会执行");
  }, 0);

  console.log('退出码为:', code);
});
console.log("程序执行结束");


退出的状态码

  1. Uncaught Fatal Exception
    有未捕获异常,并且没有被域或 uncaughtException 处理函数处理。

  2. Internal JavaScript Parse Error
    JavaScript的源码启动 Node 进程时引起解析错误。非常罕见,仅会在开发 Node 时才会有。

  3. Internal JavaScript Evaluation Failure
    JavaScript 的源码启动 Node 进程,评估时返回函数失败。非常罕见,仅会在开发 Node 时才会有。

  4. Fatal Error
    V8 里致命的不可恢复的错误。通常会打印到 stderr ,内容为: FATAL ERROR

  5. Non-function Internal Exception Handler
    未捕获异常,内部异常处理函数不知为何设置为on-function,并且不能被调用。

  6. Internal Exception Handler Run-Time Failure
    未捕获的异常, 并且异常处理函数处理时自己抛出了异常。例如,如果 process.on(‘uncaughtException’) 或 domain.on(‘error’) 抛出了异常。

  7. Invalid Argument
    可能是给了未知的参数,或者给的参数没有值。

  8. Internal JavaScript Run-Time Failure
    JavaScript的源码启动 Node 进程时抛出错误,非常罕见,仅会在开发 Node 时才会有。

  9. Invalid Debug Argument
    设置了参数–debug 和/或 –debug-brk,但是选择了错误端口。

  10. Signal Exits
    如果 Node 接收到致命信号,比如SIGKILL 或 SIGHUP,那么退出代码就是128 加信号代码。这是标准的 Unix 做法,退出信号代码放在高位。

// 输出到终端
process.stdout.write("Hello World!" + "\n");

// 通过参数读取
process.argv.forEach(function(val, index, array) {
   
   console.log(index + ': ' + val);
});

// 获取执行路局
console.log(process.execPath);

// 平台信息
console.log(process.platform);


试试看这段代码输出什么
// this in NodeJS global scope is the current module.exports object, not the global object.

console.log(this);    // {}

module.exports.foo = 5;

console.log(this);   // { foo:5 }

Buffer

在了解Nodejs的Buffer之前, 先看几个基本概念。

背景知识

1. ArrayBuffer

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。

ArrayBuffer 不能直接操作,而是要通过类型数组对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

可以把它理解为一块内存, 具体存什么需要其他的声明。

new ArrayBuffer(length)

// 参数:length 表示要创建的 ArrayBuffer 的大小,单位为字节。
// 返回值:一个指定大小的 ArrayBuffer 对象,其内容被初始化为 0。
// 异常:如果 length 大于 Number.MAX_SAFE_INTEGER(>= 2 ** 53)或为负数,则抛出一个 RangeError 异常。

ex. 比如这段代码, 可以执行一下看看输出什么

var buffer = new ArrayBuffer(8);
var view = new Int16Array(buffer);

console.log(buffer);
console.log(view);

2. Unit8Array

Uint8Array 数组类型表示一个 8 位无符号整型数组,创建时内容被初始化为 0。
创建完后,可以对象的方式或使用数组下标索引的方式引用数组中的元素。

// 来自长度
var uint8 = new Uint8Array(2);
uint8[0] = 42;
console.log(uint8[0]); // 42
console.log(uint8.length); // 2
console.log(uint8.BYTES_PER_ELEMENT); // 1

// 来自数组
var arr = new Uint8Array([21,31]);
console.log(arr[1]); // 31

// 来自另一个 TypedArray
var x = new Uint8Array([21, 31]);
var y = new Uint8Array(x);
console.log(y[0]); // 21


3. ArrayBuffer 和 TypedArray

TypedArray: Unit8Array, Int32Array这些都是TypedArray, 那些 Uint32Array 也好,Int16Array 也好,都是给 ArrayBuffer 提供了一个 “View”,MDN上的原话叫做 “Multiple views on the same data”,对它们进行下标读写,最终都会反应到它所建立在的 ArrayBuffer 之上。

ArrayBuffer 本身只是一个 0 和 1 存放在一行里面的一个集合,ArrayBuffer 不知道第一个和第二个元素在数组中该如何分配。

为了能提供上下文,我们需要将其封装在一个叫做 View 的东西里面。这些在数据上的 View 可以被添加进确定类型的数组,而且我们有很多种确定类型的数据可以使用。

  1. 总结

总之, ArrayBuffer 基本上扮演了一个原生内存的角色.

NodeJs Buffer

Buffer 类以一种更优化、更适合 Node.js 用例的方式实现了 Uint8Array API.

Buffer 类的实例类似于整数数组,但 Buffer 的大小是固定的、且在 V8 堆外分配物理内存。

Buffer 的大小在被创建时确定,且无法调整。

基本使用

// 创建一个长度为 10、且用 0 填充的 Buffer。
const buf1 = Buffer.alloc(10);

// 创建一个长度为 10、且用 0x1 填充的 Buffer。 
const buf2 = Buffer.alloc(10, 1);

// 创建一个长度为 10、且未初始化的 Buffer。
// 这个方法比调用 Buffer.alloc() 更快,
// 但返回的 Buffer 实例可能包含旧数据,
// 因此需要使用 fill() 或 write() 重写。
const buf3 = Buffer.allocUnsafe(10);

// 创建一个包含 [0x1, 0x2, 0x3] 的 Buffer。
const buf4 = Buffer.from([1, 2, 3]);

// 创建一个包含 UTF-8 字节  的 Buffer。
const buf5 = Buffer.from('tést');


tips

当调用 Buffer.allocUnsafe() 时,被分配的内存段是未初始化的(没有用 0 填充)。

虽然这样的设计使得内存的分配非常快,但已分配的内存段可能包含潜在的敏感旧数据。 使用通过 Buffer.allocUnsafe() 创建的没有被完全重写内存的 Buffer ,在 Buffer内存可读的情况下,可能泄露它的旧数据。
虽然使用 Buffer.allocUnsafe() 有明显的性能优势,但必须额外小心,以避免给应用程序引入安全漏洞。

Buffer 与字符编码

Buffer 实例一般用于表示编码字符的序列,比如 UTF-8 、 UCS2 、 Base64 、或十六进制编码的数据。 通过使用显式的字符编码,就可以在 Buffer 实例与普通的 JavaScript 字符串之间进行相互转换。

const buf = Buffer.from('hello world', 'ascii');

console.log(buf)

// 输出 68656c6c6f20776f726c64
console.log(buf
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值