Node.js 教程 - 基于 Chrome V8 引擎的 JavaScript 运行时

一、项目概述

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,它允许开发者使用 JavaScript 编写服务器端应用程序。Node.js 的出现彻底改变了 JavaScript 的应用范围,使它从一种仅限于浏览器的脚本语言转变为一种全栈开发语言。

1.1 核心概念

  • V8 引擎:Google 开发的高性能 JavaScript 引擎,是 Node.js 的核心
  • 事件循环:Node.js 的异步处理机制,用于处理非阻塞 I/O 操作
  • 模块系统:基于 CommonJS 规范的模块系统,用于代码组织和复用
  • NPM:Node.js 的包管理工具,用于管理依赖
  • 异步 I/O:非阻塞的 I/O 操作,提高应用性能
  • 事件驱动:基于事件的编程模型,适合处理并发请求

1.2 核心特点

  • 异步非阻塞 I/O:提高应用性能和并发处理能力
  • 事件驱动:基于事件的编程模型,适合处理并发请求
  • 单线程:主线程是单线程的,避免了线程同步问题
  • 跨平台:可以在 Windows、macOS、Linux 等平台上运行
  • NPM 生态系统:丰富的第三方包,加速开发
  • 高性能:基于 V8 引擎,执行 JavaScript 代码速度快

二、安装与设置

2.1 安装方式

Windows 安装:

  1. 访问 Node.js 官网
  2. 下载 Windows 安装包(LTS 版本推荐)
  3. 运行安装程序,按照提示完成安装
  4. 打开命令提示符,输入 node -v 验证安装成功

macOS 安装:

  1. 访问 Node.js 官网
  2. 下载 macOS 安装包(LTS 版本推荐)
  3. 运行安装程序,按照提示完成安装
  4. 打开终端,输入 node -v 验证安装成功

Linux 安装:

# 使用包管理器安装(Ubuntu/Debian)
sudo apt update
sudo apt install nodejs npm

# 或使用 nvm 安装(推荐)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install node
nvm use node

2.2 版本管理

使用 nvm 管理多个 Node.js 版本:

# 安装 nvm(Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# 安装特定版本的 Node.js
nvm install 18
nvm install 20

# 切换到特定版本
nvm use 18

# 设置默认版本
nvm alias default 18

# 查看已安装的版本
nvm list

2.3 基本配置

npm 配置:

# 查看 npm 配置
npm config list

# 设置 npm 镜像(加速下载)
npm config set registry https://registry.npmmirror.com/

# 设置默认前缀(全局安装路径)
npm config set prefix "$HOME/.npm-global"

创建项目:

# 创建项目目录
mkdir my-nodejs-project
cd my-nodejs-project

# 初始化项目
npm init
# 或使用默认值快速初始化
npm init -y

三、基础用法

3.1 Hello World

创建简单的 Node.js 应用:

// index.js
console.log('Hello, Node.js!');

运行应用:

node index.js

3.2 模块系统

创建和使用模块:

// utils.js - 导出模块
function add(a, b) {
  return a + b;
}

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

module.exports = {
  add,
  subtract
};

// 或使用 ES6 模块语法(需要在 package.json 中设置 "type": "module")
// export { add, subtract };
// index.js - 导入模块
const utils = require('./utils');

console.log(utils.add(2, 3)); // 输出: 5
console.log(utils.subtract(5, 2)); // 输出: 3

// 或使用 ES6 模块语法
// import { add, subtract } from './utils.js';
// console.log(add(2, 3));
// console.log(subtract(5, 2));

3.3 文件系统操作

读取文件:

const fs = require('fs');

// 同步读取
try {
  const data = fs.readFileSync('example.txt', 'utf8');
  console.log(data);
} catch (err) {
  console.error(err);
}

// 异步读取
fs.readFile('example.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
});

写入文件:

const fs = require('fs');

// 同步写入
try {
  fs.writeFileSync('output.txt', 'Hello, Node.js!');
  console.log('文件写入成功');
} catch (err) {
  console.error(err);
}

// 异步写入
fs.writeFile('output.txt', 'Hello, Node.js!', (err) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log('文件写入成功');
});

3.4 创建 HTTP 服务器

简单的 HTTP 服务器:

const http = require('http');

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, Node.js HTTP Server!\n');
});

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

运行服务器:

node server.js

然后在浏览器中访问 http://localhost:3000/ 查看结果。

四、高级特性

4.1 事件循环

理解事件循环:

const fs = require('fs');

console.log('1. 开始执行');

// 同步操作
fs.readFileSync('example.txt');
console.log('2. 同步读取文件完成');

// 异步操作
fs.readFile('example.txt', (err, data) => {
  console.log('4. 异步读取文件完成');
});

console.log('3. 继续执行其他代码');

// 输出顺序: 1 -> 2 -> 3 -> 4

事件循环的阶段:

  1. Timers:执行 setTimeout 和 setInterval 回调
  2. Pending Callbacks:执行延迟到下一个循环迭代的 I/O 回调
  3. Idle, Prepare:内部使用
  4. Poll:执行 I/O 回调,处理轮询队列
  5. Check:执行 setImmediate 回调
  6. Close Callbacks:执行 close 事件回调

4.2 异步编程模式

回调函数:

const fs = require('fs');

function readFileCallback(err, data) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
}

fs.readFile('example.txt', 'utf8', readFileCallback);

Promise:

const fs = require('fs').promises;

fs.readFile('example.txt', 'utf8')
  .then(data => {
    console.log(data);
  })
  .catch(err => {
    console.error(err);
  });

async/await:

const fs = require('fs').promises;

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

readFileAsync();

4.3 Stream(流)

使用 Stream 读取大文件:

const fs = require('fs');

// 创建可读流
const readableStream = fs.createReadStream('large-file.txt', 'utf8');

// 监听数据事件
readableStream.on('data', (chunk) => {
  console.log('接收到数据块:', chunk.length);
});

// 监听结束事件
readableStream.on('end', () => {
  console.log('文件读取完成');
});

// 监听错误事件
readableStream.on('error', (err) => {
  console.error('读取错误:', err);
});

使用 Stream 复制文件:

const fs = require('fs');

// 创建可读流和可写流
const readableStream = fs.createReadStream('source.txt');
const writableStream = fs.createWriteStream('destination.txt');

// 管道操作
readableStream.pipe(writableStream);

// 监听完成事件
writableStream.on('finish', () => {
  console.log('文件复制完成');
});

4.4 子进程

创建子进程:

const { spawn } = require('child_process');

// 执行 ls 命令
const ls = spawn('ls', ['-la']);

// 监听 stdout 事件
ls.stdout.on('data', (data) => {
  console.log(`标准输出: ${data}`);
});

// 监听 stderr 事件
ls.stderr.on('data', (data) => {
  console.error(`标准错误: ${data}`);
});

// 监听退出事件
ls.on('close', (code) => {
  console.log(`子进程退出码: ${code}`);
});

执行 shell 命令:

const { exec } = require('child_process');

exec('ls -la', (err, stdout, stderr) => {
  if (err) {
    console.error(`执行错误: ${err}`);
    return;
  }
  console.log(`标准输出: ${stdout}`);
  if (stderr) {
    console.error(`标准错误: ${stderr}`);
  }
});

五、实际应用场景

5.1 构建 RESTful API

使用 Express.js 构建 RESTful API:

// 首先安装 Express
// npm install express

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

// 中间件
app.use(express.json());

// 模拟数据
let users = [
  { id: 1, name: '张三', email: 'zhangsan@example.com' },
  { id: 2, name: '李四', email: 'lisi@example.com' }
];

// GET /users - 获取所有用户
app.get('/users', (req, res) => {
  res.json(users);
});

// GET /users/:id - 获取单个用户
app.get('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const user = users.find(user => user.id === id);
  if (!user) {
    return res.status(404).json({ message: '用户不存在' });
  }
  res.json(user);
});

// POST /users - 创建用户
app.post('/users', (req, res) => {
  const newUser = {
    id: users.length + 1,
    name: req.body.name,
    email: req.body.email
  };
  users.push(newUser);
  res.status(201).json(newUser);
});

// PUT /users/:id - 更新用户
app.put('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const userIndex = users.findIndex(user => user.id === id);
  if (userIndex === -1) {
    return res.status(404).json({ message: '用户不存在' });
  }
  users[userIndex] = {
    ...users[userIndex],
    ...req.body
  };
  res.json(users[userIndex]);
});

// DELETE /users/:id - 删除用户
app.delete('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const userIndex = users.findIndex(user => user.id === id);
  if (userIndex === -1) {
    return res.status(404).json({ message: '用户不存在' });
  }
  users.splice(userIndex, 1);
  res.json({ message: '用户删除成功' });
});

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

5.2 构建实时应用

使用 Socket.IO 构建实时聊天应用:

// 首先安装 Socket.IO
// npm install express socket.io

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);
const port = 3000;

// 提供静态文件
app.use(express.static('public'));

// 监听连接事件
io.on('connection', (socket) => {
  console.log('新用户连接');
  
  // 监听聊天消息
  socket.on('chat message', (msg) => {
    console.log('消息: ' + msg);
    // 广播消息给所有用户
    io.emit('chat message', msg);
  });
  
  // 监听断开连接事件
  socket.on('disconnect', () => {
    console.log('用户断开连接');
  });
});

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

创建客户端 HTML 文件:

<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Socket.IO 聊天</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font: 13px Helvetica, Arial; }
    form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
    form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
    form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
    #messages { list-style-type: none; margin: 0; padding: 0; }
    #messages li { padding: 5px 10px; }
    #messages li:nth-child(odd) { background: #eee; }
  </style>
</head>
<body>
  <ul id="messages"></ul>
  <form action="">
    <input id="m" autocomplete="off" /><button>发送</button>
  </form>
  <script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
  <script>
    const socket = io();
    
    document.querySelector('form').addEventListener('submit', (e) => {
      e.preventDefault();
      const message = document.getElementById('m').value;
      socket.emit('chat message', message);
      document.getElementById('m').value = '';
      return false;
    });
    
    socket.on('chat message', (msg) => {
      const li = document.createElement('li');
      li.textContent = msg;
      document.getElementById('messages').appendChild(li);
      window.scrollTo(0, document.body.scrollHeight);
    });
  </script>
</body>
</html>

5.3 构建命令行工具

创建简单的命令行工具:

// 首先安装 commander
// npm install commander

#!/usr/bin/env node

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

const program = new commander.Command();

program
  .version('1.0.0')
  .description('简单的文件操作命令行工具');

// 列出目录内容
program
  .command('ls <directory>')
  .description('列出目录内容')
  .action((directory) => {
    try {
      const files = fs.readdirSync(directory);
      console.log(`目录 ${directory} 中的文件:`);
      files.forEach(file => {
        const stats = fs.statSync(path.join(directory, file));
        const type = stats.isDirectory() ? '目录' : '文件';
        console.log(`${file} (${type})`);
      });
    } catch (err) {
      console.error('错误:', err.message);
    }
  });

// 读取文件内容
program
  .command('cat <file>')
  .description('读取文件内容')
  .action((file) => {
    try {
      const content = fs.readFileSync(file, 'utf8');
      console.log(content);
    } catch (err) {
      console.error('错误:', err.message);
    }
  });

// 写入文件
program
  .command('write <file> <content>')
  .description('写入文件内容')
  .action((file, content) => {
    try {
      fs.writeFileSync(file, content);
      console.log(`成功写入文件 ${file}`);
    } catch (err) {
      console.error('错误:', err.message);
    }
  });

program.parse(process.argv);

在 package.json 中配置:

{
  "name": "my-cli-tool",
  "version": "1.0.0",
  "description": "简单的文件操作命令行工具",
  "bin": {
    "my-cli": "./cli.js"
  },
  "dependencies": {
    "commander": "^8.0.0"
  }
}

安装并使用:

# 全局安装
npm install -g .

# 使用命令
my-cli ls .
my-cli cat example.txt
my-cli write output.txt "Hello, CLI!"

六、性能优化建议

6.1 代码优化

  1. 使用异步 I/O:避免使用同步 I/O 操作,特别是在处理并发请求时
  2. 合理使用缓存:对于频繁访问的数据,使用缓存减少 I/O 操作
  3. 优化数据库查询:使用索引,避免全表扫描
  4. 使用 Stream 处理大文件:避免一次性加载大文件到内存
  5. 合理使用模块:按需加载模块,避免一次性加载所有模块

6.2 应用架构优化

  1. 使用集群模式:利用多核 CPU
  2. 负载均衡:在多服务器之间分配请求
  3. 微服务架构:将应用拆分为多个独立的服务
  4. CDN 加速:使用 CDN 分发静态资源
  5. 数据库优化:使用连接池,合理设计数据库结构

6.3 代码优化示例

使用异步 I/O:

// 不好的做法:使用同步 I/O
const fs = require('fs');

function handleRequest(req, res) {
  const data = fs.readFileSync('large-file.txt'); // 阻塞主线程
  res.end(data);
}

// 好的做法:使用异步 I/O
const fs = require('fs');

function handleRequest(req, res) {
  fs.readFile('large-file.txt', (err, data) => { // 非阻塞
    if (err) {
      res.statusCode = 500;
      res.end('服务器错误');
      return;
    }
    res.end(data);
  });
}

使用集群模式:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`);
  
  // 衍生工作进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  cluster.on('exit', (worker) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
    // 重启工作进程
    cluster.fork();
  });
} else {
  // 工作进程创建 HTTP 服务器
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('Hello World\n');
  }).listen(8000);
  
  console.log(`工作进程 ${process.pid} 已启动`);
}

七、常见问题与解决方案

7.1 内存泄漏

问题:应用运行一段时间后内存使用量不断增加

解决方案

  • 使用 --expose-gc 参数启动应用,手动触发垃圾回收
  • 使用 node-heapdump 生成堆快照,分析内存使用情况
  • 避免全局变量积累
  • 正确关闭数据库连接和文件句柄
  • 使用 weakmap 存储临时对象

7.2 回调地狱

问题:多层嵌套的回调函数,代码可读性差

解决方案

  • 使用 Promise
  • 使用 async/await
  • 使用 async.js 等库

示例:

// 回调地狱
fs.readFile('file1.txt', (err1, data1) => {
  if (err1) throw err1;
  fs.readFile('file2.txt', (err2, data2) => {
    if (err2) throw err2;
    fs.readFile('file3.txt', (err3, data3) => {
      if (err3) throw err3;
      console.log(data1 + data2 + data3);
    });
  });
});

// 使用 Promise
fs.promises.readFile('file1.txt')
  .then(data1 => {
    return Promise.all([data1, fs.promises.readFile('file2.txt')]);
  })
  .then(([data1, data2]) => {
    return Promise.all([data1, data2, fs.promises.readFile('file3.txt')]);
  })
  .then(([data1, data2, data3]) => {
    console.log(data1 + data2 + data3);
  })
  .catch(err => {
    console.error(err);
  });

// 使用 async/await
async function readFiles() {
  try {
    const data1 = await fs.promises.readFile('file1.txt');
    const data2 = await fs.promises.readFile('file2.txt');
    const data3 = await fs.promises.readFile('file3.txt');
    console.log(data1 + data2 + data3);
  } catch (err) {
    console.error(err);
  }
}

readFiles();

7.3 模块版本冲突

问题:不同依赖包要求相同模块的不同版本

解决方案

  • 使用 npm ls 查看依赖树
  • 使用 npm dedupe 减少重复依赖
  • 在 package.json 中使用 resolutions 字段指定版本
  • 考虑使用 yarn 或 pnpm 作为包管理器

7.4 端口被占用

问题:启动应用时提示端口已被占用

解决方案

  • 使用 lsof -i :端口号 查看占用端口的进程
  • 使用 kill 进程ID 终止占用端口的进程
  • 修改应用使用的端口

八、Node.js 与其他技术的比较

8.1 Node.js vs PHP

特性 Node.js PHP
语言 JavaScript PHP
执行模型 事件驱动,非阻塞 I/O 同步,阻塞 I/O
性能 高性能,适合并发请求 中等,并发处理能力较弱
生态系统 NPM,丰富的包 Composer,丰富的包
适用场景 实时应用,API 服务,微服务 传统 Web 应用,内容管理系统
学习曲线 中等,需要理解异步编程 低,语法简单

8.2 Node.js vs Python

特性 Node.js Python
语言 JavaScript Python
执行模型 事件驱动,非阻塞 I/O 同步,阻塞 I/O(但有异步库)
性能 高性能,适合并发请求 中等,计算密集型任务性能较好
生态系统 NPM,前端友好 Pip,数据科学和机器学习库丰富
适用场景 实时应用,API 服务,微服务 数据科学,机器学习,后端服务
学习曲线 中等,需要理解异步编程 低,语法简洁易读

8.3 Node.js vs Java

特性 Node.js Java
语言 JavaScript Java
执行模型 事件驱动,非阻塞 I/O 多线程,阻塞 I/O
性能 高性能,适合 I/O 密集型任务 高性能,适合计算密集型任务
生态系统 NPM,轻量级 Maven/Gradle,企业级库丰富
适用场景 实时应用,API 服务,微服务 企业级应用,大型系统
学习曲线 中等,需要理解异步编程 高,语法和生态系统复杂

九、参考资源

9.1 官方资源

9.2 学习资源

9.3 工具与框架

Web 框架:

  • Express.js - 轻量级 Web 框架
  • Koa.js - 由 Express 团队开发的下一代 Web 框架
  • NestJS - 基于 TypeScript 的企业级框架
  • Fastify - 高性能 Web 框架

数据库 ORM:

工具:

十、总结

Node.js 是一个强大的 JavaScript 运行时,它基于 Chrome V8 引擎,提供了异步非阻塞 I/O 和事件驱动的编程模型,使 JavaScript 能够在服务器端运行。Node.js 的出现彻底改变了 JavaScript 的应用范围,使它成为一种全栈开发语言。

Node.js 的核心优势在于:

  1. 异步非阻塞 I/O:提高应用性能和并发处理能力
  2. 事件驱动:基于事件的编程模型,适合处理并发请求
  3. 跨平台:可以在 Windows、macOS、Linux 等平台上运行
  4. NPM 生态系统:丰富的第三方包,加速开发
  5. 高性能:基于 V8 引擎,执行 JavaScript 代码速度快

Node.js 适合开发各种类型的应用,包括:

  • API 服务:高性能的 RESTful API
  • 实时应用:聊天应用、游戏服务器、协作工具
  • 微服务:轻量级的服务架构
  • 命令行工具:自动化脚本和工具
  • 前端构建工具:webpack、gulp 等

通过本教程的学习,你应该已经掌握了 Node.js 的基本使用方法和高级特性,可以开始在项目中应用它来构建各种类型的应用了。随着实践经验的积累,你会发现 Node.js 不仅是一种技术,更是一种思维方式,它鼓励开发者采用异步、事件驱动的编程模式,从而构建出更加高效、可扩展的应用。

Node.js 的生态系统在不断发展壮大,新的框架和工具不断涌现,为开发者提供了更多的选择和便利。作为一名开发者,保持学习的态度,关注 Node.js 的最新发展,将会使你在技术道路上不断前进。

« 上一篇 React Spring 教程 - 基于物理的 React 动画库 下一篇 » Fastify 教程 - 高性能的 Node.js Web 框架