Node
npm npx
随同 NodeJS 一起安装的包管理工具。
详见 npm
模块化
type: "commonjs" // 默认
// require 导入,module.exports 或 exports 导出。type: "module"
// import 导入,export 导出- 内置模块
const fs = require("fs");
const path = require("path");- 第三方模块
npm install express
const express = require('express');- 自定义模块
function add(a, b) {
return a + b;
}
module.exports = { add };const math = require("./math");
console.log(math.add(2, 3)); // 输出: 5- JSON 模块
const config = require("./config.json");
console.log(config);CommonJS 和 ESModule 的区别
- CommonJS 是基于
运行时的同步加载,ESModule 是基于编译时的异步加载 - CommonJS 是可以修改值的,ESModule 值并且不可修改(可读的)
- CommonJS 不可以 tree shaking,ESModule 支持 tree shaking
- CommonJS 中顶层的 this 指向这个模块本身,而 ES6 中顶层 this 指向 undefined
全局变量
Node 中没有 DOM 和 BOM,除了这些 API,其他的 ECMAscript API 基本都能用
- global
- globalThis // ECMAScript 2020 nodejs 环境会指向
global,浏览器环境指向window
global.xxx = "123";
// or
globalThis.xxx = "123";内置 API
__dirname// 当前模块文件所在目录的绝对路径__filename// 当前模块文件的绝对路径,包括文件名本身。- process // 处理进程相关 见
- Buffer
CSR SSR SEO
node 环境中无法操作 DOM 和 BOM,但是如果非要操作 DOM 和 BOM 也是可以的我们需要使用第三方库 jsdom 帮助我们。
客户端渲染(CSR,Client-Side Rendering) 初始 HTML 由服务器发送,内容为空或很少。大部分内容由 JavaScript 在客户端加载和渲染。 优点:页面切换快,用户体验好。 缺点:首次加载慢,对 SEO 不友好。
服务器端渲染(SSR,Server-Side Rendering) HTML 内容由服务器生成并发送给客户端。 优点:首次加载快,对 SEO 友好。 缺点:每次页面切换都需要从服务器获取新的 HTML,用户体验不如 CSR。
搜索引擎优化 (SEO - Search Engine Optimization) 通过优化网站内容、结构和技术来提高在搜索引擎中的排名。SEO 直接影响网站的可见度和流量,是所有 Web 应用程序的重要考虑因素。
SSR example
npm install jsdomconst fs = require("node:fs");
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><div id='app'></div>`);
const document = dom.window.document;
const window = dom.window;
fetch("https://api.thecatapi.com/v1/images/search?limit=10&page=1")
.then((res) => res.json())
.then((data) => {
const app = document.getElementById("app");
data.forEach((item) => {
const img = document.createElement("img");
img.src = item.url;
img.style.width = "200px";
img.style.height = "200px";
app.appendChild(img);
});
// serialize 序列化字符串
fs.writeFileSync("./index.html", dom.serialize());
});核心模块
path 模块
提供了一些用于处理和操作文件路径的实用工具。
path.basename()// 返回路径的最后一部分,即文件名。path.dirname()// 返回路径的目录名,即路径中最后一个部分之前的部分。path.extname()// 返回路径中文件的扩展名。path.json()// 用于将多个路径片段拼接成一个完整路径。path.resolve()// 将路径片段解析为一个绝对路径。path.parse()// 将一个路径解析为一个对象,包含根目录、目录、文件名和扩展名等信息。path.format()// 将一个路径对象转换为字符串路径,与 path.parse() 相反。path.sep()// 返回当前操作系统使用的路径分隔符。- Windows 上,路径分隔符是
\ - Unix/Linux 和 macOS 上,路径分隔符是
/
- Windows 上,路径分隔符是
const path = require("path");
console.log(path.basename("/users/documents/file.txt")); // 输出:'file.txt'
console.log(path.dirname("/users/documents/file.txt")); // 输出:'/users/documents'
console.log(path.extname("/users/documents/file.txt")); // 输出:'.txt'
console.log(path.join("/users", "documents", "file.txt")); // 输出:'/users/documents/file.txt'
console.log(path.resolve("documents", "file.txt")); // 输出类似:'/当前工作目录/documents/file.txt'
console.log(path.parse("/users/ms/documents/file.txt"));
/* 输出:
{
root: '/',
dir: '/users/ms/documents',
base: 'file.txt',
ext: '.txt',
name: 'file'
}
*/
const pathObject = {
root: "/",
dir: "/users/ms/documents",
base: "file.txt",
ext: ".txt",
name: "file",
};
console.log(path.format(pathObject)); // 输出:'/users/ms/documents/file.txt'
console.log(path.sep); // 输出(在 Unix 上):'/'os 模块
用于提供与操作系统相关的基本信息和功能。
os.paltform()// 返回 Node.js 进程的操作系统平台。例如:'darwin' (macOS), 'win32' (Windows), 'linux' (Linux)os.release()// 返回操作系统的发布版本。例如:'20.6.0' (macOS 版本), '10.0.19042' (Windows 版本)os.type()// 返回操作系统名称。例如:'Darwin', 'Windows_NT', 'Linux'os.version()// 返回操作系统的版本。例如:'Version 10.15.7 (Build 19H2)' (macOS), 'Version 2004 (Build 19041.572)' (Windows)os.homedir()// 返回获取当前用户的主目录路径。- Windows 系统中,
%USERPROFILE%环境变量指向当前用户的主目录。 - Linux 系统中,
$HOME环境变量指向当前用户的主目录。
- Windows 系统中,
os.arch()// 返回一个表示操作系统 CPU 架构的字符串,常见的值包括 x64(64 位系统)和 arm(ARM 架构)等。os.cpus()// 返回关于每个逻辑 CPU 内核的信息。os.networkInterfaces()// 返回一个对象,其中的每个属性都是一个网络接口的名称,每个属性值是一个数组,包含有关该接口的网络信息。每个数组项是一个包含网络接口详细信息的对象。
const os = require("os");
console.log(os.platform()); // 输出操作系统平台。例如:'darwin' (macOS), 'win32' (Windows), 'linux' (Linux)
console.log(os.release()); // 输出操作系统的发布版本。例如:'20.6.0' (macOS 版本), '10.0.19042' (Windows 版本)
console.log(os.type()); // 输出操作系统名称。例如:'Darwin', 'Windows_NT', 'Linux'
console.log(os.version()); // 输出操作系统的版本。例如:'Version 10.15.7 (Build 19H2)' (macOS), 'Version 2004 (Build 19041.572)' (Windows)example
npm install open
# 打开默认浏览器
const open = require('open');
open('http://localhost:3000').catch(err => {
console.error(`Error opening browser: ${err.message}`);
});模拟 open 打开不同操作系统的浏览器
const os = require("os");
const { exec } = require("child_process");
// 要打开的 URL
const url = "http://localhost:3000";
// 根据操作系统平台选择打开浏览器的方式
if (os.platform() === "win32") {
// Windows
exec(`start ${url}`);
} else if (os.platform() === "darwin") {
// macOS
exec(`open ${url}`);
} else if (os.platform() === "linux") {
// Linux
exec(`xdg-open ${url}`);
} else {
console.error("Unsupported OS");
}process 模块
提供了与当前 Node.js 进程交互的功能。它是一个全局对象,允许你获取进程信息、控制进程的生命周期、管理环境变量等。
process.arch()// 返回当前 Node.js 进程的操作系统架构(如 'x64', 'arm', 'ia32')。process.platform()// 返回当前操作系统平台的字符串(如 'win32', 'darwin', 'linux')。process.argv()// 返回一个包含启动 Node.js 进程时传入的命令行参数的数组。process.env()// 返回一个包含用户环境信息的对象(环境变量)process.cwd()// 返回 Node.js 进程的当前工作目录 同__dirnameesm 使用不了 用__dirname代替 cwd()process.memoryUsage()// 返回一个对象,显示 Node.js 进程的内存使用情况(单位为字节)。process.exit([code])// 以给定的退出码退出当前 Node.js 进程。code 是一个整数,默认值为 0(表示成功)process.on(event, listener)// 指定的事件注册一个监听器。常用于监听进程级别的事件,如 exit、uncaughtException 等。process.kill(process.pid)// 发送信号给进程,通常用来结束进程。process.pid 是当前进程的 PID。
// 假设运行命令为: node app.js arg1 arg2
console.log(process.argv);
// 输出: [ '/path/to/node', '/path/to/app.js', 'arg1', 'arg2' ]
// 设置环境变量
process.env.NODE_ENV = "production";
console.log(process.env.NODE_ENV); // production
if (someCondition) {
console.log("Exiting process");
process.exit(1); // 非零退出码表示错误
}
console.log(process.cwd()); // /path/to/app.js
process.on("exit", (code) => {
console.log(`About to exit with code: ${code}`);
});
process.on("uncaughtException", (err) => {
console.error("There was an uncaught error:", err);
process.exit(1); // 非零退出码表示错误
});corss-env 库
一个用于在不同操作系统上设置环境变量的工具。它可以帮助你在开发和构建过程中设置环境变量,而不需要考虑操作系统之间的差异。
原理
- Windows: 使用
set VAR_NAME=value语法设置环境变量。 - 类 Unix 系统(如 Linux 和 macOS): 使用
export VAR_NAME=value或直接使用VAR_NAME=value语法来设置环境变量。
npm install cross-env --save-dev{
"scripts": {
"start:dev": "cross-env NODE_ENV=development node app.js",
"start:prod": "cross-env NODE_ENV=production node app.js"
}
}// app.js
console.log(
`The current environment is ${process.env.NODE_ENV || "undefined"}`
);npm run start:dev # 输出: The current environment is development
npm run start:prod # 输出: The current environment is productionchild_process 子进程 模块
允许创建子进程来执行系统命令、运行其他脚本或程序。它提供了多种方式来管理和控制子进程,从而实现并发处理或利用操作系统的功能。
exec(command [, options], callback)// 执行一个 shell 命令,适合运行简单的命令。execSync(command [, options])// 同步执行一个 shell 命令,返回标准输出的结果。spawn(command [, args [, options]])// 启动一个新进程来执行命令,适合长时间运行的进程或需要与进程进行交互的场景。spawnSync(command [, args [, options]])// 同步启动一个新进程来执行命令,返回结果。
| options 配置项 | 说明 |
|---|---|
cwd | 指定子进程的当前工作目录。默认为 process.cwd()。 |
env | 环境变量的键值对对象。默认为 process.env。 |
encoding | 用于 stdout 和 stderr 的字符编码。默认为 'utf8'。 |
timeout | 进程的最大执行时间(毫秒)。超时后进程会被终止。默认为 0,表示没有超时限制。 |
maxBuffer | stdout 或 stderr 可以占用的最大内存(字节)。默认值为 1024 * 1024(1MB)。 |
killSignal | 终止进程时使用的信号。默认值为 'SIGTERM'。 |
shell | 指定要执行命令的 shell。默认值为 '/bin/sh' (POSIX) 或 'cmd.exe' (Windows)。 |
windowsHide | 在 Windows 上隐藏子进程的控制台窗口。默认为 false。 |
argv0 | 覆盖子进程的 argv[0],通常用于修改命令名称显示。 |
stdio | 子进程的标准输入输出配置,可以是 'pipe'(默认)、'ignore'、'inherit',或文件描述符。 |
detached | 子进程与父进程分离,在父进程退出后继续运行。默认为 false。 |
uid | 设置子进程的用户 ID(仅适用于 POSIX)。 |
gid | 设置子进程的组 ID(仅适用于 POSIX)。 |
windowsVerbatimArguments | 在 Windows 上不对参数进行转义或引用。默认为 false。 |
execFile(file [, args [, options]], callback)// 执行可执行文件,适用于.sh文件(macOS、Linux)或.cmd文件(Windows)。execFileSync(file [, args [, options]])// 同步执行可执行文件,返回标准输出的结果。fork()// 创建一个新的 Node.js 子进程,并且通过建立 IPC(进程间通信)通道,使父进程和子进程能够相互发送消息。
example
- exec execSync
const {
exec,
execSync,
spawn,
spawnSync,
execFile,
execFileSync,
fork,
} = require("child_process");
// exec 异步 回调函数 默认返回 buffer 可以执行 shell 命令 或者软件交互
// execSync 同步 默认返回 buffer
exec("node -v", (err, stdout, stderr) => {
if (err) return;
console.log(stdout.toString()); // 输出 Node.js 版本号
});
execSync("mkdir aa"); // 创建目录
// spawn
const { stdout } = spawn("ls", ["-l"]);
stdout.on("data", (data) => {
console.log(`输出: ${data}`);
});
stdout.on("close", (code) => {
console.log(`子进程退出,退出码 ${code}`);
});- execFile
// a.sh
// #!/bin/bash
// echo "start"
// mkdir a
// cd a
// echo "console.log('print aaa')" > a.js
// echo "end"
// node ./a.js
execFile(path.resolve(__dirname, "./a.sh"), null, (error, stdout) => {
if (error) {
console.log(error);
return;
}
console.log(stdout.toString());
});- fork
// 创建子进程
const child = fork("child.js");
// 向子进程发送消息
child.send({ hello: "world" });
// 接收子进程发来的消息
child.on("message", (message) => {
console.log("Received from child:", message);
});process.on("message", (message) => {
console.log("Received from parent:", message);
// 向父进程发送消息
process.send({ reply: "Hi parent!" });
});底层
实现顺序 exec -> execFile -> spawn
spawn 是最底层的实现,直接调用操作系统的底层 API 来启动子进程。
execFile 是基于 spawn 封装的一个更高效的 API,用于执行特定的可执行文件。
exec 是基于 spawn 和 execFile 封装的 API,用于通过 shell 执行命令。
fork 调用 进程间通信(IPC)-> libuv 来处理跨平台的异步 I/O 操作 libuv
- Windows: 使用命名管道(Named Pipes)
- POSIX: 使用 Unix 域套接字(Unix Domain Sockets)
ffmpeg
用于录制、转换和传输音频和视频的完整跨平台解决方案。 详见
// child_process exec 调用 ffmpeg 处理视频
const { execSync } = require("child_process");
execSync(
// 格式转换
"ffmpeg -i input.mp4 output.avi",
// # 提取音频
// "ffmpeg -i input.mp4 -vn -acodec copy output.mp3",
// # 裁剪 10-20s `-ss 10 -to 20`
// "ffmpeg -i input.mp4 -ss 10 -to 20 -c copy output.mp4"
// 子进程将直接继承父进程的标准输入(stdin)、标准输出(stdout)和标准错误(stderr)流。
// 子进程的输出会直接显示在父进程的终端窗口中,就像是父进程自己输出的一样。
{ stdio: "inherit" }
);pngquant
用于压缩 PNG 图像的开源工具。详见
// child_process exec 调用 pngquant 压缩 png 图片
const { exec } = require("child_process");
exec(
"pngquant input.png --quality 30-50 --speed 1 -o ouput.png ",
(error, stdout) => {
if (error) {
console.log(error);
return;
}
console.log(stdout);
}
);Event 事件触发器
Node.js 提供了内置的 EventEmitter 类,几乎所有的核心模块(如 http、fs、net 等)都依赖于它。
发布订阅模式
类似 vue2 event bus 第三方库 mitt
on(event, listener) 注册事件监听器
off(event, listener) 移除事件监听器
emit(event, listener) 触发事件
once(event, listener) 注册一次性事件监听器
const EventEmitter = require("events");
const emitter = new EventEmitter();
// 监听事件 on
emitter.on("sayHello1", (name) => {
console.log(`Hello, ${name}`);
});
// 触发事件 emit
emitter.emit("sayHello1", "Node.js");
// 使用 once 注册一次性监听器
emitter.once("sayHello1", (name) => {
console.log(`Hello once, ${name}`);
});
// 再次触发事件
emitter.emit("sayHello1", "Node.js"); // 第二次不会触发 once 监听器
console.log(` on off -----`);
const fn = (data) => {
console.log(`Hello, ${data}`);
};
emitter.on("sayHello2", fn);
emitter.emit("sayHello2", "Node.js emit");
// 移除事件监听器 off
emitter.off("sayHello2", fn);
emitter.emit("sayHello2", "Node.js emit"); // 第二次不会触发 emit 监听器util
提供了一些实用的功能,用于帮助开发者进行常见的任务,如格式化、继承、调试等
- util.promisify() 将回调风格的函数转换为返回 Promise 的函数。
- util.callbackify() 将返回 Promise 的函数转换为传统的基于回调的函数。
- util.format() 用于格式化字符串,类似于在 C 语言中的 printf() 函数。
example
- util.promisify
// 使用 util.promisify 将 exec 转换为返回 Promise 的函数
const { exec } = require("child_process");
const util = require("util");
exec("node -v", (error, stdout) => {
if (error) {
console.log(error);
return;
}
console.log(stdout);
});
// 使用 util.promisify
const execPromise = util.promisify(exec);
execPromise("node -v")
.then((res) => {
console.log({ ...res });
})
.catch((error) => {
console.log(error);
});
// 模拟 util.promisify
const promisify = (fn) => {
return (...arg) => {
return new Promise((resolve, reject) => {
fn(...arg, (err, ...values) => {
if (err) {
reject(err);
}
if (values && values.length > 1) {
let obj = {};
for (const key in values) {
obj[key] = values[key];
}
resolve(obj);
} else {
resolve(values[0]);
}
});
});
};
};
const execPromise1 = promisify(exec);
execPromise1("node -v")
.then((res) => {
console.log({ ...res });
})
.catch((error) => {
console.log(error);
});- util.callbackify
const util = require("util");
// util.callbackify 将返回 promise 风格的函数转换为 回调函数风格
const fn = (type) => {
if (type == 1) {
return Promise.resolve("yes");
} else {
return Promise.reject("no");
}
};
const callback = util.callbackify(fn);
callback(1, (err, value) => {
console.log(err, value);
});- util.format()
const util = require("util");
const name = "Alice";
const age = 25;
const info = { city: "Wonderland" };
const formattedString = util.format(
"My name is %s, I am %d years old, and I live in %j.",
name,
age,
info
);
console.log(formattedString);
// 输出: My name is Alice, I am 25 years old, and I live in {"city":"Wonderland"}.| 占位符 | 匹配 |
|---|---|
| %s | 字符串 |
| %d | 数字(整数或浮点数) |
| %j | JSON |
| %% | 字符 % |
fs (File System)
fs 是 Node.js 中用于与文件系统进行交互的核心模块之一。它提供了多种方法来读取、写入、删除和操作文件与目录。 fs 模块有同步(阻塞)和异步(非阻塞)两种接口,适应不同的编程需求。还可以通过 fs/promises 以 Promise 方式操作文件系统。
编程方式
- 异步方式: 通过回调函数处理结果,避免阻塞主线程。 // 非阻塞
- 同步方式: 操作会阻塞,代码执行会等待文件操作完成。 // 阻塞
- Promise 方式: 使用 fs/promises 提供的 Promise API,可结合 async/await 进行异步操作。
文件操作 API
读取文件
fs.readFile(path[, options], callback)// 异步读取文件fs.readFileSync(path[, options])// 同步读取文件
写入文件
fs.writeFile(path, data[, options], callback)// 异步写入文件fs.writeFileSync(path, data[, options])// 同步写入文件
追加文件
如果文件不存在,会自动创建文件。
fs.appendFile(path, data[, options], callback)// 异步追加到文件的末尾fs.appendFileSync(path, data[, options])// 同步追加到文件的末尾
options 类型
string | objectencoding: 文件编码,默认值为null,返回 Buffer。flag: 文件系统标志,类型为string。
| 标志 | 英文 | 解释 |
|---|---|---|
r | read | 以读取模式打开文件。如果文件不存在则报错。 |
w | write | 以写入模式打开文件。如果文件不存在则创建文件,文件已存在则清空。 |
a | append | 以追加模式打开文件。如果文件不存在则创建文件,数据写入文件末尾。 |
x | exclusive | 以独占写入模式打开文件。如果文件已存在则操作失败。 |
+ | plus | 结合使用 r、w 或 a 标志,表示可以同时进行读取和写入操作。 |
s | synchronous | 同步模式,文件的所有操作(包括数据写入和文件元数据更新)都同步完成。 |
文件流操作
fs.createReadStream(path[, options])// 创建可读文件流,适合处理大文件或逐块读取文件内容。options选项:
| 选项 | 类型 | 解释 | 默认值 |
|---|---|---|---|
flags | string | 文件系统标志,'r' 表示以读取模式打开文件。 | 'r' |
encoding | string | 指定字符编码,如 'utf8'。 | |
fd | number | 文件描述符,指定文件的打开方式。如果指定了此选项,path 将被忽略。 | |
mode | number | 文件模式(权限),仅在文件创建时生效。 | 0o666 |
autoClose | boolean | 当流关闭时,自动关闭文件描述符。 | true |
start | number | 指定文件读取的起始字节位置。 | |
end | number | 指定文件读取的结束字节位置。 | |
highWaterMark | number | 控制流在读取过程中缓冲的字节数,默认是 64KB。 | 64*1024 |
fs.createWriteStream(path[, options])// 创建可写文件流,适合逐块写入文件内容。options选项:
| 选项 | 类型 | 解释 | 默认值 |
|---|---|---|---|
flags | string | 文件系统标志,'w' 表示以写入模式打开文件,'a' 表示追加模式。 | 'w' |
encoding | string | 指定字符编码,如 'utf8'。 | 'utf8' |
fd | number | 文件描述符,指定文件的打开方式。如果指定了此选项,path 将被忽略。 | |
mode | number | 文件模式(权限),仅在文件创建时生效。 | 0o666 |
autoClose | boolean | 当流关闭时,自动关闭文件描述符。 | true |
highWaterMark | number | 控制流在写入过程中缓冲的字节数,默认是 16KB。 | 16*1024 |
创建与删除目录
fs.mkdirSync(path[, options])// 同步创建目录fs.mkdir(path[, options], callback)// 异步创建目录fs.rmSync(path[, options])// 同步删除文件或目录fs.rm(path[, options], callback)// 异步删除文件或目录
重命名与移动文件
fs.renameSync(oldPath, newPath)// 同步重命名文件或移动文件
文件监听
fs.watch(path[, options], callback)// 监听文件或目录的变化
链接操作
fs.link(path, path, callback)// 异步创建硬链接fs.linkSync(path, path)// 同步创建硬链接fs.symlink(path, path[, type], callback)// 异步创建符号链接(软链接)fs.symlinkSync(path, path[, type])// 同步创建符号链接(软链接)type: 可选,可以是'file'或'dir'。
example
- readFile
const fs = require("fs");
const fs2 = require("fs/promises"); // promise 方式引入
// readFile 异步
fs.readFile("./example.txt", { encoding: "utf-8" }, (err, data) => {
console.log(data);
});
// readFileSync 同步
try {
const data = fs.readFileSync("example.txt", "utf8");
console.log("文件内容:", data);
} catch (err) {
console.error("读取文件出错:", err);
}
// promise 方式
fs2
.readFile("./example.txt")
.then((data) => {
console.log(data.toString());
})
.catch((error) => {
console.log(error);
});- fs.writeFile
fs.writeFile("./index.text", "index write", () => {});- fs.appendFile
fs.appendFile("./index.text", "index append", "utf-8", () => {});- fs.createReadStream
const fs = require("fs");
const readStream = fs.createReadStream("./example.txt", { encoding: "utf8" });
readStream.on("data", (chunk) => {
console.log("读取到的数据:", chunk);
});
readStream.on("end", () => {
console.log("文件读取完毕");
});
readStream.on("error", (err) => {
console.error("读取文件时出错:", err);
});- fs.createWriteStream
const fs = require("fs");
const lines = [
"这是写入的第一行内容。\n",
"这是写入的第二行内容。\n",
"这是写入的第三行内容。\n",
];
const writeStream = fs.createWriteStream("./output.txt", { encoding: "utf8" });
lines.forEach((line) => {
writeStream.write(line);
});
writeStream.end();
writeStream.on("finish", () => {
console.log("写入操作完成");
});
writeStream.on("error", (err) => {
console.error("写入文件时出错:", err);
});- fs.mkdirSync
const fs = require("fs");
// 创建文件夹
fs.mkdirSync("./dir");
// recursive 递归创建文件夹
fs.mkdirSync("./dir_out/dir_inner", {
recursive: true,
});- fs.rmSync
const fs = require("fs");
// 删除文件夹
fs.rmSync("./dir");
// recursive 递归删除文件夹
fs.rmSync("./dir_out", {
recursive: true,
});- fs.watch
const fs = require("fs");
// 监听文件变化
fs.watch("./example.txt", (event, filename) => {
console.log(event, filename);
});- fs.link
fs.link("index.text", "index2.text", (err) => {
if (err) throw err;
console.log("硬链接创建成功");
});- fs.linkSync
try {
fs.linkSync("targetPath", "linkPath");
console.log("硬链接创建成功");
} catch (err) {
console.error("创建硬链接时出错:", err);
}- fs.symlink
fs.symlink("./index.text", "./index2.text", (err) => {
if (err) throw err;
console.log("软链接创建成功");
});- fs.symlinkSync
try {
fs.symlinkSync("targetPath", "linkPath");
console.log("硬链接创建成功");
} catch (err) {
console.error("创建硬链接时出错:", err);
}原理 libuv
fs 底层使用 libuv。
fs 源码是通过 C++ 层的 FSReqCallback 这个类 对 libuv 的 uv_fs_t 的一个封装,其实也就是将我们 fs 的参数透传给 libuv 层
一个多平台支持库,主要用于提供异步 I/O 操作,它是 Node.js 的核心依赖之一。
example
// mkdir
// 创建目录的异步操作函数,通过uv_fs_mkdir函数调用
// 参数:
// - loop: 事件循环对象,用于处理异步操作
// - req: 文件系统请求对象,用于保存操作的状态和结果
// - path: 要创建的目录的路径
// - mode: 目录的权限模式 777 421
// - cb: 操作完成后的回调函数
int uv_fs_mkdir(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
int mode,
uv_fs_cb cb) {
INIT(MKDIR);
PATH;
req->mode = mode;
if (cb != NULL)
if (uv__iou_fs_mkdir(loop, req))
return 0;
POST;
}注意
setImmediate 先执行,而不是 fs
Node.js 读取文件 I/O 操作 都是使用 libuv 进行调度的
而 setImmediate 是由 V8 进行调度的
文件读取完成后 libuv 才会将 fs 的结果 推入 V8 的队列
const fs = require("fs");
fs.readFile(
"./index.txt",
{
encoding: "utf-8",
flag: "r",
},
(err, dataStr) => {
if (err) throw err;
console.log("fs");
}
);
setImmediate(() => {
console.log("setImmediate");
});
>>>
setImmediate
indexcrypto 加密
提供了多种加密、解密、生成哈希、HMAC、签名和验证签名的方法。
哈希
哈希(Hashing)是一种将输入数据(无论长度如何)转换为固定长度字符串或数字的过程。这个过程使用哈希函数(Hash Function)来进行。哈希函数将输入的数据称为“消息”或“消息摘要”,通过一系列复杂的计算生成一个唯一的输出值,称为哈希值或哈希码。
常见的哈希算法
- MD5(Message Digest Algorithm 5):输出 128 位(16 字节)的哈希值。已被认为不再安全,容易发生碰撞。
- SHA-1(Secure Hash Algorithm 1):输出 160 位(20 字节)的哈希值,也已被认为不再安全。
- SHA-256:输出 256 位(32 字节)的哈希值,是目前较为安全的选择之一,属于 SHA-2 系列。
使用场景
- 密码存储:传统上用于避免明文存储密码(不再推荐使用 MD5,因其易受碰撞攻击和暴力破解的影响)。
- 文件完整性验证:确保文件在传输或存储过程中未被篡改。
example
const crypto = require("crypto");
const hash = crypto.createHash("sha256");
hash.update("hello");
console.log(hash.digest("hex"));
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824HAMC (Hash-based Message Authentication Code)
使用一个密钥对消息进行加密,生成一个哈希值,用于消息完整性和身份验证。
const crypto = require("crypto");
const hmac = crypto.createHmac("sha256", "a secret");
hmac.update("hello");
console.log(hmac.digest("hex"));
// f4e44dfb5af3e6399ff5f6c4d35faedeb7e339d2cc1a2c89eb42ff08464d09da对称加密算法
对称加密算法使用同一个密钥来进行加密和解密。它们通常用于数据保护和安全传输。
常见对称加密算法
- AES:高级加密标准(Advanced Encryption Standard),目前最广泛使用的对称加密算法,适合大多数安全需求。
- DES:数据加密标准(Data Encryption Standard),由于密钥长度较短,已被淘汰,通常使用其增强版 3DES。
example
const crypto = require("crypto");
// 生成随机密钥和初始化向量
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
// 加密
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
let encrypted = cipher.update("hello", "utf-8", "hex");
encrypted += cipher.final("hex");
console.log("Encrypted:", encrypted);
// 解密
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
let decrypted = decipher.update(encrypted, "hex", "utf-8");
decrypted += decipher.final("utf-8");
console.log("Decrypted:", decrypted);非对称加密
使用一对密钥:公钥用于加密,私钥用于解密。它通常用于安全的密钥交换、数字签名和身份验证。
常见非对称加密算法
- RSA:一种广泛使用的非对称加密算法,具有高安全性。RSA 密钥的长度通常为 2048 位或 4096 位。
- DSA(Digital Signature Algorithm):主要用于数字签名。
const crypto = require("crypto");
// 生成 RSA 密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
});
// 使用公钥加密
let encrypted = crypto.publicEncrypt(publicKey, Buffer.from("hello"));
console.log("Encrypted:", encrypted.toString("hex"));
// 使用私钥解密
let decrypted = crypto.privateDecrypt(privateKey, encrypted);
console.log("Decrypted:", decrypted.toString("utf-8"));cli 脚手架
commander一个强大的命令行参数解析工具,适用于处理命令、选项和参数。inquirer一个用于与命令行用户进行交互的库,能够创建漂亮的命令行提示。ora一个用于在命令行中显示加载动画(spinner)的库,常用于显示异步操作的进度。download-git-repo一个用于从 Git 仓库(如 GitHub、GitLab 等)下载项目模板的库,常用于脚手架工具中自动拉取模板。
install
npm i commander inquirer ora download-git-repo编写脚手架
- 自定义命令
// cli.js
#!/usr/bin/env node
// Node.js 脚本的一个标准开头,它确保脚本可以在不同的环境和系统上找到正确的 Node.js 解释器来运行。
console.log("mycli run");- 挂载自定义命令 -> 创建软连接 挂载到全局
# package.json
+ "bin": {
+ "mycli": "src/cli.js"
+ },npm link # 创建软连接 挂载到全局- 编写脚手架
#!/usr/bin/env node
// Node.js 脚本的一个标准开头,它确保脚本可以在不同的环境和系统上找到正确的 Node.js 解释器来运行。
import { program } from "commander";
import inquirer from "inquirer";
import fs from "node:fs";
import { checkPath, downloadTemp } from "./util.js";
// console.log("mycli run");
let package_json = fs.readFileSync("./package.json");
package_json = JSON.parse(package_json);
program.version(package_json.version);
program
.command("create <projectName>")
.alias("c")
.description("创建项目")
.action((name) => {
console.log(name);
inquirer
.prompt([
{
type: "input",
name: "projectName",
message: "请输入项目名名称",
default: name,
},
{ type: "confirm", name: "isTS", message: "是否启用 TS" },
])
.then((res) => {
// console.log(res);
if (checkPath(res.projectName)) {
console.log("文件夹已存在");
return;
}
if (res.isTS) {
downloadTemp("ts", res.projectName);
} else {
downloadTemp("js", res.projectName);
}
})
.catch(() => {
console.error("操作被取消");
process.exit(1); // 处理异常后退出
});
});
program.parse(process.argv);import fs from "fs";
import download from "download-git-repo";
import ora from "ora";
export const checkPath = (path) => {
if (fs.existsSync(path)) {
return true;
} else {
return false;
}
};
export const downloadTemp = (branch, name) => {
return new Promise((resolve, reject) => {
const spinner = ora("Loading...").start();
download(
`direct:https://gitee.com/chinafaker/vue-template.git#${branch}`,
name,
{ clone: true },
function (err) {
if (err) reject(err);
resolve();
spinner.succeed("下载完成");
}
);
});
};- login
npm loginWARNING
note npm registry
npm config get registry
npm config set registry https://registry.npmjs.org/
# taobao
npm config set registry https://registry.npmmirror.com/- publish
npm publishzlib
用于提供文件压缩和解压缩功能,支持多种压缩算法,如 gzip 和 deflate。
算法
gzip算法 后缀.gz常用于 文件压缩deflate算法 后缀.flate常用于 网络传输 动态资源的压缩传输brotli算法 后缀.br常用于 网络传输 静态资源的压缩传输
API
它们都有 同步、异步版本
zlib.gzip() 压缩
zlib.gunzip() 解压
zlib.defalte() 压缩
zlib.infalte() 解压
zlib.brotliCompress() 压缩
zlib.brotliDdcompress() 解压
example
- 文件压缩与解压缩
const zlib = require("node:zlib");
const fs = require("fs");
// gzip
const readStream = fs.createReadStream("./example.txt");
const writeStream = fs.createWriteStream("./example.txt.gz");
readStream.pipe(zlib.createGzip()).pipe(writeStream);
const readStream = fs.createReadStream("./example.txt.gz");
const writeStream = fs.createWriteStream("./example1.txt");
readStream.pipe(zlib.createGunzip()).pipe(writeStream);
// deflate
const readStream = fs.createReadStream("./example.txt");
const writeStream = fs.createWriteStream("example.txt.flate");
readStream.pipe(zlib.createDeflate()).pipe(writeStream);
const readStream = fs.createReadStream("example.txt.flate");
const writeStream = fs.createWriteStream("example2.txt");
readStream.pipe(zlib.createInflate()).pipe(writeStream);
// Brotli
const inputBrotli = fs.createReadStream("./example.txt");
const writeStream = fs.createWriteStream("./example.txt.br");
inputBrotli.pipe(zlib.createBrotliCompress()).pipe(writeStream);
const readStream = fs.createReadStream("./example.txt.br");
const writeStream = fs.createWriteStream("./example-decompressed.txt");
readStream.pipe(zlib.createBrotliDecompress()).pipe(writeStream);- HTTP 压缩与解压缩
const fs = require("fs");
const http = require("http");
const zlib = require("zlib");
// gzip
const server = http.createServer((req, res) => {
const text = "123".repeat(10000);
res.setHeader("Content-Type", "text/plain;charset=utf-8");
res.setHeader("Content-Encoding", "gzip");
const result = zlib.gzipSync(text);
res.end(result);
});
// deflate
const server = http.createServer((req, res) => {
const text = "123".repeat(10000);
res.setHeader("Content-Type", "text/plain;charset=utf-8");
res.setHeader("Content-Encoding", "deflate");
const result = zlib.deflateSync(text);
res.end(result);
});
const server = http.createServer((req, res) => {
const text = "123".repeat(10000);
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.setHeader("Content-Encoding", "br");
const result = zlib.brotliCompressSync(text);
res.end(result);
});
server.listen(3000);http
构建 HTTP 服务器和客户端的核心模块之一。它允许你轻松创建服务器来处理 HTTP 请求和响应,也可以用来发起 HTTP 请求。
const http = require("http");
const url = require("url");
http
.createServer((req, res) => {
const { method } = req;
const { pathname, query } = url.parse(req.url, true);
if (method == "GET") {
if (pathname === "/get") {
console.log(query);
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.statusCode = 200;
res.end(JSON.stringify(query));
}
} else if (method == "POST") {
if (pathname === "/login") {
let data = "";
req.on("data", (chunk) => {
data += chunk;
});
req.on("end", () => {
res.setHeader("Content-Type", "appliaction/json");
res.statusCode = 200;
res.end(data);
});
} else {
res.statusCode = 404;
res.end("404");
}
}
})
.listen(3000);反向代理
npm i http-proxy-middleware代理 80 /api 服务到 3000 端口
const http = require("http");
const url = require("url");
const fs = require("fs");
const { createProxyMiddleware } = require("http-proxy-middleware");
const config = require("./proxy.config.js");
http
.createServer((req, res) => {
const { pathname } = url.parse(req.url);
console.log(pathname);
const proxyList = Object.keys(config.server.proxy);
console.log(proxyList);
if (proxyList.includes(pathname)) {
const proxy = createProxyMiddleware(config.server.proxy[pathname]);
return proxy(req, res);
}
const html = fs.readFileSync("./index.html");
res.writeHead(200, { "Content-Type": "text/html" });
res.end(html);
})
.listen(80);module.exports = {
server: {
proxy: {
"/api": {
target: "http://localhost:3000/",
changeOrgin: true,
},
},
},
};const http = require("http");
const url = require("url");
http
.createServer((req, res) => {
const { pathname } = url.parse(req.url);
if (pathname == "/api") {
res.writeHead(200, { "Content-Type": "text/plan; charset=utf-8" });
res.end("proxy success");
}
})
.listen(3000, () => {
console.log("3000");
});动静分离
动静分离是一种在 Web 服务器架构中常用的优化技术,旨在提高网站的性能和可伸缩性。 它基于一个简单的原则:将动态生成的内容(如动态网页、API 请求)与静态资源(如 HTML、CSS、JavaScript、图像文件)分开处理和分发。
npm i mimeimport http from "node:http";
import fs from "node:fs";
import path from "node:path";
import mime from "mime"; // 导入mime模块
const server = http.createServer((req, res) => {
const { url, method } = req;
// 处理静态资源
if (method === "GET" && url.startsWith("/static")) {
const filePath = path.join(process.cwd(), url); // 获取文件路径
const mimeType = mime.getType(filePath); // 获取文件的MIME类型
console.log(mimeType); // 打印MIME类型
fs.readFile(filePath, (err, data) => {
// 读取文件内容
if (err) {
res.writeHead(404, {
"Content-Type": "text/plain", // 设置响应头为纯文本类型
});
res.end("not found"); // 返回404 Not Found
} else {
res.writeHead(200, {
"Content-Type": mimeType, // 设置响应头为对应的MIME类型
"Cache-Control": "public, max-age=3600", // 设置缓存控制头
});
res.end(data); // 返回文件内容
}
});
}
// 处理动态资源
if ((method === "GET" || method === "POST") && url.startsWith("/api")) {
// ...处理动态资源的逻辑
}
});
server.listen(80);邮件服务
- js-yaml 用于在 JavaScript 中解析和生成 YAML 数据
- nodemailer 用于在 Node.js 环境下发送电子邮件的库,它支持多种传输方式,如 SMTP。
npm i js-yaml nodemailerconst yaml = require("js-yaml");
const fs = require("node:fs");
const http = require("node:http");
const url = require("node:url");
const nodemailer = require("nodemailer");
// 加载邮箱配置信息
const email = yaml.load(fs.readFileSync("email.yaml", "utf-8"));
console.log(email);
let transporter = nodemailer.createTransport({
host: "smtp.qq.com", // QQ 邮箱 SMTP 服务器
port: 465,
secure: true,
auth: {
user: email.emailconfig.user,
pass: email.emailconfig.pass,
},
});
http
.createServer((req, res) => {
const { method } = req;
const { pathname } = url.parse(req.url);
if (method === "POST" && pathname == "/post/email") {
let data = "";
req.on("data", (chunk) => {
data += chunk;
});
req.on("end", () => {
console.log("接收到的数据:", data);
try {
const postData = JSON.parse(data);
console.log("解析后的数据:", postData);
// 准备发送邮件
transporter.sendMail(
{
from: email.emailconfig.user, // 发件人
to: postData.user, // 收件人
subject: postData.subject, // 邮件主题
text: postData.content, // 邮件内容
},
(error, info) => {
if (error) {
console.error("邮件发送失败:", error);
res.statusCode = 500;
res.end(`邮件发送失败: ${error.toString()}`);
} else {
console.log("邮件发送成功:", info);
res.statusCode = 200;
res.end(`邮件发送成功: ${info.messageId}`);
}
}
);
} catch (err) {
console.error("数据解析错误:", err);
res.statusCode = 400;
res.end("请求数据格式错误");
}
});
} else {
res.statusCode = 404;
res.end("路径或方法不正确");
}
})
.listen(3000, () => {
console.log("服务器在端口 3000 上运行");
});请求头 响应头
Access-Control-Allow-Origin 跨域
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
next();
});app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "http://localhost:5500");
next();
});WARNING
res.setHeader("Access-Control-Allow-Origin", "*"); 浏览器出于安全考虑,不会发送或接收 cookies,包括 session cookies。 这是因为跨域请求中,cookies 被视为敏感信息,只有当 Access-Control-Allow-Origin 设置为具体的域时,浏览器才允许携带和共享 cookies。
Access-Control-Allow-Methods 允许的请求方法
指定在跨域请求中允许的 HTTP 方法
fetch("http://localhost:3000/patch", { method: "PATCH" })
.then((res) => res.json())
.then((res) => {
console.log(res);
});app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "http://localhost:5500");
res.setHeader(
"Access-Control-Allow-Methods",
"GET,POST,PUT,PATCH,DELETE,OPTIONS"
);
next();
});WARNING
如果服务器没有明确设置 Access-Control-Allow-Methods 头,浏览器在处理跨域请求时会阻止非简单请求(例如 POST 以外的方法或者携带自定义请求头的请求),并且浏览器会返回跨域请求错误。
Access-Control-Allow-Headers
- Content-Type 允许客户端指定请求体的类型
- Authorization 允许客户端发送认证相关的头字段
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
next();
});fetch("http://localhost:3000/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
hello: "post",
}),
})
.then((res) => {
return res.json();
})
.then((res) => {
console.log(res);
});Access-Control-Expose-Headers 自定义响应头 暴露自定义响应头
app.post("/post", (req, res) => {
res.set({ "X-Custom-Header": "test" });
res.setHeader("Access-Control-Expose-Headers", "X-Custom-Header");
res.json({
code: 200,
hello: "post",
});
});fetch("http://localhost:3000/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
hello: "post",
}),
})
.then((res) => {
console.log(res.headers.get("X-Custom-Header"));
return res.json();
})
.then((res) => {
console.log(res);
});SSE 响应
const sse = new EventSource("http://localhost:3000/sse");
sse.addEventListener("message", (e) => {
console.log(e.data);
});
// 接受自定义事件名称
sse.addEventListener("xxx", (e) => {
console.log(e.data);
});app.get("/sse", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.status(200);
const intervalId = setInterval(() => {
res.write("event: xxx\n"); // 自定义事件名称 默认为 message
res.write("data: " + Date.now() + "\n\n");
}, 2000);
// 当客户端断开连接时清除定时器
req.on("close", () => {
clearInterval(intervalId);
res.end();
});
});串口 Serial Port
Node SerialPort 是一个用于连接串行端口的 JavaScript 库,可在 NodeJS 和 Electron 中运行。
npm install serialportimport { SerialPort } from "serialport";
SerialPort.list()
.then((ports) => {
console.log(ports); // 输出全部串口
})
.catch((err) => {
console.error("获取串口列表失败:", err.message);
});import { SerialPort } from "serialport";
// 创建端口
const port = new SerialPort({
path: "/dev/tty-usbserial1", //
baudRate: 57600, // 波特率
});import { SerialPort } from "serialport";
const serialPort = new SerialPort({
path: "COM4", //单片机串口
baudRate: 9600, //波特率
});
serialPort.on("data", () => {
console.log("data"); //监听单片机的消息
});
let flag = 1;
setInterval(() => {
serialPort.write(flag + ""); //跟单片机进行通讯 传值
flag = Number(!flag);
console.log(flag == 0 ? "开" : "关"); //进行开关的切换
}, 2000);net
- 用于创建底层的 TCP 或 IPC 服务器和客户端。
- 提供了对原始数据流的操作,适合实现自定义协议或需要直接操作 TCP 连接的场景。
- 不包含 HTTP 协议相关的解析和封装,开发者需要自己处理数据格式。
- 示例用途:Socket 通信、聊天服务器、消息推送等。
const net = require("net");
const server = net.createServer((socket) => {
console.log("客户端已连接");
socket.on("data", (data) => {
console.log("接收到数据:", data.toString());
});
socket.on("end", () => {
console.log("客户端已断开连接");
});
socket.write("欢迎连接到服务器!\n");
});
server.listen(8080, () => {
console.log("服务器正在监听端口 8080");
});const net = require("net");
const client = net.createConnection({ port: 8080 }, () => {
console.log("已连接到服务器");
client.write("你好,服务器!");
});
client.on("data", (data) => {
console.log("接收到数据:", data.toString());
client.end();
});
client.on("end", () => {
console.log("已从服务器断开连接");
});