高优先级知识点,需要熟练掌握。
中间件主要是指封装 http 请求细节处理的方法
当一个中间件函数执行完成并且没有调用 await next() 时,它不会将控制权交给下一个中间件,而是直接返回或抛出异常。
基于 ES6 generator 特性的异步流程控制,解决了 "callback hell" 和麻烦的错误处理问题。
9.1 不同一台电脑 使用 HTTP 协议
9.2 同一台电脑
SELECT name,country FROM Websites WHERE country='CN' AND alexa > 50;
INSERT INTO table_name (column1,column2...) VALUES (value1,value2,...);
UPDATE Websites SET alexa='5000', country='USA' WHERE name='菜鸟教程';
DELETE FROM Websites WHERE name='Facebook' AND country='USA';
内连接(INNER JOIN) 只返回两个表中满足连接条件的记录。
SELECT * FROM table1 INNER JOIN table2 ON table1.id = table2.id;
左连接(LEFT JOIN 或 LEFT OUTER JOIN) 返回左表的所有记录,以及右表中满足连接条件的记录。右表没有匹配时,结果为 NULL。
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id;
右连接(RIGHT JOIN 或 RIGHT OUTER JOIN) 返回右表的所有记录,以及左表中满足连接条件的记录。左表没有匹配时,结果为 NULL。
SELECT * FROM table1 RIGHT JOIN table2 ON table1.id = table2.id;
全连接(FULL JOIN 或 FULL OUTER JOIN) 返回两个表中所有记录,只要其中一个表有匹配就显示。MySQL 不直接支持 FULL JOIN,可以用 UNION 实现。
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id
UNION
SELECT * FROM table1 RIGHT JOIN table2 ON table1.id = table2.id;
交叉连接(CROSS JOIN) 返回两个表的笛卡尔积,即每个表的每一行都与另一个表的每一行组合。
SELECT * FROM table1 CROSS JOIN table2;
自连接(SELF JOIN) 表与自身进行连接,常用于树形结构或需要比较同一表中不同记录的场景。
SELECT a.id, a.name, b.name AS parent_name
FROM category a
LEFT JOIN category b ON a.parent_id = b.id;
自然连接(NATURAL JOIN) 自连接用于表自身的层级或关联查询,自然连接适合字段名一致且需要自动匹配的场景。
SELECT * FROM table1 NATURAL JOIN table2;
中间件机制 Express 默认集成了路由、模板等常用功能,中间件采用回调函数;Koa 不集成任何中间件
异步处理 Express 通过回调(Callback)函数处理异步,容易出现回调地狱;Koa 基于 ES6 的 async/await 或 generator,异步代码更优雅,错误处理更方便。
响应机制
express 在调用 res.send 方法后就立即响应了,而 koa 则是在所有中间件调用完成之后,在最外层中间件进行响应。
错误处理 express 对错捕获处理起来很不友好,每一个回调都拥有一个新的调用栈,因此你没法对一个 callback 做 try catch 捕获,你需要在 Callback 里做错误捕获,然后一层一层向外传递。
性能 Koa 更轻量,底层直接基于 Node.js http 模块,性能更高;Express 功能多,体积更大。
Koa 可以使用 ES6 中引入的生成器函数(generator functions)来实现洋葱模型。
总结:Koa 的洋葱模型 = compose 递归 Promise 链 ⇒ 请求先由外向内穿透,再由内向外回溯,优雅地把「前置逻辑」「核心业务」「后置逻辑」串成一条可插拔的流水线。
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
console.log(1) // 1️⃣ 外层
await next() // 2️⃣ 进入下一层
console.log(2) // 5️⃣ 返回外层
})
app.use(async (ctx, next) => {
console.log(3) // 2️⃣ 中层
await next()
console.log(4) // 4️⃣ 返回中层
})
app.use(async (ctx, next) => {
console.log(5) // 3️⃣ 内层
console.log(6) // 3️⃣ 内层
next() // 有没有next()都可以,因为是最后一层
})
/**
*访问 http://localhost:3000/ 输出结果:
* 1 3 5 6 4 2
*/
app.listen(3000)
function compose(middleware) {
return function (ctx, next) {
let index = -1
return dispatch(0)
function dispatch(i) {
if (i <= index) throw new Error('next() called multiple times')
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next // 边界
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
框架集成:Koa 的 3 步调用链
它通过中间件(Middleware)机制实现了业务逻辑的分层和复用
Koa 洋葱模型的处理流程可以大致分为四个阶段:
请求阶段:从外到内依次执行请求相关的中间件,例如解析请求体、设置响应头等操作。
业务阶段:执行业务逻辑相关的中间件,例如处理授权、验证身份、路由分发等操作。
响应阶段:从内到外依次执行响应相关的中间件,例如格式化响应数据、设置响应头等操作。
错误处理阶段:如果在前面的中间件过程中出现了错误,则会跳过后续中间件并交给错误处理中间件来处理异常情况。
总结:NestJS 的本质 =「装饰器 + 元数据 + IoC 容器」组成的运行时框架,它把 Express/Fastify 的「函数式」API 映射为「类 + 装饰器」的声明式 API,并在启动阶段通过反射扫描元数据,完成依赖注入与路由注册。
1.扫描装饰器 → 2. 生成路由 + 依赖表 → 3. IoC 容器注入 → 4. 挂到 Express/Fastify 于是你写的「类 + 装饰器」就等价于传统的「一堆回调函数」,却拥有了类型安全、依赖注入、生命周期和 AOP 的全部能力。
基于 Node.js 和 Express/Koa NestJS 底层使用 Node.js,默认集成 Express,也可切换为 Koa 作为底层 HTTP 框架。
依赖注入(DI)机制 借鉴 Angular 的依赖注入思想,通过装饰器和容器自动管理服务、控制器等实例的生命周期和依赖关系。
模块化开发 所有功能都以模块(Module)为单位组织,模块之间可以自由组合和复用,提升项目可维护性和扩展性。
装饰器语法 使用 TypeScript 的装饰器(@Controller、@Injectable、@Module 等)描述路由、服务、模块等,代码结构清晰。
中间件与管道机制 支持中间件、守卫(Guard)、拦截器(Interceptor)、管道(Pipe)等,灵活处理请求生命周期和数据校验。
统一异常处理 提供全局异常过滤器,统一处理错误和异常响应。
生态丰富 全面支持 TypeScript,类型安全,内置支持 WebSocket、GraphQL、微服务、数据库 ORM(TypeORM、Prisma 等),方便扩展和集成。
大量同步阻塞操作 比如频繁使用同步的文件或网络操作,导致事件循环被阻塞,无法及时处理其他任务。
死循环或递归 代码中存在无限循环或递归,导致 CPU 持续高负载。
大数据量处理未分片或异步化 一次性处理大量数据(如大文件、复杂计算)没有采用流式或异步方式,导致主线程被占满。
内存泄漏 对象未及时释放,导致垃圾回收频繁或内存占用过高,间接影响 CPU。
频繁的 I/O 操作未优化 例如频繁读写磁盘、数据库或网络请求,未做批量处理或缓存优化。
第三方库或依赖性能问题 使用的 npm 包存在性能瓶颈或 bug,导致 CPU 占用异常。
日志、监控等中间件写入过多 日志量过大或监控频率过高,影响主业务流程。
进程数量过多或负载均衡不合理 多进程模式下进程数设置不合理,或负载均衡策略不当,导致部分进程 CPU 过高。
排查建议:
可使用 node --inspect、top、pm2 monit 等工具进行性能分析,定位具体问题。
内存泄漏 对象、定时器、闭包等未及时释放,导致内存持续增长。
缓存未合理清理 使用全局变量或缓存时未定期清理,数据不断堆积。
大对象或数组频繁创建 频繁创建大对象、数组或 Buffer,未及时释放或复用。
未关闭的数据库或文件句柄 数据库连接、文件句柄等未正确关闭,导致资源占用。
第三方库或依赖存在内存问题 使用的 npm 包存在内存泄漏或管理不当。
高并发请求导致堆积 短时间内大量请求,导致内存瞬时占用过高。
错误的循环或递归 循环或递归中不断分配新内存,未释放旧数据。
排查建议:
可使用 node --inspect、heapdump、process.memoryUsage() 等工具进行内存分析,定位具体问题。
spawn、child_process
父子进程通讯:使用 IPC 模块,父进程发送消息给子进程,子进程接收消息并处理。
// 主进程(main.js)
const { fork } = require('child_process') // fork 是 spawn 的特例,自动创建 IPC 通道
const child = fork('./child.js')
// 主进程发送消息
child.send({ type: 'task', data: '计算 1+2' })
// 主进程接收消息
child.on('message', msg => {
console.log(`子进程返回:${msg.result}`) // 输出:子进程返回:3
})
// 子进程(child.js)
process.on('message', msg => {
if (msg.type === 'task') {
const result = 1 + 2
// 子进程发送消息给主进程
process.send({ result })
process.exit() // 完成后退出
}
})
// 主进程(需用 --experimental-sharedarraybuffer 启动)
const { fork } = require('child_process')
const buffer = new SharedArrayBuffer(4) // 4 字节共享内存(存储一个 32 位整数)
const arr = new Int32Array(buffer)
arr[0] = 100 // 主进程写入数据
const child = fork('./child.js')
child.send(buffer) // 发送共享内存引用
// 子进程(child.js)
process.on('message', buffer => {
const arr = new Int32Array(buffer)
console.log(`共享内存数据:${arr[0]}`) // 输出:100
arr[0] = 200 // 修改共享数据
})
// 主进程后续可读取修改后的数据
setTimeout(() => {
console.log(`主进程读取共享数据:${arr[0]}`) // 输出:200
}, 100)
// 主进程写入数据到文件
const fs = require('fs')
fs.writeFileSync('data.txt', '需要处理的数据')
// 启动子进程处理文件
const { exec } = require('child_process')
exec('node child.js', (err, stdout) => {
console.log(`子进程处理结果:${stdout}`)
})
// 子进程(child.js)读取并处理文件
const data = fs.readFileSync('data.txt', 'utf8')
const result = data.toUpperCase() // 示例:转为大写
console.log(result)
// server.js
const net = require('net')
const fs = require('fs')
const pipeFile =
process.platform === 'win32' ? '\\\\.\\pipe\\mypip' : '/tmp/unix.sock'
const server = net.createServer(connection => {
console.log('socket connected.')
connection.on('close', () => console.log('disconnected.'))
connection.on('data', data => {
console.log(`receive: ${data}`)
connection.write(data)
console.log(`send: ${data}`)
})
connection.on('error', err => console.error(err.message))
})
try {
fs.unlinkSync(pipeFile)
} catch (error) {}
server.listen(pipeFile)
// client.js
const net = require('net')
const pipeFile =
process.platform === 'win32' ? '\\\\.\\pipe\\mypip' : '/tmp/unix.sock'
const client = net.connect(pipeFile)
client.on('connect', () => console.log('connected.'))
client.on('data', data => console.log(`receive: ${data}`))
client.on('end', () => console.log('disconnected.'))
client.on('error', err => console.error(err.message))
setInterval(() => {
const msg = 'hello'
console.log(`send: ${msg}`)
client.write(msg)
}, 3000)
Buffer 是一个内置类,用于表示二进制数据(如文件内容、网络流、加密数据等)。它是 Node.js 处理 I/O 操作(如文件读写、网络通信)的基础,提供了高效的二进制数据处理能力。 核心特点
Buffer 直接分配原始内存(不经过 JavaScript 堆),适合处理大量二进制数据,避免垃圾回收的性能开销。没有引用指向 Buffer 时,内存会被垃圾回收。
创建后大小固定,无法动态调整(但可通过 Buffer.concat() 合并多个 Buffer)。
专为二进制数据设计,提供多种编码转换(如 UTF-8、Base64、Hex 等)和位操作方法。
无需 require() 即可在 Node.js 环境中使用。
(如 utf8 中一个汉字占 3 字节,base64 中每 3 字节转换为 4 字符)。