Web API 与 Node 基础
存储 API 对比
| API |
容量 |
作用域 |
过期 |
每次请求发送 |
API 易用性 |
同步/异步 |
| Cookie |
4KB |
同源 + path |
手动设置 expires/max-age |
是(自动) |
差(字符串解析) |
同步 |
| localStorage |
5~10MB |
同源(协议+域名+端口) |
永不过期(需手动清除) |
否 |
好(key-value) |
同步 |
| sessionStorage |
5~10MB |
同源 + 同标签页 |
标签页关闭即清除 |
否 |
好(key-value) |
同步 |
| IndexedDB |
视磁盘空间(通常 >= 250MB) |
同源 |
永不过期(手动清除) |
否 |
中(事务+游标) |
异步 |
使用场景总结
| 场景 |
推荐方案 |
原因 |
| 会话标识(session) |
Cookie(httpOnly) |
自动发送,防 XSS 窃取 |
| 用户偏好(主题/语言) |
localStorage |
简单 key-value,无需发送到服务端 |
| 表单草稿(单页面) |
sessionStorage |
关闭即清理 |
| 大量结构化数据(离线数据) |
IndexedDB |
索引检索、大容量、事务支持 |
| 离线请求缓存 |
Cache API |
与 Service Worker 配合,HTTP 请求级缓存 |
Cache API
// Service Worker 中缓存资源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/app.js',
]);
})
);
});
// 拦截请求,优先从缓存返回
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
return cachedResponse || fetch(event.request);
})
);
});
通信 API
WebSocket
Client Server
│ │
│ HTTP Upgrade Request (101) │
│ ─────────────────────────────────► │
│ 101 Switching Protocols │
│ ◄───────────────────────────────── │
│ │
│ 双向全双工通信 │
│ ◄══════════════════════════════► │
│ (消息帧,无 HTTP 头开销) │
const ws = new WebSocket('wss://api.example.com/ws');
// 心跳 keepalive
let heartbeatTimer;
ws.onopen = () => {
heartbeatTimer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000); // 30 秒心跳
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'pong') return; // 心跳响应
handleMessage(data);
};
ws.onclose = () => {
clearInterval(heartbeatTimer);
// 重连机制(指数退避)
setTimeout(() => {
reconnect();
}, Math.min(1000 * 2 ** retryCount, 30000));
};
ws.onerror = (err) => {
console.error('WebSocket error:', err);
};
| 特性 |
说明 |
| 协议 |
ws://(明文)/ wss://(TLS 加密) |
| 握手 |
通过 HTTP Upgrade 请求(101 状态码)协商升级 |
| 通信 |
双向全双工,不需要客户端轮询 |
| 心跳 |
检测连接断开,通常 30s~60s 发送 ping/pong |
| 重连 |
断线后指数退避重连(1s, 2s, 4s, 8s... 最大 30s) |
Server-Sent Events(SSE)
// 客户端
const eventSource = new EventSource('/api/events');
eventSource.onmessage = (event) => {
console.log('收到消息:', event.data);
};
eventSource.addEventListener('custom-event', (event) => {
console.log('自定义事件:', event.data);
});
eventSource.onerror = () => {
console.log('连接断开(浏览器会自动重连)');
};
# 服务端响应
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: {"message": "hello"}
event: custom-event
data: {"type": "notification"}
| 特性 |
SSE |
WebSocket |
| 方向 |
服务端 → 客户端单向 |
双向全双工 |
| 协议 |
HTTP(标准) |
独立协议(ws://) |
| 自动重连 |
浏览器原生支持 |
需手动实现 |
| 消息格式 |
text/event-stream |
任意(文本/二进制帧) |
| 连接数 |
HTTP/2 多路复用单连接 |
每个连接占用一个 socket |
| 适用场景 |
通知流、实时价格、日志推送 |
聊天、游戏、协同编辑 |
WebRTC(P2P)
Alice STUN/TURN Server Bob
│ │ │
│── SDP Offer ──────────────────► │── 转发 Offer ────────► │
│◄─ SDP Answer ────────────────── │◄── 转发 Answer ──────── │
│ │ │
│◄══════ ICE 穿透 (STUN) ═══════► │◄════ ICE 穿透 ═══════► │
│ │ │
│◄══════════════ P2P 直连 ═══════════════════════════════► │
│ (NAT 穿透失败时走 TURN 中继) │
| 组件 |
作用 |
| 信令(Signaling) |
交换 SDP(媒体元数据)和 ICE Candidate(网络信息),不属 WebRTC 标准 |
| SDP |
Session Description Protocol,描述媒体类型/编码/IP/端口 |
| STUN |
发现公网 IP 和端口,辅助 NAT 穿透 |
| TURN |
中继转发(P2P 穿透失败时的兜底方案) |
| DataChannel |
任意数据的 P2P 通道(文件/消息),非音视频 |
Fetch API
// 超时取消(AbortController)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('/api/data', {
signal: controller.signal,
});
clearTimeout(timeoutId);
} catch (err) {
if (err.name === 'AbortError') {
console.log('请求超时');
}
}
// 流式响应读取
const response = await fetch('/api/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
processChunk(chunk);
}
// 请求/响应拦截模式
fetch = ((originalFetch) => {
return (...args) => {
// 请求拦截:添加 token
args[1] = args[1] || {};
args[1].headers = {
...args[1].headers,
'Authorization': `Bearer ${getToken()}`,
};
return originalFetch(...args).then(async (response) => {
// 响应拦截:401 自动刷新
if (response.status === 401) {
await refreshToken();
return originalFetch(...args);
}
return response;
});
};
})(fetch);
Fetch vs XMLHttpRequest
| 对比项 |
Fetch |
XMLHttpRequest |
| Promise 原生 |
是 |
否(需手动封装) |
| 流式响应 |
是(response.body.getReader()) |
否(responseText 一次性返回) |
| 请求取消 |
AbortController |
xhr.abort() |
| body 只读 |
是(不可重复读取) |
否(responseText 可多次读取) |
| 进度监听 |
否(需 ReadableStream 自行实现) |
是(xhr.onprogress) |
| 跨域 Cookie |
需 credentials: 'include' |
默认带同源 Cookie |
| 超时 |
AbortController 实现 |
xhr.timeout 原生 |
浏览器能力
Notifications API
// 请求权限
const permission = await Notification.requestPermission();
if (permission === 'granted') {
// 创建通知
new Notification('新消息', {
body: '您有一条新通知',
icon: '/icon.png',
tag: 'unique-tag', // 相同 tag 替换旧通知
});
}
// Service Worker 离线推送
self.registration.showNotification('离线消息', {
body: '内容已在后台同步',
actions: [
{ action: 'view', title: '查看' },
{ action: 'dismiss', title: '忽略' },
],
});
Geolocation API
navigator.geolocation.getCurrentPosition(
(position) => {
console.log('纬度:', position.coords.latitude);
console.log('经度:', position.coords.longitude);
console.log('精度:', position.coords.accuracy); // 米
},
(error) => {
// 1: PERMISSION_DENIED
// 2: POSITION_UNAVAILABLE
// 3: TIMEOUT
},
{ enableHighAccuracy: true, timeout: 5000, maximumAge: 60000 }
);
Clipboard API
// 写入剪贴板
await navigator.clipboard.writeText('要复制的文本');
// 读取剪贴板(需权限)
const text = await navigator.clipboard.readText();
// 写入图片
const blob = new Blob(['<svg>...</svg>'], { type: 'image/svg+xml' });
await navigator.clipboard.write([
new ClipboardItem({ 'image/svg+xml': blob })
]);
File API / Blob
// 文件预览
const url = URL.createObjectURL(file);
img.src = url;
// 释放
URL.revokeObjectURL(url);
// FileReader 读取文件
const reader = new FileReader();
reader.onload = (e) => {
console.log(e.target.result); // ArrayBuffer 或 data URL
};
reader.readAsArrayBuffer(file);
reader.readAsDataURL(file);
reader.readAsText(file, 'utf-8');
// ArrayBuffer ↔ Blob
const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });
const buffer = await blob.arrayBuffer();
Web Crypto API
// SHA-256 哈希
const encoder = new TextEncoder();
const data = encoder.encode('待哈希数据');
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
// 安全随机数
const array = new Uint32Array(10);
crypto.getRandomValues(array); // 安全随机(比 Math.random 安全)
Idle Until Urgent
// requestAnimationFrame — 每一帧开始前执行(动画)
function animate() {
element.style.transform = `translateX(${x}px)`;
x += 1;
requestAnimationFrame(animate);
}
// requestIdleCallback — 帧空闲时执行(非关键任务)
requestIdleCallback(
(deadline) => {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
processTask(tasks.shift()); // 每帧时间片内执行,不抢主线程
}
if (tasks.length > 0) {
requestIdleCallback(processTasks); // 未完成,下一帧继续
}
},
{ timeout: 2000 } // 最多等 2 秒,超时后下次空闲强制执行
);
| API |
执行时机 |
适用场景 |
requestAnimationFrame |
下一帧绘制前 |
动画、DOM 变更 |
requestIdleCallback |
帧空闲时(每帧剩余时间) |
分析上报、非关键数据处理 |
Node 基础
事件循环
┌──────────────────────────┐
│ timers │ ← setTimeout / setInterval 回调
└──────────┬───────────────┘
┌──────────▼───────────────┐
│ pending callbacks │ ← 上一轮循环未完成的 I/O 回调
└──────────┬───────────────┘
┌──────────▼───────────────┐
│ idle, prepare │ ← 内部使用
└──────────┬───────────────┘
┌──────────▼───────────────┐
│ poll │ ← I/O 轮询(获取新的事件、执行 I/O 回调)
└──────────┬───────────────┘
┌──────────▼───────────────┐
│ check │ ← setImmediate 回调
└──────────┬───────────────┘
┌──────────▼───────────────┐
│ close callbacks │ ← close 事件(socket.on('close'))
└──────────────────────────┘
每阶段切换前执行:
- process.nextTick(优先级最高,每个阶段切换时执行)
- 微任务(Promise.then / queueMicrotask)
// 执行顺序验证
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
// 输出: nextTick → promise → timeout → immediate
// (nextTick 和微任务在每阶段切换前执行,setTimeout 在 timers 阶段,setImmediate 在 check 阶段)
| 任务类型 |
执行阶段 |
优先级 |
process.nextTick |
每阶段切换前 |
最高 |
| Promise / queueMicrotask |
每阶段切换前 |
高 |
| setTimeout(fn, 0) |
timers 阶段 |
中 |
| setImmediate |
check 阶段 |
中 |
| I/O 回调 |
poll 阶段 |
低 |
模块系统
| 特性 |
CJS(CommonJS) |
ESM(ES Module) |
| 导入 |
require() |
import |
| 导出 |
module.exports / exports |
export / export default |
| 加载 |
同步 |
异步 |
| 文件扩展 |
.js(默认) / .cjs |
.mjs / .js("type": "module") |
| 路径 |
支持动态路径 |
静态 import(动态可用 import()) |
| 循环依赖 |
返回未完成的 exports |
引入时已就绪的引用 |
// package.json type 字段
{
"type": "module" // .js 按 ESM 解析
// "type": "commonjs" // 默认,.js 按 CJS 解析
}
核心模块
| 模块 |
用途 |
关键 API |
fs |
文件系统 |
readFile / writeFile / createReadStream / watch / access |
http |
HTTP 服务器/客户端 |
createServer / request |
path |
路径处理 |
join / resolve / basename / extname |
Buffer |
二进制数据处理 |
Buffer.from / alloc / toString |
stream |
流式数据 |
Readable / Writable / Transform / Duplex / pipeline |
child_process |
子进程 |
spawn / exec / fork |
cluster |
多核集群 |
cluster.fork / cluster.isPrimary |
worker_threads |
多线程 |
Worker / SharedArrayBuffer |
Stream
const { Readable, Writable, Transform, pipeline } = require('stream');
// 可读流 → 转换流 → 可写流
pipeline(
fs.createReadStream('input.txt'), // Readable
new Transform({ // Transform(双工)
transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
}),
fs.createWriteStream('output.txt'), // Writable
(err) => {
if (err) console.error('Pipeline failed', err);
else console.log('Pipeline succeeded');
}
);
背压处理(Backpressure):
- 可读流读取速度 > 可写流写入速度时,
readable.push() 返回 false → 暂停读取
- 可写流 drain 事件触发后恢复
pipeline() 自动处理背压,pipe() 也会但需谨慎
Cluster(多核利用)
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isPrimary) {
// 主进程:fork 子进程
for (let i = 0; i < numCPUs; i++) {
const worker = cluster.fork();
}
// 进程间通信
Object.values(cluster.workers).forEach(worker => {
worker.send({ msg: 'hello from master' });
worker.on('message', (msg) => {
console.log(`Worker ${worker.id}:`, msg);
});
});
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork(); // 自动重启
});
} else {
// 子进程:处理请求
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello');
}).listen(8000);
}
Worker Threads(真正的多线程)
const { Worker, parentPort, isMainThread, workerData, SharedArrayBuffer } = require('worker_threads');
if (isMainThread) {
// 主线程
const sharedBuffer = new SharedArrayBuffer(4);
const view = new Int32Array(sharedBuffer);
const worker = new Worker(__filename, {
workerData: { sharedBuffer }
});
worker.on('message', (msg) => console.log(msg));
Atomics.wait(view, 0, 0); // 阻塞等待
} else {
// Worker 线程
const { sharedBuffer } = workerData;
const view = new Int32Array(sharedBuffer);
view[0] = 42;
Atomics.notify(view, 0); // 唤醒主线程
parentPort.postMessage('计算完成');
}
| 方案 |
进程/线程 |
内存 |
通信 |
适用场景 |
| Cluster |
多进程 |
独立(每进程~30MB+) |
IPC(send/message) |
HTTP 服务器多核分流 |
| Worker Threads |
多线程 |
共享(SharedArrayBuffer) |
MessagePort / 共享内存 |
CPU 密集型任务 |
| Child Process |
多进程 |
独立 |
IPC / stdio |
执行外部命令 |