Node.js - 服务器端JavaScript运行时
1. 什么是Node.js?
Node.js是一个基于Chrome V8 JavaScript引擎的服务器端JavaScript运行时环境。它允许开发者使用JavaScript编写服务器端应用程序,实现了"一次编写,多处运行"的理念。Node.js采用事件驱动、非阻塞I/O模型,使其轻量且高效,非常适合构建可扩展的网络应用。
1.1 核心特性
- 事件驱动:基于事件循环机制,处理异步操作
- 非阻塞I/O:避免了传统I/O操作的阻塞,提高并发性能
- 单线程:主线程单线程,但通过事件循环和工作线程处理并发
- 跨平台:可在Windows、Linux、macOS等多个平台运行
- 丰富的包生态:npm是世界上最大的开源包管理器
- V8引擎:使用高性能的Chrome V8 JavaScript引擎
1.2 Node.js的应用场景
- Web服务器:构建高性能的HTTP服务器
- API服务:创建RESTful或GraphQL API
- 实时应用:如聊天应用、在线游戏等
- 微服务:构建分布式系统
- 命令行工具:开发CLI工具
- 桌面应用:通过Electron构建跨平台桌面应用
- 物联网:处理IoT设备的数据
2. 安装与配置
2.1 安装Node.js
Windows系统
- 访问 Node.js官网 下载Windows安装包
- 运行安装程序,按照提示完成安装
- 打开命令提示符,运行
node -v验证安装成功
macOS系统
- 访问 Node.js官网 下载macOS安装包
- 运行安装程序,按照提示完成安装
- 打开终端,运行
node -v验证安装成功 - 或使用Homebrew安装:
brew install node
Linux系统
# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# CentOS/RHEL
curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -
sudo yum install -y nodejs
# 验证安装
node -v
npm -v2.2 使用nvm管理Node.js版本
nvm(Node Version Manager)是一个用于管理多个Node.js版本的工具。
安装nvm
# macOS/Linux
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
# Windows
# 下载并安装 nvm-windows: https://github.com/coreybutler/nvm-windows/releases使用nvm
# 查看可用版本
nvm ls-remote
# 安装特定版本
nvm install 18.17.0
# 切换版本
nvm use 18.17.0
# 设置默认版本
nvm alias default 18.17.0
# 查看已安装版本
nvm ls2.3 环境变量配置
Node.js的环境变量配置主要包括:
- NODE_PATH:模块查找路径
- NODE_ENV:运行环境(development, production, test)
- PATH:包含Node.js和npm的可执行文件路径
# 在.bashrc或.zshrc中添加
export NODE_ENV=development
export NODE_PATH=$NODE_PATH:/usr/local/lib/node_modules3. 核心模块
3.1 文件系统模块(fs)
fs模块用于文件系统操作,如读取、写入、删除文件等。
const fs = require('fs');
// 同步读取文件
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
// 异步读取文件
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// 写入文件
fs.writeFile('output.txt', 'Hello World!', (err) => {
if (err) throw err;
console.log('文件已写入');
});
// 追加文件
fs.appendFile('output.txt', '追加内容', (err) => {
if (err) throw err;
console.log('内容已追加');
});
// 删除文件
fs.unlink('file.txt', (err) => {
if (err) throw err;
console.log('文件已删除');
});
// 创建目录
fs.mkdir('newdir', (err) => {
if (err) throw err;
console.log('目录已创建');
});
// 读取目录
fs.readdir('.', (err, files) => {
if (err) throw err;
files.forEach(file => {
console.log(file);
});
});3.2 路径模块(path)
path模块用于处理文件路径。
const path = require('path');
// 连接路径
const fullPath = path.join(__dirname, 'subdir', 'file.txt');
console.log(fullPath);
// 解析路径
const parsedPath = path.parse(__filename);
console.log(parsedPath);
// 获取绝对路径
const absolutePath = path.resolve('relative/path');
console.log(absolutePath);
// 获取路径扩展名
const ext = path.extname('file.txt');
console.log(ext);
// 获取文件名
const basename = path.basename('path/to/file.txt');
console.log(basename);
// 获取目录名
const dirname = path.dirname('path/to/file.txt');
console.log(dirname);3.3 HTTP模块
HTTP模块用于创建HTTP服务器和客户端。
const http = require('http');
// 创建HTTP服务器
const server = http.createServer((req, res) => {
// 设置响应头
res.writeHead(200, { 'Content-Type': 'text/plain' });
// 写入响应内容
res.write('Hello World!');
// 结束响应
res.end();
});
// 监听端口
server.listen(3000, 'localhost', () => {
console.log('服务器运行在 http://localhost:3000/');
});
// 创建HTTP客户端
http.get('http://localhost:3000/', (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log(data);
});
}).on('error', (err) => {
console.error('错误:', err);
});3.4 URL模块
URL模块用于解析和处理URL。
const url = require('url');
// 解析URL
const parsedUrl = url.parse('http://localhost:3000/path?name=test&age=20', true);
console.log(parsedUrl);
console.log(parsedUrl.query); // 解析查询参数
// 格式化URL
const formattedUrl = url.format({
protocol: 'http',
hostname: 'localhost',
port: 3000,
pathname: '/path',
query: { name: 'test', age: 20 }
});
console.log(formattedUrl);
// 使用URL构造函数(推荐)
const myUrl = new URL('http://localhost:3000/path?name=test&age=20');
console.log(myUrl.href);
console.log(myUrl.searchParams.get('name'));3.5 事件模块(events)
events模块用于处理事件,是Node.js事件驱动架构的核心。
const EventEmitter = require('events');
// 创建事件发射器实例
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
// 注册事件监听器
myEmitter.on('event', (arg1, arg2) => {
console.log('事件触发:', arg1, arg2);
});
// 触发事件
myEmitter.emit('event', '参数1', '参数2');
// 注册一次性事件监听器
myEmitter.once('onceEvent', () => {
console.log('一次性事件触发');
});
// 触发一次性事件
myEmitter.emit('onceEvent');
myEmitter.emit('onceEvent'); // 不会再次触发
// 移除事件监听器
function listener() {
console.log('可移除的事件监听器');
}
myEmitter.on('removableEvent', listener);
myEmitter.emit('removableEvent');
myEmitter.removeListener('removableEvent', listener);
myEmitter.emit('removableEvent'); // 不会触发3.6 流模块(stream)
stream模块用于处理流式数据,如文件读写、网络通信等。
const fs = require('fs');
// 创建可读流
const readable = fs.createReadStream('input.txt');
// 创建可写流
const writable = fs.createWriteStream('output.txt');
// 管道流
readable.pipe(writable);
// 监听流事件
readable.on('data', (chunk) => {
console.log('读取到数据:', chunk.length, '字节');
});
readable.on('end', () => {
console.log('读取完成');
});
readable.on('error', (err) => {
console.error('读取错误:', err);
});
// 创建转换流
const { Transform } = require('stream');
class UpperCaseTransform extends Transform {
_transform(chunk, encoding, callback) {
const upperCaseChunk = chunk.toString().toUpperCase();
this.push(upperCaseChunk);
callback();
}
}
const upperCaseTransform = new UpperCaseTransform();
// 使用转换流
fs.createReadStream('input.txt')
.pipe(upperCaseTransform)
.pipe(fs.createWriteStream('output-uppercase.txt'));4. 模块系统
4.1 CommonJS模块系统
Node.js默认使用CommonJS模块系统,使用require和module.exports进行模块导入和导出。
导出模块
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
// 导出单个函数
module.exports.add = add;
module.exports.subtract = subtract;
// 或导出整个对象
module.exports = {
add,
subtract
};导入模块
// app.js
const math = require('./math');
console.log(math.add(1, 2)); // 3
console.log(math.subtract(5, 3)); // 2
// 或解构导入
const { add, subtract } = require('./math');
console.log(add(1, 2)); // 3
console.log(subtract(5, 3)); // 24.2 ES模块系统
Node.js v12+支持ES模块系统,使用import和export语句。
启用ES模块
- 在package.json中添加:
"type": "module" - 或使用.mjs文件扩展名
导出模块
// math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// 或导出默认值
export default {
add,
subtract
};导入模块
// app.js
import * as math from './math.js';
console.log(math.add(1, 2)); // 3
console.log(math.subtract(5, 3)); // 2
// 或解构导入
import { add, subtract } from './math.js';
console.log(add(1, 2)); // 3
console.log(subtract(5, 3)); // 2
// 或导入默认值
import math from './math.js';
console.log(math.add(1, 2)); // 3
console.log(math.subtract(5, 3)); // 24.3 模块查找机制
Node.js的模块查找机制遵循以下规则:
- 核心模块:如果是核心模块(如fs、http),直接加载
- 相对路径模块:以./或../开头,按照相对路径加载
- 绝对路径模块:以/开头,按照绝对路径加载
- 第三方模块:在node_modules目录中查找
// 查找过程
require('module-name')
→ 检查是否为核心模块
→ 检查 ./node_modules/module-name
→ 检查 ../node_modules/module-name
→ 检查 ../../node_modules/module-name
→ 直到根目录5. 异步编程
5.1 回调函数
回调函数是Node.js中最基本的异步编程方式。
const fs = require('fs');
// 回调函数方式
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取错误:', err);
return;
}
console.log('文件内容:', data);
// 嵌套回调(回调地狱)
fs.writeFile('output.txt', data.toUpperCase(), (err) => {
if (err) {
console.error('写入错误:', err);
return;
}
console.log('文件已转换为大写并写入');
});
});5.2 Promise
Promise是ES6引入的异步编程解决方案,用于解决回调地狱问题。
const fs = require('fs').promises;
// Promise方式
fs.readFile('file.txt', 'utf8')
.then(data => {
console.log('文件内容:', data);
return fs.writeFile('output.txt', data.toUpperCase());
})
.then(() => {
console.log('文件已转换为大写并写入');
})
.catch(err => {
console.error('错误:', err);
});
// 自定义Promise
function readFileAsync(path, encoding) {
return new Promise((resolve, reject) => {
fs.readFile(path, encoding, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
readFileAsync('file.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));5.3 async/await
async/await是ES7引入的异步编程语法糖,基于Promise,使异步代码看起来像同步代码。
const fs = require('fs').promises;
// async/await方式
async function processFile() {
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log('文件内容:', data);
await fs.writeFile('output.txt', data.toUpperCase());
console.log('文件已转换为大写并写入');
} catch (err) {
console.error('错误:', err);
}
}
processFile();
// 并行执行
async function parallelTasks() {
try {
const [data1, data2] = await Promise.all([
fs.readFile('file1.txt', 'utf8'),
fs.readFile('file2.txt', 'utf8')
]);
console.log('文件1内容:', data1);
console.log('文件2内容:', data2);
} catch (err) {
console.error('错误:', err);
}
}
parallelTasks();5.4 事件循环
Node.js的事件循环是其异步编程的核心机制,负责处理异步操作的回调。
事件循环的阶段
- timers:执行setTimeout和setInterval的回调
- pending callbacks:执行系统操作的回调
- idle, prepare:内部使用
- poll:执行I/O回调,获取新的I/O事件
- check:执行setImmediate的回调
- close callbacks:执行close事件的回调
示例
// 事件循环示例
console.log('开始');
// timers阶段
setTimeout(() => {
console.log('setTimeout');
}, 0);
// check阶段
setImmediate(() => {
console.log('setImmediate');
});
// I/O操作
const fs = require('fs');
fs.readFile(__filename, () => {
console.log('fs.readFile回调');
setTimeout(() => {
console.log('fs.readFile后setTimeout');
}, 0);
setImmediate(() => {
console.log('fs.readFile后setImmediate');
});
});
// 微任务
process.nextTick(() => {
console.log('process.nextTick');
});
console.log('结束');
// 输出顺序:
// 开始
// 结束
// process.nextTick
// setTimeout
// setImmediate
// fs.readFile回调
// fs.readFile后setImmediate
// fs.readFile后setTimeout6. Express.js基础
Express.js是Node.js最流行的Web框架,提供了简洁而强大的API来构建Web应用。
6.1 安装Express.js
npm install express6.2 基本用法
const express = require('express');
const app = express();
const port = 3000;
// 中间件
app.use(express.json()); // 解析JSON请求体
app.use(express.urlencoded({ extended: true })); // 解析URL编码的请求体
// 路由
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.get('/users', (req, res) => {
res.json([{ id: 1, name: '张三' }, { id: 2, name: '李四' }]);
});
app.post('/users', (req, res) => {
const newUser = req.body;
res.status(201).json(newUser);
});
app.get('/users/:id', (req, res) => {
const id = req.params.id;
res.json({ id, name: '用户' + id });
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('服务器内部错误');
});
// 404处理
app.use((req, res) => {
res.status(404).send('页面不存在');
});
// 启动服务器
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});6.3 路由模块化
// routes/users.js
const express = require('express');
const router = express.Router();
// 用户路由
router.get('/', (req, res) => {
res.json([{ id: 1, name: '张三' }, { id: 2, name: '李四' }]);
});
router.post('/', (req, res) => {
const newUser = req.body;
res.status(201).json(newUser);
});
router.get('/:id', (req, res) => {
const id = req.params.id;
res.json({ id, name: '用户' + id });
});
module.exports = router;
// app.js
const express = require('express');
const app = express();
const usersRouter = require('./routes/users');
app.use('/users', usersRouter);
app.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});6.4 中间件
Express.js的中间件是一个函数,可访问请求对象、响应对象和下一个中间件函数。
const express = require('express');
const app = express();
// 自定义中间件
const logger = (req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next(); // 调用下一个中间件
};
// 应用级中间件
app.use(logger);
// 路由级中间件
const auth = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).send('未授权');
}
// 验证token
next();
};
app.get('/protected', auth, (req, res) => {
res.send('受保护的路由');
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('服务器内部错误');
});
app.listen(3000);7. 数据库操作
7.1 使用MongoDB
const mongoose = require('mongoose');
// 连接数据库
mongoose.connect('mongodb://localhost:27017/test', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// 定义模型
const User = mongoose.model('User', {
name: String,
email: String,
age: Number
});
// 创建用户
async function createUser() {
const user = new User({ name: '张三', email: 'zhangsan@example.com', age: 30 });
await user.save();
console.log('用户创建成功:', user);
}
// 查询用户
async function findUsers() {
const users = await User.find();
console.log('所有用户:', users);
}
// 更新用户
async function updateUser() {
const user = await User.findOne({ name: '张三' });
user.age = 31;
await user.save();
console.log('用户更新成功:', user);
}
// 删除用户
async function deleteUser() {
const result = await User.deleteOne({ name: '张三' });
console.log('用户删除成功:', result);
}
// 执行操作
async function run() {
await createUser();
await findUsers();
await updateUser();
await deleteUser();
await mongoose.disconnect();
}
run();7.2 使用MySQL
const mysql = require('mysql2/promise');
// 创建连接池
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 执行SQL
async function run() {
// 创建表
await pool.execute(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
age INT
)
`);
console.log('表创建成功');
// 插入数据
await pool.execute(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
['张三', 'zhangsan@example.com', 30]
);
console.log('数据插入成功');
// 查询数据
const [rows] = await pool.execute('SELECT * FROM users');
console.log('查询结果:', rows);
// 更新数据
await pool.execute(
'UPDATE users SET age = ? WHERE name = ?',
[31, '张三']
);
console.log('数据更新成功');
// 删除数据
await pool.execute('DELETE FROM users WHERE name = ?', ['张三']);
console.log('数据删除成功');
// 关闭连接池
await pool.end();
}
run().catch(console.error);8. 部署与性能优化
8.1 部署方式
使用PM2
PM2是一个Node.js应用的进程管理器,用于生产环境部署。
# 安装PM2
npm install -g pm2
# 启动应用
pm2 start app.js --name "my-app"
# 查看状态
pm2 status
# 查看日志
pm2 logs
# 重启应用
pm2 restart my-app
# 停止应用
pm2 stop my-app
# 开机自启
pm2 startup
pm2 save使用Docker
Docker是一个容器化平台,可用于部署Node.js应用。
# Dockerfile
FROM node:18-alpine
# 创建工作目录
WORKDIR /app
# 复制package.json和package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm install --production
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动应用
CMD ["node", "app.js"]# 构建镜像
docker build -t my-node-app .
# 运行容器
docker run -p 3000:3000 my-node-app8.2 性能优化
- 使用连接池:数据库连接池、HTTP连接池
- 缓存:使用Redis等缓存数据
- 压缩:启用gzip压缩
- 代码优化:
- 使用异步编程
- 避免同步I/O操作
- 使用适当的数据结构
- 减少模块依赖
- 服务器优化:
- 使用集群模式
- 优化内存使用
- 配置适当的超时时间
- 前端优化:
- 静态资源缓存
- 代码分割
- 懒加载
8.3 监控与日志
使用Winston记录日志
const winston = require('winston');
// 配置日志
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// 开发环境输出到控制台
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// 使用日志
logger.info('应用启动');
logger.error('发生错误');使用Prometheus监控
const prometheus = require('prom-client');
// 定义指标
const httpRequestDurationMicroseconds = new prometheus.Histogram({
name: 'http_request_duration_ms',
help: 'HTTP请求持续时间(毫秒)',
labelNames: ['method', 'route', 'status'],
buckets: [0.1, 5, 10, 25, 50, 100, 250, 500, 1000]
});
// 中间件
const metricsMiddleware = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
httpRequestDurationMicroseconds.labels(req.method, req.route.path, res.statusCode).observe(duration);
});
next();
};
// 指标端点
app.get('/metrics', async (req, res) => {
res.set('Content-Type', prometheus.register.contentType);
res.end(await prometheus.register.metrics());
});9. 最佳实践
9.1 代码规范
- 使用ESLint:保持代码风格一致
- 使用Prettier:自动格式化代码
- 使用TypeScript:提供类型检查
- 遵循CommonJS或ES模块规范
- 使用语义化版本:遵循SemVer规范
9.2 项目结构
project/
├── src/
│ ├── controllers/ # 控制器
│ ├── models/ # 数据模型
│ ├── routes/ # 路由
│ ├── middleware/ # 中间件
│ ├── services/ # 业务逻辑
│ ├── utils/ # 工具函数
│ ├── config/ # 配置
│ └── app.js # 应用入口
├── test/ # 测试文件
├── package.json # 项目配置
├── README.md # 项目说明
└── .gitignore # Git忽略文件9.3 安全措施
- 使用HTTPS:加密传输
- 防止SQL注入:使用参数化查询
- 防止XSS攻击:转义用户输入
- 防止CSRF攻击:使用CSRF令牌
- 密码加密:使用bcrypt等加密库
- 使用Helmet:设置安全相关的HTTP头
- 限制请求速率:防止DoS攻击
- 验证输入:使用JOI等验证库
9.4 测试
- 单元测试:测试单个函数或模块
- 集成测试:测试多个模块的交互
- 端到端测试:测试整个应用流程
- 使用Jest:流行的测试框架
- 使用Supertest:测试HTTP请求
// 单元测试示例
const sum = require('./sum');
test('1 + 2 应该等于 3', () => {
expect(sum(1, 2)).toBe(3);
});
// 集成测试示例
const request = require('supertest');
const app = require('./app');
test('GET /users 应该返回用户列表', async () => {
const response = await request(app).get('/users');
expect(response.statusCode).toBe(200);
expect(Array.isArray(response.body)).toBe(true);
});9.5 文档
- README.md:项目说明、安装步骤、使用方法
- API文档:使用Swagger等工具生成
- 代码注释:解释复杂的代码逻辑
- CHANGELOG.md:记录版本变更
10. 总结
Node.js是一个强大的服务器端JavaScript运行时,具有以下优势:
10.1 核心优势
- 高性能:基于V8引擎和非阻塞I/O
- 生态丰富:npm拥有大量开源包
- 开发效率高:JavaScript全栈开发
- 可扩展性强:适合构建分布式系统
- 跨平台:可在多个操作系统运行
10.2 适用场景
- Web应用:高性能HTTP服务器
- API服务:RESTful和GraphQL API
- 实时应用:WebSocket、Socket.io
- 微服务:轻量级服务架构
- 命令行工具:开发CLI应用
- 桌面应用:Electron框架
- 物联网:边缘计算和设备管理
10.3 未来发展
Node.js持续演进,未来的发展趋势包括:
- 性能优化:进一步提升V8引擎性能
- ES模块普及:默认使用ES模块
- TypeScript集成:更好的类型支持
- WebAssembly支持:运行高性能代码
- Serverless:与云服务深度集成
- 边缘计算:在边缘节点运行Node.js
通过本教程的学习,您应该已经掌握了Node.js的基本概念、核心模块、异步编程、Web框架、数据库操作等知识,可以开始构建各种类型的Node.js应用了。随着实践的深入,您将能够充分发挥Node.js的优势,构建高性能、可扩展的应用系统。