Node.js 全栈开发指南(1)

Node.js 全栈开发指南

高阶函数

什么是高阶函数?

高阶函数(Higher-Order Function)是指满足以下任意一个条件的函数:

  1. 接受一个或多个函数作为参数
  2. 返回一个函数作为结果

高阶函数的应用场景

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);
    }
  }
}
核心特性解析
  1. 状态管理

    • Promise 有三种状态:PENDING、FULFILLED、REJECTED
    • 状态只能从 PENDING 变为 FULFILLED 或 REJECTED
    • 状态一旦改变,不能再次改变
  2. then 方法实现

    javascript 复制代码
    then(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;
    }
  3. Promise 链式调用

    • then 方法返回新的 Promise 实例
    • 支持链式调用
    • 处理 then 中返回的 Promise 实例
  4. 静态方法实现

    • Promise.resolve:将值转换为 Promise
    • Promise.reject:创建拒绝的 Promise
    • Promise.all:等待所有 Promise 完成
    • Promise.race:返回最快的 Promise 结果
    • Promise.finally:无论成功失败都执行
  5. Promise 解析过程

    javascript 复制代码
    function 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);
      }
    }
重要特性说明
  1. 微任务处理

    • 使用 process.nextTick 确保 then 回调在微任务队列中执行
    • 保证异步执行顺序的正确性
  2. 错误处理

    • 构造函数中的 try-catch 捕获执行器错误
    • then 方法中的 try-catch 捕获回调函数错误
    • 支持错误穿透
  3. Promise 互操作性

    • 支持不同 Promise 实现之间的互操作
    • 通过 resolvePromise 处理 thenable 对象
  4. 状态保护

    • 使用 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');
  });
注意事项
  1. Promise 构造函数是同步执行的
  2. then 回调是异步执行的(微任务)
  3. Promise 链式调用中,每个 then 都返回新的 Promise
  4. Promise 状态一旦改变,不能再次改变
  5. 错误处理要放在链式调用的最后

浏览器事件环

进程与线程

  • 进程:操作系统分配资源的最小单位
  • 线程:进程内的执行单元

浏览器进程模型

  • 主进程:负责浏览器界面显示、用户交互、子进程管理
  • 渲染进程:负责网页渲染、JavaScript 执行
  • 网络进程:负责网络请求、资源加载
  • 插件进程:负责浏览器插件运行
  • GPU 进程:负责 GPU 加速、3D 渲染

渲染进程中的线程

  • JS 引擎线程:执行 JavaScript 代码
  • 渲染线程:负责页面渲染(与 JS 引擎线程互斥)
  • 网络线程:处理网络请求
  • 合成线程:图层合成
  • 事件触发线程:处理异步事件

事件环(Event Loop)

任务分类
  • 宏任务

    • 脚本执行
    • UI 渲染
    • 定时器(setTimeout)
    • HTTP 请求
    • 事件处理
    • MessageChannel
    • setImmediate
  • 微任务

    • Promise.then
    • MutationObserver
    • process.nextTick
    • queueMicrotask
执行顺序
  1. 执行一个宏任务
  2. 清空微任务队列
  3. 重复步骤 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);
执行顺序分析
  1. 同步代码执行

    • console.log(1) → 输出 1
    • console.log(6) → 输出 6(Promise 构造函数中的同步代码)
    • console.log(2) → 输出 2(async 函数开始执行)
    • console.log(3) → 输出 3(await 后面的同步代码)
    • console.log(8) → 输出 8
  2. 微任务队列

    • Promise.then 回调(输出 7)
    • async 函数中 await 后的代码(输出 4)
  3. 宏任务队列

    • setTimeout 回调(输出 5)
最终输出顺序
复制代码
1
6
2
3
8
7
4
5
执行过程详解
  1. 首先执行所有同步代码(1, 6, 2, 3, 8)
  2. 然后清空微任务队列:
    • 执行 Promise.then 回调(7)
    • 执行 async 函数中 await 后的代码(4)
  3. 最后执行宏任务队列中的 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 应用场景

  1. 服务器开发

    • Koa
    • Express
    • Egg.js
    • NestJS
  2. 中间层(BFF - Backend For Frontend)

    • 解决跨域问题
    • 为前端服务
  3. 实时应用

    • 即时通信
    • 聊天应用
    • 爬虫
  4. 服务端渲染

    • React SSR
    • Vue SSR
  5. 工具链开发

    • 构建工具(Webpack、Gulp、Vite、Rollup)
    • 前端工具链
    • 脚手架

Node.js 与浏览器 JavaScript 的区别

  • 运行环境:Node.js 运行在服务端,浏览器运行在客户端
  • API 差异:Node.js 没有 DOM API,但有内置模块
  • 事件环实现:Node.js 和浏览器的事件环实现不同
  • 全局对象 :Node.js 是 global,浏览器是 window

Node.js 模块系统

为什么需要模块化?

  1. 解决命名冲突:通过模块化可以避免全局命名空间污染
  2. 代码复用:模块可以被其他程序引用,提高代码复用性
  3. 代码隔离:每个模块都有独立的作用域
  4. 更好的扩展性:模块化使代码结构更清晰,便于扩展

模块化规范的发展

  • 早期规范
    • CommonJS(Node.js 采用)
    • AMD(RequireJS)
    • CMD(SeaJS)
    • UMD(通用模块定义)
    • IIFE(立即执行函数)
    • SystemJS

CommonJS 规范

核心概念
  1. 每个文件都是一个模块

    • 每个模块在运行时都会产生一个函数
    • 模块内部有独立的作用域
  2. 模块的导入导出

    • 使用 require 导入模块
    • 使用 module.exports 导出模块 a.js
ini 复制代码
module.exports = "cccc";
  1. 模块类型
    • 内置模块:Node.js 核心模块(如 fs、path)
    • 第三方模块:通过 npm 安装的模块
    • 文件模块:用户自己编写的模块
模块加载机制
  1. 模块查找顺序

    • 内置模块(优先级最高)
    • 文件模块(相对路径或绝对路径)
    • 第三方模块(node_modules)
  2. 文件模块查找规则

    • 查找同名文件,尝试添加后缀(.js、.json 等)
    • 查找同名文件夹,寻找 package.json 中 main 字段指定的入口
    • 如果没有 package.json,则查找 index.js
  3. 第三方模块查找规则

    • 在 node_modules 文件夹中递归查找
    • 查找同名文件夹/文件
    • 按照文件模块的规则处理
模块加载流程
  1. 加载模块(Module._load)

    • 返回 module.exports 的结果
  2. 解析文件名(Module._resolveFilename)

    • 生成可读取的文件名(.js、.json 等)
  3. 缓存检查(Module._cache)

    • 如果模块已缓存,直接返回缓存结果
  4. 创建模块(new Module)

    • 创建模块实例,包含 id 和 exports
  5. 缓存模块

    • 将模块存入缓存,供后续使用
  6. 加载模块(module.load)

    • 读取文件内容
    • 根据扩展名调用对应的加载方式
  7. 执行模块

    • 将文件内容包装成函数
    • 传入五个参数: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);
  }
};

注意事项

  1. 模块路径最好使用绝对路径,避免相对路径可能带来的问题
  2. 模块加载是同步的,可能影响性能
  3. 模块会被缓存,多次 require 同一个模块只会执行一次
  4. 循环依赖需要特别注意处理
  5. 模块的 exports 是 module.exports 的引用,直接修改 exports 不会生效

npm 包管理

什么是 npm?

npm(Node Package Manager)是 Node.js 的包管理工具,用于:

  • 管理项目依赖
  • 安装第三方包
  • 管理包版本
  • 发布自己的包
package.json
  • 通过 npm init -y 快速创建
  • 记录项目信息和依赖配置
  • 定义项目脚本命令
依赖类型
  1. 全局依赖(-g)

    • 安装命令:npm install package-name -g
    • 只能在命令行中使用
    • 通过 npm link 可以将本地项目链接到全局
    • 示例:npm install http-server -g
  2. 本地依赖

    • 生产依赖(dependencies)

      • 安装命令:npm install package-namenpm install package-name --save
      • 项目运行必需的依赖
      • 会被打包到生产环境
    • 开发依赖(devDependencies)

      • 安装命令:npm install package-name --save-devnpm install package-name -D
      • 仅在开发时需要的依赖
      • 如构建工具、转译工具等
      • 不会打包到生产环境
  3. 其他依赖类型

    • peerDependencies:同等依赖,会自动安装
    • optionalDependencies:可选依赖
    • bundledDependencies:打包依赖
版本管理
  1. 版本号格式major.minor.patch

    • major:主版本号(不兼容的 API 修改)
    • minor:次版本号(向下兼容的功能性新增)
    • patch:修订号(向下兼容的问题修正)
  2. 版本范围符号

    • ^2.0.0:兼容补丁和小版本更新(2.x.x)
    • ~1.3.4:只兼容补丁更新(1.3.x)
    • >=2.1.1:大于等于指定版本
    • <=2.1.1:小于等于指定版本
    • 1.0.0 ~ 2.0.0:版本范围
  3. 预发布版本

    • alpha:内部测试版
    • beta:公测版
    • rc:候选版本
    • 示例:1.0.0-alpha.31.0.0-beta.31.0.0-rc.3
包管理机制
  1. package-lock.json

    • 锁定依赖版本
    • 确保团队成员使用相同版本的依赖
    • 记录依赖树的具体版本
  2. npm install

    • 安装 package.json 中声明的所有依赖
    • --production 只安装生产依赖
    • 自动生成 package-lock.json
  3. npm scripts

    • 通过 npm run 执行 package.json 中定义的脚本
    • 执行时会临时将 node_modules/.bin 添加到 PATH
    • 可以执行本地安装的命令行工具
  4. npx

    • 执行本地安装的命令行工具
    • 如果命令不存在,会临时安装并执行
    • 适合一次性使用的命令
    • 示例:npx mime
最佳实践
  1. 依赖管理

    • 区分开发依赖和生产依赖
    • 使用 package-lock.json 锁定版本
    • 定期更新依赖版本
  2. 版本控制

    • 遵循语义化版本规范
    • 合理使用版本范围符号
    • 注意预发布版本的使用
  3. 脚本管理

    • 将常用命令写入 npm scripts
    • 使用 npx 执行一次性命令
    • 避免全局安装不必要的包
  4. 安全考虑

    • 定期检查依赖安全漏洞
    • 使用 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 的引用

导出机制说明

  1. module.exports 是真正的导出对象
  2. exportsmodule.exports 的引用
  3. 最终返回的是 module.exports
  4. 不能同时使用 module.exportsexports 赋值
2. 模块缓存机制
javascript 复制代码
// module.js 示例
let obj = { a: 1 };
setInterval(() => {
  obj.a++;
}, 1000);
module.exports = obj;

// 在多个文件中 require 这个模块
// 会共享同一个 obj 对象,因为模块会被缓存

缓存机制说明

  1. 模块在第一次加载时会被缓存
  2. 多次 require 同一个模块会返回缓存的结果
  3. 缓存的是 module.exports 的值
  4. 对于对象类型的导出,会共享同一个引用
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();

循环依赖处理说明

  1. 使用延迟加载方式处理循环依赖
  2. 先导出模块对象
  3. 通过方法动态设置依赖
  4. 在初始化时使用依赖
4. JSON 模块
json 复制代码
// module.json
{
  "name": "jw"
}
  • JSON 文件可以直接通过 require 导入
  • 会被自动解析为 JavaScript 对象
  • 不支持注释和函数
模块化最佳实践
  1. 导出方式选择

    • 使用 module.exports 导出单个值
    • 使用 exports.xxx 添加多个属性
    • 避免混用 module.exportsexports 赋值
  2. 循环依赖处理

    • 使用延迟加载
    • 通过方法动态设置依赖
    • 避免直接 require 循环依赖的模块
  3. 模块缓存利用

    • 合理利用模块缓存机制
    • 注意对象类型导出的共享特性
    • 避免在模块中修改全局状态
  4. 模块设计原则

    • 保持模块功能单一
    • 明确模块的输入输出
    • 避免模块间的强耦合
    • 使用清晰的命名约定
  5. 注意事项

    • 模块路径使用相对路径时要注意位置
    • 避免在模块中直接修改 exports 对象
    • 注意模块缓存对状态共享的影响
    • 合理处理异步模块加载

全局对象与进程

全局对象
  • 浏览器中的全局对象是 window
  • Node.js 中的全局对象是 global
  • 一些特殊的全局变量:
    • exportsmodulerequire:模块系统相关
    • __filename__dirname:文件路径相关
    • 这些变量虽然可以直接访问,但不是 global 对象的属性
进程对象(process)
  1. 平台信息(process.platform)

    • 获取操作系统平台信息
    • 用于开发跨平台工具和脚手架
    • 示例:console.log(process.platform)
  2. 工作目录操作

    • process.cwd():获取当前工作目录
    • process.chdir():改变工作目录
    • 注意:__dirname 是固定的,不能修改
  3. 环境变量(process.env)

    • 获取和设置环境变量
    • 跨平台设置:
      • Windows: set a=1
      • Mac/Linux: export a=1
    • 使用 cross-env 实现跨平台设置
    • 使用 dotenv 管理环境变量
  4. 命令行参数(process.argv)

    • 获取运行时的命令行参数
    • 通常使用第三方库处理:
      • minimist
      • yargs
      • commander
事件循环(Event Loop)
  1. Node.js 事件循环阶段

    • timers:执行定时器回调(setTimeout、setInterval)
    • pending callbacks:执行上一轮未完成的回调
    • idle, prepare:Node.js 内部使用
    • poll:执行 I/O 回调
    • check:执行 setImmediate 回调
    • close callbacks:执行关闭回调
  2. 微任务执行时机

    • Node.js 10 版本之前:每个阶段结束后清空微任务
    • Node.js 10 版本之后:与浏览器一致,每执行一个宏任务就清空微任务
  3. 异步任务类型

    • 宏任务
      • 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');  // 微任务,优先级最高
});
最佳实践
  1. 环境变量管理

    • 使用 dotenv 管理环境变量
    • 区分开发和生产环境
    • 使用 cross-env 实现跨平台
  2. 命令行工具开发

    • 使用 commander 等库处理命令行参数
    • 提供清晰的命令说明
    • 支持配置选项
  3. 事件循环使用

    • 理解不同阶段的执行顺序
    • 合理使用微任务和宏任务
    • 注意 Node.js 版本的差异
  4. 进程管理

    • 合理使用工作目录
    • 正确处理环境变量
    • 注意跨平台兼容性

Buffer 与编码

Buffer 简介
  • Node.js 中用于处理二进制数据的类
  • 前端没有原生的二进制数据类型,只有字符串
  • Buffer 是 Node.js 特有的二进制数据类型
  • 以 16 进制形式展示
  • 与前端 Blob 对象的区别:Buffer 可修改,Blob 不可修改
进制转换
  1. 进制表示

    • 二进制:0b 开头(如:0b1010
    • 八进制:0o 开头(如:0o10
    • 十六进制:0x 开头(如:0xff
  2. 进制转换方法

    • 任意进制转十进制:parseInt(string, radix)

    • 十进制转任意进制:number.toString(radix)

    • 示例:

      javascript 复制代码
      parseInt("1010", 2);  // 二进制转十进制
      (0b1010).toString(10);  // 二进制转十进制
  3. 字节与位

    • 1 字节 = 8 位(bit)
    • 1 字节最大值:255(2^8 - 1)
    • 二进制:11111111
    • 十六进制:ff
字符编码
  1. ASCII 编码

    • 使用 7 位二进制数表示字符
    • 范围:0-127
    • 主要用于英文字符
  2. 中文编码

    • GB2312:支持简体中文,2字节表示一个汉字
    • GBK:GB2312 的扩展,支持更多汉字
    • GB18030:GBK 的扩展,支持更多字符
    • UTF-8
      • 可变字节长度编码
      • 英文字符:1字节
      • 汉字:3字节
      • Node.js 默认使用 UTF-8
  3. Unicode

    • 统一字符编码标准
    • 未被广泛推广
    • UTF-8 是其实现方式之一
Base64 编码
  1. Base64 原理

    • 使用 64 个字符:A-Z, a-z, 0-9, +, /
    • 将二进制数据转换为可打印字符
    • 每 3 个字节转换为 4 个 Base64 字符
    • 编码后数据大小增加约 1/3
  2. 编码过程示例

    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
  3. 使用场景

    • 传输二进制数据
    • 处理中文等非 ASCII 字符
    • 图片等二进制文件的编码传输
注意事项
  1. 浮点数精度

    • JavaScript 中浮点数计算可能存在精度问题
    • 原因:二进制存储方式导致
    • 示例:0.1 + 0.2 !== 0.3
  2. 编码选择

    • 处理中文时注意编码方式
    • Node.js 默认使用 UTF-8
    • 需要支持 GBK 等编码时需额外处理
  3. Base64 使用

    • 适合小数据量的编码
    • 大文件编码会增加体积
    • 常用于图片等二进制数据的传输
最佳实践
  1. Buffer 使用

    • 处理二进制数据时使用 Buffer
    • 注意 Buffer 的大小限制
    • 合理使用 Buffer 的转换方法
  2. 编码处理

    • 明确指定字符编码
    • 处理中文时注意编码兼容性
    • 使用 UTF-8 作为默认编码
  3. Base64 应用

    • 小文件编码传输
    • 图片等二进制数据的处理
    • 注意编码后的数据大小
  4. 性能考虑

    • 避免频繁的编码转换
    • 大文件慎用 Base64
    • 合理使用 Buffer 的切片操作
Buffer 的创建与操作
  1. 创建 Buffer

    javascript 复制代码
    // 1. 指定长度的 Buffer
    Buffer.alloc(3);  // 创建长度为 3 的 Buffer,默认填充 0
    
    // 2. 从字符串创建
    Buffer.from("中文");  // 将字符串转换为二进制数据
    
    // 3. 从数组创建(不常用)
    Buffer.from([1, 2, 3, 4, 5, 100]);
  2. Buffer 转换

    javascript 复制代码
    // Buffer 转字符串
    Buffer.from("中文").toString();  // 将二进制数据转换回字符串
  3. 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
  4. 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
  5. Buffer 切片

    javascript 复制代码
    let b4 = Buffer.from([1, 2, 3, 4]);
    let b5 = b4.slice(0, 1);  // 浅拷贝,共享内存
    b5[0] = 100;  // 修改 b5 会影响 b4
Buffer 常用方法
  1. 基础方法

    • Buffer.alloc(size):创建指定大小的 Buffer
    • Buffer.from():从字符串或数组创建 Buffer
    • toString():将 Buffer 转换为字符串
    • slice():创建 Buffer 的浅拷贝
    • Buffer.isBuffer():判断是否为 Buffer 实例
  2. 操作方法

    • copy():复制 Buffer 内容
    • concat():拼接多个 Buffer
    • indexOf():查找子 Buffer
    • split():分割 Buffer(需自定义实现)
Buffer 使用注意事项
  1. 内存管理

    • Buffer 是引用类型
    • 创建时需要指定长度
    • 注意内存使用和释放
  2. 数据操作

    • 处理数据时统一使用 Buffer
    • 注意编码一致性
    • 避免频繁转换
  3. 性能优化

    • 合理使用 Buffer 池
    • 避免频繁创建新 Buffer
    • 使用 slice 时注意内存共享
  4. 常见应用场景

    • 文件操作
    • 网络传输
    • 数据流处理
    • 二进制数据处理
实际应用示例
  1. 文件拼接

    javascript 复制代码
    // 处理分段传输的数据
    let chunks = [];
    // 接收数据时
    chunks.push(chunk);
    // 数据接收完成后
    let result = Buffer.concat(chunks);
  2. 数据分割

    javascript 复制代码
    // 处理 formData 格式数据
    let boundary = "----WebKitFormBoundary";
    let data = Buffer.from("...");  // formData 数据
    let parts = data.split(boundary);
  3. 二进制数据处理

    javascript 复制代码
    // 处理二进制协议
    let header = Buffer.alloc(4);
    let body = Buffer.from("数据内容");
    let packet = Buffer.concat([header, body]);
最佳实践
  1. Buffer 创建

    • 优先使用 Buffer.alloc() 创建固定大小的 Buffer
    • 使用 Buffer.from() 处理字符串转换
    • 避免使用已废弃的 new Buffer()
  2. 数据操作

    • 统一使用 Buffer 处理二进制数据
    • 注意编码转换的性能开销
    • 合理使用 Buffer 的切片操作
  3. 内存管理

    • 及时释放不再使用的 Buffer
    • 注意 Buffer 切片的内存共享
    • 避免内存泄漏
  4. 错误处理

    • 检查 Buffer 操作的有效性
    • 处理编码转换可能的错误
    • 注意 Buffer 大小限制

Node.js 文件系统

文件操作基础

文件操作模式
  1. 读取模式

    • r:只读模式,文件不存在时报错
    • r+:读写模式,文件不存在时报错
  2. 写入模式

    • w:写入模式,文件不存在则创建,存在则清空
    • w+:读写模式,文件不存在则创建,存在则清空
    • a:追加模式,文件不存在则创建,存在则追加
  3. 文件权限

    javascript 复制代码
    // 权限表示方式
    // rwx r-x r-x
    // 用户 用户组 其他人
    // 1(执行) 2(写入) 4(读取)
    // 例如:0o666 (438) 表示所有用户可读写
基础文件操作
  1. 同步读取文件

    javascript 复制代码
    // 一次性读取整个文件
    fs.readFile(path.resolve(__dirname, "name.txt"), function(err, data) {
      if (err) return console.log(err);
      console.log(data);
    });
  2. 同步写入文件

    javascript 复制代码
    // 一次性写入整个文件
    fs.writeFile(path.resolve(__dirname, "copy.txt"), data, function(err) {
      if (err) return console.log(err);
      console.log("写入成功");
    });
  3. 文件追加

    javascript 复制代码
    // 追加内容到文件末尾
    fs.appendFile(path.resolve(__dirname, "log.txt"), "新内容", function(err) {
      if (err) return console.log(err);
      console.log("追加成功");
    });
流式文件操作
  1. 文件描述符操作

    javascript 复制代码
    // 打开文件获取文件描述符
    fs.open(path.resolve(__dirname, "name.txt"), "r", function(err, fd) {
      // fd: file-descriptor,表示当前操作的文件
      // 使用文件描述符进行读写操作
    });
  2. 分块读取

    javascript 复制代码
    const 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());
      });
    });
  3. 分块写入

    javascript 复制代码
    fs.open(path.resolve(__dirname, "copy.txt"), "w", 0o666, function(err, wfd) {
      // 从缓冲区写入数据到文件
      fs.write(wfd, buf, 0, bytesRead, 0, function(err, written) {
        // written: 实际写入的字节数
        console.log("写入成功");
      });
    });
  4. 文件复制示例

    javascript 复制代码
    fs.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();
      });
    });
注意事项
  1. 内存使用

    • 大文件操作应使用流式处理
    • 避免一次性读取整个文件
    • 合理设置缓冲区大小
  2. 错误处理

    • 检查文件是否存在
    • 处理权限问题
    • 处理读写错误
  3. 性能优化

    • 使用流式处理大文件
    • 合理使用缓冲区
    • 避免频繁的文件操作
  4. 文件权限

    • 注意文件权限设置
    • 考虑跨平台兼容性
    • 遵循最小权限原则
最佳实践
  1. 文件操作选择

    • 小文件:使用 readFile/writeFile
    • 大文件:使用流式操作
    • 追加内容:使用 appendFile
  2. 流式处理

    • 使用 createReadStream/createWriteStream
    • 实现读写分离
    • 使用管道(pipe)处理数据流
  3. 错误处理

    • 使用 try-catch 处理同步操作
    • 检查回调函数中的错误
    • 实现适当的错误恢复机制
  4. 性能考虑

    • 使用适当的缓冲区大小
    • 避免阻塞操作
    • 实现断点续传等高级功能
实际应用示例
  1. 文件复制

    javascript 复制代码
    // 使用流式处理复制大文件
    const readStream = fs.createReadStream('source.txt');
    const writeStream = fs.createWriteStream('target.txt');
    readStream.pipe(writeStream);
  2. 文件监控

    javascript 复制代码
    // 监控文件变化
    fs.watch('file.txt', (eventType, filename) => {
      console.log(`文件 ${filename} 发生 ${eventType} 事件`);
    });
  3. 目录操作

    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();
核心方法
  1. 事件监听

    javascript 复制代码
    // 添加事件监听器
    girl.on('eventName', callback);
    
    // 添加一次性监听器
    girl.once('eventName', callback);
    
    // 移除事件监听器
    girl.off('eventName', callback);
    // 或使用 removeListener
    girl.removeListener('eventName', callback);
  2. 事件触发

    javascript 复制代码
    // 触发事件,可传递多个参数
    girl.emit('eventName', arg1, arg2, ...);
  3. 特殊事件

    javascript 复制代码
    // 监听新的事件监听器添加
    girl.on('newListener', (eventName) => {
      console.log(`新的事件监听器:${eventName}`);
    });
事件处理示例
  1. 基本事件处理

    javascript 复制代码
    const girl = new Girl();
    
    // 添加多个事件监听器
    girl.on('失恋', () => console.log('cry'));
    girl.on('失恋', () => console.log('sleep'));
    girl.on('失恋', () => console.log('eat'));
    
    // 触发事件
    girl.emit('失恋');  // 输出:cry, sleep, eat
  2. 一次性事件

    javascript 复制代码
    const girl = new Girl();
    
    // 一次性事件监听器
    girl.once('失恋', () => console.log('cry once'));
    girl.emit('失恋');  // 输出:cry once
    girl.emit('失恋');  // 无输出
  3. 事件监听器管理

    javascript 复制代码
    const girl = new Girl();
    const cry = () => console.log('cry');
    
    // 添加监听器
    girl.on('失恋', cry);
    
    // 移除监听器
    girl.off('失恋', cry);
    
    // 触发事件
    girl.emit('失恋');  // 无输出
高级特性
  1. newListener 事件

    javascript 复制代码
    const 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);
        });
      });
    });
  2. 事件参数传递

    javascript 复制代码
    const girl = new Girl();
    
    girl.on('失恋', (reason, time) => {
      console.log(`因为${reason}失恋了,时间:${time}`);
    });
    
    girl.emit('失恋', '性格不合', '2024-01-01');
注意事项
  1. 内存管理

    • 及时移除不需要的事件监听器
    • 避免过多的事件监听器
    • 注意事件监听器的引用关系
  2. 错误处理

    • 监听 error 事件
    • 处理异步事件中的错误
    • 避免事件循环中的错误传播
  3. 性能考虑

    • 合理使用事件机制
    • 避免过多的事件嵌套
    • 注意事件触发的频率
最佳实践
  1. 事件命名

    • 使用清晰的事件名称
    • 遵循命名规范
    • 避免事件名称冲突
  2. 事件处理

    • 保持事件处理函数简洁
    • 避免在事件处理中阻塞
    • 合理使用异步处理
  3. 代码组织

    • 合理使用事件继承
    • 模块化事件处理
    • 避免过度使用事件
  4. 调试技巧

    • 使用 listenerCount 检查监听器数量
    • 使用 eventNames 获取所有事件名
    • 合理使用 newListener 事件
实际应用示例
  1. 自定义事件类

    javascript 复制代码
    class CustomEmitter extends EventEmitter {
      constructor() {
        super();
        this._maxListeners = 10;
      }
      
      emitError(error) {
        this.emit('error', error);
      }
    }
  2. 事件防抖处理

    javascript 复制代码
    class 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);
        });
      }
    }
  3. 事件组合处理

    javascript 复制代码
    class 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();  // 打开文件
  }
}
可读流事件
  1. 基础事件

    javascript 复制代码
    // 数据读取事件
    rs.on('data', (chunk) => {
      console.log(chunk);  // chunk 是 Buffer 类型
    });
    
    // 数据读取完成事件
    rs.on('end', () => {
      console.log('读取完成');
    });
    
    // 错误事件
    rs.on('error', (err) => {
      console.error('读取错误', err);
    });
  2. 文件特有事件

    javascript 复制代码
    // 文件打开事件
    rs.on('open', (fd) => {
      console.log('文件打开', fd);
    });
    
    // 文件关闭事件
    rs.on('close', () => {
      console.log('文件关闭');
    });
可读流方法
  1. 暂停和恢复

    javascript 复制代码
    // 暂停读取
    rs.pause();
    
    // 恢复读取
    rs.resume();
  2. 数据收集

    javascript 复制代码
    const chunks = [];
    rs.on('data', (chunk) => {
      chunks.push(chunk);
    });
    
    rs.on('end', () => {
      // 合并所有数据块
      const result = Buffer.concat(chunks).toString();
      console.log(result);
    });
可读流配置选项
  1. 基本选项

    • flags:文件打开模式,默认 'r'
    • encoding:编码方式,默认 null(Buffer)
    • autoClose:是否自动关闭文件,默认 true
    • emitClose:是否触发 close 事件,默认 true
  2. 读取控制选项

    • start:开始读取的位置
    • end:结束读取的位置
    • highWaterMark:水位线,控制每次读取的数据量
使用示例
  1. 基本文件读取

    javascript 复制代码
    const rs = fs.createReadStream('file.txt', {
      highWaterMark: 3  // 每次读取 3 字节
    });
    
    rs.on('data', (chunk) => {
      console.log(chunk);
      rs.pause();  // 暂停读取
    });
    
    // 定时恢复读取
    setInterval(() => {
      rs.resume();
    }, 1000);
  2. 带错误处理的读取

    javascript 复制代码
    const 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);
    });
注意事项
  1. 内存管理

    • 注意 highWaterMark 的设置
    • 及时处理读取到的数据
    • 避免数据堆积
  2. 错误处理

    • 监听 error 事件
    • 处理文件不存在的情况
    • 处理读取权限问题
  3. 性能优化

    • 合理设置 highWaterMark
    • 使用 pause/resume 控制读取速度
    • 避免频繁创建流实例
最佳实践
  1. 流的选择

    • 小文件:使用 fs.readFile
    • 大文件:使用可读流
    • 实时数据:使用可读流
  2. 数据处理

    • 使用 Buffer 处理二进制数据
    • 注意编码转换
    • 合理使用暂停和恢复
  3. 错误处理

    • 实现完整的错误处理
    • 确保资源正确释放
    • 处理异常情况
  4. 性能考虑

    • 根据文件大小选择读取方式
    • 合理设置缓冲区大小
    • 避免内存泄漏
实际应用场景
  1. 大文件读取

    javascript 复制代码
    // 分块读取大文件
    const rs = fs.createReadStream('large-file.txt', {
      highWaterMark: 1024 * 1024  // 1MB
    });
    
    rs.on('data', (chunk) => {
      // 处理每个数据块
      processChunk(chunk);
    });
  2. 文件复制

    javascript 复制代码
    // 使用流复制文件
    const rs = fs.createReadStream('source.txt');
    const ws = fs.createWriteStream('target.txt');
    
    rs.pipe(ws);  // 自动处理数据流动
  3. 实时数据处理

    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
});
可写流方法
  1. 写入数据

    javascript 复制代码
    // 写入数据,返回布尔值表示是否可以继续写入
    const flag = ws.write('数据', (err) => {
      if (err) {
        console.error('写入错误:', err);
      }
      console.log('写入完成');
    });
    
    // 结束写入
    ws.end('最后的数据');  // 写入最后的数据并关闭流
  2. 背压处理

    javascript 复制代码
    let 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();
    });
可写流事件
  1. 基础事件

    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('写入完成');
    });
  2. drain 事件

    javascript 复制代码
    // 当缓冲区数据写入完成时触发
    ws.on('drain', () => {
      console.log('缓冲区已清空');
    });
可写流配置选项
  1. 基本选项

    • flags:文件打开模式,默认 'w'
    • encoding:编码方式,默认 'utf8'
    • autoClose:是否自动关闭文件,默认 true
    • emitClose:是否触发 close 事件,默认 true
  2. 写入控制选项

    • start:开始写入的位置
    • highWaterMark:水位线,控制写入缓冲区大小
背压机制
  1. 背压原理

    • 当写入速度大于处理速度时,数据会在内存中堆积
    • 通过 highWaterMark 控制缓冲区大小
    • 使用 write 返回值控制写入速度
    • drain 事件用于恢复写入
  2. 背压处理示例

    javascript 复制代码
    const 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();
注意事项
  1. 内存管理

    • 注意 highWaterMark 的设置
    • 及时处理背压情况
    • 避免数据堆积
  2. 错误处理

    • 监听 error 事件
    • 处理写入权限问题
    • 处理磁盘空间不足
  3. 性能优化

    • 合理设置 highWaterMark
    • 使用背压机制控制写入速度
    • 避免频繁的小数据写入
最佳实践
  1. 写入策略

    • 大文件:使用流式写入
    • 小文件:使用 writeFile
    • 实时数据:使用流式写入
  2. 背压处理

    • 实现完整的背压机制
    • 合理使用 drain 事件
    • 控制写入速度
  3. 错误处理

    • 实现完整的错误处理
    • 确保资源正确释放
    • 处理异常情况
  4. 性能考虑

    • 根据数据大小选择写入方式
    • 合理设置缓冲区大小
    • 避免内存泄漏
实际应用场景
  1. 文件写入

    javascript 复制代码
    // 使用流写入大文件
    const ws = fs.createWriteStream('large-file.txt', {
      highWaterMark: 64 * 1024  // 64KB
    });
    
    // 处理背压
    function write(data) {
      if (!ws.write(data)) {
        ws.once('drain', () => write(data));
      }
    }
  2. 日志记录

    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));
      }
    }
  3. 数据转换

    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)操作
  • 用于连接可读流和可写流
  • 自动处理背压机制
  • 简化流之间的数据传输
  • 基于事件机制实现
文件复制实现方式
  1. 使用 pipe 方法(推荐)

    javascript 复制代码
    const 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);  // 自动处理背压,读一点写一点
  2. 手动实现流复制

    javascript 复制代码
    const 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 方法的优势
  1. 自动处理背压

    • 自动管理数据流动
    • 防止内存溢出
    • 无需手动处理 pause/resume
  2. 错误处理

    • 自动传播错误
    • 确保资源正确释放
    • 简化错误处理逻辑
  3. 代码简洁

    • 一行代码完成流连接
    • 减少样板代码
    • 提高代码可读性
手动实现的优势
  1. 更细粒度的控制

    • 可以自定义数据处理逻辑
    • 可以添加中间处理步骤
    • 可以实现更复杂的流控制
  2. 自定义错误处理

    • 可以自定义错误处理逻辑
    • 可以实现特定的错误恢复机制
    • 更灵活的异常处理
  3. 性能优化

    • 可以针对特定场景优化
    • 可以实现自定义的缓冲策略
    • 可以控制数据流动的时机
使用建议
  1. 选择合适的方式

    • 简单场景:使用 pipe
    • 需要自定义处理:手动实现
    • 考虑代码维护性
  2. 性能考虑

    • 合理设置 highWaterMark
    • 注意内存使用
    • 考虑数据量大小
  3. 错误处理

    • 实现完整的错误处理
    • 确保资源正确释放
    • 处理异常情况
实际应用场景
  1. 文件复制

    javascript 复制代码
    // 使用 pipe 复制大文件
    const rs = fs.createReadStream('large-file.txt');
    const ws = fs.createWriteStream('copy.txt');
    rs.pipe(ws);
  2. 数据转换

    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);
  3. 压缩文件

    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);
注意事项
  1. 内存管理

    • 注意 highWaterMark 的设置
    • 避免数据堆积
    • 及时处理错误情况
  2. 错误处理

    • 监听 error 事件
    • 处理管道断开的情况
    • 确保资源正确释放
  3. 性能优化

    • 根据数据大小选择合适的方式
    • 合理设置缓冲区大小
    • 避免不必要的中间处理
最佳实践
  1. 使用 pipe 的场景

    • 简单的文件复制
    • 数据转换链
    • 压缩/解压缩
    • 网络数据传输
  2. 手动实现的场景

    • 需要自定义数据处理
    • 需要细粒度控制
    • 需要特殊的错误处理
    • 需要中间处理步骤
  3. 通用建议

    • 优先使用 pipe
    • 需要特殊处理时再手动实现
    • 注意错误处理
    • 合理设置缓冲区大小
相关推荐
江城开朗的豌豆1 小时前
Vue的keep-alive魔法:让你的组件"假死"也能满血复活!
前端·javascript·vue.js
BillKu1 小时前
Vue3 + TypeScript 中 let data: any[] = [] 与 let data = [] 的区别
前端·javascript·typescript
我想说一句1 小时前
当JavaScript的new操作符开始内卷:手写实现背后的奇妙冒险
前端·javascript
快起来别睡了2 小时前
深入理解 JavaScript 中的 `this`:从底层原理到实践应用
javascript
梅一一2 小时前
JavaScript 通吃指南:从浏览器到你的LED灯
前端·javascript·后端
阿珊和她的猫2 小时前
`shallowReactive` 与 `shallowRef`:浅层响应式 API
前端·javascript·vue.js·typescript·状态模式
fly啊2 小时前
app Router VS pages Router(Next.js学习笔记)
前端·javascript·react.js
coding随想2 小时前
*JavaScript 中的反射:用 Reflect 对象掌控对象的“自我认知”
开发语言·javascript·ecmascript
lky不吃香菜3 小时前
JavaScript中的Map与Set:哈希表与集合的妙用指南
javascript