Node.js
Node.js 是基于 Chrome V8 引擎的 JavaScript 运行时环境,让 JavaScript 可以在服务端运行。它的事件驱动、非阻塞 I/O 模型使其非常适合构建高并发的网络应用。
npm 包管理
npm(Node Package Manager)是 Node.js 的包管理工具,用于安装、管理项目依赖。
语义化版本 (SemVer)
version = 主版本.次版本.补丁版本| 版本规则 | 说明 | 示例 |
|---|---|---|
^1.2.3 | 兼容次版本更新 | 1.x.x |
~1.2.3 | 兼容补丁更新 | 1.2.x |
1.2.3 | 精确版本 | 固定版本 |
主版本号变更意味着不兼容的 API 修改,次版本号变更意味着向下兼容的功能新增,补丁版本号变更意味着向下兼容的问题修正。
常用命令
| 命令 | 说明 |
|---|---|
npm init | 初始化项目,生成 package.json |
npm install | 安装所有依赖 |
npm install <pkg> | 安装指定包 |
npm install -g <pkg> | 全局安装 |
npm uninstall <pkg> | 卸载包 |
npm update <pkg> | 更新包 |
npm run <script> | 运行脚本 |
常用 npm 包
| 包 | 用途 |
|---|---|
express | Web 框架 |
body-parser | 请求体解析 |
cookie-parser | Cookie 解析 |
multer | 文件上传 |
express-session | Session 管理 |
ejs / art-template | 模板引擎 |
superagent | HTTP 请求库 |
cheerio | 服务端 HTML 解析 |
异步编程
Node.js 的核心特性是异步非阻塞 I/O。理解异步编程模式是掌握 Node.js 的关键。
回调地狱问题
多层嵌套的回调函数会导致代码难以维护,这被称为"回调地狱":
// 回调地狱
fs.readFile('a.json', (err, data) => {
fs.readFile('b.json', (err, data) => {
fs.readFile('c.json', (err, data) => {
// 嵌套太深,难以维护
});
});
});Promise 模式
Promise 是解决回调地狱的一种方式,它代表一个异步操作的最终完成或失败:
const promise = new Promise((resolve, reject) => {
fs.readFile('a.json', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
promise
.then(data => console.log(data))
.catch(err => console.error(err));Promise 的三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。状态一旦改变就不可逆。
async/await 现代写法
async/await 是基于 Promise 的语法糖,让异步代码看起来像同步代码:
async function fetchData() {
try {
const res = await fetch('https://api.example.com');
const data = await res.json();
return data;
} catch (err) {
console.error(err);
}
}async 函数总是返回 Promise,await 只能在 async 函数内部使用。当 await 遇到错误时会抛出异常,可以用 try/catch 捕获。
eventproxy 并发控制
eventproxy 通过事件机制控制多个异步任务的并发执行:
const eventproxy = require('eventproxy');
const ep = new eventproxy();
// 注册事件:所有事件触发后执行回调
ep.all('event1', 'event2', 'event3', (data1, data2, data3) => {
console.log('所有请求完成');
});
// 触发事件
superagent.get('url1').end((err, res) => {
ep.emit('event1', res.body);
});async 库
async 库提供了多种流程控制方法:
| 方法 | 说明 |
|---|---|
series(tasks, callback) | 串行执行 |
parallel(tasks, callback) | 并行执行 |
waterfall(tasks, callback) | 串行,下一个依赖上一个结果 |
map(arr, iterator, callback) | 并行处理数组 |
each(arr, iterator, callback) | 遍历处理 |
const async = require('async');
async.series([
(callback) => {
callback(null, 'result1');
},
(callback) => {
callback(null, 'result2');
}
], (err, results) => {
console.log(results); // ['result1', 'result2']
});作用域与闭包
this 指向
普通函数的 this 指向调用者,箭头函数的 this 指向定义时的外层作用域:
// 普通函数:this 指向调用者
function greet() {
console.log(this.name);
}
const obj = { name: 'Alice' };
greet.call(obj); // 'Alice'
// 箭头函数:this 指向定义时的外层
const greet = () => console.log(this.name);var vs let/const
| 特性 | var | let/const |
|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 |
| 变量提升 | 声明提升,值为 undefined | 不提升 |
| 重复声明 | 允许 | 不允许 |
var 的函数作用域会导致经典的循环闭包问题:
// var 问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(var 是函数作用域)
// let 解决
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2(let 是块级作用域)闭包
闭包是指函数能够访问其外部作用域中的变量,即使外部函数已经执行完毕:
function counter() {
let count = 0;
return () => ++count;
}
const add = counter();
add(); // 1
add(); // 2闭包的常见用途:数据私有化、函数工厂、柯里化。
IIFE 立即执行函数
// 旧写法:避免全局污染
(function() {
var local = '私有变量';
})();
// 现代写法:直接用块级作用域
{
let local = '私有变量';
}正则表达式
常用正则
| 正则 | 匹配 |
|---|---|
\d+ | 数字 |
\w+ | 字母、数字、下划线 |
[a-zA-Z] | 字母 |
^start | 以 start 开头 |
end$ | 以 end 结尾 |
a{3,5} | 3-5 个 a |
JavaScript 正则
// 创建
const regex = /pattern/flags;
const regex2 = new RegExp('pattern', 'flags');
// 方法
'Hello World'.match(/\w+/g); // ['Hello', 'World']
'Hello'.replace(/l/g, 'x'); // 'Hexxo'
'Hello'.search(/world/i); // -1(未找到)
// 标志
const r = /test/gi; // g=全局,i=忽略大小写性能测试
使用 benchmark 库进行性能基准测试:
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite();
suite.add('for loop', function() {
let sum = 0;
for (let i = 0; i < 1000; i++) {
sum += i;
}
})
.add('while loop', function() {
let sum = 0, i = 0;
while (i < 1000) {
sum += i++;
}
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest: ' + this.filter('fastest').map('name'));
})
.run({ async: true });最佳实践
代码规范
| 规范 | 说明 |
|---|---|
| 缩进 | 2 空格 |
| 分号 | 语句结尾加 ; |
| 变量 | 优先使用 const,需要变重用 let |
| 字符串 | 优先使用单引号 |
| 命名 | camelCase(变量)、PascalCase(类) |
错误处理
// 同步错误
try {
JSON.parse('invalid');
} catch (err) {
console.error(err);
}
// 异步错误
asyncOperation((err, result) => {
if (err) {
console.error(err);
return;
}
});
// async/await 错误
try {
const result = await asyncOperation();
} catch (err) {
console.error(err);
}现代写法
本课程写于 2015 年,现在更推荐使用:
async/await替代回调const/let替代var- ES6+ 语法
适用场景
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| Web API 服务 | Express + async/await | 中间件模式,生态成熟 |
| 实时应用 | Socket.io + 事件驱动 | 非阻塞 I/O,高并发 |
| 命令行工具 | Node.js 原生 | 跨平台,npm 生态丰富 |
| 前端构建 | Webpack/Vite + Node.js | 构建工具基于 Node.js |
FAQ
Q: 回调、Promise、async/await 该用哪个? A: 新项目统一使用 async/await。回调是最早的异步模式,存在回调地狱问题;Promise 通过链式调用解决了嵌套问题,但 .then().then() 仍然不够直观;async/await 让异步代码看起来像同步代码,可读性最好。三者可以混用,因为 async/await 本质是 Promise 的语法糖。
Q: var、let、const 该用哪个? A: 默认用 const,需要重新赋值时用 let,永远不要用 var。var 是函数作用域,存在变量提升问题,容易导致 bug(如循环闭包问题)。const 和 let 是块级作用域,更符合直觉。即使声明对象或数组,也优先用 const(可以修改属性,不能重新赋值)。
Q: Node.js 适合做什么,不适合做什么? A: 适合:I/O 密集型应用(Web 服务器、API 服务、实时应用)、命令行工具、前端构建工具。不适合:CPU 密集型计算(图像处理、视频编码、机器学习),因为单线程模型会阻塞事件循环。CPU 密集型任务可以用 Worker Threads 或子进程处理。