Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它让 JavaScript 可以在浏览器之外运行。本文将从概念到实践,全面介绍 Node.js,并深入分析它与浏览器 JavaScript 的差异。
一、Node.js 是什么?
1.1 官方定义
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境。
关键点解析:
- 运行时环境(Runtime):提供 JavaScript 执行所需的环境和 API
- 基于 V8 引擎:使用 Google Chrome 的 V8 JavaScript 引擎,执行速度快
- 非浏览器环境:让 JavaScript 脱离浏览器,在服务器端运行
1.2 为什么要创造 Node.js?
在 Node.js 出现之前,JavaScript 只能运行在浏览器中。Ryan Dahl 在 2009 年创造了 Node.js,解决了以下问题:
| 问题 | Node.js 的解决方案 |
|---|
| JavaScript 只能在浏览器运行 | 让 JavaScript 可以在服务器端运行 |
| 传统服务器(如 Apache)高并发性能差 | 基于事件驱动、非阻塞 I/O,高并发性能好 |
| 前后端语言不一致 | 前后端都用 JavaScript,降低学习成本 |
| 线程模型资源消耗大 | 单线程 + 事件循环,资源占用少 |
1.3 Node.js 的核心特性
特性一:单线程
Node.js 使用单线程模型,所有 JavaScript 代码在主线程执行。
优点:
- 避免了多线程的上下文切换开销
- 编程模型简单,没有死锁问题
- 内存占用少
缺点:
- 不适合 CPU 密集型任务
- 一个请求阻塞会影响其他请求
特性二:非阻塞 I/O
Node.js 的 I/O 操作(文件读写、网络请求等)都是非阻塞的。
1 2 3 4 5 6 7 8 9 10 11 12
| const fs = require('fs'); const data = fs.readFileSync('file.txt'); console.log('文件内容:', data); console.log('这行代码等待文件读取完成后才执行');
fs.readFile('file.txt', (err, data) => { if (err) throw err; console.log('文件内容:', data); }); console.log('这行代码立即执行,不等待文件读取');
|
特性三:事件驱动
Node.js 使用事件驱动架构,通过事件循环处理异步操作。
1 2 3 4 5 6 7 8 9
| ┌─────────────────────────────────────────────────────────┐ │ 事件循环 (Event Loop) │ ├─────────────────────────────────────────────────────────┤ │ 1. 执行同步代码 │ │ 2. 遇到异步操作,交给底层系统处理 │ │ 3. 继续执行后续代码 │ │ 4. 异步操作完成,将回调函数放入任务队列 │ │ 5. 事件循环检测任务队列,执行回调函数 │ └─────────────────────────────────────────────────────────┘
|
二、Node.js 的核心概念
2.1 模块系统(CommonJS)
Node.js 使用 CommonJS 模块规范,通过 require 和 module.exports 进行模块化。
导出模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
module.exports = { add, subtract };
exports.multiply = (a, b) => a * b;
|
导入模块
1 2 3 4 5 6 7 8 9 10
| const math = require('./math.js');
console.log(math.add(1, 2)); console.log(math.subtract(5, 3)); console.log(math.multiply(2, 3));
const { add, subtract } = require('./math.js'); console.log(add(1, 2));
|
内置模块 vs 第三方模块 vs 自定义模块
1 2 3 4 5 6 7 8 9 10 11 12
| const fs = require('fs'); const path = require('path'); const http = require('http');
const express = require('express'); const lodash = require('lodash');
const myModule = require('./myModule'); const utils = require('../utils');
|
2.2 核心模块详解
Node.js 提供了丰富的内置模块,无需安装即可使用。
fs(文件系统)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| const fs = require('fs'); const fsPromises = require('fs').promises;
const data = fs.readFileSync('file.txt', 'utf8');
fs.readFile('file.txt', 'utf8', (err, data) => { if (err) { console.error('读取失败:', err); return; } console.log('文件内容:', data); });
async function readFile() { try { const data = await fsPromises.readFile('file.txt', 'utf8'); console.log('文件内容:', data); } catch (err) { console.error('读取失败:', err); } }
fs.writeFileSync('output.txt', 'Hello Node.js');
fs.appendFileSync('output.txt', '\nNew line');
const exists = fs.existsSync('file.txt');
fs.mkdirSync('new-folder', { recursive: true });
const files = fs.readdirSync('./'); console.log(files);
|
path(路径处理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const path = require('path');
const fullPath = path.join(__dirname, 'folder', 'file.txt');
const parsed = path.parse('/home/user/file.txt'); console.log(parsed);
console.log(path.basename('/home/user/file.txt')); console.log(path.basename('/home/user/file.txt', '.txt'));
console.log(path.dirname('/home/user/file.txt'));
console.log(path.extname('/home/user/file.txt'));
console.log(path.resolve('file.txt'));
console.log(path.relative('/data/orange', '/data/pear'));
|
http(创建 Web 服务器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| const http = require('http');
const server = http.createServer((req, res) => { res.setHeader('Content-Type', 'text/html; charset=utf-8'); if (req.url === '/') { res.statusCode = 200; res.end('<h1>首页</h1>'); } else if (req.url === '/about') { res.statusCode = 200; res.end('<h1>关于我们</h1>'); } else if (req.url === '/api/users') { res.setHeader('Content-Type', 'application/json'); res.statusCode = 200; res.end(JSON.stringify({ users: [ { id: 1, name: '张三' }, { id: 2, name: '李四' } ] })); } else { res.statusCode = 404; res.end('<h1>404 页面不存在</h1>'); } });
const PORT = 3000; server.listen(PORT, () => { console.log(`服务器运行在 http://localhost:${PORT}`); });
|
events(事件触发器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('message', (data) => { console.log('收到消息:', data); });
emitter.once('connect', () => { console.log('连接成功(只执行一次)'); });
emitter.emit('message', 'Hello World'); emitter.emit('connect'); emitter.emit('connect');
const callback = (data) => console.log(data); emitter.on('event', callback); emitter.off('event', callback);
console.log(emitter.listenerCount('message'));
|
stream(流)
流是 Node.js 处理数据的高效方式,特别适合处理大文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const fs = require('fs');
const readStream = fs.createReadStream('large-file.txt', { encoding: 'utf8', highWaterMark: 1024 });
const writeStream = fs.createWriteStream('output.txt');
readStream.pipe(writeStream);
readStream.on('data', (chunk) => { console.log('收到数据块:', chunk.length); });
readStream.on('end', () => { console.log('读取完成'); });
readStream.on('error', (err) => { console.error('读取错误:', err); });
writeStream.write('Hello '); writeStream.write('World'); writeStream.end();
|
2.3 npm 包管理器
npm(Node Package Manager)是 Node.js 的包管理工具,也是世界上最大的软件注册表。
常用命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| npm init npm init -y
npm install lodash npm i lodash
npm install --save-dev nodemon npm i -D nodemon
npm install -g @vue/cli
npm uninstall lodash
npm update lodash
npm install npm i
npm run start npm run build npm test
|
package.json 详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| { "name": "my-project", "version": "1.0.0", "description": "项目描述", "main": "index.js", "scripts": { "start": "node index.js", "dev": "nodemon index.js", "test": "jest" }, "dependencies": { "express": "^4.18.0", "lodash": "~4.17.0" }, "devDependencies": { "nodemon": "^2.0.0", "jest": "^29.0.0" }, "engines": { "node": ">=14.0.0" } }
|
版本号说明:
^4.18.0:兼容次要版本和补丁版本(4.x.x,但不包括 5.0.0)~4.17.0:只兼容补丁版本(4.17.x)4.18.0:固定版本*:最新版本
三、Node.js 实践示例
3.1 创建 RESTful API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| const http = require('http'); const url = require('url');
let users = [ { id: 1, name: '张三', age: 25 }, { id: 2, name: '李四', age: 30 } ];
const server = http.createServer((req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.setHeader('Content-Type', 'application/json'); const parsedUrl = url.parse(req.url, true); const path = parsedUrl.pathname; const method = req.method; if (path === '/api/users' && method === 'GET') { res.statusCode = 200; res.end(JSON.stringify({ success: true, data: users })); } else if (path.match(/\/api\/users\/\d+/) && method === 'GET') { const id = parseInt(path.split('/')[3]); const user = users.find(u => u.id === id); if (user) { res.statusCode = 200; res.end(JSON.stringify({ success: true, data: user })); } else { res.statusCode = 404; res.end(JSON.stringify({ success: false, message: '用户不存在' })); } } else if (path === '/api/users' && method === 'POST') { let body = ''; req.on('data', chunk => body += chunk); req.on('end', () => { const newUser = JSON.parse(body); newUser.id = users.length + 1; users.push(newUser); res.statusCode = 201; res.end(JSON.stringify({ success: true, data: newUser })); }); } else if (path.match(/\/api\/users\/\d+/) && method === 'PUT') { const id = parseInt(path.split('/')[3]); let body = ''; req.on('data', chunk => body += chunk); req.on('end', () => { const updateData = JSON.parse(body); const index = users.findIndex(u => u.id === id); if (index !== -1) { users[index] = { ...users[index], ...updateData }; res.statusCode = 200; res.end(JSON.stringify({ success: true, data: users[index] })); } else { res.statusCode = 404; res.end(JSON.stringify({ success: false, message: '用户不存在' })); } }); } else if (path.match(/\/api\/users\/\d+/) && method === 'DELETE') { const id = parseInt(path.split('/')[3]); const index = users.findIndex(u => u.id === id); if (index !== -1) { users.splice(index, 1); res.statusCode = 200; res.end(JSON.stringify({ success: true, message: '删除成功' })); } else { res.statusCode = 404; res.end(JSON.stringify({ success: false, message: '用户不存在' })); } } else { res.statusCode = 404; res.end(JSON.stringify({ success: false, message: '接口不存在' })); } });
const PORT = 3000; server.listen(PORT, () => { console.log(`API 服务器运行在 http://localhost:${PORT}`); });
|
3.2 使用 Express 框架
Express 是 Node.js 最流行的 Web 框架。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| const express = require('express'); const app = express();
app.use(express.json());
app.use((req, res, next) => { console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); next(); });
app.get('/', (req, res) => { res.json({ message: '欢迎使用 Express API' }); });
app.get('/api/users', (req, res) => { res.json({ success: true, data: users }); });
app.get('/api/users/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)); if (user) { res.json({ success: true, data: user }); } else { res.status(404).json({ success: false, message: '用户不存在' }); } });
app.post('/api/users', (req, res) => { const newUser = { id: users.length + 1, ...req.body }; users.push(newUser); res.status(201).json({ success: true, data: newUser }); });
app.put('/api/users/:id', (req, res) => { const index = users.findIndex(u => u.id === parseInt(req.params.id)); if (index !== -1) { users[index] = { ...users[index], ...req.body }; res.json({ success: true, data: users[index] }); } else { res.status(404).json({ success: false, message: '用户不存在' }); } });
app.delete('/api/users/:id', (req, res) => { const index = users.findIndex(u => u.id === parseInt(req.params.id)); if (index !== -1) { users.splice(index, 1); res.json({ success: true, message: '删除成功' }); } else { res.status(404).json({ success: false, message: '用户不存在' }); } });
app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ success: false, message: '服务器内部错误' }); });
const PORT = 3000; app.listen(PORT, () => { console.log(`Express 服务器运行在 http://localhost:${PORT}`); });
|
3.3 文件上传服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| const http = require('http'); const fs = require('fs'); const path = require('path');
const server = http.createServer((req, res) => { if (req.url === '/upload' && req.method === 'POST') { const boundary = req.headers['content-type'].split('boundary=')[1]; let data = ''; req.on('data', chunk => { data += chunk; }); req.on('end', () => { const parts = data.split(`--${boundary}`); const filePart = parts.find(p => p.includes('filename=')); if (filePart) { const filenameMatch = filePart.match(/filename="(.+)"/); const filename = filenameMatch ? filenameMatch[1] : 'uploaded-file'; const contentStart = filePart.indexOf('\r\n\r\n') + 4; const contentEnd = filePart.lastIndexOf('\r\n'); const fileContent = filePart.slice(contentStart, contentEnd); const uploadPath = path.join(__dirname, 'uploads', filename); fs.mkdirSync(path.dirname(uploadPath), { recursive: true }); fs.writeFileSync(uploadPath, fileContent, 'binary'); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, message: '文件上传成功', filename: filename })); } }); } else if (req.url === '/' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <!DOCTYPE html> <html> <body> <h2>文件上传</h2> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <button type="submit">上传</button> </form> </body> </html> `); } else { res.writeHead(404); res.end('Not Found'); } });
server.listen(3000, () => { console.log('上传服务器运行在 http://localhost:3000'); });
|
四、Node.js vs 浏览器 JavaScript
4.1 核心差异对比
| 特性 | Node.js | 浏览器 JavaScript |
|---|
| 运行环境 | 服务器端 | 客户端(浏览器) |
| 主要用途 | 构建服务器、工具脚本、CLI | 网页交互、DOM 操作 |
| 模块系统 | CommonJS (require/module.exports) | ES Modules (import/export) |
| 全局对象 | global | window |
| DOM 操作 | ❌ 没有 DOM | ✅ 可以操作 DOM |
| 文件系统 | ✅ 可以读写文件 | ❌ 不能直接访问文件系统 |
| 操作系统 | ✅ 可以调用操作系统 API | ❌ 受限的沙箱环境 |
| 网络请求 | ✅ 可以作为服务器接收请求 | ❌ 只能作为客户端发送请求 |
| 数据库 | ✅ 可以连接数据库 | ❌ 不能直接连接数据库 |
4.2 全局对象差异
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
global.myVar = 'hello'; console.log(global.myVar);
console.log(__dirname); console.log(__filename); console.log(process); console.log(Buffer);
window.myVar = 'hello'; console.log(window.myVar);
console.log(document); console.log(location); console.log(history); console.log(navigator);
|
4.3 模块系统差异
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
const fs = require('fs'); const myModule = require('./myModule');
module.exports = { foo, bar }; exports.baz = baz;
import fs from 'fs'; import { readFile } from 'fs'; import * as utils from './utils';
export const foo = 'bar'; export default myFunction;
|
注意: Node.js 12+ 也支持 ES Modules,但需要将文件后缀改为 .mjs 或在 package.json 中设置 "type": "module"。
4.4 API 差异
Node.js 特有的 API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const fs = require('fs'); fs.readFileSync('file.txt');
const path = require('path'); path.join(__dirname, 'folder', 'file.txt');
const os = require('os'); console.log(os.cpus()); console.log(os.totalmem()); console.log(os.freemem());
process.exit(1); process.env.NODE_ENV; process.argv;
const { exec, spawn } = require('child_process'); exec('ls -la', (err, stdout) => { console.log(stdout); });
|
浏览器特有的 API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| document.getElementById('app'); document.querySelector('.class'); element.addEventListener('click', handler);
window.alert('Hello'); window.localStorage.setItem('key', 'value'); window.location.href = 'https://example.com';
setTimeout(() => {}, 1000); setInterval(() => {}, 1000);
fetch('/api/data') .then(res => res.json()) .then(data => console.log(data));
|
4.5 this 指向差异
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
console.log(this);
function test() { console.log(this); } test();
console.log(this === module.exports);
console.log(this);
function test() { console.log(this); } test();
document.getElementById('btn').addEventListener('click', function() { console.log(this); });
|
4.6 运行环境差异
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
const fs = require('fs'); const os = require('os');
const http = require('http'); http.createServer((req, res) => { res.end('Hello'); }).listen(3000);
const { exec } = require('child_process'); exec('node --version', (err, stdout) => { console.log(stdout); });
document.title = 'New Title'; document.body.style.backgroundColor = 'red';
localStorage.setItem('user', JSON.stringify({ name: '张三' })); const user = JSON.parse(localStorage.getItem('user'));
|
4.7 事件循环差异
虽然 Node.js 和浏览器都使用事件循环,但实现细节有所不同。
浏览器事件循环
1 2 3 4 5 6 7 8 9 10 11 12 13
| ┌─────────────────────────────────────────────────────────┐ │ 浏览器事件循环 │ ├─────────────────────────────────────────────────────────┤ │ 调用栈 (Call Stack) │ │ 微任务队列 (Microtask): Promise.then, MutationObserver │ │ 宏任务队列 (Macrotask): setTimeout, setInterval, I/O │ │ │ │ 流程: │ │ 1. 执行同步代码 │ │ 2. 执行所有微任务 │ │ 3. 执行一个宏任务 │ │ 4. 重复步骤 2-3 │ └─────────────────────────────────────────────────────────┘
|
Node.js 事件循环
1 2 3 4 5 6 7 8 9 10 11 12 13
| ┌─────────────────────────────────────────────────────────┐ │ Node.js 事件循环 │ ├─────────────────────────────────────────────────────────┤ │ 1. timers: setTimeout, setInterval │ │ 2. pending callbacks: 系统操作的回调 │ │ 3. idle, prepare: 内部使用 │ │ 4. poll: 获取新的 I/O 事件 │ │ 5. check: setImmediate │ │ 6. close callbacks: socket.on('close', ...) │ │ │ │ 微任务:process.nextTick, Promise.then │ │ process.nextTick 优先级高于 Promise │ └─────────────────────────────────────────────────────────┘
|
1 2 3 4 5 6 7 8 9 10 11
| setTimeout(() => console.log('setTimeout'), 0); setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick')); Promise.resolve().then(() => console.log('Promise'));
|
五、使用场景对比
5.1 什么时候用 Node.js?
| 场景 | 说明 |
|---|
| Web 服务器 | 构建 API 服务、实时应用(配合 WebSocket) |
| 微服务 | 轻量级、高并发的服务架构 |
| 工具脚本 | 自动化构建、文件处理、代码转换 |
| CLI 工具 | 命令行工具(如 Vue CLI、Create React App) |
| 实时应用 | 聊天室、在线游戏、股票行情等 |
| 流式处理 | 大文件处理、视频流、日志处理 |
| 中间层 | BFF(Backend For Frontend)、代理服务器 |
5.2 什么时候用浏览器 JavaScript?
| 场景 | 说明 |
|---|
| 网页交互 | 表单验证、动画效果、用户交互 |
| DOM 操作 | 动态修改页面内容、样式 |
| 前端框架 | React、Vue、Angular 等框架运行 |
| 数据可视化 | 图表库(ECharts、D3.js) |
| 客户端存储 | LocalStorage、IndexedDB、Cookie |
| 单页应用(SPA) | 路由切换、状态管理 |
六、总结
6.1 Node.js 核心要点
| 要点 | 内容 |
|---|
| 本质 | 基于 V8 引擎的 JavaScript 运行时 |
| 特点 | 单线程、非阻塞 I/O、事件驱动 |
| 模块 | CommonJS 规范(require/module.exports) |
| 核心模块 | fs、path、http、events、stream |
| 包管理 | npm / yarn / pnpm |
| 适用场景 | 服务器、工具脚本、CLI、实时应用 |
6.2 Node.js vs 浏览器 JavaScript 对比表
| 对比项 | Node.js | 浏览器 JavaScript |
|---|
| 运行环境 | 服务器 | 浏览器 |
| 全局对象 | global | window |
| 模块系统 | CommonJS | ES Modules |
| DOM 操作 | ❌ 不支持 | ✅ 支持 |
| 文件系统 | ✅ 支持 | ❌ 不支持 |
| 数据库连接 | ✅ 支持 | ❌ 不支持 |
| 操作系统 API | ✅ 支持 | ❌ 受限 |
| 事件循环 | 6 个阶段 | 宏任务 + 微任务 |
| 主要用途 | 后端服务、工具 | 前端交互、DOM |
6.3 学习建议
- 先学好 JavaScript 基础:Node.js 和浏览器 JS 语法相同,基础通用
- 理解异步编程:Promise、async/await 在两者中都重要
- 掌握模块系统:CommonJS 和 ES Modules 都要了解
- 多写实践项目:从简单的 CLI 工具到 Web 服务器
- 了解核心模块:fs、path、http 是最常用的
Node.js 让 JavaScript 从一门浏览器脚本语言成长为全栈开发语言,掌握 Node.js 意味着你可以用同一种语言完成前后端开发,大大提高开发效率!