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系统

  1. 访问 Node.js官网 下载Windows安装包
  2. 运行安装程序,按照提示完成安装
  3. 打开命令提示符,运行 node -v 验证安装成功

macOS系统

  1. 访问 Node.js官网 下载macOS安装包
  2. 运行安装程序,按照提示完成安装
  3. 打开终端,运行 node -v 验证安装成功
  4. 或使用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 -v

2.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 ls

2.3 环境变量配置

Node.js的环境变量配置主要包括:

  1. NODE_PATH:模块查找路径
  2. NODE_ENV:运行环境(development, production, test)
  3. PATH:包含Node.js和npm的可执行文件路径
# 在.bashrc或.zshrc中添加
export NODE_ENV=development
export NODE_PATH=$NODE_PATH:/usr/local/lib/node_modules

3. 核心模块

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)); // 2

4.2 ES模块系统

Node.js v12+支持ES模块系统,使用import和export语句。

启用ES模块

  1. 在package.json中添加:"type": "module"
  2. 或使用.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)); // 2

4.3 模块查找机制

Node.js的模块查找机制遵循以下规则:

  1. 核心模块:如果是核心模块(如fs、http),直接加载
  2. 相对路径模块:以./或../开头,按照相对路径加载
  3. 绝对路径模块:以/开头,按照绝对路径加载
  4. 第三方模块:在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的事件循环是其异步编程的核心机制,负责处理异步操作的回调。

事件循环的阶段

  1. timers:执行setTimeout和setInterval的回调
  2. pending callbacks:执行系统操作的回调
  3. idle, prepare:内部使用
  4. poll:执行I/O回调,获取新的I/O事件
  5. check:执行setImmediate的回调
  6. 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后setTimeout

6. Express.js基础

Express.js是Node.js最流行的Web框架,提供了简洁而强大的API来构建Web应用。

6.1 安装Express.js

npm install express

6.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-app

8.2 性能优化

  1. 使用连接池:数据库连接池、HTTP连接池
  2. 缓存:使用Redis等缓存数据
  3. 压缩:启用gzip压缩
  4. 代码优化
    • 使用异步编程
    • 避免同步I/O操作
    • 使用适当的数据结构
    • 减少模块依赖
  5. 服务器优化
    • 使用集群模式
    • 优化内存使用
    • 配置适当的超时时间
  6. 前端优化
    • 静态资源缓存
    • 代码分割
    • 懒加载

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 代码规范

  1. 使用ESLint:保持代码风格一致
  2. 使用Prettier:自动格式化代码
  3. 使用TypeScript:提供类型检查
  4. 遵循CommonJS或ES模块规范
  5. 使用语义化版本:遵循SemVer规范

9.2 项目结构

project/
├── src/
│   ├── controllers/    # 控制器
│   ├── models/         # 数据模型
│   ├── routes/         # 路由
│   ├── middleware/     # 中间件
│   ├── services/       # 业务逻辑
│   ├── utils/          # 工具函数
│   ├── config/         # 配置
│   └── app.js          # 应用入口
├── test/               # 测试文件
├── package.json        # 项目配置
├── README.md           # 项目说明
└── .gitignore          # Git忽略文件

9.3 安全措施

  1. 使用HTTPS:加密传输
  2. 防止SQL注入:使用参数化查询
  3. 防止XSS攻击:转义用户输入
  4. 防止CSRF攻击:使用CSRF令牌
  5. 密码加密:使用bcrypt等加密库
  6. 使用Helmet:设置安全相关的HTTP头
  7. 限制请求速率:防止DoS攻击
  8. 验证输入:使用JOI等验证库

9.4 测试

  1. 单元测试:测试单个函数或模块
  2. 集成测试:测试多个模块的交互
  3. 端到端测试:测试整个应用流程
  4. 使用Jest:流行的测试框架
  5. 使用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 文档

  1. README.md:项目说明、安装步骤、使用方法
  2. API文档:使用Swagger等工具生成
  3. 代码注释:解释复杂的代码逻辑
  4. 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的优势,构建高性能、可扩展的应用系统。

« 上一篇 61. Redis - 高性能内存数据库 下一篇 » 63. npm - Node.js包管理器