From 2d95118b8ae1a68978e2cf848510a755407c62e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=92=B8=E9=B1=BC=E5=91=B5=E5=91=B5=E5=93=88=E5=91=B5?= <2181336186@qq.com> Date: Sun, 22 Mar 2026 19:06:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BD=9C=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\346\234\254\345\212\237\350\203\275.md" | 46 ++ ...05\347\275\256\346\250\241\345\235\227.md" | 451 ++++++++++++ ...41\345\235\227\347\263\273\347\273\237.md" | 428 ++++++++++++ .../20260320-http\346\250\241\345\235\227.md" | 661 ++++++++++++++++++ 4 files changed, 1586 insertions(+) create mode 100644 "\345\274\240\351\271\217\347\277\224/20260316-\345\242\236\345\210\240\346\224\271\346\237\245\345\237\272\346\234\254\345\212\237\350\203\275.md" create mode 100644 "\345\274\240\351\271\217\347\277\224/20260318-\345\237\272\346\234\254\345\206\205\347\275\256\346\250\241\345\235\227.md" create mode 100644 "\345\274\240\351\271\217\347\277\224/20260319-\346\226\207\344\273\266\346\250\241\345\235\227\347\263\273\347\273\237.md" create mode 100644 "\345\274\240\351\271\217\347\277\224/20260320-http\346\250\241\345\235\227.md" diff --git "a/\345\274\240\351\271\217\347\277\224/20260316-\345\242\236\345\210\240\346\224\271\346\237\245\345\237\272\346\234\254\345\212\237\350\203\275.md" "b/\345\274\240\351\271\217\347\277\224/20260316-\345\242\236\345\210\240\346\224\271\346\237\245\345\237\272\346\234\254\345\212\237\350\203\275.md" new file mode 100644 index 0000000..c13684c --- /dev/null +++ "b/\345\274\240\351\271\217\347\277\224/20260316-\345\242\236\345\210\240\346\224\271\346\237\245\345\237\272\346\234\254\345\212\237\350\203\275.md" @@ -0,0 +1,46 @@ +```js +import fs from "fs"; +let commmand = process.argv[2]; +let parmas = process.argv[3]; + +if (commmand == "list") { + console.log("列表"); + let list = readFile(); + console.log(list); +} else if (commmand == "add") { + console.log("添加"); + let arr = readFile(); + arr.push({ + 名称: parmas, + }); + writeFile(arr); + let newArr = readFile(); + console.log(newArr); +} else if (commmand == "del") { + console.log("删除"); + let del = parseInt(parmas); + let delArr = readFile(); + delArr.splice(del - 1, 1); + writeFile(delArr); +} else if (commmand == "done") { + console.log("标记完成"); +} else if (commmand == "clear") { + console.log("清除"); +} else { + console.log("未知命令,请确认后重试"); +} + +function readFile(filePath) { + filePath = filePath || "./tool.json"; + if (fs.existsSync(filePath)) { + let data = fs.readFileSync(filePath, "utf-8"); + return JSON.parse(data) || []; + } + return []; +} +function writeFile(fileContent, filePath) { + filePath = filePath || "./tool.json"; + let jsonstring = JSON.stringify(fileContent); + fs.writeFileSync(filePath, jsonstring); +} +``` diff --git "a/\345\274\240\351\271\217\347\277\224/20260318-\345\237\272\346\234\254\345\206\205\347\275\256\346\250\241\345\235\227.md" "b/\345\274\240\351\271\217\347\277\224/20260318-\345\237\272\346\234\254\345\206\205\347\275\256\346\250\241\345\235\227.md" new file mode 100644 index 0000000..3fc96c6 --- /dev/null +++ "b/\345\274\240\351\271\217\347\277\224/20260318-\345\237\272\346\234\254\345\206\205\347\275\256\346\250\241\345\235\227.md" @@ -0,0 +1,451 @@ +# Node.js内置模块知识点总结 + +## 📦 内置模块概述 + +**知识点**:Node.js自带的模块,无需安装,直接`require`使用 + +```javascript +// 无需npm install,直接引入 +const fs = require("fs"); +const path = require("path"); +const http = require("http"); +``` + +### 内置模块的特点 + +| 特点 | 说明 | +| -------- | ----------------------- | +| 无需安装 | Node.js自带,开箱即用 | +| 性能优化 | 由C/C++编写,执行效率高 | +| 功能强大 | 涵盖文件、网络、加密等 | +| 稳定可靠 | 经过长期测试,生产可用 | + +## 📢 console模块 - 控制台输出 + +**知识点**:提供标准输出、错误输出、计时、计数、表格等功能 + +### 基本输出 + +```javascript +// 普通输出 +console.log("普通信息"); +console.info("提示信息"); +console.warn("⚠️ 警告信息"); +console.error("❌ 错误信息"); + +// 格式化输出 +console.log("%s 今天学习了 %d 小时", "小明", 2); // 小明 今天学习了 2 小时 +console.log("进度: %d%%", 75); // 进度: 75% +console.log("%j", { name: "张三", age: 20 }); // {"name":"张三","age":20} +``` + +### 高级用法 + +```javascript +// 表格输出 +console.table([ + { name: "张三", age: 20 }, + { name: "李四", age: 25 }, +]); + +// 计数 +console.count("click"); // click: 1 +console.count("click"); // click: 2 +console.countReset("click"); +console.count("click"); // click: 1 + +// 计时 +console.time("loop"); +for (let i = 0; i < 10000; i++) {} +console.timeEnd("loop"); // loop: 0.123ms + +// 断言(条件为false时输出) +console.assert(1 === 1, "不会输出"); +console.assert(1 === 2, "会输出: Assertion failed"); + +// 调用栈追踪 +function fn1() { + fn2(); +} +function fn2() { + console.trace("追踪"); +} +fn1(); +``` + +## ⚙️ process模块 - 进程信息与控制 + +**知识点**:获取进程信息、环境变量、命令行参数,控制进程行为 + +### 进程信息 + +```javascript +// Node.js版本 +console.log(process.version); // v24.8.0 +console.log(process.versions.node); // 24.8.0 +console.log(process.versions.v8); // 12.4.254 + +// 平台信息 +console.log(process.platform); // win32 / darwin / linux +console.log(process.arch); // x64 / arm64 + +// 工作目录 +console.log(process.cwd()); // 当前工作目录 + +// 环境变量 +console.log(process.env.NODE_ENV); // development / production +console.log(process.env.PATH); // 系统PATH +``` + +### 进程控制 + +```javascript +// 退出程序 +process.exit(0); // 正常退出 +process.exit(1); // 异常退出 + +// 监听退出事件 +process.on("exit", (code) => { + console.log(`退出码: ${code}`); +}); + +// 监听Ctrl+C +process.on("SIGINT", () => { + console.log("收到Ctrl+C,准备退出"); + process.exit(0); +}); + +// 捕获未处理的异常 +process.on("uncaughtException", (err) => { + console.error("未捕获异常:", err); + process.exit(1); +}); +``` + +### 命令行参数 - process.argv + +```javascript +// 运行: node app.js add 买书 --name 张三 + +console.log(process.argv); +// 输出: +// [ +// 'node路径', +// 'app.js路径', +// 'add', +// '买书', +// '--name', +// '张三' +// ] + +// 简单解析 +const args = process.argv.slice(2); +console.log(args); // ['add', '买书', '--name', '张三'] + +// 提取命令和参数 +const command = args[0]; +const value = args[1]; +``` + +## 💾 Buffer模块 - 二进制数据处理 + +**知识点**:Buffer用于处理二进制数据,如文件、网络、图片、加密等 + +### 创建Buffer + +```javascript +// 从字符串创建 +const buf1 = Buffer.from("Hello"); +console.log(buf1); // + +// 从字符串创建(指定编码) +const buf2 = Buffer.from("你好", "utf8"); +console.log(buf2); // + +// 创建指定大小的Buffer(清零) +const buf3 = Buffer.alloc(10); +console.log(buf3); // + +// 快速创建(可能包含旧数据,性能高) +const buf4 = Buffer.allocUnsafe(10); + +// 从数组创建 +const buf5 = Buffer.from([72, 101, 108, 108, 111]); // "Hello" +console.log(buf5.toString()); // Hello +``` + +### Buffer操作 + +```javascript +// 转字符串 +const buf = Buffer.from("Hello"); +console.log(buf.toString()); // Hello +console.log(buf.toString("utf8", 0, 3)); // Hel + +// 获取长度(字节数) +console.log(Buffer.from("你好").length); // 6(中文占3字节) + +// 写入 +const bufWrite = Buffer.alloc(20); +bufWrite.write("Hello", 0, 5, "utf8"); +console.log(bufWrite.toString()); // Hello + +// 读取字节 +console.log(buf.readUInt8(0)); // 72 (H的ASCII) + +// 拼接 +const bufA = Buffer.from("Hello"); +const bufB = Buffer.from(" World"); +const combined = Buffer.concat([bufA, bufB]); +console.log(combined.toString()); // Hello World +``` + +## 📁 path模块 - 路径处理 + +**知识点**:跨平台处理文件路径,避免手写字符串拼接 + +### 路径拼接与解析 + +```javascript +const path = require("path"); + +// __dirname 和 __filename 是全局变量 +console.log(__dirname); // 当前文件所在目录 +console.log(__filename); // 当前文件的完整路径 + +// 拼接路径(自动处理分隔符) +const fullPath = path.join(__dirname, "src", "index.js"); +console.log(fullPath); // C:\project\src\index.js 或 /home/project/src/index.js + +// 解析为绝对路径 +const absPath = path.resolve("index.js"); +console.log(absPath); // 当前工作目录下的index.js绝对路径 + +// 路径信息解析 +const filePath = "/Users/zhangsan/project/src/index.js"; +console.log(path.basename(filePath)); // index.js +console.log(path.basename(filePath, ".js")); // index +console.log(path.extname(filePath)); // .js +console.log(path.dirname(filePath)); // /Users/zhangsan/project/src + +// 完整解析 +console.log(path.parse(filePath)); +// { +// root: '/', +// dir: '/Users/zhangsan/project/src', +// base: 'index.js', +// ext: '.js', +// name: 'index' +// } +``` + +### 路径判断 + +```javascript +// 判断绝对路径 +console.log(path.isAbsolute("/Users/zhangsan")); // true +console.log(path.isAbsolute("./index.js")); // false + +// 路径比较(标准化后比较) +console.log(path.equals("/Users/a/b", "/Users/a/b")); // true +``` + +## 🛠️ 实战示例:日志工具 + +```javascript +// logger.js - 带颜色的日志输出 +const colors = { + info: "\x1b[36m", // 青色 + warn: "\x1b[33m", // 黄色 + error: "\x1b[31m", // 红色 + reset: "\x1b[0m", // 重置 +}; + +function log(level, message) { + const time = new Date().toLocaleString(); + console.log( + `${colors[level]}[${level.toUpperCase()}]${colors.reset} ${time} - ${message}`, + ); +} + +module.exports = { + info: (msg) => log("info", msg), + warn: (msg) => log("warn", msg), + error: (msg) => log("error", msg), +}; + +// test.js +const logger = require("./logger"); +logger.info("服务启动"); +logger.warn("内存占用高"); +logger.error("数据库连接失败"); +``` + +## 📊 常用方法速查 + +| 模块 | 常用方法 | 作用 | +| ------- | ------------------------------ | ------------ | +| console | `log`, `info`, `warn`, `error` | 输出信息 | +| | `table()` | 表格输出 | +| | `time()` / `timeEnd()` | 计时 | +| | `count()` / `countReset()` | 计数 | +| process | `version` | Node版本 | +| | `platform`, `arch` | 系统信息 | +| | `cwd()` | 当前工作目录 | +| | `argv` | 命令行参数 | +| | `exit()` | 退出进程 | +| Buffer | `from()` | 创建Buffer | +| | `alloc()` | 分配Buffer | +| | `toString()` | 转字符串 | +| | `concat()` | 合并Buffer | +| path | `join()` | 拼接路径 | +| | `resolve()` | 绝对路径 | +| | `basename()` | 文件名 | +| | `extname()` | 扩展名 | +| | `dirname()` | 目录名 | + +--- + +## ✅ 练习答案 + +### 6.1 选择题 + +**1. 下面哪个方法可以获取Node.js的版本号?** + +- **A. process.platform**(获取操作系统平台) +- **B. process.version** ✅(获取Node.js版本) +- **C. process.cwd**(获取当前工作目录) +- **D. process.argv**(获取命令行参数) + +**2. Buffer.alloc(10)创建的Buffer,长度是多少字节?** + +- **A. 10** ✅(分配10个字节) +- **B. 20**(错误) +- **C. 1**(错误) +- **D. 不确定**(alloc会清零,长度确定) + +**3. "你好"的Buffer长度是?** + +- **A. 2**(字符个数) +- **B. 3**(错误) +- **C. 6** ✅(UTF-8编码下,每个中文字符占3字节) +- **D. 10**(错误) + +**4. \_\_dirname表示什么?** + +- **A. 当前工作目录**(process.cwd()) +- **B. 当前文件所在目录** ✅ +- **C. Node.js安装目录**(错误) +- **D. 用户主目录**(错误) + +**5. console.time()和console.timeEnd()的作用是?** + +- **A. 计时** ✅(测量代码执行时间) +- **B. 计数**(console.count) +- **C. 打印表格**(console.table) +- **D. 格式化输出**(console.log格式化) + +### 6.2 简答题 + +**1. 请解释console.log和console.error的区别。** + +**答案**: + +- `console.log()`:输出到标准输出流(stdout),通常用于常规信息 +- `console.error()`:输出到标准错误流(stderr),用于错误信息 +- 区别在于: + - 输出流不同,可以分别重定向 + - 在终端中,`console.error` 通常显示为红色(取决于终端设置) + - 日志工具可以根据不同级别过滤或着色 + +```bash +# 重定向示例 +node app.js > output.txt 2> error.txt +# 普通输出到output.txt,错误输出到error.txt +``` + +**2. process.argv返回的数组包含哪些内容?请举例说明。** + +**答案**:`process.argv` 返回一个数组,包含: + +- 第0项:Node.js可执行文件的绝对路径 +- 第1项:当前执行的JavaScript文件的绝对路径 +- 第2项及以后:用户传入的命令行参数 + +```javascript +// 运行命令:node app.js add 买书 --name 张三 +// process.argv 输出: +[ + "C:\\Program Files\\nodejs\\node.exe", // Node.js路径 + "D:\\project\\app.js", // 脚本路径 + "add", // 命令 + "买书", // 参数 + "--name", // 选项 + "张三", // 选项值 +]; +``` + +**3. Buffer和普通数组有什么区别?** + +**答案**: + +| 维度 | Buffer | 普通数组 | +| -------- | --------------------------------- | ------------------ | +| 元素类型 | 0-255的整数(字节) | 任意JavaScript类型 | +| 内存分配 | 固定大小,连续内存 | 动态增长 | +| 性能 | 二进制操作性能高 | 适合存储对象 | +| 用途 | 处理二进制数据(文件、网络) | 通用数据存储 | +| 创建方式 | `Buffer.from()`, `Buffer.alloc()` | `new Array()` | + +```javascript +// Buffer示例(字节数组) +const buf = Buffer.from("Hello"); +console.log(buf[0]); // 72 (ASCII码) + +// 数组示例(任意类型) +const arr = ["Hello", 123, { name: "张三" }]; +``` + +**4. \_\_dirname和process.cwd()有什么区别?** + +**答案**: + +- `__dirname`:当前**脚本文件**所在的目录(静态,取决于文件位置) +- `process.cwd()`:当前**工作目录**(动态,取决于从哪个目录启动Node.js进程) + +```javascript +// 示例目录结构: +// /home/user/project/ +// ├── app.js +// └── data/ + +// 在 app.js 中: +console.log(__dirname); // /home/user/project +console.log(process.cwd()); // /home/user/project (如果从该目录启动) + +// 如果从 /home/user 启动: node project/app.js +console.log(__dirname); // /home/user/project (不变) +console.log(process.cwd()); // /home/user (变化) +``` + +**5. path.join()和path.resolve()有什么区别?** + +**答案**: + +| 方法 | 特点 | 示例 | +| ---------------- | ------------------------------------------- | ------------------------------------------ | +| `path.join()` | 拼接路径,不修改相对路径,不处理绝对路径 | `path.join('/a', 'b', 'c')` → `/a/b/c` | +| `path.resolve()` | 解析为绝对路径,处理`..`和`.`,返回绝对路径 | `path.resolve('a', 'b')` → `/当前目录/a/b` | + +```javascript +const path = require("path"); + +// join: 简单拼接 +console.log(path.join("/a", "b", "c")); // /a/b/c +console.log(path.join("a", "b", "c")); // a/b/c + +// resolve: 解析为绝对路径 +console.log(path.resolve("a", "b")); // /current/dir/a/b +console.log(path.resolve("/a", "b", "c")); // /a/b/c +console.log(path.resolve("a", "..", "b")); // /current/dir/b (处理..) +``` diff --git "a/\345\274\240\351\271\217\347\277\224/20260319-\346\226\207\344\273\266\346\250\241\345\235\227\347\263\273\347\273\237.md" "b/\345\274\240\351\271\217\347\277\224/20260319-\346\226\207\344\273\266\346\250\241\345\235\227\347\263\273\347\273\237.md" new file mode 100644 index 0000000..464732e --- /dev/null +++ "b/\345\274\240\351\271\217\347\277\224/20260319-\346\226\207\344\273\266\346\250\241\345\235\227\347\263\273\347\273\237.md" @@ -0,0 +1,428 @@ +# Node.js文件系统模块知识点总结 + +## 📁 文件系统模块概述 + +**知识点**:fs模块是Node.js内置的文件系统模块,提供文件读写、目录操作等功能 + +```javascript +const fs = require("fs"); +``` + +### 模块特点 + +| 特点 | 说明 | +| -------- | ---------------------------------- | +| 功能完善 | 读写、复制、删除、移动、监听等 | +| 两种方式 | 同步(Sync)和异步(回调/Promise) | +| 流式处理 | 支持大文件流式读写 | +| 权限控制 | 支持设置文件权限 | + +## 📖 读取文件 + +### 同步读取 + +**知识点**:使用`readFileSync`阻塞式读取文件 + +```javascript +const fs = require("fs"); + +// 读取文本文件 +const content = fs.readFileSync("./data.txt", "utf8"); +console.log(content); + +// 读取图片等二进制文件 +const buffer = fs.readFileSync("./image.png"); +console.log("文件大小:", buffer.length, "字节"); +``` + +### 异步读取(回调) + +**知识点**:非阻塞式读取,文件读取完成后回调 + +```javascript +fs.readFile("./data.txt", "utf8", (err, data) => { + if (err) { + console.error("读取失败:", err); + return; + } + console.log("文件内容:", data); +}); +``` + +### 异步读取(Promise) + +**知识点**:使用`fs.promises`提供Promise风格API + +```javascript +const fs = require("fs").promises; + +async function readFile() { + try { + const content = await fs.readFile("./data.txt", "utf8"); + console.log(content); + } catch (err) { + console.error("读取失败:", err); + } +} +readFile(); +``` + +## ✍️ 写入文件 + +### 同步写入 + +**知识点**:使用`writeFileSync`写入文件(覆盖) + +```javascript +// 写入文件(覆盖) +fs.writeFileSync("./data.txt", "Hello Node.js"); + +// 追加写入 +fs.appendFileSync("./log.txt", "新内容\n"); + +// 写入二进制 +const buffer = Buffer.from([1, 2, 3, 4]); +fs.writeFileSync("./data.bin", buffer); +``` + +### 异步写入 + +**知识点**:非阻塞写入,完成后回调 + +```javascript +// 覆盖写入 +fs.writeFile("./data.txt", "Hello", (err) => { + if (err) throw err; + console.log("写入成功"); +}); + +// 追加写入 +fs.appendFile("./log.txt", "新内容\n", (err) => { + if (err) throw err; + console.log("追加成功"); +}); +``` + +## 🔍 文件操作 + +### 检查文件状态 + +**知识点**:检查文件是否存在、获取详细信息 + +```javascript +// 检查存在性 +if (fs.existsSync("./data.txt")) { + console.log("文件存在"); +} + +// 获取状态信息 +const stats = fs.statSync("./data.txt"); +console.log("是文件:", stats.isFile()); // true +console.log("是目录:", stats.isDirectory()); // false +console.log("文件大小:", stats.size, "字节"); +console.log("修改时间:", stats.mtime); +``` + +### 删除文件 + +**知识点**:使用`unlink`删除文件 + +```javascript +// 同步删除 +fs.unlinkSync("./temp.txt"); + +// 异步删除 +fs.unlink("./temp.txt", (err) => { + if (err) throw err; + console.log("删除成功"); +}); +``` + +### 复制文件 + +**知识点**:Node.js 8.5+ 支持`copyFile` + +```javascript +// 简单复制 +fs.copyFileSync("./source.txt", "./dest.txt"); + +// 异步复制 +fs.copyFile("./source.txt", "./dest.txt", (err) => { + if (err) throw err; + console.log("复制完成"); +}); + +// 手动复制(适用于旧版本) +const data = fs.readFileSync("./source.txt"); +fs.writeFileSync("./dest.txt", data); +``` + +### 重命名/移动文件 + +**知识点**:`rename`实现重命名或移动文件 + +```javascript +// 重命名 +fs.renameSync("./old.txt", "./new.txt"); + +// 移动(跨目录) +fs.renameSync("./file.txt", "./backup/file.txt"); +``` + +## 📂 目录操作 + +### 创建目录 + +**知识点**:`mkdir`创建目录,`recursive`选项可创建多级目录 + +```javascript +// 创建单级目录 +fs.mkdirSync("./backup"); + +// 创建多级目录(递归) +fs.mkdirSync("./backup/data/images", { recursive: true }); + +// 异步创建 +fs.mkdir("./backup", { recursive: true }, (err) => { + if (err) throw err; + console.log("创建成功"); +}); +``` + +### 读取目录 + +**知识点**:`readdir`列出目录内容 + +```javascript +// 列出文件名 +const files = fs.readdirSync("./src"); +console.log(files); // ['index.js', 'utils.js'] + +// 获取详细信息 +const items = fs.readdirSync("./src", { withFileTypes: true }); +for (const item of items) { + console.log(`${item.name} - ${item.isDirectory() ? "目录" : "文件"}`); +} +``` + +### 删除目录 + +**知识点**:删除空目录用`rmdir`,递归删除用`rm` + +```javascript +// 删除空目录 +fs.rmdirSync("./empty-dir"); + +// 删除非空目录(Node.js 16+) +fs.rmSync("./backup", { recursive: true, force: true }); + +// 异步删除 +fs.rm("./backup", { recursive: true }, (err) => { + if (err) throw err; + console.log("删除成功"); +}); +``` + +## 👁️ 监听文件变化 + +**知识点**:`fs.watch`和`fs.watchFile`监听文件变化 + +```javascript +// 监听文件/目录变化(轻量级) +fs.watch("./data.txt", (eventType, filename) => { + console.log("事件:", eventType); // change / rename + console.log("文件:", filename); +}); + +// 监听文件(更详细的状态信息) +fs.watchFile("./data.txt", { persistent: true }, (curr, prev) => { + console.log("修改时间变化"); + console.log("之前:", prev.mtime); + console.log("现在:", curr.mtime); +}); + +// 停止监听 +fs.unwatchFile("./data.txt"); +``` + +## 🛠️ 实战示例 + +### 文件复制工具 + +```javascript +// copy.js - 命令行文件复制 +const fs = require("fs"); +const path = require("path"); + +function copyFile(src, dest) { + // 检查源文件 + if (!fs.existsSync(src)) { + console.error("源文件不存在:", src); + return false; + } + + // 确保目标目录存在 + const destDir = path.dirname(dest); + if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); + } + + // 复制文件 + fs.copyFileSync(src, dest); + console.log(`复制成功: ${src} -> ${dest}`); + return true; +} + +// 获取命令行参数 +const [src, dest] = process.argv.slice(2); +if (!src || !dest) { + console.log("用法: node copy.js <源文件> <目标文件>"); + process.exit(1); +} +copyFile(src, dest); +``` + +## 📊 常用方法速查 + +| 操作 | 同步方法 | 异步方法(回调) | Promise方法 | +| -------- | ---------------------- | ---------------- | --------------------- | +| 读取文件 | `readFileSync` | `readFile` | `promises.readFile` | +| 写入文件 | `writeFileSync` | `writeFile` | `promises.writeFile` | +| 追加内容 | `appendFileSync` | `appendFile` | `promises.appendFile` | +| 检查存在 | `existsSync` | - | - | +| 获取状态 | `statSync` | `stat` | `promises.stat` | +| 删除文件 | `unlinkSync` | `unlink` | `promises.unlink` | +| 复制文件 | `copyFileSync` | `copyFile` | `promises.copyFile` | +| 重命名 | `renameSync` | `rename` | `promises.rename` | +| 创建目录 | `mkdirSync` | `mkdir` | `promises.mkdir` | +| 读取目录 | `readdirSync` | `readdir` | `promises.readdir` | +| 删除目录 | `rmdirSync` / `rmSync` | `rmdir` / `rm` | `promises.rm` | + +--- + +## ✅ 练习答案 + +### 6.1 选择题 + +**1. 下面哪个方法可以检查文件是否存在?** + +- **A. fs.checkFileSync()**(错误) +- **B. fs.existsSync()** ✅(正确) +- **C. fs.statSync()**(可获取状态,但文件不存在会报错) +- **D. fs.findSync()**(错误) + +**2. 写入文件使用哪个方法?** + +- **A. fs.readFileSync()**(读取文件) +- **B. fs.writeFileSync()** ✅(写入文件) +- **C. fs.copyFileSync()**(复制文件) +- **D. fs.mkdirSync()**(创建目录) + +**3. appendFileSync和writeFileSync的区别是?** + +- **A. 没有区别**(错误) +- **B. append是追加,write是覆盖** ✅ +- **C. append是同步,write是异步**(都是同步) +- **D. append只能追加字符串**(可追加Buffer) + +**4. 如何创建一个多级目录?** + +- **A. fs.mkdirSync('./a/b/c')**(只能创建单级) +- **B. fs.mkdirSync('./a/b/c', { recursive: true })** ✅ +- **C. fs.mkdirSync('./a/b/c', { deep: true })**(参数错误) +- **D. fs.createDirSync('./a/b/c')**(方法不存在) + +**5. 下面哪个方法可以获取文件的详细信息(大小、时间等)?** + +- **A. fs.existsSync()**(只返回布尔值) +- **B. fs.readdirSync()**(读取目录内容) +- **C. fs.statSync()** ✅(返回Stats对象) +- **D. fs.watch()**(监听变化) + +### 6.2 简答题 + +**1. 请解释同步方法和异步方法的区别。** + +**答案**: + +- **同步方法**:阻塞代码执行,等待操作完成后才继续执行。适用于初始化、简单脚本,代码简单直观。 +- **异步方法**:非阻塞,操作进行时继续执行后续代码,操作完成后通过回调或Promise通知。适用于高并发、Web服务器,性能更好。 + +```javascript +// 同步:阻塞 +const data = fs.readFileSync("./file.txt"); // 等待读完 +console.log("执行"); + +// 异步:非阻塞 +fs.readFile("./file.txt", (err, data) => { + console.log("读完"); +}); +console.log("先执行"); // 这行先打印 +``` + +**2. fs.readFile和fs.readFileSync有什么区别?** + +**答案**: + +| 维度 | fs.readFile | fs.readFileSync | +| -------- | ----------------- | ------------------ | +| 执行方式 | 异步非阻塞 | 同步阻塞 | +| 返回值 | 无,通过回调获取 | 直接返回内容 | +| 错误处理 | 回调的第一个参数 | try/catch | +| 性能 | 高并发场景更好 | 简单脚本更简单 | +| 适用场景 | Web服务器、大文件 | 启动配置、工具脚本 | + +**3. 如何实现文件的追加写入?** + +**答案**: + +- 使用`fs.appendFile` / `fs.appendFileSync`方法 +- 或在`writeFile`时传入`{ flag: 'a' }`选项 + +```javascript +// 方法1:appendFile +fs.appendFileSync("./log.txt", "新内容\n"); + +// 方法2:writeFile with flag +fs.writeFileSync("./log.txt", "新内容\n", { flag: "a" }); + +// 异步版本 +await fs.appendFile("./log.txt", "新内容\n"); +``` + +**4. 什么是流式处理?它适合什么场景?** + +**答案**: +流式处理是将数据分成小块逐块处理,而不是一次性加载全部到内存。适合: + +- 大文件读写(避免内存溢出) +- 网络传输(如HTTP响应) +- 实时数据处理(如日志分析) + +```javascript +const readStream = fs.createReadStream("./largeFile.txt"); +const writeStream = fs.createWriteStream("./copy.txt"); +readStream.pipe(writeStream); +``` + +**5. 如何监听文件的变化?** + +**答案**: +使用`fs.watch`或`fs.watchFile`: + +```javascript +// watch - 监听文件/目录变化(轻量) +fs.watch("./file.txt", (eventType, filename) => { + console.log("事件:", eventType); // 'change' 或 'rename' +}); + +// watchFile - 更详细的状态信息 +fs.watchFile("./file.txt", (curr, prev) => { + if (curr.mtime > prev.mtime) { + console.log("文件已修改"); + } +}); + +// 停止监听 +fs.unwatchFile("./file.txt"); +``` diff --git "a/\345\274\240\351\271\217\347\277\224/20260320-http\346\250\241\345\235\227.md" "b/\345\274\240\351\271\217\347\277\224/20260320-http\346\250\241\345\235\227.md" new file mode 100644 index 0000000..9998179 --- /dev/null +++ "b/\345\274\240\351\271\217\347\277\224/20260320-http\346\250\241\345\235\227.md" @@ -0,0 +1,661 @@ +# Node.js HTTP模块知识点总结 + +## 🌐 HTTP模块概述 + +**知识点**:Node.js内置模块,用于创建Web服务器和发送HTTP请求,让JavaScript可以处理HTTP协议 + +```javascript +// 引入HTTP模块 +const http = require('http'); + +// 核心作用 +- 创建服务器:处理客户端请求 +- 发送请求:调用第三方API +- 处理路由:根据URL返回不同内容 +- 返回响应:发送HTML、JSON、图片等 +``` + +## 🚀 创建HTTP服务器 + +### 最简单的服务器 +**知识点**:使用 `http.createServer()` 创建服务器,`listen()` 监听端口 + +```javascript +const http = require('http'); + +// 创建服务器 +const server = http.createServer((req, res) => { + // req: 请求对象(包含客户端信息) + // res: 响应对象(用于返回数据) + + // 设置响应头 + res.writeHead(200, { 'Content-Type': 'text/plain;charset=utf-8' }); + + // 返回响应 + res.end('Hello World!'); +}); + +// 监听端口3000 +server.listen(3000, () => { + console.log('服务器运行在 http://localhost:3000'); +}); +``` + +### 请求对象(req) +**知识点**:req包含客户端请求的所有信息 + +```javascript +const server = http.createServer((req, res) => { + // 请求方法 + console.log('方法:', req.method); // GET, POST, PUT, DELETE + + // 请求URL + console.log('URL:', req.url); // /, /about, /api/user?name=张三 + + // 请求头 + console.log('User-Agent:', req.headers['user-agent']); + console.log('Host:', req.headers.host); + + // 解析URL(获取路径和参数) + const urlObj = new URL(req.url, `http://${req.headers.host}`); + console.log('路径:', urlObj.pathname); // /api/user + console.log('查询参数:', urlObj.searchParams.get('name')); // 张三 + + res.end('OK'); +}); +``` + +### 响应对象(res) +**知识点**:res用于向客户端返回数据,可以设置状态码、响应头、响应体 + +```javascript +const server = http.createServer((req, res) => { + // 1. 设置状态码 + res.statusCode = 200; // 成功 + res.statusCode = 404; // 未找到 + res.statusCode = 500; // 服务器错误 + + // 2. 设置响应头(单个) + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.setHeader('Set-Cookie', 'name=张三'); + + // 3. 设置响应头(多个,同时设置状态码) + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + 'Access-Control-Allow-Origin': '*', // CORS跨域 + 'Cache-Control': 'no-cache' + }); + + // 4. 发送响应体(必须调用) + res.end('Hello World'); + + // 发送JSON数据 + res.end(JSON.stringify({ name: '张三', age: 20 })); +}); +``` + +## 📡 处理不同请求 + +### 处理不同请求方法 +**知识点**:根据req.method判断请求类型,实现RESTful API + +```javascript +const http = require('http'); + +const server = http.createServer((req, res) => { + const urlObj = new URL(req.url, `http://${req.headers.host}`); + const pathname = urlObj.pathname; + + // 设置CORS和响应类型 + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + + // GET请求 - 获取数据 + if (req.method === 'GET' && pathname === '/api/users') { + res.end(JSON.stringify([ + { id: 1, name: '张三' }, + { id: 2, name: '李四' } + ])); + } + // POST请求 - 创建数据 + else if (req.method === 'POST' && pathname === '/api/users') { + let body = ''; + req.on('data', chunk => body += chunk); + req.on('end', () => { + const user = JSON.parse(body); + console.log('创建用户:', user); + res.end(JSON.stringify({ success: true, id: Date.now() })); + }); + } + // PUT请求 - 更新数据 + else if (req.method === 'PUT' && pathname.startsWith('/api/users/')) { + const id = pathname.split('/').pop(); + let body = ''; + req.on('data', chunk => body += chunk); + req.on('end', () => { + const updates = JSON.parse(body); + console.log(`更新用户${id}:`, updates); + res.end(JSON.stringify({ success: true })); + }); + } + // DELETE请求 - 删除数据 + else if (req.method === 'DELETE' && pathname.startsWith('/api/users/')) { + const id = pathname.split('/').pop(); + console.log(`删除用户${id}`); + res.end(JSON.stringify({ success: true })); + } + // 404处理 + else { + res.statusCode = 404; + res.end(JSON.stringify({ error: 'API不存在' })); + } +}); + +server.listen(3000); +``` + +### 处理URL参数 +**知识点**:使用URL API获取查询参数 + +```javascript +const server = http.createServer((req, res) => { + // 解析URL + const urlObj = new URL(req.url, `http://${req.headers.host}`); + + // 获取查询参数 + const name = urlObj.searchParams.get('name'); + const age = urlObj.searchParams.get('age'); + const page = urlObj.searchParams.get('page') || 1; + + // 获取路径参数 + const pathname = urlObj.pathname; + const userId = pathname.startsWith('/users/') ? pathname.split('/')[2] : null; + + console.log('参数:', { name, age, page, userId }); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + name: name || '游客', + age: age || '未知', + page: page, + message: `你好,${name || '游客'}!` + })); +}); + +// 测试URL: +// http://localhost:3000?name=张三&age=20&page=2 +// http://localhost:3000/users/123 +``` + +## 📤 HTTP客户端请求 + +### GET请求 +**知识点**:使用http.get或http.request发送GET请求 + +```javascript +const http = require('http'); + +// 方式1:http.get(简化版) +http.get('http://localhost:3000/api/users', (res) => { + let data = ''; + + // 接收数据(分块传输) + res.on('data', chunk => { + data += chunk; + }); + + // 接收完成 + res.on('end', () => { + console.log('响应数据:', JSON.parse(data)); + }); +}).on('error', (err) => { + console.error('请求失败:', err); +}); + +// 方式2:http.request(完整版) +const options = { + hostname: 'localhost', + port: 3000, + path: '/api/users?name=张三', + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer token123' + } +}; + +const req = http.request(options, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + console.log('响应:', data); + }); +}); + +req.on('error', (err) => { + console.error('请求失败:', err); +}); + +req.end(); // 必须调用 +``` + +### POST请求 +**知识点**:发送POST请求需要携带请求体数据 + +```javascript +const http = require('http'); + +const postData = JSON.stringify({ + name: '张三', + email: 'zhangsan@example.com', + age: 20 +}); + +const options = { + hostname: 'localhost', + port: 3000, + path: '/api/users', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData) // 必须设置长度 + } +}; + +const req = http.request(options, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + console.log('创建成功:', JSON.parse(data)); + }); +}); + +req.on('error', (err) => { + console.error('请求失败:', err); +}); + +// 写入请求体 +req.write(postData); +req.end(); // 结束请求 +``` + +## 🛠️ 路由处理 + +**知识点**:根据URL路径和请求方法分发不同的处理逻辑 + +```javascript +const http = require('http'); + +// 路由表(模拟) +const routes = { + GET: {}, + POST: {}, + PUT: {}, + DELETE: {} +}; + +// 注册路由 +function route(method, path, handler) { + routes[method][path] = handler; +} + +// 处理请求 +const server = http.createServer((req, res) => { + const urlObj = new URL(req.url, `http://${req.headers.host}`); + const pathname = urlObj.pathname; + + // 查找路由 + const handler = routes[req.method][pathname]; + + if (handler) { + handler(req, res); + } else { + res.statusCode = 404; + res.end(JSON.stringify({ error: 'Not Found' })); + } +}); + +// 定义路由 +route('GET', '/', (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end('

首页

'); +}); + +route('GET', '/api/users', (req, res) => { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify([ + { id: 1, name: '张三' }, + { id: 2, name: '李四' } + ])); +}); + +route('POST', '/api/users', (req, res) => { + let body = ''; + req.on('data', chunk => body += chunk); + req.on('end', () => { + const user = JSON.parse(body); + res.end(JSON.stringify({ success: true, user })); + }); +}); + +server.listen(3000); +``` + +## 📋 实战示例 + +### 完整的RESTful API +```javascript +const http = require('http'); + +// 模拟数据库 +let users = [ + { id: 1, name: '张三', email: 'zhangsan@example.com' }, + { id: 2, name: '李四', email: 'lisi@example.com' } +]; + +const server = http.createServer((req, res) => { + // CORS头(让前端可以跨域调用) + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + + // 处理预检请求 + if (req.method === 'OPTIONS') { + res.writeHead(204); + res.end(); + return; + } + + const urlObj = new URL(req.url, `http://${req.headers.host}`); + const pathname = urlObj.pathname; + + // GET /api/users - 获取所有用户 + if (req.method === 'GET' && pathname === '/api/users') { + res.end(JSON.stringify(users)); + } + // GET /api/users/:id - 获取单个用户 + else if (req.method === 'GET' && pathname.startsWith('/api/users/')) { + const id = parseInt(pathname.split('/').pop()); + const user = users.find(u => u.id === id); + + if (user) { + res.end(JSON.stringify(user)); + } else { + res.statusCode = 404; + res.end(JSON.stringify({ error: '用户不存在' })); + } + } + // POST /api/users - 创建用户 + else if (req.method === 'POST' && pathname === '/api/users') { + let body = ''; + req.on('data', chunk => body += chunk); + req.on('end', () => { + const newUser = JSON.parse(body); + newUser.id = users.length + 1; + users.push(newUser); + res.end(JSON.stringify({ success: true, user: newUser })); + }); + } + // PUT /api/users/:id - 更新用户 + else if (req.method === 'PUT' && pathname.startsWith('/api/users/')) { + const id = parseInt(pathname.split('/').pop()); + let body = ''; + req.on('data', chunk => body += chunk); + req.on('end', () => { + const updates = JSON.parse(body); + const index = users.findIndex(u => u.id === id); + + if (index !== -1) { + users[index] = { ...users[index], ...updates }; + res.end(JSON.stringify({ success: true, user: users[index] })); + } else { + res.statusCode = 404; + res.end(JSON.stringify({ error: '用户不存在' })); + } + }); + } + // DELETE /api/users/:id - 删除用户 + else if (req.method === 'DELETE' && pathname.startsWith('/api/users/')) { + const id = parseInt(pathname.split('/').pop()); + const index = users.findIndex(u => u.id === id); + + if (index !== -1) { + const deleted = users.splice(index, 1)[0]; + res.end(JSON.stringify({ success: true, user: deleted })); + } else { + res.statusCode = 404; + res.end(JSON.stringify({ error: '用户不存在' })); + } + } + // 404 + else { + res.statusCode = 404; + res.end(JSON.stringify({ error: 'API不存在' })); + } +}); + +server.listen(3000, () => { + console.log('API服务器运行在 http://localhost:3000'); + console.log('可用接口:'); + console.log(' GET /api/users - 获取所有用户'); + console.log(' GET /api/users/:id - 获取单个用户'); + console.log(' POST /api/users - 创建用户'); + console.log(' PUT /api/users/:id - 更新用户'); + console.log(' DELETE /api/users/:id - 删除用户'); +}); +``` + +### 静态文件服务器 +```javascript +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +const server = http.createServer((req, res) => { + // 获取文件路径 + let filePath = path.join(__dirname, 'public', req.url); + + // 默认首页 + if (req.url === '/') { + filePath = path.join(__dirname, 'public', 'index.html'); + } + + // 获取文件扩展名 + const extname = path.extname(filePath); + + // 设置MIME类型 + const mimeTypes = { + '.html': 'text/html', + '.css': 'text/css', + '.js': 'application/javascript', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.gif': 'image/gif', + '.txt': 'text/plain' + }; + + const contentType = mimeTypes[extname] || 'application/octet-stream'; + + // 读取文件 + fs.readFile(filePath, (err, content) => { + if (err) { + if (err.code === 'ENOENT') { + // 404 + res.writeHead(404, { 'Content-Type': 'text/html' }); + res.end('

404 - 文件不存在

'); + } else { + // 500 + res.writeHead(500); + res.end('服务器错误'); + } + } else { + // 成功返回文件 + res.writeHead(200, { 'Content-Type': contentType }); + res.end(content); + } + }); +}); + +server.listen(3000, () => { + console.log('静态服务器运行在 http://localhost:3000'); +}); +``` + +## 📊 常用命令速查 + +| 操作 | 代码示例 | +|------|----------| +| 创建服务器 | `http.createServer((req, res) => {})` | +| 监听端口 | `server.listen(3000, () => {})` | +| 获取请求方法 | `req.method` | +| 获取URL | `req.url` | +| 解析URL | `new URL(req.url, host)` | +| 获取参数 | `urlObj.searchParams.get('name')` | +| 设置状态码 | `res.statusCode = 200` | +| 设置响应头 | `res.setHeader('Content-Type', 'text/html')` | +| 发送响应 | `res.end('数据')` | +| GET请求 | `http.get(url, callback)` | +| POST请求 | `http.request(options, callback)` | + +--- + +## ✅ 练习答案 + +### 6.1 选择题 + +**1. 创建一个HTTP服务器需要使用哪个模块?** +- **A. url**(URL解析模块) +- **B. http** ✅(HTTP模块) +- **C. fs**(文件系统模块) +- **D. path**(路径处理模块) + +**2. 获取请求的方法(GET、POST)应该访问哪个属性?** +- **A. req.url**(请求URL) +- **B. req.method** ✅(请求方法) +- **C. req.headers**(请求头) +- **D. req.body**(请求体,需要手动解析) + +**3. 设置响应状态码使用哪个属性?** +- **A. res.status**(不存在) +- **B. res.statusCode** ✅(正确) +- **C. res.code**(不存在) +- **D. res.statusNumber**(不存在) + +**4. 返回JSON数据需要设置什么响应头?** +- **A. 'Content-Type': 'text/html'**(HTML格式) +- **B. 'Content-Type': 'application/json'** ✅(JSON格式) +- **C. 'Content-Type': 'text/plain'**(纯文本) +- **D. 'Content-Type': 'application/xml'**(XML格式) + +**5. 使用哪个方法发送响应?** +- **A. res.send()**(Express方法) +- **B. res.write()**(可多次调用) +- **C. res.end()** ✅(必须调用结束响应) +- **D. res.close()**(关闭连接) + +### 6.2 简答题 + +**1. 请解释req和res分别代表什么?** + +**答案**: +- **req(请求对象)**:代表客户端发来的HTTP请求,包含请求方法、URL、请求头、请求体等信息 +- **res(响应对象)**:代表服务器要返回的HTTP响应,用于设置状态码、响应头、响应体等内容 + +```javascript +// req常用属性 +req.method // 请求方法 GET/POST/PUT/DELETE +req.url // 请求URL /api/users?name=张三 +req.headers // 请求头对象 + +// res常用方法 +res.statusCode = 200 // 设置状态码 +res.setHeader() // 设置响应头 +res.writeHead() // 同时设置状态码和响应头 +res.end() // 结束响应,发送数据 +``` + +**2. http.createServer的回调函数接收哪些参数?** + +**答案**:回调函数接收两个参数: +- **req(请求对象)**:包含客户端请求的所有信息 +- **res(响应对象)**:用于向客户端返回响应 + +```javascript +const server = http.createServer((req, res) => { + // req: 请求对象 + // res: 响应对象 +}); +``` + +**3. 如何获取URL中的查询参数?** + +**答案**:使用Node.js的URL API: + +```javascript +const urlObj = new URL(req.url, `http://${req.headers.host}`); + +// 获取单个参数 +const name = urlObj.searchParams.get('name'); +const page = urlObj.searchParams.get('page'); + +// 获取所有参数 +const params = Object.fromEntries(urlObj.searchParams); + +// 示例:http://localhost:3000?name=张三&age=20&page=2 +console.log(name); // 张三 +console.log(page); // 2 +console.log(params); // { name: '张三', age: '20', page: '2' } +``` + +**4. res.writeHead和res.setHeader有什么区别?** + +**答案**: + +| 方法 | 特点 | 使用场景 | +|------|------|----------| +| `res.setHeader()` | 只能设置单个响应头,可多次调用 | 动态设置响应头 | +| `res.writeHead()` | 同时设置状态码和多个响应头,只能调用一次 | 一次性设置所有响应头 | + +```javascript +// res.setHeader示例 +res.setHeader('Content-Type', 'text/html'); +res.setHeader('Set-Cookie', 'name=张三'); +res.setHeader('Cache-Control', 'no-cache'); + +// res.writeHead示例(一次设置多个) +res.writeHead(200, { + 'Content-Type': 'text/html', + 'Set-Cookie': 'name=张三', + 'Cache-Control': 'no-cache' +}); +``` + +**5. http.get和http.request有什么区别?** + +**答案**: + +| 方法 | 特点 | 适用场景 | +|------|------|----------| +| `http.get()` | 专门用于GET请求,更简洁 | 只需要发送GET请求 | +| `http.request()` | 通用方法,支持所有HTTP方法 | POST、PUT、DELETE等 | + +```javascript +// http.get - 简洁,只支持GET +http.get('http://example.com/api', (res) => { + // 处理响应 +}); + +// http.request - 完整,支持所有方法 +const req = http.request({ + hostname: 'example.com', + path: '/api', + method: 'POST', // 可改为PUT、DELETE等 + headers: { 'Content-Type': 'application/json' } +}, (res) => { + // 处理响应 +}); +req.write(JSON.stringify(data)); // POST数据 +req.end(); // 必须调用 +``` \ No newline at end of file -- Gitee