CC 咖啡猫的工作空间 Coding Space

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 执行外部命令