Node.js 基础概念
Node.js 是一个基于 Chrome V8 JavaScript 引擎构建的 JavaScript 运行时环境。简单来说,Node.js 让 JavaScript 可以在服务器端运行,而不仅仅局限于浏览器中。
Node.js 的核心特点
1. 单线程事件循环
- Node.js 使用单线程的事件循环模型
- 通过事件驱动和回调函数处理并发请求
- 避免了传统多线程编程中的线程切换开销
2. 非阻塞 I/O
- 所有 I/O 操作(文件读写、网络请求等)都是异步的
- 当等待 I/O 操作完成时,程序不会被阻塞,可以继续处理其他任务
- 大大提高了应用程序的吞吐量
3. 跨平台
- 支持 Windows、macOS、Linux 等多种操作系统
- 一次编写,到处运行
4. 丰富的生态系统
- npm(Node Package Manager)拥有数百万个开源包
- 活跃的开发者社区
与传统服务器端技术的区别
1、传统服务器端技术(如 Apache + PHP):
请求1 → 创建线程1 → 处理请求 → 返回响应 → 销毁线程1 请求2 → 创建线程2 → 处理请求 → 返回响应 → 销毁线程2 请求3 → 创建线程3 → 处理请求 → 返回响应 → 销毁线程3
2、Node.js 的处理方式:
请求1 → 事件循环 → 处理请求 → 返回响应 请求2 → 事件循环 → 处理请求 → 返回响应 (复用同一线程) 请求3 → 事件循环 → 处理请求 → 返回响应
特性 | 传统多线程模型 | Node.js 单线程模型 |
---|---|---|
内存占用 | 每个线程占用 2MB 左右 | 单线程,内存占用少 |
并发处理 | 线程数量限制并发数 | 事件循环处理高并发 |
上下文切换 | 频繁的线程切换开销 | 无线程切换开销 |
编程复杂度 | 需要处理线程同步 | 避免了锁和线程同步问题 |
适用场景 | CPU 密集型任务 | I/O 密集型任务 |
Node.js 的应用场景
1、适合的应用场景
-
Web 应用程序
- RESTful API 服务
- 单页应用(SPA)的后端服务
- 实时 Web 应用
-
实时应用
- 聊天应用
- 在线游戏
- 协作工具(如在线文档编辑)
-
微服务架构
- 轻量级的微服务
- API 网关
- 服务间通信
-
工具和命令行应用
- 构建工具(如 Webpack、Gulp)
- 脚手架工具
- 自动化脚本
-
物联网(IoT)应用
- 设备数据收集
- 传感器数据处理
2、不适合的应用场景
-
CPU 密集型任务
- 图像/视频处理
- 复杂的数学计算
- 大数据分析
-
需要大量计算的应用
- 机器学习训练
- 科学计算
- 加密货币挖矿
事件驱动和非阻塞 I/O 模型
传统阻塞 I/O 模型示例:
实例
const data1 = readFileSync('file1.txt'); // 等待文件读取完成
const data2 = readFileSync('file2.txt'); // 等待文件读取完成
const data3 = readFileSync('file3.txt'); // 等待文件读取完成
console.log('所有文件读取完成');
Node.js 非阻塞 I/O 模型示例:
实例
const fs = require('fs');
fs.readFile('file1.txt', (err, data1) => {
console.log('文件1读取完成');
});
fs.readFile('file2.txt', (err, data2) => {
console.log('文件2读取完成');
});
fs.readFile('file3.txt', (err, data3) => {
console.log('文件3读取完成');
});
console.log('程序继续执行,不等待文件读取');
事件循环机制:
事件循环是 Node.js 实现非阻塞 I/O 的核心机制:
- 调用栈(Call Stack):执行同步代码
- 事件队列(Event Queue):存储异步操作的回调函数
- 事件循环(Event Loop):监控调用栈和事件队列
┌───────────────────────────┐ ┌─>│ timers │ ← setTimeout, setInterval │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ ← I/O 回调 │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ ← 内部使用 │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ poll │ ← 获取新的 I/O 事件 │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ check │ ← setImmediate 回调 │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ ← 关闭事件回调 └───────────────────────────┘
JavaScript 运行时环境
V8 引擎简介
V8 是 Google 开发的高性能 JavaScript 引擎,也是 Chrome 浏览器的核心组件。
Node.js 使用 V8 引擎来执行 JavaScript 代码。
V8 引擎的特点:
-
即时编译(JIT)
- 将 JavaScript 代码直接编译为机器码
- 无需中间字节码,执行效率更高
-
垃圾收集
- 自动内存管理
- 使用分代垃圾收集算法
-
优化技术
- 内联缓存(Inline Caching)
- 隐藏类(Hidden Classes)
- 动态优化
浏览器 JavaScript vs Node.js JavaScript
虽然都使用 JavaScript 语言,但运行环境的差异导致了一些重要区别:
相同点:
- 都使用相同的 JavaScript 语法
- 都支持 ES6+ 特性
- 都使用 V8 引擎(Chrome 浏览器)
不同点:
特性 | 浏览器 JavaScript | Node.js JavaScript |
---|---|---|
全局对象 | window |
global |
模块系统 | ES6 modules, AMD | CommonJS, ES6 modules |
文件系统访问 | 不可访问 | 完全访问 |
网络请求 | XMLHttpRequest, Fetch | http, https 模块 |
DOM 操作 | 支持 | 不支持 |
进程控制 | 不支持 | 支持 |
浏览器环境示例:
实例
console.log(window); // 全局对象
document.getElementById('app'); // DOM 操作
localStorage.setItem('key', 'value'); // 本地存储
Node.js 环境示例:
实例
console.log(global); // 全局对象
const fs = require('fs'); // 文件系统模块
fs.readFile('data.txt', 'utf8', callback); // 文件操作
全局对象的差异
浏览器中的全局对象:
实例
console.log(this === window); // true
var globalVar = 'hello';
console.log(window.globalVar); // 'hello'
Node.js 中的全局对象:
实例
console.log(this === global); // false (在模块中)
var globalVar = 'hello';
console.log(global.globalVar); // undefined
// Node.js 中的模块作用域
console.log(this); // {} (空对象)
console.log(module.exports === this); // true
Node.js 特有的全局变量:
实例
console.log(__filename); // 当前模块的文件路径
console.log(process); // 进程对象
console.log(Buffer); // 缓冲区构造函数
Node.js 的优势与局限
优势详解
1. 高并发处理能力
传统多线程服务器处理 10,000 个并发连接需要约 20GB 内存(每个线程2MB),而 Node.js 只需要很少的内存就能处理相同数量的连接。
实例
const http = require('http');
const server = http.createServer((req, res) => {
// 模拟异步操作
setTimeout(() => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}, 100);
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000/');
});
// 这个服务器可以同时处理数千个请求而不会阻塞
2. 快速开发
统一的 JavaScript 语言栈使得前后端开发更加高效:
实例
function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
// 前端使用
if (validateEmail(userInput)) {
// 发送到服务器
}
// 后端使用
if (validateEmail(req.body.email)) {
// 保存到数据库
}
3. 统一语言栈的优势
- 代码复用:前后端可以共享工具函数、验证逻辑等
- 团队效率:开发人员可以同时负责前后端开发
- 技术栈简化:减少学习成本和技术复杂度
4. 丰富的 npm 生态系统
# npm 提供了数百万个包 npm search express # 搜索包 npm install express # 安装包 npm update # 更新包
局限性分析
1. CPU 密集型任务的性能问题
实例
function fibonacciSync(n) {
if (n < 2) return n;
return fibonacciSync(n - 1) + fibonacciSync(n - 2);
}
// 这会阻塞整个应用
console.log(fibonacciSync(40)); // 阻塞数秒
// 解决方案:使用 Worker Threads 或将任务分解
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if (isMainThread) {
// 主线程
const worker = new Worker(__filename, {
workerData: { n: 40 }
});
worker.on('message', (result) => {
console.log('结果:', result);
});
} else {
// Worker 线程
const result = fibonacciSync(workerData.n);
parentPort.postMessage(result);
}
2. 回调地狱问题
实例
fs.readFile('file1.txt', (err, data1) => {
if (err) throw err;
fs.readFile('file2.txt', (err, data2) => {
if (err) throw err;
fs.readFile('file3.txt', (err, data3) => {
if (err) throw err;
// 嵌套越来越深...
console.log('所有文件读取完成');
});
});
});
// 现代解决方案:使用 Promise 和 async/await
const fsPromises = require('fs').promises;
async function readFiles() {
try {
const data1 = await fsPromises.readFile('file1.txt');
const data2 = await fsPromises.readFile('file2.txt');
const data3 = await fsPromises.readFile('file3.txt');
console.log('所有文件读取完成');
} catch (err) {
console.error('读取文件失败:', err);
}
}
3. 单线程的脆弱性
实例
setTimeout(() => {
throw new Error('未捕获的异常'); // 这会导致应用崩溃
}, 1000);
// 解决方案:全局异常处理
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
// 记录日志并优雅关闭应用
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise 拒绝:', reason);
// 记录日志并优雅关闭应用
process.exit(1);
});
适用场景分析
1、RESTful API 服务
实例
setTimeout(() => {
throw new Error('未捕获的异常'); // 这会导致应用崩溃
}, 1000);
// 解决方案:全局异常处理
process.on('uncaughtException', (err) => {
console.error('未捕获的异常:', err);
// 记录日志并优雅关闭应用
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise 拒绝:', reason);
// 记录日志并优雅关闭应用
process.exit(1);
});
2、实时应用
实例
const io = require('socket.io')(server);
io.on('connection', (socket) => {
socket.on('chat message', (msg) => {
io.emit('chat message', msg); // 广播消息
});
});
3、中间件和代理服务
实例
const httpProxy = require('http-proxy-middleware');
const proxy = httpProxy({
target: 'http://api.example.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
});