系列文章目录
《JavaScript 基础与进阶笔记》(前期偏基础巩固与常见面试点,后续进入闭包、异步、工程化等进阶主题)
- 第 01 篇:数据类型与类型判断
- 第 02 篇:变量声明与作用域
- 第 03 篇:闭包与高阶函数
- 第 04 篇:函数工厂
- 第 05 篇:this 指向与绑定
- 第 06 篇:原型与原型链
- 第 07 篇:类与继承
- 第 08 篇:JS 执行机制与异步队列
- 第 09 篇:数组常用方法
- 第 10 篇:字符串算法
- 第 11 篇:常见手写题合集(上)
- 第 12 篇:常见手写题合集(下)
- 第 13 篇:Promise 与 async/await
- 第 14 篇:数据结构基础
- 第 15 篇:垃圾回收与内存
- 第 16 篇:DOM 基础全面解析
- 第 17 篇:DOM 性能与渲染
- 第 18 篇:DOM 交互补充
- 第 19 篇:DOM 实战案例
- 第 20 篇:CSS 布局与可视化高频
- 第 21 篇:移动端与 viewport
- 第 22 篇:BOM 核心对象
- 第 23 篇:前端路由原理
- 第 24 篇:浏览器存储对比
- 第 25 篇:网络与跨域
- 第 26 篇:网络请求与实时通道(本文)
文章目录
- 系列文章目录
- 前言
- [一、XHR 与 Fetch](#一、XHR 与 Fetch)
-
- [1.1 对比](#1.1 对比)
- [1.2 Fetch 注意点](#1.2 Fetch 注意点)
- [1.3 取消请求:AbortController](#1.3 取消请求:AbortController)
- 二、实时数据:四种方式对比
- [三、SSE(Server-Sent Events)](#三、SSE(Server-Sent Events))
- 四、WebSocket
-
- [4.1 心跳与重连(工程要点)](#4.1 心跳与重连(工程要点))
- [4.2 `readyState`](#4.2
readyState)
- [五、SSE vs WebSocket(面试口述)](#五、SSE vs WebSocket(面试口述))
- 六、选型口诀
- 七、易混淆点归纳
- 八、思考与练习
- 总结
前言
第 25 篇讲了缓存、跨域与鉴权;本篇聚焦 怎么发请求、怎么收实时数据 :XHR 与 Fetch 的取舍,以及 轮询 / SSE / WebSocket 如何选型。面试常问:Fetch 和 XHR 区别、SSE 和 WebSocket 区别、wss、断线如何重连。
一、XHR 与 Fetch
1.1 对比
| XHR(XMLHttpRequest) | Fetch API | |
|---|---|---|
| API 风格 | 事件回调 / 需封装 Promise | 原生返回 Promise |
| 使用场景 | 老项目、部分库底层 | 现代首选 |
| 请求取消 | xhr.abort() |
AbortController |
| 进度 | onprogress 支持较好 |
需 ReadableStream 等 |
| Cookie | withCredentials |
credentials: 'include' |
| 响应类型 | responseType(json/blob...) |
需 .json() / .text() 等 |
javascript
/* XHR --- 了解即可,新项目少用 */
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/user");
xhr.withCredentials = true;
xhr.onload = () => console.log(JSON.parse(xhr.responseText));
xhr.send();
/* Fetch --- 推荐 */
const res = await fetch("/api/user", { credentials: "include" });
if (!res.ok) throw new Error(res.status);
const data = await res.json();
1.2 Fetch 注意点
- 只有网络错误才 reject ;HTTP 4xx/5xx 仍 resolve ,需判断
res.ok(status 200~299)。 - 默认不带 Cookie ;跨域要带 Cookie 用
credentials: 'include'(配合第 25 篇 CORS)。 - 不会自动发 JSON ;
POST发对象需自己JSON.stringify并设Content-Type。
javascript
const res = await fetch("/api/save", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ name: "Tom" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
1.3 取消请求:AbortController
javascript
const controller = new AbortController();
fetch("/api/slow", { signal: controller.signal })
.then((r) => r.json())
.catch((e) => {
if (e.name === "AbortError") console.log("已取消");
});
/* 组件卸载或用户取消时 */
controller.abort();
二、实时数据:四种方式对比
| 方式 | 方向 | 连接 | 典型场景 |
|---|---|---|---|
| 短轮询 | 客户端反复请求 | 多次 HTTP | 简单通知、兼容性要求高 |
| 长轮询 | 服务端 hold 住直到有数据 | 多次 HTTP(单次较长) | 旧 IM、降级方案 |
| SSE | 服务端 → 客户端 单向 | 长连接 HTTP | 股票行情、日志流、AI 流式输出 |
| WebSocket | 双向 | 长连接,协议升级 | 聊天、协作、游戏、弹幕 |
口诀 :只要 服务端推、客户端不常发 → 优先 SSE ;要 双向、低延迟 → WebSocket ;临时方案可用 轮询。
三、SSE(Server-Sent Events)
基于 HTTP 长连接 ,服务端持续推送 text/event-stream 格式数据;浏览器用 EventSource。
javascript
const es = new EventSource("/api/stream", { withCredentials: true });
es.onmessage = (e) => {
console.log("默认事件:", e.data);
};
es.addEventListener("price", (e) => {
const { symbol, value } = JSON.parse(e.data);
console.log(symbol, value);
});
es.onerror = () => {
/* 断线后浏览器会自动重连(有间隔) */
console.warn("SSE 连接异常");
};
/* 关闭 */
// es.close();
特点:
- 单向:客户端发消息仍要走普通 HTTP
- 自动重连(浏览器内置)
- 仅文本;二进制需 Base64 或换 WebSocket
- HTTP/2 多路复用 下表现良好
四、WebSocket
通过 HTTP Upgrade 升级为 ws/wss 协议,全双工、帧开销小。
javascript
function connect(url) {
const ws = new WebSocket(url); /* 生产用 wss:// */
ws.onopen = () => {
ws.send(JSON.stringify({ type: "ping" }));
};
ws.onmessage = ({ data }) => {
console.log("收到:", JSON.parse(data));
};
ws.onclose = (e) => {
console.log("关闭", e.code, e.reason);
/* 简单重连 --- 生产建议指数退避 + 上限 */
setTimeout(() => connect(url), 3000);
};
ws.onerror = () => console.error("WS 错误");
return ws;
}
const ws = connect("wss://api.example.com/ws");
4.1 心跳与重连(工程要点)
| 问题 | 做法 |
|---|---|
| 中间代理超时断开 | 定时发 ping(应用层 JSON 或协议层 ping) |
| 断线无感知 | 监听 close/error ,指数退避重连(1s、2s、4s... capped) |
| 重复连接 | 重连前 close 旧实例;页面 visibility 隐藏时可暂停 |
| 安全 | 生产必须用 wss://(TLS) |
javascript
/* 指数退避重连示意 */
let retry = 0;
function reconnect(url) {
const delay = Math.min(1000 * 2 ** retry, 30000);
setTimeout(() => {
retry++;
connect(url);
}, delay);
}
4.2 readyState
| 值 | 状态 |
|---|---|
| 0 | CONNECTING |
| 1 | OPEN |
| 2 | CLOSING |
| 3 | CLOSED |
五、SSE vs WebSocket(面试口述)
| SSE | WebSocket | |
|---|---|---|
| 通信 | 单向推送 | 双向 |
| 协议 | 普通 HTTP | 需 Upgrade |
| 实现 | 简单,EventSource |
稍复杂,需处理心跳重连 |
| 数据 | 文本事件流 | 文本 + 二进制 |
| 代理/CDN | 友好 | 部分代理需额外配置 |
| 自动重连 | 浏览器内置 | 需自己实现 |
六、选型口诀
| 需求 | 优先 |
|---|---|
| 普通 REST API | Fetch + axios 封装 |
| 服务端持续推送、客户端少发 | SSE |
| 聊天、对战、双向高频 | WebSocket |
| 极简、防火墙只认 HTTP | 轮询 / SSE |
| 上传进度、极老浏览器 | XHR |
七、易混淆点归纳
- Fetch 的 404 不会进 catch ,要看
res.ok。 - SSE 不能代替 WebSocket 做双向聊天(除非另开 HTTP 上报)。
- WebSocket 不会自动重连;SSE 会。
ws://明文 ,生产用wss://。- 轮询 浪费连接与流量;实时性要求高时避免高频短轮询。
- AbortController 可取消 Fetch;XHR 用
abort()。
八、思考与练习
1. Fetch 收到 HTTP 500,await fetch(url) 会抛错吗?
解析:不会 (网络层成功);需判断 !res.ok 再抛业务错误。
2. 股票行情只从服务端推价格,客户端不发消息,选 SSE 还是 WebSocket?
解析:优先 SSE (更简单);若要二进制或极低延迟双向再考虑 WebSocket。
3. WebSocket 断线后为何要做指数退避?
解析:避免服务端故障时 大量客户端同时狂重连(惊群),逐步拉长间隔。
4. credentials: 'include' 在 Fetch 里做什么?与 CORS 有何关系?
解析:跨域请求 携带 Cookie ;服务端须 Access-Control-Allow-Credentials: true 且 Origin 不能为 *。
5. EventSource 与 WebSocket 谁自带自动重连?
解析:EventSource(SSE) 自带;WebSocket 需自行实现。
总结
- Fetch 替代 XHR 为主流;注意
res.ok、credentials、AbortController。 - 实时推送 :单向 SSE ;双向 WebSocket(wss + 心跳 + 重连)。
- 轮询 作兜底,非首选。
下一篇讲 Service Worker 与 PWA :离线缓存、fetch 拦截(系列第 27 篇)。