Node.js 全栈开发指南
高阶函数
什么是高阶函数?
高阶函数(Higher-Order Function)是指满足以下任意一个条件的函数:
- 接受一个或多个函数作为参数
- 返回一个函数作为结果
高阶函数的应用场景
1. 函数功能扩展
javascript
// 示例:在不修改原函数的情况下扩展功能
function coreFunction(a, b, c) {
console.log(a, b, c);
}
Function.prototype.before = function (callback) {
return (...args) => {
callback(); // 扩展方法
this(...args); // 执行原来的核心方法
};
};
const enhancedCoreFunction = coreFunction.before(function () {
console.log("before");
});
2. 参数预置
javascript
// 示例:预置函数参数
function sum(a, b, c) {
return a + b + c;
}
function apply(fn, ...presetArgs) {
return function (...args) {
return fn(...presetArgs, ...args);
};
}
let add12 = apply(sum, 1, 2);
console.log(add12(3)); // 输出:6
高阶函数与回调函数的区别
- 高阶函数:接受函数作为参数或返回函数的函数
- 回调函数:作为参数传递给另一个函数的函数,通常用于异步操作
异步编程
异步并发控制
javascript
// 使用高阶函数处理异步并发
function after(times, cb) {
const person = {};
return (key, value) => {
person[key] = value;
if (--times == 0) {
cb(person);
}
};
}
// 使用示例
const cb = after(2, (person) => {
console.log(person);
});
fs.readFile("age.txt", "utf8", (err, data) => {
cb("age", data);
});
fs.readFile("name.txt", "utf8", (err, data) => {
cb("name", data);
});
Promise 实现原理
Promise 核心实现
javascript
class Promise {
constructor(executor) {
this.status = PENDING; // 状态:PENDING、FULFILLED、REJECTED
this.value = undefined; // 成功值
this.reason = undefined; // 失败原因
this.onResolvedCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject); // 递归解析 Promise
}
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
}
核心特性解析
-
状态管理
- Promise 有三种状态:PENDING、FULFILLED、REJECTED
- 状态只能从 PENDING 变为 FULFILLED 或 REJECTED
- 状态一旦改变,不能再次改变
-
then 方法实现
javascriptthen(onFulfilled, onRejected) { // 参数可选,提供默认值 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v; onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }; let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { process.nextTick(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); } // ... 其他状态处理 }); return promise2; }
-
Promise 链式调用
- then 方法返回新的 Promise 实例
- 支持链式调用
- 处理 then 中返回的 Promise 实例
-
静态方法实现
- Promise.resolve:将值转换为 Promise
- Promise.reject:创建拒绝的 Promise
- Promise.all:等待所有 Promise 完成
- Promise.race:返回最快的 Promise 结果
- Promise.finally:无论成功失败都执行
-
Promise 解析过程
javascriptfunction resolvePromise(promise2, x, resolve, reject) { // 防止循环引用 if (promise2 === x) { return reject(new TypeError('循环引用')); } // 处理 thenable 对象 if ((typeof x === 'object' && x !== null) || typeof x === 'function') { let called = false; try { let then = x.then; if (typeof then === 'function') { then.call(x, y => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, r => { if (called) return; called = true; reject(r); } ); } else { resolve(x); } } catch (e) { if (called) return; called = true; reject(e); } } else { resolve(x); } }
重要特性说明
-
微任务处理
- 使用
process.nextTick
确保 then 回调在微任务队列中执行 - 保证异步执行顺序的正确性
- 使用
-
错误处理
- 构造函数中的 try-catch 捕获执行器错误
- then 方法中的 try-catch 捕获回调函数错误
- 支持错误穿透
-
Promise 互操作性
- 支持不同 Promise 实现之间的互操作
- 通过 resolvePromise 处理 thenable 对象
-
状态保护
- 使用 called 标志防止多次调用
- 确保 Promise 状态只能改变一次
使用示例
javascript
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1000);
});
promise
.then(value => {
console.log(value);
return 'next value';
})
.then(value => {
console.log(value);
return Promise.resolve('promise value');
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log('finally');
});
注意事项
- Promise 构造函数是同步执行的
- then 回调是异步执行的(微任务)
- Promise 链式调用中,每个 then 都返回新的 Promise
- Promise 状态一旦改变,不能再次改变
- 错误处理要放在链式调用的最后
浏览器事件环
进程与线程
- 进程:操作系统分配资源的最小单位
- 线程:进程内的执行单元
浏览器进程模型
- 主进程:负责浏览器界面显示、用户交互、子进程管理
- 渲染进程:负责网页渲染、JavaScript 执行
- 网络进程:负责网络请求、资源加载
- 插件进程:负责浏览器插件运行
- GPU 进程:负责 GPU 加速、3D 渲染
渲染进程中的线程
- JS 引擎线程:执行 JavaScript 代码
- 渲染线程:负责页面渲染(与 JS 引擎线程互斥)
- 网络线程:处理网络请求
- 合成线程:图层合成
- 事件触发线程:处理异步事件
事件环(Event Loop)
任务分类
-
宏任务:
- 脚本执行
- UI 渲染
- 定时器(setTimeout)
- HTTP 请求
- 事件处理
- MessageChannel
- setImmediate
-
微任务:
- Promise.then
- MutationObserver
- process.nextTick
- queueMicrotask
执行顺序
- 执行一个宏任务
- 清空微任务队列
- 重复步骤 1 和 2
微任务与宏任务执行顺序示例
javascript
console.log(1);
async function async() {
console.log(2);
await console.log(3); // 相当于 Promise.resolve(console.log(3)).then(()=>{console.log(4)})
console.log(4); // 也会产生一个 then,在之后执行
}
setTimeout(() => {
console.log(5);
}, 0);
const promise = new Promise((resolve, reject) => {
console.log(6);
resolve(7);
});
promise.then((res) => {
console.log(res);
});
async();
console.log(8);
执行顺序分析
-
同步代码执行:
console.log(1)
→ 输出 1console.log(6)
→ 输出 6(Promise 构造函数中的同步代码)console.log(2)
→ 输出 2(async 函数开始执行)console.log(3)
→ 输出 3(await 后面的同步代码)console.log(8)
→ 输出 8
-
微任务队列:
- Promise.then 回调(输出 7)
- async 函数中 await 后的代码(输出 4)
-
宏任务队列:
- setTimeout 回调(输出 5)
最终输出顺序
1
6
2
3
8
7
4
5
执行过程详解
- 首先执行所有同步代码(1, 6, 2, 3, 8)
- 然后清空微任务队列:
- 执行 Promise.then 回调(7)
- 执行 async 函数中 await 后的代码(4)
- 最后执行宏任务队列中的 setTimeout 回调(5)
关键点说明
async/await
本质上是 Promise 的语法糖await
后面的代码会被包装成 Promise.then 回调- 微任务优先于宏任务执行
- 同步代码优先于异步代码执行
- Promise 构造函数中的代码是同步执行的
- setTimeout 是宏任务,即使延迟时间为 0
其他重要概念
- requestAnimationFrame:用于优化动画
- requestIdleCallback:用于空闲时间任务调度
- 进程间通信(IPC):多进程间通信机制
Node.js 核心概念
什么是 Node.js?
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,让 JavaScript 可以运行在服务端。其特点是:
- 事件驱动
- 非阻塞 I/O
- 支持 JavaScript 所有语法
- 没有 DOM API
- 内置模块
- 第三方模块
事件驱动
- Node.js 实现了自己的事件环(Event Loop),用于处理异步 API 操作
- 类似浏览器的事件环,但专门用于处理 Node.js 的异步 API
单线程与多线程
单线程特点
- 优点 :
- 节约内存
- 避免多线程资源竞争问题
- 适合 I/O 密集型任务
- 编程模型简单
- 缺点 :
- 不适合 CPU 密集型任务
- 无法充分利用多核 CPU
多线程特点
- 优点 :
- 可以同时处理多个任务
- 能够充分利用多核 CPU
- 缺点 :
- 需要处理线程锁问题
- 线程间通信复杂
- 资源竞争问题
阻塞与非阻塞
- 阻塞:调用方法后必须等待结果返回才能继续执行
- 非阻塞:调用方法后可以立即返回,继续执行其他操作
- 同步:调用方等待被调用方返回结果
- 异步:调用方不等待被调用方返回结果
常见组合:
- 异步非阻塞(最常用)
- 同步阻塞(最常用)
- 异步阻塞
- 同步非阻塞
Node.js 应用场景
-
服务器开发
- Koa
- Express
- Egg.js
- NestJS
-
中间层(BFF - Backend For Frontend)
- 解决跨域问题
- 为前端服务
-
实时应用
- 即时通信
- 聊天应用
- 爬虫
-
服务端渲染
- React SSR
- Vue SSR
-
工具链开发
- 构建工具(Webpack、Gulp、Vite、Rollup)
- 前端工具链
- 脚手架
Node.js 与浏览器 JavaScript 的区别
- 运行环境:Node.js 运行在服务端,浏览器运行在客户端
- API 差异:Node.js 没有 DOM API,但有内置模块
- 事件环实现:Node.js 和浏览器的事件环实现不同
- 全局对象 :Node.js 是
global
,浏览器是window
Node.js 模块系统
为什么需要模块化?
- 解决命名冲突:通过模块化可以避免全局命名空间污染
- 代码复用:模块可以被其他程序引用,提高代码复用性
- 代码隔离:每个模块都有独立的作用域
- 更好的扩展性:模块化使代码结构更清晰,便于扩展
模块化规范的发展
- 早期规范 :
- CommonJS(Node.js 采用)
- AMD(RequireJS)
- CMD(SeaJS)
- UMD(通用模块定义)
- IIFE(立即执行函数)
- SystemJS
CommonJS 规范
核心概念
-
每个文件都是一个模块
- 每个模块在运行时都会产生一个函数
- 模块内部有独立的作用域
-
模块的导入导出
- 使用
require
导入模块 - 使用
module.exports
导出模块 a.js
- 使用
ini
module.exports = "cccc";
- 模块类型
- 内置模块:Node.js 核心模块(如 fs、path)
- 第三方模块:通过 npm 安装的模块
- 文件模块:用户自己编写的模块
模块加载机制
-
模块查找顺序
- 内置模块(优先级最高)
- 文件模块(相对路径或绝对路径)
- 第三方模块(node_modules)
-
文件模块查找规则
- 查找同名文件,尝试添加后缀(.js、.json 等)
- 查找同名文件夹,寻找 package.json 中 main 字段指定的入口
- 如果没有 package.json,则查找 index.js
-
第三方模块查找规则
- 在 node_modules 文件夹中递归查找
- 查找同名文件夹/文件
- 按照文件模块的规则处理
模块加载流程
-
加载模块(Module._load)
- 返回 module.exports 的结果
-
解析文件名(Module._resolveFilename)
- 生成可读取的文件名(.js、.json 等)
-
缓存检查(Module._cache)
- 如果模块已缓存,直接返回缓存结果
-
创建模块(new Module)
- 创建模块实例,包含 id 和 exports
-
缓存模块
- 将模块存入缓存,供后续使用
-
加载模块(module.load)
- 读取文件内容
- 根据扩展名调用对应的加载方式
-
执行模块
- 将文件内容包装成函数
- 传入五个参数:exports、require、module、__filename、__dirname
- 执行函数,获取 module.exports 的值
重要变量
- __filename:当前文件的绝对路径
- __dirname:当前文件所在目录的绝对路径
- module.exports:模块导出对象
- exports:module.exports 的引用
模块包装
javascript
// 模块实际上被包装成如下函数
(function(exports, require, module, __filename, __dirname) {
// 模块代码
});
示例:自定义模块加载器
javascript
function Module(id) {
this.id = id;
this.exports = {};
}
Module._cache = {};
Module._extensions = {
'.js'(module) {
const content = fs.readFileSync(module.id, 'utf8');
let wrapperFn = vm.compileFunction(content, [
'exports', 'require', 'module', '__filename', '__dirname'
]);
// 执行模块代码
Reflect.apply(wrapperFn, module.exports, [
module.exports,
myRequire,
module,
module.id,
path.dirname(module.id)
]);
},
'.json'(module) {
const content = fs.readFileSync(module.id, 'utf8');
module.exports = JSON.parse(content);
}
};
注意事项
- 模块路径最好使用绝对路径,避免相对路径可能带来的问题
- 模块加载是同步的,可能影响性能
- 模块会被缓存,多次 require 同一个模块只会执行一次
- 循环依赖需要特别注意处理
- 模块的 exports 是 module.exports 的引用,直接修改 exports 不会生效
npm 包管理
什么是 npm?
npm(Node Package Manager)是 Node.js 的包管理工具,用于:
- 管理项目依赖
- 安装第三方包
- 管理包版本
- 发布自己的包
package.json
- 通过
npm init -y
快速创建 - 记录项目信息和依赖配置
- 定义项目脚本命令
依赖类型
-
全局依赖(-g)
- 安装命令:
npm install package-name -g
- 只能在命令行中使用
- 通过
npm link
可以将本地项目链接到全局 - 示例:
npm install http-server -g
- 安装命令:
-
本地依赖
-
生产依赖(dependencies)
- 安装命令:
npm install package-name
或npm install package-name --save
- 项目运行必需的依赖
- 会被打包到生产环境
- 安装命令:
-
开发依赖(devDependencies)
- 安装命令:
npm install package-name --save-dev
或npm install package-name -D
- 仅在开发时需要的依赖
- 如构建工具、转译工具等
- 不会打包到生产环境
- 安装命令:
-
-
其他依赖类型
- peerDependencies:同等依赖,会自动安装
- optionalDependencies:可选依赖
- bundledDependencies:打包依赖
版本管理
-
版本号格式 :
major.minor.patch
- major:主版本号(不兼容的 API 修改)
- minor:次版本号(向下兼容的功能性新增)
- patch:修订号(向下兼容的问题修正)
-
版本范围符号
^2.0.0
:兼容补丁和小版本更新(2.x.x)~1.3.4
:只兼容补丁更新(1.3.x)>=2.1.1
:大于等于指定版本<=2.1.1
:小于等于指定版本1.0.0 ~ 2.0.0
:版本范围
-
预发布版本
- alpha:内部测试版
- beta:公测版
- rc:候选版本
- 示例:
1.0.0-alpha.3
、1.0.0-beta.3
、1.0.0-rc.3
包管理机制
-
package-lock.json
- 锁定依赖版本
- 确保团队成员使用相同版本的依赖
- 记录依赖树的具体版本
-
npm install
- 安装 package.json 中声明的所有依赖
--production
只安装生产依赖- 自动生成 package-lock.json
-
npm scripts
- 通过
npm run
执行 package.json 中定义的脚本 - 执行时会临时将
node_modules/.bin
添加到 PATH - 可以执行本地安装的命令行工具
- 通过
-
npx
- 执行本地安装的命令行工具
- 如果命令不存在,会临时安装并执行
- 适合一次性使用的命令
- 示例:
npx mime
最佳实践
-
依赖管理
- 区分开发依赖和生产依赖
- 使用 package-lock.json 锁定版本
- 定期更新依赖版本
-
版本控制
- 遵循语义化版本规范
- 合理使用版本范围符号
- 注意预发布版本的使用
-
脚本管理
- 将常用命令写入 npm scripts
- 使用 npx 执行一次性命令
- 避免全局安装不必要的包
-
安全考虑
- 定期检查依赖安全漏洞
- 使用
npm audit
进行安全审计 - 及时更新有安全问题的依赖
模块化实践示例
1. 模块导出方式
javascript
// module.js 示例
// 方式1:直接导出值
module.exports = "abc";
// 方式2:导出对象属性
exports.abc = 1;
// 方式3:导出对象
module.exports = {
a: 1,
b: 2
};
// 注意:以下方式是错误的
exports = "abc"; // 错误,因为 exports 是 module.exports 的引用
导出机制说明:
module.exports
是真正的导出对象exports
是module.exports
的引用- 最终返回的是
module.exports
- 不能同时使用
module.exports
和exports
赋值
2. 模块缓存机制
javascript
// module.js 示例
let obj = { a: 1 };
setInterval(() => {
obj.a++;
}, 1000);
module.exports = obj;
// 在多个文件中 require 这个模块
// 会共享同一个 obj 对象,因为模块会被缓存
缓存机制说明:
- 模块在第一次加载时会被缓存
- 多次 require 同一个模块会返回缓存的结果
- 缓存的是
module.exports
的值 - 对于对象类型的导出,会共享同一个引用
3. 循环依赖处理
javascript
// module-a.js
let moduleB;
module.exports = {
saveModule(b) {
moduleB = b;
},
say() {
console.log("a的say");
},
init() {
moduleB.say();
}
};
// module-b.js
let moduleA;
module.exports = {
saveModule(a) {
moduleA = a;
},
say() {
console.log("b的say");
},
init() {
moduleA.say();
}
};
// cc.js - 使用示例
const useA = require("./module-a");
const useB = require("./module-b");
// 先保存模块引用
useA.saveModule(useB);
useB.saveModule(useA);
// 再初始化
useA.init();
useB.init();
循环依赖处理说明:
- 使用延迟加载方式处理循环依赖
- 先导出模块对象
- 通过方法动态设置依赖
- 在初始化时使用依赖
4. JSON 模块
json
// module.json
{
"name": "jw"
}
- JSON 文件可以直接通过 require 导入
- 会被自动解析为 JavaScript 对象
- 不支持注释和函数
模块化最佳实践
-
导出方式选择
- 使用
module.exports
导出单个值 - 使用
exports.xxx
添加多个属性 - 避免混用
module.exports
和exports
赋值
- 使用
-
循环依赖处理
- 使用延迟加载
- 通过方法动态设置依赖
- 避免直接 require 循环依赖的模块
-
模块缓存利用
- 合理利用模块缓存机制
- 注意对象类型导出的共享特性
- 避免在模块中修改全局状态
-
模块设计原则
- 保持模块功能单一
- 明确模块的输入输出
- 避免模块间的强耦合
- 使用清晰的命名约定
-
注意事项
- 模块路径使用相对路径时要注意位置
- 避免在模块中直接修改
exports
对象 - 注意模块缓存对状态共享的影响
- 合理处理异步模块加载
全局对象与进程
全局对象
- 浏览器中的全局对象是
window
- Node.js 中的全局对象是
global
- 一些特殊的全局变量:
exports
、module
、require
:模块系统相关__filename
、__dirname
:文件路径相关- 这些变量虽然可以直接访问,但不是
global
对象的属性
进程对象(process)
-
平台信息(process.platform)
- 获取操作系统平台信息
- 用于开发跨平台工具和脚手架
- 示例:
console.log(process.platform)
-
工作目录操作
process.cwd()
:获取当前工作目录process.chdir()
:改变工作目录- 注意:
__dirname
是固定的,不能修改
-
环境变量(process.env)
- 获取和设置环境变量
- 跨平台设置:
- Windows:
set a=1
- Mac/Linux:
export a=1
- Windows:
- 使用
cross-env
实现跨平台设置 - 使用
dotenv
管理环境变量
-
命令行参数(process.argv)
- 获取运行时的命令行参数
- 通常使用第三方库处理:
minimist
yargs
commander
事件循环(Event Loop)
-
Node.js 事件循环阶段
- timers:执行定时器回调(setTimeout、setInterval)
- pending callbacks:执行上一轮未完成的回调
- idle, prepare:Node.js 内部使用
- poll:执行 I/O 回调
- check:执行 setImmediate 回调
- close callbacks:执行关闭回调
-
微任务执行时机
- Node.js 10 版本之前:每个阶段结束后清空微任务
- Node.js 10 版本之后:与浏览器一致,每执行一个宏任务就清空微任务
-
异步任务类型
- 宏任务 :
- setTimeout/setInterval
- setImmediate
- I/O 操作
- 微任务 :
- process.nextTick(Node.js 特有)
- Promise.then
- 宏任务 :
示例:命令行工具开发
javascript
const { program } = require('commander');
const pkg = require('./package.json');
// 配置命令行工具
program
.name(pkg.name)
.usage('<command> [options]')
.version(pkg.version);
// 添加命令
program
.command('create')
.description('create project')
.option('-d, --directory <d>', 'set directory', process.cwd())
.action((value, { args }) => {
console.log(value, args);
});
program
.command('serve')
.description('start server')
.option('-p, --port [p]', 'set port', 3000)
.action((value, { args }) => {
console.log(value, args);
});
环境变量管理
javascript
// 使用 dotenv 加载环境变量
require('dotenv').config({
path: process.env.NODE_ENV === 'production'
? '.env.prod'
: '.env.dev'
});
// 环境变量文件格式(.env)
NODE_ENV=development
PORT=3000
DB_HOST=localhost
事件循环示例
javascript
// 宏任务和微任务的执行顺序
fs.readFile('./.env', function() {
setTimeout(() => {
console.log('timer'); // 宏任务
}, 0);
setImmediate(() => {
console.log('setImmediate'); // 宏任务
});
});
// 微任务
Promise.resolve().then(() => {
console.log('then');
});
process.nextTick(() => {
console.log('next tick'); // 微任务,优先级最高
});
最佳实践
-
环境变量管理
- 使用
dotenv
管理环境变量 - 区分开发和生产环境
- 使用
cross-env
实现跨平台
- 使用
-
命令行工具开发
- 使用
commander
等库处理命令行参数 - 提供清晰的命令说明
- 支持配置选项
- 使用
-
事件循环使用
- 理解不同阶段的执行顺序
- 合理使用微任务和宏任务
- 注意 Node.js 版本的差异
-
进程管理
- 合理使用工作目录
- 正确处理环境变量
- 注意跨平台兼容性
Buffer 与编码
Buffer 简介
- Node.js 中用于处理二进制数据的类
- 前端没有原生的二进制数据类型,只有字符串
- Buffer 是 Node.js 特有的二进制数据类型
- 以 16 进制形式展示
- 与前端 Blob 对象的区别:Buffer 可修改,Blob 不可修改
进制转换
-
进制表示
- 二进制:
0b
开头(如:0b1010
) - 八进制:
0o
开头(如:0o10
) - 十六进制:
0x
开头(如:0xff
)
- 二进制:
-
进制转换方法
-
任意进制转十进制:
parseInt(string, radix)
-
十进制转任意进制:
number.toString(radix)
-
示例:
javascriptparseInt("1010", 2); // 二进制转十进制 (0b1010).toString(10); // 二进制转十进制
-
-
字节与位
- 1 字节 = 8 位(bit)
- 1 字节最大值:255(2^8 - 1)
- 二进制:11111111
- 十六进制:ff
字符编码
-
ASCII 编码
- 使用 7 位二进制数表示字符
- 范围:0-127
- 主要用于英文字符
-
中文编码
- GB2312:支持简体中文,2字节表示一个汉字
- GBK:GB2312 的扩展,支持更多汉字
- GB18030:GBK 的扩展,支持更多字符
- UTF-8 :
- 可变字节长度编码
- 英文字符:1字节
- 汉字:3字节
- Node.js 默认使用 UTF-8
-
Unicode
- 统一字符编码标准
- 未被广泛推广
- UTF-8 是其实现方式之一
Base64 编码
-
Base64 原理
- 使用 64 个字符:A-Z, a-z, 0-9, +, /
- 将二进制数据转换为可打印字符
- 每 3 个字节转换为 4 个 Base64 字符
- 编码后数据大小增加约 1/3
-
编码过程示例
javascript// 汉字"帅"的 UTF-8 编码 Buffer.from("帅"); // <Buffer e5 b8 85> // 转换为二进制 (0xe5).toString(2); // 11100101 (0xb8).toString(2); // 10111000 (0x85).toString(2); // 10000101 // Base64 编码 // 00111001 00011011 00100010 00000101 // 57 27 34 5 // 5biF
-
使用场景
- 传输二进制数据
- 处理中文等非 ASCII 字符
- 图片等二进制文件的编码传输
注意事项
-
浮点数精度
- JavaScript 中浮点数计算可能存在精度问题
- 原因:二进制存储方式导致
- 示例:
0.1 + 0.2 !== 0.3
-
编码选择
- 处理中文时注意编码方式
- Node.js 默认使用 UTF-8
- 需要支持 GBK 等编码时需额外处理
-
Base64 使用
- 适合小数据量的编码
- 大文件编码会增加体积
- 常用于图片等二进制数据的传输
最佳实践
-
Buffer 使用
- 处理二进制数据时使用 Buffer
- 注意 Buffer 的大小限制
- 合理使用 Buffer 的转换方法
-
编码处理
- 明确指定字符编码
- 处理中文时注意编码兼容性
- 使用 UTF-8 作为默认编码
-
Base64 应用
- 小文件编码传输
- 图片等二进制数据的处理
- 注意编码后的数据大小
-
性能考虑
- 避免频繁的编码转换
- 大文件慎用 Base64
- 合理使用 Buffer 的切片操作
Buffer 的创建与操作
-
创建 Buffer
javascript// 1. 指定长度的 Buffer Buffer.alloc(3); // 创建长度为 3 的 Buffer,默认填充 0 // 2. 从字符串创建 Buffer.from("中文"); // 将字符串转换为二进制数据 // 3. 从数组创建(不常用) Buffer.from([1, 2, 3, 4, 5, 100]);
-
Buffer 转换
javascript// Buffer 转字符串 Buffer.from("中文").toString(); // 将二进制数据转换回字符串
-
Buffer 拼接
javascript// 方法一:使用 copy 方法 let b1 = Buffer.from("你好"); let b2 = Buffer.from("世界"); let b3 = Buffer.alloc(12); b1.copy(b3, 0, 0, 6); // 从 b3 的 0 位置开始,复制 b1 的 0-6 字节 b2.copy(b3, 6, 0, 6); // 从 b3 的 6 位置开始,复制 b2 的 0-6 字节 // 方法二:使用 concat 方法 Buffer.concat([b1, b2, b1]); // 返回拼接后的新 Buffer
-
Buffer 分割
javascript// 自定义 split 方法 Buffer.prototype.split = function(sep) { sep = Buffer.isBuffer(sep) ? sep : Buffer.from(sep); let offset = 0; let arr = []; let idx = 0; while (-1 != (idx = this.indexOf(sep, offset))) { arr.push(this.slice(offset, idx)); offset = idx + sep.length; } arr.push(this.slice(offset)); return arr; }; // 使用示例 let buf = Buffer.from("你好爱1你好爱你好1爱你好"); console.log(buf.split("1")); // 按 "1" 分割 Buffer
-
Buffer 切片
javascriptlet b4 = Buffer.from([1, 2, 3, 4]); let b5 = b4.slice(0, 1); // 浅拷贝,共享内存 b5[0] = 100; // 修改 b5 会影响 b4
Buffer 常用方法
-
基础方法
Buffer.alloc(size)
:创建指定大小的 BufferBuffer.from()
:从字符串或数组创建 BuffertoString()
:将 Buffer 转换为字符串slice()
:创建 Buffer 的浅拷贝Buffer.isBuffer()
:判断是否为 Buffer 实例
-
操作方法
copy()
:复制 Buffer 内容concat()
:拼接多个 BufferindexOf()
:查找子 Buffersplit()
:分割 Buffer(需自定义实现)
Buffer 使用注意事项
-
内存管理
- Buffer 是引用类型
- 创建时需要指定长度
- 注意内存使用和释放
-
数据操作
- 处理数据时统一使用 Buffer
- 注意编码一致性
- 避免频繁转换
-
性能优化
- 合理使用 Buffer 池
- 避免频繁创建新 Buffer
- 使用 slice 时注意内存共享
-
常见应用场景
- 文件操作
- 网络传输
- 数据流处理
- 二进制数据处理
实际应用示例
-
文件拼接
javascript// 处理分段传输的数据 let chunks = []; // 接收数据时 chunks.push(chunk); // 数据接收完成后 let result = Buffer.concat(chunks);
-
数据分割
javascript// 处理 formData 格式数据 let boundary = "----WebKitFormBoundary"; let data = Buffer.from("..."); // formData 数据 let parts = data.split(boundary);
-
二进制数据处理
javascript// 处理二进制协议 let header = Buffer.alloc(4); let body = Buffer.from("数据内容"); let packet = Buffer.concat([header, body]);
最佳实践
-
Buffer 创建
- 优先使用
Buffer.alloc()
创建固定大小的 Buffer - 使用
Buffer.from()
处理字符串转换 - 避免使用已废弃的
new Buffer()
- 优先使用
-
数据操作
- 统一使用 Buffer 处理二进制数据
- 注意编码转换的性能开销
- 合理使用 Buffer 的切片操作
-
内存管理
- 及时释放不再使用的 Buffer
- 注意 Buffer 切片的内存共享
- 避免内存泄漏
-
错误处理
- 检查 Buffer 操作的有效性
- 处理编码转换可能的错误
- 注意 Buffer 大小限制
Node.js 文件系统
文件操作基础
文件操作模式
-
读取模式
r
:只读模式,文件不存在时报错r+
:读写模式,文件不存在时报错
-
写入模式
w
:写入模式,文件不存在则创建,存在则清空w+
:读写模式,文件不存在则创建,存在则清空a
:追加模式,文件不存在则创建,存在则追加
-
文件权限
javascript// 权限表示方式 // rwx r-x r-x // 用户 用户组 其他人 // 1(执行) 2(写入) 4(读取) // 例如:0o666 (438) 表示所有用户可读写
基础文件操作
-
同步读取文件
javascript// 一次性读取整个文件 fs.readFile(path.resolve(__dirname, "name.txt"), function(err, data) { if (err) return console.log(err); console.log(data); });
-
同步写入文件
javascript// 一次性写入整个文件 fs.writeFile(path.resolve(__dirname, "copy.txt"), data, function(err) { if (err) return console.log(err); console.log("写入成功"); });
-
文件追加
javascript// 追加内容到文件末尾 fs.appendFile(path.resolve(__dirname, "log.txt"), "新内容", function(err) { if (err) return console.log(err); console.log("追加成功"); });
流式文件操作
-
文件描述符操作
javascript// 打开文件获取文件描述符 fs.open(path.resolve(__dirname, "name.txt"), "r", function(err, fd) { // fd: file-descriptor,表示当前操作的文件 // 使用文件描述符进行读写操作 });
-
分块读取
javascriptconst buf = Buffer.alloc(3); // 创建 3 字节的缓冲区 fs.open(path.resolve(__dirname, "name.txt"), "r", function(err, fd) { // 从文件读取数据到缓冲区 fs.read(fd, buf, 0, 3, 0, function(err, bytesRead) { // bytesRead: 实际读取的字节数 console.log(buf.toString()); }); });
-
分块写入
javascriptfs.open(path.resolve(__dirname, "copy.txt"), "w", 0o666, function(err, wfd) { // 从缓冲区写入数据到文件 fs.write(wfd, buf, 0, bytesRead, 0, function(err, written) { // written: 实际写入的字节数 console.log("写入成功"); }); });
-
文件复制示例
javascriptfs.open(path.resolve(__dirname, "name.txt"), "r", function(err, fd) { fs.open(path.resolve(__dirname, "copy.txt"), "w", 0o666, function(err, wfd) { let readOffset = 0; let writeOffset = 0; function next() { fs.read(fd, buf, 0, buf.length, readOffset, function(err, bytesRead) { readOffset += bytesRead; fs.write(wfd, buf, 0, bytesRead, writeOffset, function(err, written) { writeOffset += written; if (bytesRead < buf.length) { return console.log("拷贝完毕"); } next(); }); }); } next(); }); });
注意事项
-
内存使用
- 大文件操作应使用流式处理
- 避免一次性读取整个文件
- 合理设置缓冲区大小
-
错误处理
- 检查文件是否存在
- 处理权限问题
- 处理读写错误
-
性能优化
- 使用流式处理大文件
- 合理使用缓冲区
- 避免频繁的文件操作
-
文件权限
- 注意文件权限设置
- 考虑跨平台兼容性
- 遵循最小权限原则
最佳实践
-
文件操作选择
- 小文件:使用
readFile
/writeFile
- 大文件:使用流式操作
- 追加内容:使用
appendFile
- 小文件:使用
-
流式处理
- 使用
createReadStream
/createWriteStream
- 实现读写分离
- 使用管道(pipe)处理数据流
- 使用
-
错误处理
- 使用 try-catch 处理同步操作
- 检查回调函数中的错误
- 实现适当的错误恢复机制
-
性能考虑
- 使用适当的缓冲区大小
- 避免阻塞操作
- 实现断点续传等高级功能
实际应用示例
-
文件复制
javascript// 使用流式处理复制大文件 const readStream = fs.createReadStream('source.txt'); const writeStream = fs.createWriteStream('target.txt'); readStream.pipe(writeStream);
-
文件监控
javascript// 监控文件变化 fs.watch('file.txt', (eventType, filename) => { console.log(`文件 ${filename} 发生 ${eventType} 事件`); });
-
目录操作
javascript// 创建目录 fs.mkdir('newDir', { recursive: true }, (err) => { if (err) throw err; console.log('目录创建成功'); });
Node.js 事件机制
EventEmitter 基础
事件机制简介
- Node.js 中的事件机制基于发布订阅模式
- 通过
EventEmitter
类实现事件处理 - 支持异步事件处理
- 广泛应用于 Node.js 核心模块
基本使用
javascript
const EventEmitter = require('events');
// 方式一:原型继承
function Girl() {}
Object.setPrototypeOf(Girl.prototype, EventEmitter.prototype);
// 或使用 Girl.prototype.__proto__ = EventEmitter.prototype
// 方式二:直接继承
class Girl extends EventEmitter {}
const girl = new Girl();
核心方法
-
事件监听
javascript// 添加事件监听器 girl.on('eventName', callback); // 添加一次性监听器 girl.once('eventName', callback); // 移除事件监听器 girl.off('eventName', callback); // 或使用 removeListener girl.removeListener('eventName', callback);
-
事件触发
javascript// 触发事件,可传递多个参数 girl.emit('eventName', arg1, arg2, ...);
-
特殊事件
javascript// 监听新的事件监听器添加 girl.on('newListener', (eventName) => { console.log(`新的事件监听器:${eventName}`); });
事件处理示例
-
基本事件处理
javascriptconst girl = new Girl(); // 添加多个事件监听器 girl.on('失恋', () => console.log('cry')); girl.on('失恋', () => console.log('sleep')); girl.on('失恋', () => console.log('eat')); // 触发事件 girl.emit('失恋'); // 输出:cry, sleep, eat
-
一次性事件
javascriptconst girl = new Girl(); // 一次性事件监听器 girl.once('失恋', () => console.log('cry once')); girl.emit('失恋'); // 输出:cry once girl.emit('失恋'); // 无输出
-
事件监听器管理
javascriptconst girl = new Girl(); const cry = () => console.log('cry'); // 添加监听器 girl.on('失恋', cry); // 移除监听器 girl.off('失恋', cry); // 触发事件 girl.emit('失恋'); // 无输出
高级特性
-
newListener 事件
javascriptconst girl = new Girl(); const set = new Set(); let waiting = false; // 监听新的事件监听器添加 girl.on('newListener', (eventName) => { if (!set.has(eventName)) { set.add(eventName); } // 防抖处理 if (waiting) return; waiting = true; process.nextTick(() => { waiting = false; set.forEach(eventName => { girl.emit(eventName); }); }); });
-
事件参数传递
javascriptconst girl = new Girl(); girl.on('失恋', (reason, time) => { console.log(`因为${reason}失恋了,时间:${time}`); }); girl.emit('失恋', '性格不合', '2024-01-01');
注意事项
-
内存管理
- 及时移除不需要的事件监听器
- 避免过多的事件监听器
- 注意事件监听器的引用关系
-
错误处理
- 监听 error 事件
- 处理异步事件中的错误
- 避免事件循环中的错误传播
-
性能考虑
- 合理使用事件机制
- 避免过多的事件嵌套
- 注意事件触发的频率
最佳实践
-
事件命名
- 使用清晰的事件名称
- 遵循命名规范
- 避免事件名称冲突
-
事件处理
- 保持事件处理函数简洁
- 避免在事件处理中阻塞
- 合理使用异步处理
-
代码组织
- 合理使用事件继承
- 模块化事件处理
- 避免过度使用事件
-
调试技巧
- 使用
listenerCount
检查监听器数量 - 使用
eventNames
获取所有事件名 - 合理使用
newListener
事件
- 使用
实际应用示例
-
自定义事件类
javascriptclass CustomEmitter extends EventEmitter { constructor() { super(); this._maxListeners = 10; } emitError(error) { this.emit('error', error); } }
-
事件防抖处理
javascriptclass DebouncedEmitter extends EventEmitter { constructor() { super(); this._waiting = false; } debouncedEmit(event, ...args) { if (this._waiting) return; this._waiting = true; process.nextTick(() => { this._waiting = false; this.emit(event, ...args); }); } }
-
事件组合处理
javascriptclass EventComposer extends EventEmitter { compose(events, callback) { const results = new Map(); events.forEach(event => { this.once(event, (data) => { results.set(event, data); if (results.size === events.length) { callback(Array.from(results.values())); } }); }); } }
Node.js 流(Stream)
可读流(Readable Stream)
可读流简介
- 用于读取数据的流
- 支持分段读取
- 可以暂停和恢复
- 基于事件机制实现
- 常用于文件读取、网络请求等场景
创建可读流
javascript
const fs = require('fs');
const path = require('path');
// 方式一:使用 fs.createReadStream
const rs = fs.createReadStream(path.resolve(__dirname, 'file.txt'), {
flags: 'r', // 读取模式
encoding: null, // 编码方式,默认 Buffer
autoClose: true, // 自动关闭文件
emitClose: true, // 触发 close 事件
start: 0, // 开始读取的位置
end: Infinity, // 结束读取的位置
highWaterMark: 64 * 1024 // 水位线,默认 64KB
});
// 方式二:自定义可读流
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
super();
this.path = path;
this.flags = options.flags || 'r';
this.encoding = options.encoding;
this.autoClose = options.autoClose !== false;
this.emitClose = options.emitClose !== false;
this.start = options.start || 0;
this.end = options.end;
this.highWaterMark = options.highWaterMark || 64 * 1024;
this.flowing = false; // 是否是流动模式
this.offset = this.start;
this.open(); // 打开文件
}
}
可读流事件
-
基础事件
javascript// 数据读取事件 rs.on('data', (chunk) => { console.log(chunk); // chunk 是 Buffer 类型 }); // 数据读取完成事件 rs.on('end', () => { console.log('读取完成'); }); // 错误事件 rs.on('error', (err) => { console.error('读取错误', err); });
-
文件特有事件
javascript// 文件打开事件 rs.on('open', (fd) => { console.log('文件打开', fd); }); // 文件关闭事件 rs.on('close', () => { console.log('文件关闭'); });
可读流方法
-
暂停和恢复
javascript// 暂停读取 rs.pause(); // 恢复读取 rs.resume();
-
数据收集
javascriptconst chunks = []; rs.on('data', (chunk) => { chunks.push(chunk); }); rs.on('end', () => { // 合并所有数据块 const result = Buffer.concat(chunks).toString(); console.log(result); });
可读流配置选项
-
基本选项
flags
:文件打开模式,默认 'r'encoding
:编码方式,默认 null(Buffer)autoClose
:是否自动关闭文件,默认 trueemitClose
:是否触发 close 事件,默认 true
-
读取控制选项
start
:开始读取的位置end
:结束读取的位置highWaterMark
:水位线,控制每次读取的数据量
使用示例
-
基本文件读取
javascriptconst rs = fs.createReadStream('file.txt', { highWaterMark: 3 // 每次读取 3 字节 }); rs.on('data', (chunk) => { console.log(chunk); rs.pause(); // 暂停读取 }); // 定时恢复读取 setInterval(() => { rs.resume(); }, 1000);
-
带错误处理的读取
javascriptconst rs = fs.createReadStream('file.txt'); const chunks = []; rs.on('error', (err) => { console.error('读取错误:', err); }); rs.on('data', (chunk) => { chunks.push(chunk); }); rs.on('end', () => { const content = Buffer.concat(chunks).toString(); console.log('文件内容:', content); });
注意事项
-
内存管理
- 注意 highWaterMark 的设置
- 及时处理读取到的数据
- 避免数据堆积
-
错误处理
- 监听 error 事件
- 处理文件不存在的情况
- 处理读取权限问题
-
性能优化
- 合理设置 highWaterMark
- 使用 pause/resume 控制读取速度
- 避免频繁创建流实例
最佳实践
-
流的选择
- 小文件:使用
fs.readFile
- 大文件:使用可读流
- 实时数据:使用可读流
- 小文件:使用
-
数据处理
- 使用 Buffer 处理二进制数据
- 注意编码转换
- 合理使用暂停和恢复
-
错误处理
- 实现完整的错误处理
- 确保资源正确释放
- 处理异常情况
-
性能考虑
- 根据文件大小选择读取方式
- 合理设置缓冲区大小
- 避免内存泄漏
实际应用场景
-
大文件读取
javascript// 分块读取大文件 const rs = fs.createReadStream('large-file.txt', { highWaterMark: 1024 * 1024 // 1MB }); rs.on('data', (chunk) => { // 处理每个数据块 processChunk(chunk); });
-
文件复制
javascript// 使用流复制文件 const rs = fs.createReadStream('source.txt'); const ws = fs.createWriteStream('target.txt'); rs.pipe(ws); // 自动处理数据流动
-
实时数据处理
javascript// 处理实时数据流 const rs = fs.createReadStream('data.txt'); rs.on('data', (chunk) => { // 实时处理数据 processData(chunk); }); // 控制读取速度 rs.pause(); setInterval(() => { rs.resume(); }, 1000);
可写流(Writable Stream)
可写流简介
- 用于写入数据的流
- 支持分块写入
- 具有背压(backpressure)机制
- 基于事件机制实现
- 常用于文件写入、网络响应等场景
创建可写流
javascript
const fs = require('fs');
const path = require('path');
// 使用 fs.createWriteStream 创建可写流
const ws = fs.createWriteStream(path.resolve(__dirname, 'file.txt'), {
flags: 'w', // 写入模式
encoding: 'utf8', // 编码方式
autoClose: true, // 自动关闭文件
emitClose: true, // 触发 close 事件
start: 0, // 开始写入的位置
highWaterMark: 16 * 1024 // 水位线,默认 16KB
});
可写流方法
-
写入数据
javascript// 写入数据,返回布尔值表示是否可以继续写入 const flag = ws.write('数据', (err) => { if (err) { console.error('写入错误:', err); } console.log('写入完成'); }); // 结束写入 ws.end('最后的数据'); // 写入最后的数据并关闭流
-
背压处理
javascriptlet i = 0; function write() { let flag = true; // 当 flag 为 true 且未达到目标时继续写入 while (flag && i < 10) { flag = ws.write(i++ + ''); console.log('是否可以继续写入:', flag); } } // 当缓冲区清空时继续写入 ws.on('drain', () => { console.log('缓冲区已清空,继续写入'); write(); });
可写流事件
-
基础事件
javascript// 文件打开事件 ws.on('open', (fd) => { console.log('文件打开:', fd); }); // 文件关闭事件 ws.on('close', () => { console.log('文件关闭'); }); // 错误事件 ws.on('error', (err) => { console.error('写入错误:', err); }); // 完成事件 ws.on('finish', () => { console.log('写入完成'); });
-
drain 事件
javascript// 当缓冲区数据写入完成时触发 ws.on('drain', () => { console.log('缓冲区已清空'); });
可写流配置选项
-
基本选项
flags
:文件打开模式,默认 'w'encoding
:编码方式,默认 'utf8'autoClose
:是否自动关闭文件,默认 trueemitClose
:是否触发 close 事件,默认 true
-
写入控制选项
start
:开始写入的位置highWaterMark
:水位线,控制写入缓冲区大小
背压机制
-
背压原理
- 当写入速度大于处理速度时,数据会在内存中堆积
- 通过 highWaterMark 控制缓冲区大小
- 使用 write 返回值控制写入速度
- drain 事件用于恢复写入
-
背压处理示例
javascriptconst ws = fs.createWriteStream('file.txt', { highWaterMark: 1 // 设置较小的水位线 }); let i = 0; function write() { let flag = true; while (flag && i < 10) { // 当 flag 为 false 时,表示缓冲区已满 flag = ws.write(i++ + ''); console.log('写入状态:', flag); } } // 当缓冲区清空时继续写入 ws.on('drain', () => { console.log('缓冲区已清空,继续写入'); write(); }); // 开始写入 write();
注意事项
-
内存管理
- 注意 highWaterMark 的设置
- 及时处理背压情况
- 避免数据堆积
-
错误处理
- 监听 error 事件
- 处理写入权限问题
- 处理磁盘空间不足
-
性能优化
- 合理设置 highWaterMark
- 使用背压机制控制写入速度
- 避免频繁的小数据写入
最佳实践
-
写入策略
- 大文件:使用流式写入
- 小文件:使用 writeFile
- 实时数据:使用流式写入
-
背压处理
- 实现完整的背压机制
- 合理使用 drain 事件
- 控制写入速度
-
错误处理
- 实现完整的错误处理
- 确保资源正确释放
- 处理异常情况
-
性能考虑
- 根据数据大小选择写入方式
- 合理设置缓冲区大小
- 避免内存泄漏
实际应用场景
-
文件写入
javascript// 使用流写入大文件 const ws = fs.createWriteStream('large-file.txt', { highWaterMark: 64 * 1024 // 64KB }); // 处理背压 function write(data) { if (!ws.write(data)) { ws.once('drain', () => write(data)); } }
-
日志记录
javascript// 使用可写流记录日志 const logStream = fs.createWriteStream('app.log', { flags: 'a', // 追加模式 encoding: 'utf8' }); function log(message) { const data = `${new Date().toISOString()} - ${message}\n`; if (!logStream.write(data)) { logStream.once('drain', () => log(message)); } }
-
数据转换
javascript// 使用可写流进行数据转换 const transformStream = new Transform({ transform(chunk, encoding, callback) { // 转换数据 const result = chunk.toString().toUpperCase(); callback(null, result); } }); const ws = fs.createWriteStream('output.txt'); transformStream.pipe(ws);
流的管道操作与复制
管道(pipe)操作
- 用于连接可读流和可写流
- 自动处理背压机制
- 简化流之间的数据传输
- 基于事件机制实现
文件复制实现方式
-
使用 pipe 方法(推荐)
javascriptconst fs = require('fs'); const path = require('path'); // 创建可读流和可写流 const rs = fs.createReadStream(path.resolve(__dirname, 'copy.txt'), { highWaterMark: 4 // 每次读取 4 字节 }); const ws = fs.createWriteStream(path.resolve(__dirname, 'copy1.txt'), { highWaterMark: 1 // 每次写入 1 字节 }); // 使用 pipe 方法连接流 rs.pipe(ws); // 自动处理背压,读一点写一点
-
手动实现流复制
javascriptconst rs = fs.createReadStream('copy.txt', { highWaterMark: 4 }); const ws = fs.createWriteStream('copy1.txt', { highWaterMark: 1 }); // 监听数据读取 rs.on('data', (data) => { // 尝试写入数据 const flag = ws.write(data); console.log('读取数据:', data); // 如果写入缓冲区已满,暂停读取 if (!flag) { rs.pause(); } }); // 当写入缓冲区清空时,恢复读取 ws.on('drain', () => { rs.resume(); }); // 读取完成时关闭写入流 rs.on('end', () => { rs.close(); ws.end(); // 相当于 write + close }); // 监听关闭事件 ws.on('close', () => { console.log('写入流关闭'); }); rs.on('close', () => { console.log('读取流关闭'); });
pipe 方法的优势
-
自动处理背压
- 自动管理数据流动
- 防止内存溢出
- 无需手动处理 pause/resume
-
错误处理
- 自动传播错误
- 确保资源正确释放
- 简化错误处理逻辑
-
代码简洁
- 一行代码完成流连接
- 减少样板代码
- 提高代码可读性
手动实现的优势
-
更细粒度的控制
- 可以自定义数据处理逻辑
- 可以添加中间处理步骤
- 可以实现更复杂的流控制
-
自定义错误处理
- 可以自定义错误处理逻辑
- 可以实现特定的错误恢复机制
- 更灵活的异常处理
-
性能优化
- 可以针对特定场景优化
- 可以实现自定义的缓冲策略
- 可以控制数据流动的时机
使用建议
-
选择合适的方式
- 简单场景:使用 pipe
- 需要自定义处理:手动实现
- 考虑代码维护性
-
性能考虑
- 合理设置 highWaterMark
- 注意内存使用
- 考虑数据量大小
-
错误处理
- 实现完整的错误处理
- 确保资源正确释放
- 处理异常情况
实际应用场景
-
文件复制
javascript// 使用 pipe 复制大文件 const rs = fs.createReadStream('large-file.txt'); const ws = fs.createWriteStream('copy.txt'); rs.pipe(ws);
-
数据转换
javascript// 使用 Transform 流进行数据转换 const { Transform } = require('stream'); const transform = new Transform({ transform(chunk, encoding, callback) { // 转换数据 const result = chunk.toString().toUpperCase(); callback(null, result); } }); rs.pipe(transform).pipe(ws);
-
压缩文件
javascript// 使用 pipe 压缩文件 const zlib = require('zlib'); const rs = fs.createReadStream('file.txt'); const ws = fs.createWriteStream('file.txt.gz'); const gzip = zlib.createGzip(); rs.pipe(gzip).pipe(ws);
注意事项
-
内存管理
- 注意 highWaterMark 的设置
- 避免数据堆积
- 及时处理错误情况
-
错误处理
- 监听 error 事件
- 处理管道断开的情况
- 确保资源正确释放
-
性能优化
- 根据数据大小选择合适的方式
- 合理设置缓冲区大小
- 避免不必要的中间处理
最佳实践
-
使用 pipe 的场景
- 简单的文件复制
- 数据转换链
- 压缩/解压缩
- 网络数据传输
-
手动实现的场景
- 需要自定义数据处理
- 需要细粒度控制
- 需要特殊的错误处理
- 需要中间处理步骤
-
通用建议
- 优先使用 pipe
- 需要特殊处理时再手动实现
- 注意错误处理
- 合理设置缓冲区大小