Node.js 学习笔记

从入门到进阶的全栈之路

一、Node.js 简介与安装

1.1 什么是 Node.js?

Node.js 是一个基于 Chrome V8 JavaScript 引擎的 JavaScript 运行时环境,让 JavaScript 可以脱离浏览器在服务器端执行。它的主要特点包括:

  • 事件驱动:采用观察者模式,通过事件循环处理异步操作
  • 非阻塞 I/O:使用异步编程模型,提高并发处理能力
  • 单线程:通过事件循环处理并发,避免多线程编程的复杂性

1.2 安装 Node.js

访问 Node.js 官网下载安装包,推荐选择 LTS(长期支持)版本。安装完成后,在终端验证:

node -v  # 查看 Node.js 版本
npm -v   # 查看 npm 版本

提示: npm(Node Package Manager)是 Node.js 的包管理工具,随 Node.js 一起安装。

二、JavaScript 基础回顾

学习 Node.js 前,需要掌握以下 JavaScript 核心概念:

2.1 ES6+ 重要特性

  • 箭头函数() => {}
  • 解构赋值const {name, age} = user
  • 模板字符串`Hello, ${name}!`
  • let/const:块级作用域变量声明

2.2 异步编程基础

Node.js 的核心优势在于异步非阻塞 I/O,需掌握以下异步编程方式:

回调函数(Callback)

const fs = require('fs');

fs.readFile('file.txt', 'utf8', (err, data) => {
    if (err) {
        console.error('读取文件出错:', err);
        return;
    }
    console.log('文件内容:', data);
});

Promise

const readFilePromise = new Promise((resolve, reject) => {
    fs.readFile('file.txt', 'utf8', (err, data) => {
        if (err) reject(err);
        else resolve(data);
    });
});

readFilePromise
    .then(data => console.log(data))
    .catch(err => console.error(err));

Async/Await(推荐)

async function readFileAsync() {
    try {
        const data = await fs.promises.readFile('file.txt', 'utf8');
        console.log(data);
    } catch (err) {
        console.error(err);
    }
}

readFileAsync();

三、Node.js 核心模块

Node.js 提供了丰富的内置模块,无需安装即可使用:

模块名 功能描述 常用方法/类
fs 文件系统操作 readFile, writeFile, createReadStream
path 路径处理 join, resolve, dirname
http HTTP 服务器/客户端 createServer, request
events 事件处理 EventEmitter, on, emit
os 操作系统信息 platform, arch, cpus
url URL 解析 parse, format

3.1 文件系统操作示例

const fs = require('fs');
const path = require('path');

// 路径拼接
const filePath = path.join(__dirname, 'files', 'test.txt');

// 异步读取文件
fs.readFile(filePath, 'utf8', (err, data) => {
    if (err) throw err;
    console.log('文件内容:', data);
});

// 同步读取文件
try {
    const data = fs.readFileSync(filePath, 'utf8');
    console.log('文件内容:', data);
} catch (err) {
    console.error('读取文件出错:', err);
}

3.2 创建 HTTP 服务器

const http = require('http');

const server = http.createServer((req, res) => {
    // 设置响应头
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    
    // 根据 URL 路由处理
    if (req.url === '/') {
        res.end('<h1>首页</h1><p>欢迎访问我的网站!</p>');
    } else if (req.url === '/about') {
        res.end('<h1>关于我们</h1>');
    } else {
        res.writeHead(404);
        res.end('<h1>页面未找到</h1>');
    }
});

server.listen(3000, () => {
    console.log('服务器运行在 http://localhost:3000');
});

四、模块系统与 NPM

4.1 CommonJS 模块规范

Node.js 使用 CommonJS 模块系统:

导出模块(math.js)

// 方法一:通过 exports 导出
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;

// 方法二:通过 module.exports 导出
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;

module.exports = {
    multiply,
    divide
};

导入模块(app.js)

const { add, subtract } = require('./math');
const math = require('./math');  // 导入整个模块

console.log(add(5, 3));      // 8
console.log(math.multiply(4, 5));  // 20

4.2 包管理与 NPM

npm 是 Node.js 的包管理工具,用于安装和管理第三方模块:

# 初始化项目(生成 package.json)
npm init -y

# 安装包
npm install express          # 本地安装
npm install -g nodemon       # 全局安装
npm install jest --save-dev  # 开发依赖

# 卸载包
npm uninstall express

# 查看已安装的包
npm list

package.json 重要字段:

  • dependencies:生产环境依赖
  • devDependencies:开发环境依赖
  • scripts:自定义命令脚本

五、Express 框架

Express 是 Node.js 最流行的 Web 框架,简化了 HTTP 服务器开发。

5.1 基本使用

const express = require('express');
const app = express();
const port = 3000;

// 解析 application/json
app.use(express.json());

// 解析 application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));

// 静态文件服务
app.use(express.static('public'));

// 路由定义
app.get('/', (req, res) => {
    res.send('首页');
});

app.get('/users/:id', (req, res) => {
    const userId = req.params.id;
    res.send(`用户 ID: ${userId}`);
});

app.post('/users', (req, res) => {
    const userData = req.body;
    // 处理用户创建逻辑
    res.json({ message: '用户创建成功', data: userData });
});

app.listen(port, () => {
    console.log(`Express 服务器运行在 http://localhost:${port}`);
});

5.2 中间件(Middleware)

中间件是在请求-响应周期中执行的函数:

// 日志中间件
app.use((req, res, next) => {
    console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
    next(); // 调用下一个中间件
});

// 身份验证中间件
const authMiddleware = (req, res, next) => {
    const token = req.headers.authorization;
    
    if (token === 'valid_token') {
        next();
    } else {
        res.status(401).json({ error: '未授权访问' });
    }
};

// 路由特定中间件
app.get('/admin', authMiddleware, (req, res) => {
    res.send('管理员页面');
});

// 错误处理中间件
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('服务器内部错误!');
});

5.3 路由模块化

routes/users.js

const express = require('express');
const router = express.Router();

// GET /users
router.get('/', (req, res) => {
    res.send('用户列表');
});

// GET /users/:id
router.get('/:id', (req, res) => {
    res.send(`用户详情: ${req.params.id}`);
});

// POST /users
router.post('/', (req, res) => {
    res.json({ message: '创建用户' });
});

module.exports = router;

主文件 app.js

const express = require('express');
const app = express();
const userRoutes = require('./routes/users');

app.use('/users', userRoutes);

// ... 其他配置

六、数据库操作

6.1 MongoDB 与 Mongoose

MongoDB 是常用的 NoSQL 数据库,Mongoose 是 MongoDB 的对象文档建模工具(ODM):

const mongoose = require('mongoose');

// 连接数据库
mongoose.connect('mongodb://localhost:27017/mydatabase', {
    useNewUrlParser: true,
    useUnifiedTopology: true
});

// 定义用户模式(Schema)
const userSchema = new mongoose.Schema({
    name: { type: String, required: true },
    email: { type: String, required: true, unique: true },
    age: { type: Number, min: 0 }
}, { timestamps: true });

// 创建模型(Model)
const User = mongoose.model('User', userSchema);

// 使用模型进行 CRUD 操作
async function createUser() {
    try {
        const user = new User({
            name: '张三',
            email: 'zhangsan@example.com',
            age: 25
        });
        
        await user.save();
        console.log('用户创建成功:', user);
    } catch (error) {
        console.error('创建用户失败:', error.message);
    }
}

// 查询用户
async function getUsers() {
    try {
        const users = await User.find({ age: { $gte: 18 } }); // 年龄大于等于18岁的用户
        console.log(users);
    } catch (error) {
        console.error('查询用户失败:', error.message);
    }
}

createUser();
getUsers();

6.2 MySQL 与 Sequelize

Sequelize 是基于 Promise 的 Node.js ORM,支持多种 SQL 数据库:

const { Sequelize, DataTypes } = require('sequelize');

// 连接数据库
const sequelize = new Sequelize('database', 'username', 'password', {
    host: 'localhost',
    dialect: 'mysql'
});

// 定义用户模型
const User = sequelize.define('User', {
    name: { type: DataTypes.STRING, allowNull: false },
    email: { type: DataTypes.STRING, unique: true }
});

// 同步模型到数据库
async function syncDatabase() {
    try {
        await sequelize.authenticate();
        console.log('数据库连接成功');
        
        await sequelize.sync(); // 创建表(如果不存在)
        console.log('所有模型同步成功');
    } catch (error) {
        console.error('数据库操作失败:', error);
    }
}

syncDatabase();

七、异步编程与事件循环

7.1 Node.js 事件循环(Event Loop)

事件循环是 Node.js 实现非阻塞 I/O 的核心机制:

// 示例:理解事件循环的不同阶段
console.log('开始');

// 下一轮事件循环执行
setImmediate(() => {
    console.log('setImmediate');
});

// 当前轮次结束后执行
process.nextTick(() => {
    console.log('nextTick');
});

// 定时器阶段
setTimeout(() => {
    console.log('setTimeout 0ms');
}, 0);

// Promise 微任务
Promise.resolve().then(() => {
    console.log('Promise');
});

console.log('结束');

// 执行顺序:
// 开始 → 结束 → nextTick → Promise → setTimeout 0ms → setImmediate

7.2 EventEmitter 事件机制

const EventEmitter = require('events');

// 创建自定义事件发射器
class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

// 注册事件监听器
myEmitter.on('event', (data) => {
    console.log('事件触发,数据:', data);
});

// 一次性事件监听
myEmitter.once('onceEvent', () => {
    console.log('这个事件只触发一次');
});

// 触发事件
myEmitter.emit('event', { message: 'Hello' });
myEmitter.emit('event', { message: 'World' });

myEmitter.emit('onceEvent'); // 触发
myEmitter.emit('onceEvent'); // 不触发

事件循环阶段:

  1. timers:执行 setTimeout 和 setInterval 回调
  2. pending callbacks:执行系统操作的回调
  3. idle, prepare:内部使用
  4. poll:检索新的 I/O 事件
  5. check:执行 setImmediate 回调
  6. close callbacks:执行关闭事件的回调(如 socket.close)

八、流(Streams)与缓冲区(Buffer)

8.1 流的基本概念

流是处理读写数据的高效方式,特别适合大文件操作:

const fs = require('fs');

// 创建可读流
const readableStream = fs.createReadStream('largefile.txt', {
    encoding: 'utf8',
    highWaterMark: 1024 * 1024 // 每次读取 1MB
});

// 创建可写流
const writableStream = fs.createWriteStream('copy.txt');

// 管道操作(自动管理数据流)
readableStream.pipe(writableStream);

// 或者手动处理数据流
readableStream.on('data', (chunk) => {
    console.log(`接收到 ${chunk.length} 字节的数据`);
    writableStream.write(chunk);
});

readableStream.on('end', () => {
    console.log('文件读取完成');
    writableStream.end();
});

readableStream.on('error', (err) => {
    console.error('读取文件出错:', err);
});

8.2 Buffer 处理二进制数据

// 创建 Buffer
const buf1 = Buffer.alloc(10); // 创建长度为10的Buffer,填充0
const buf2 = Buffer.from('Hello'); // 从字符串创建Buffer
const buf3 = Buffer.from([1, 2, 3]); // 从数组创建Buffer

// Buffer 操作
console.log(buf2.toString()); // "Hello"
console.log(buf2.toString('base64')); // Base64 编码

// Buffer 拼接
const buf4 = Buffer.concat([buf2, Buffer.from(' World')]);
console.log(buf4.toString()); // "Hello World"

// 写入 Buffer
const buf5 = Buffer.alloc(256);
const len = buf5.write('Node.js 学习笔记');
console.log(`写入字节数: ${len}`);

注意: 流处理可以显著降低内存占用,特别是在处理大文件时。应当优先使用流而不是一次性读取整个文件。

九、调试与测试

9.1 调试工具

Node.js 提供内置调试器和 Chrome DevTools 支持:

// 使用 console.log 调试
console.log('变量值:', variable);

// 使用 console.time 计时
console.time('操作耗时');
// 执行某些操作
console.timeEnd('操作耗时'); // 输出: 操作耗时: 15.234ms

// 使用 debugger 语句
function complexCalculation(a, b) {
    debugger; // 在此处设置断点
    return a * b + Math.sqrt(a + b);
}

// 启动调试: node --inspect-brk app.js

9.2 单元测试

使用 Jest 进行单元测试:

// math.js
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

module.exports = { add, subtract };

// math.test.js
const { add, subtract } = require('./math');

test('加法测试', () => {
    expect(add(1, 2)).toBe(3);
    expect(add(0.1, 0.2)).toBeCloseTo(0.3);
});

test('减法测试', () => {
    expect(subtract(5, 3)).toBe(2);
});

// 异步测试
test('异步数据获取', async () => {
    const data = await fetchData();
    expect(data).toBeDefined();
});

十、项目实战与部署

10.1 项目结构规划

my-node-app/
├── src/
│   ├── controllers/     # 控制器
│   ├── models/          # 数据模型
│   ├── routes/          # 路由
│   ├── middleware/      # 中间件
│   ├── utils/           # 工具函数
│   └── app.js           # 应用入口
├── config/              # 配置文件
├── public/              # 静态文件
├── tests/               # 测试文件
├── node_modules/        # 依赖包
├── package.json
└── README.md

10.2 使用 PM2 进行进程管理

PM2 是 Node.js 应用的生产环境进程管理器:

# 安装 PM2
npm install -g pm2

# 启动应用
pm2 start app.js --name "my-app"

# 常用命令
pm2 list              # 查看所有进程
pm2 logs my-app       # 查看日志
pm2 restart my-app    # 重启应用
pm2 stop my-app       # 停止应用
pm2 delete my-app     # 删除应用

# 开机自启
pm2 startup
pm2 save

10.3 环境变量与配置管理

// 安装 dotenv: npm install dotenv
require('dotenv').config();

const config = {
    port: process.env.PORT || 3000,
    database: {
        host: process.env.DB_HOST || 'localhost',
        port: process.env.DB_PORT || 27017,
        name: process.env.DB_NAME || 'mydatabase'
    },
    jwtSecret: process.env.JWT_SECRET || 'default-secret'
};

module.exports = config;

实战练习:构建 RESTful API

创建一个完整的用户管理系统 API,包含以下功能:

  1. 用户注册(POST /api/register)
  2. 用户登录(POST /api/login)
  3. 获取用户列表(GET /api/users)
  4. 获取用户详情(GET /api/users/:id)
  5. 更新用户信息(PUT /api/users/:id)
  6. 删除用户(DELETE /api/users/:id)

技术要求: Express 框架、MongoDB 数据库、JWT 认证、输入验证、错误处理、单元测试。

十一、学习路线与资源推荐

学习路线图

  1. 第一阶段(1-2周):JavaScript 基础 → Node.js 基础 → 核心模块
  2. 第二阶段(2-3周):Express 框架 → 数据库操作 → 用户认证
  3. 第三阶段(2-3周):实时应用 → 测试调试 → 性能优化
  4. 第四阶段(持续):项目实战 → 源码阅读 → 架构设计

推荐资源

  • 官方文档Node.js 官方文档
  • 在线教程:MDN Web Docs、FreeCodeCamp Node.js 教程
  • 书籍:《Node.js 设计模式》、《深入浅出 Node.js》
  • 视频课程:黑马程序员 Node.js 全套教程

学习建议: 理论学习与项目实践相结合,从简单项目开始逐步增加复杂度。参与开源项目、阅读优质源码是提升技能的有效途径。