网络基础
涵盖 TCP 三握四挥、HTTP 状态码、HTTP 方法、HTTP 1/2/3、跨域(CORS / JSONP / postMessage)、Axios 原理与封装、跨域请求是否到达后端。
一、OSI 七层模型 vs TCP/IP 四层模型
OSI(参考模型):
7. 应用层 HTTP / DNS / FTP / SMTP
6. 表示层 SSL/TLS / JPEG / MIME
5. 会话层 RPC / SQL / NetBIOS
4. 传输层 TCP / UDP
3. 网络层 IP / ICMP / ARP / 路由器
2. 数据链路层 PPP / 以太网 / 交换机
1. 物理层 光纤 / 网线 / 集线器
TCP/IP(实际模型):
应用层(合并 OSI 5/6/7)
传输层(TCP / UDP)
网络层(IP / ICMP / ARP)
网络接口层(合并 OSI 1/2)二、TCP 三次握手
1. 三次握手流程
客户端 服务端
│ │
│ SYN=1, seq=x │
│ ──────────────────────→ │ 第一次握手:客户端发 SYN,进入 SYN_SENT
│ │
│ SYN=1, ACK=1, │
│ seq=y, ack=x+1 │
│ ←────────────────────── │ 第二次握手:服务端回 SYN+ACK,进入 SYN_RCVD
│ │
│ ACK=1, │
│ seq=x+1, ack=y+1 │
│ ──────────────────────→ │ 第三次握手:客户端回 ACK,连接建立
│ │
ESTABLISHED ←────────→ ESTABLISHED2. 为什么需要三次握手?
核心目的:双方都确认对方的收发能力正常
类比打电话:
- 你:喂,听到吗?(SYN → 测自己发送 + 服务端接收)
- 对方:听到了,你听到吗?(SYN+ACK → 测自己发送 + 你接收 + 你发送确认)
- 你:听到了。(ACK → 确认对方发送正常)
两次握手的问题:
- 服务端无法确认客户端是否收到了自己的 SYN+ACK
- 假如客户端的 SYN 超时重传,旧 SYN 和新 SYN 都到达服务端,服务端都会建立连接
- 浪费资源,还可能接受失效请求3. SYN 洪水攻击
攻击:客户端只发 SYN,不回 ACK
- 服务端维护大量半连接状态(SYN_RCVD)
- 资源耗尽,无法响应正常请求
防御:
- SYN Cookie:不分配资源,把信息编码到 seq
- 限制最大半连接数
- 缩短 SYN Timeout三、TCP 四次挥手
1. 四次挥手流程
客户端 服务端
│ │
│ FIN=1, seq=u │
│ ──────────────────────→ │ 第一次挥手:客户端发 FIN,进入 FIN_WAIT_1
│ │
│ ACK=1, ack=u+1 │
│ ←────────────────────── │ 第二次挥手:服务端回 ACK,进入 CLOSE_WAIT
│ │ 客户端进入 FIN_WAIT_2
│ │ (服务端可能还有数据要发送)
│ FIN=1, ACK=1, │
│ seq=w, ack=u+1 │
│ ←────────────────────── │ 第三次挥手:服务端发 FIN,进入 LAST_ACK
│ │
│ ACK=1, ack=w+1 │
│ ──────────────────────→ │ 第四次挥手:客户端回 ACK,进入 TIME_WAIT
│ │ 服务端进入 CLOSED
│ 等待 2*MSL 后 │
│ 进入 CLOSED │2. 为什么需要四次挥手?
TCP 是全双工的,每一方都要单独关闭:
- 第一次:客户端说"我这边数据发完了"(FIN)
- 第二次:服务端确认(ACK),但可能还有数据要发
- 第三次:服务端也说"我数据也发完了"(FIN)
- 第四次:客户端确认(ACK)
合并为三次的情况:
- 服务端收到 FIN 后立即也发 FIN + ACK(无数据要发了)
- 这种情况就是三次挥手3. TIME_WAIT 状态
为什么等待 2*MSL(通常 60-120 秒)?
1. 确保最后一个 ACK 能到达服务端
- 假如 ACK 丢失,服务端会重发 FIN
- 客户端能在 TIME_WAIT 期间收到并重发 ACK
2. 让这次连接的所有报文在网络中消失
- 防止下一次连接收到旧报文
为什么 TIME_WAIT 在主动关闭方?
- 主动关闭方最后发 ACK
- 最后一个 ACK 可能丢失,需要重发
- 所以需要 TIME_WAIT四、HTTP 状态码
1. 五大类状态码
| 类别 | 含义 |
|---|---|
| 1xx | 信息响应(请求已接收,继续处理) |
| 2xx | 成功响应 |
| 3xx | 重定向 |
| 4xx | 客户端错误 |
| 5xx | 服务器错误 |
2. 常见状态码详解
2xx 成功
200 OK 请求成功
201 Created 资源已创建(POST/PUT)
202 Accepted 已接受,但未处理完成
204 No Content 成功,无返回体(DELETE)
206 Partial Content 部分内容(Range 请求)3xx 重定向
301 Moved Permanently 永久重定向(历史兼容下 POST 可能被改为 GET)
302 Found 临时重定向(历史兼容下 POST 可能被改为 GET)
303 See Other 重定向到 GET
304 Not Modified 缓存命中
307 Temporary Redirect 临时重定向(保持方法)
308 Permanent Redirect 永久重定向(保持方法)4xx 客户端错误
400 Bad Request 请求语法错误
401 Unauthorized 未认证(需要登录)
403 Forbidden 已认证但权限不够
404 Not Found 资源不存在
405 Method Not Allowed 方法不允许
406 Not Acceptable 内容类型不匹配
408 Request Timeout 请求超时
409 Conflict 冲突(资源状态冲突)
410 Gone 资源永久删除
413 Payload Too Large 请求体过大
415 Unsupported Media Type 不支持的媒体类型
429 Too Many Requests 请求过多(限流)5xx 服务端错误
500 Internal Server Error 服务器内部错误
501 Not Implemented 未实现
502 Bad Gateway 网关错误(上游返回无效响应)
503 Service Unavailable 服务不可用(过载/维护)
504 Gateway Timeout 网关超时(上游未响应)
505 HTTP Version Not Supported 不支持的 HTTP 版本3. 状态码分类与原因短语
1xx(Informational):
100 Continue 继续发送请求体
101 Switching Protocols 切换协议(如 WebSocket)
2xx(Success):
200 OK 成功
201 Created 已创建
204 No Content 无内容
206 Partial Content 部分内容
3xx(Redirection):
301 Moved Permanently 永久重定向(GET 化)
302 Found 临时重定向
303 See Other 重定向到 GET
304 Not Modified 未修改(缓存)
307 Temporary Redirect 临时重定向(保持方法)
308 Permanent Redirect 永久重定向(保持方法)
4xx(Client Error):
400 Bad Request 请求错误
401 Unauthorized 未授权
403 Forbidden 禁止
404 Not Found 未找到
405 Method Not Allowed 方法不允许
408 Request Timeout 请求超时
409 Conflict 冲突
429 Too Many Requests 请求过多
5xx(Server Error):
500 Internal Server Error 内部错误
502 Bad Gateway 网关错误
503 Service Unavailable 服务不可用
504 Gateway Timeout 网关超时五、HTTP 方法
GET 获取资源(幂等)
POST 创建资源
PUT 更新资源(幂等,完整替换)
PATCH 部分更新资源
DELETE 删除资源(幂等)
HEAD 获取元数据(同 GET,但无响应体)
OPTIONS 获取支持的 HTTP 方法(预检请求)
CONNECT 建立隧道(HTTP 代理)
TRACE 回显请求(用于调试)
幂等性:重复相同请求的预期服务端效果与执行一次相同,不要求响应完全一致
安全方法:客户端不请求改变服务端状态;GET、HEAD、OPTIONS、TRACE 属于安全方法六、HTTP 各版本对比
1. HTTP/0.9 (1991)
- 仅支持 GET 方法
- 仅 HTML 格式
- 无状态码、无头部2. HTTP/1.0 (1996)
- 支持 POST、HEAD
- 引入状态码、头部
- 每个请求都要建立新 TCP 连接
- 短连接3. HTTP/1.1 (1997)
- 默认长连接(keep-alive)
- 管道化(pipeline,但浏览器默认禁用)
- 分块传输编码(chunked)
- 引入 PUT、DELETE、OPTIONS 等
- 引入 Host 头(虚拟主机支持)
- 引入 Range 范围请求
- 引入缓存控制(Cache-Control)HTTP/1.1 队头阻塞:同一连接内,请求按顺序处理,前一个未返回前,后一个等
4. HTTP/2 (2015)
- 二进制协议(不再纯文本)
- 多路复用(一个连接并发多个请求-响应,解决 HTTP/1.x 的应用层队头阻塞)
- 头部压缩(HPACK)
- 服务器推送(Server Push,协议支持,但主流浏览器已很少使用)
- 流量优先级
- 必须 TLS(主流实现)5. HTTP/3 (2022)
- 基于 QUIC(UDP)而非 TCP
- 解决 TCP 队头阻塞
- 内置 TLS 1.3
- 连接迁移(切换网络不断连)
- 恢复已有会话时可发送 0-RTT 数据(首次连接通常仍需往返,且 0-RTT 有重放风险)七、跨域
1. 同源策略
源 = 协议 + 域名 + 端口
同源:三者完全相同
跨域:三者任一不同
例:
http://example.com:80/page1
http://example.com:80/page2 ✅ 同源
https://example.com/page1 ❌ 跨域(协议)
http://example.com:8080/page1 ❌ 跨域(端口)
http://other.com/page1 ❌ 跨域(域名)
浏览器默认同源策略限制:
- Cookie、LocalStorage、SessionStorage 不能跨域
- DOM 不能跨域访问
- AJAX 受限(同源才能发)2. 跨域解决方案
方案 1:CORS(跨域资源共享,主流)
// 服务端设置响应头(关键)
Access-Control-Allow-Origin: https://example.com // 允许的源
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true // 是否允许带 Cookie
Access-Control-Max-Age: 86400 // 预检缓存时间// 简单请求:直接发(无需预检)
// 满足以下条件:
// 1. 方法是 GET/HEAD/POST
// 2. 头部只能是 CORS 安全头
// 3. Content-Type 是 text/plain / multipart/form-data / application/x-www-form-urlencoded
// 客户端
fetch('https://api.example.com/data', {
credentials: 'include' // 允许带 Cookie
})
// 非简单请求:先发 OPTIONS 预检
// 例:PUT 请求,Content-Type: application/json
// 浏览器先发 OPTIONS,服务端返回允许的源/方法/头
// 然后浏览器才发真正的请求方案 2:JSONP(仅 GET)
<!-- 服务端返回 JS -->
callbackFunction({ data: 'xxx' })// 客户端
function jsonp(url, callbackName) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = `${url}?callback=${callbackName}`
window[callbackName] = (data) => {
resolve(data)
document.body.removeChild(script)
}
script.onerror = reject
document.body.appendChild(script)
})
}
jsonp('https://api.example.com/data', 'callback')
.then(data => console.log(data))JSONP 局限:
- 仅 GET
- 不安全(注入风险)
- 错误处理难
- 已不推荐,新项目优先 CORS
方案 3:Nginx 反向代理
server {
listen 80;
server_name mydomain.com;
location /api {
proxy_pass https://api.example.com;
# 把服务端反向代理到同源路径
}
}优点:前端无感知,浏览器认为是同源请求 缺点:需要运维配置
方案 4:postMessage(iframe 跨域通信)
// 子页面发送
window.parent.postMessage({ msg: 'hello' }, 'https://parent.com')
// 父页面接收
window.addEventListener('message', (e) => {
if (e.origin !== 'https://trusted.com') return
console.log(e.data)
})方案 5:WebSocket(无同源限制)
const ws = new WebSocket('wss://api.example.com/socket')
ws.onmessage = (e) => console.log(e.data)方案 6:document.domain(历史方案,不应新用)
// 父页面:example.com
document.domain = 'example.com'
// 子页面:sub.example.com
document.domain = 'example.com'
// 历史代码曾用这种方式放宽同源判断document.domain setter 已被弃用,而且会改变 origin 的安全语义;新代码应使用明确 targetOrigin 并校验 event.origin 的 postMessage 等机制。
3. 跨域请求是否会到达后端?
答:会到达,浏览器只是拦截了响应。
详细流程:
1. 浏览器发请求(跨域)
2. 服务端正常处理并返回响应(带 CORS 头)
3. 浏览器收到响应,检查 Access-Control-Allow-Origin
4. 如果不匹配 → 拦截响应,抛出 CORS 错误
5. 如果匹配(或带 *)→ 正常返回给 JS
为什么重要:
- 服务端需要正确实现 CORS
- 即使服务端没设 CORS 头,请求也会到(不要靠 CORS 做鉴权!)
- 重要操作应该用 token / cookie 等应用层鉴权4. 跨域与 Cookie
默认 AJAX 跨域不携带目标域 Cookie
要带 Cookie:
1. 服务端:Access-Control-Allow-Credentials: true
2. 服务端:Access-Control-Allow-Origin 不能是 *,必须是具体源
3. 客户端:fetch(..., { credentials: 'include' })
axios:withCredentials: true
4. Cookie:SameSite=None; Secure(现代浏览器)八、Axios 原理与封装
1. Axios 核心特性
- 基于 Promise 的 HTTP 客户端
- 浏览器用 XMLHttpRequest,Node 用 http
- 自动转换 JSON 数据
- 拦截请求/响应
- 取消请求
- 客户端防御 XSRF(自动从 Cookie 读 XSRF-TOKEN,构造请求头)2. Axios 封装实战
// utils/request.js
import axios from 'axios'
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000,
withCredentials: false
})
// 请求拦截器
request.interceptors.request.use(
config => {
// 附加 token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => Promise.reject(error)
)
// 响应拦截器
request.interceptors.response.use(
response => {
const res = response.data
// 自定义业务码
if (res.code !== 0) {
// 401 未登录
if (res.code === 401) {
// 跳转登录
}
return Promise.reject(new Error(res.message))
}
return res.data
},
error => {
if (error.response?.status === 401) {
// 处理 401
}
return Promise.reject(error)
}
)
export default request3. Axios 取消请求
import axios from 'axios'
// 方式 1:CancelToken(已废弃但兼容)
const source = axios.CancelToken.source()
axios.get('/api/data', { cancelToken: source.token })
source.cancel('请求被取消')
// 方式 2:AbortController(推荐)
const controller = new AbortController()
axios.get('/api/data', { signal: controller.signal })
controller.abort()4. Axios 与 Fetch 对比
| 维度 | Axios | Fetch |
|---|---|---|
| API 风格 | 链式(then/catch) | 链式 |
| JSON 转换 | ✅ 自动 | ❌ 需手动 |
| 请求拦截 | ✅ 内置 | ❌ 需包装 |
| 响应拦截 | ✅ 内置 | ❌ 需包装 |
| 取消请求 | ✅ 内置 | ✅ AbortController |
| 超时 | ✅ 内置 | ❌ 需 AbortController |
| 进度事件 | ✅ onUploadProgress | ❌ 需 XMLHttpRequest |
| 错误捕获 | ✅ 自动 throw | ❌ HTTP 错误不 throw |
| CSRF | ✅ 自动 | ❌ 手动 |
| 适配环境 | 浏览器 + Node | 浏览器 |
| 包大小 | ~13 KB | 0(原生) |
九、面试高频问答
Q1: TCP 为什么需要三次握手?
答:核心是双方都确认对方的收发能力正常:
- 第一次:客户端发 SYN,确认"客户端发送 + 服务端接收"正常
- 第二次:服务端回 SYN+ACK,确认"服务端发送 + 客户端接收 + 客户端发送确认"
- 第三次:客户端回 ACK,确认"客户端发送确认"
如果是两次握手,服务端无法确认客户端是否收到了自己的 SYN+ACK,可能导致服务端建立无效连接、浪费资源。
Q2: TCP 为什么需要四次挥手?
答:TCP 是全双工通信,双方都可能有数据要发送。关闭连接需要分别关闭各自方向:
- 第一次:客户端发 FIN(关闭客户端→服务端方向)
- 第二次:服务端 ACK(确认关闭客户端→服务端方向)
- 第三次:服务端发 FIN(关闭服务端→客户端方向)
- 第四次:客户端 ACK(确认关闭服务端→服务端方向)
合并为三次的特殊情况:服务端收到 FIN 后立即也发 FIN(无数据要发了)。
Q3: 跨域请求会到后端吗?
答:会到。浏览器只是拦截响应,不会拦截请求发送。具体流程:
- 浏览器发起跨域请求
- 服务端正常处理(即使没有 CORS 头)
- 服务端返回响应
- 浏览器检查
Access-Control-Allow-Origin - 不匹配则拦截响应,抛出 CORS 错误
所以不能用 CORS 做鉴权。
Q4: HTTP/1.1 队头阻塞和 HTTP/2 多路复用?
答:
- HTTP/1.1 队头阻塞:同一 TCP 连接内,请求按顺序处理。前一个请求未返回,后一个请求必须等待
- HTTP/2 多路复用:单个 TCP 连接中并发多个请求-响应(通过 stream + 二进制分帧)。彻底解决 HTTP 层的队头阻塞
- HTTP/2 仍存在 TCP 队头阻塞:底层 TCP 丢包时整个连接的所有 stream 都要等
- HTTP/3:基于 QUIC(UDP),彻底解决 TCP 队头阻塞
Q5: 如何实现一个带拦截和取消的 Axios?
答:
import axios from 'axios'
const request = axios.create({ baseURL: '/api', timeout: 10000 })
request.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
request.interceptors.response.use(
response => response.data,
error => {
if (error.response?.status === 401) {
// 跳转登录
}
return Promise.reject(error)
}
)
// 取消请求
const controller = new AbortController()
request.get('/data', { signal: controller.signal })
controller.abort()
export default request十一、HTTP 协议基础详解
一、HTTP 的定义
HTTP(HyperText Transfer Protocol)超文本传输协议,是 Web 的基础协议,用于客户端(浏览器)和服务器之间的通信。
- 传输映射因版本而异:HTTP/1.1 和 HTTP/2 通常运行在 TCP 上;HTTP/3 运行在基于 UDP 的 QUIC 上
- 请求-响应模型:客户端发请求,服务端回响应
- 无状态:每次请求都是独立的,服务器不记得客户端历史
- 是否加密取决于使用方式:明文 HTTP 可被窃听篡改;HTTPS 是运行在 TLS 上的 HTTP,可提供传输机密性、完整性和服务器身份认证
二、HTTP 是无状态协议
为什么这样设计?
- HTTP 设计目标是"简单、灵活、可扩展",无状态让服务器不需要维护客户端状态,扩展性极强
- 服务端可以无差别处理请求,配合负载均衡、Nginx 等可水平扩展
如何实现状态跟踪?
- Cookie + Session:服务端存 Session,浏览器存 Cookie 带 SessionId
- Token:服务端签发 JWT,前端存 localStorage / HttpOnly Cookie
- URL 参数(不推荐):拼在 URL 上,泄漏风险大
三、HTTP 协议的演进
HTTP/1.0(1996 年)
- 默认是每个请求后关闭连接,但可用
Connection: keep-alive扩展复用连接,不能写成“每个请求都必须新建连接” - 仅支持 GET / POST / HEAD
HTTP/1.1(1997 年,使用最广泛的版本)
- 长连接(keep-alive):一个 TCP 连接可以传输多个 HTTP 请求
- 管道化(pipelining):客户端可以不等响应就发下一个请求(但实际服务端仍按顺序响应)
- 引入
Host头 → 同一 IP 可部署多个网站(虚拟主机) - 引入
Range→ 支持断点续传 - 标准化了 PUT、DELETE、OPTIONS 等方法;PATCH 由后来的 RFC 5789 定义,不是 HTTP/1.1 原始规范新增
缺点:没有 HTTP/2 的专用头部压缩和多路复用。浏览器通常会为同一源开启多个 TCP 连接,但连接数是实现策略,不能当作协议固定为 6。
HTTP/2(2015 年)
- 二进制分帧:将 HTTP 报文拆成更小的二进制帧
- 多路复用:一个 TCP 连接上并发多个请求/响应(解决 HTTP/1.1 的队头阻塞)
- 头部压缩(HPACK):用 Huffman 编码 + 索引表压缩头部
- 服务端推送(Server Push):服务端可以主动推资源(实际很少用)
- 流量控制 + 请求优先级
缺点:基于 TCP,TCP 的队头阻塞仍然存在(一个包丢失会阻塞后续所有包)
HTTP/3(基于 QUIC,2022 年标准化)
- 基于 UDP 的 QUIC,使一个流的丢包通常不会阻塞其他流,消除了 HTTP/2 所受的 TCP 跨流队头阻塞
- QUIC 集成 TLS 1.3;恢复已有会话时可使用 0-RTT,首次连接并不是 0-RTT
- 内置 TLS 1.3,更安全
- 连接迁移:网络切换(WiFi → 4G)不需要重连(用 Connection ID 标识连接)
版本对比总结
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 连接模型 | 长连接 | 多路复用 | 多路复用 + 连接迁移 |
| 头部压缩 | 无 | HPACK | QPACK |
| 传输层 | TCP | TCP | QUIC (UDP) |
| 跨流队头阻塞 | 应用层/响应顺序 | TCP 层 | QUIC 流之间通常互不阻塞 |
四、常见 HTTP 状态码
1xx —— 信息性
100 Continue:服务器已收到请求头,客户端继续发请求体
2xx —— 成功
200 OK:请求成功201 Created:资源已创建(POST 后)204 No Content:成功但无返回内容(DELETE 后)
3xx —— 重定向
301 Moved Permanently:永久重定向(浏览器会缓存)302 Found:临时重定向304 Not Modified:协商缓存命中(资源未变)
4xx —— 客户端错误
400 Bad Request:请求语法错401 Unauthorized:未登录403 Forbidden:已登录但没权限404 Not Found:资源不存在405 Method Not Allowed:方法不允许409 Conflict:资源冲突429 Too Many Requests:限流
5xx —— 服务端错误
500 Internal Server Error:服务器内部错502 Bad Gateway:网关响应无效503 Service Unavailable:服务暂时不可用504 Gateway Timeout:网关超时
五、HTTP 请求方法
GET:获取资源的当前表示(安全且幂等;查询条件常放在 URL)POST:让目标资源按其自身语义处理请求内容(通常非幂等,常用于提交或创建)PUT:全量更新(幂等)PATCH:应用部分修改;是否幂等取决于补丁格式与服务端语义DELETE:删除(幂等)OPTIONS:预检请求(CORS)HEAD:仅返回响应头CONNECT:建立隧道(代理用)TRACE:回显请求
GET vs POST 的关键区别
| 维度 | GET | POST |
|---|---|---|
| 用途 | 获取资源 | 按目标资源语义处理内容 |
| 幂等 | 规范语义是幂等 | 规范语义不保证幂等 |
| 常见参数位置 | URL 查询参数 | 请求体 |
| 大小限制 | URL 上限由客户端、服务器和中间设施决定,没有通用 2KB 标准 | 请求体也受客户端、服务器、网关和应用配置限制,并非无限 |
| 缓存 | 可缓存 | 满足显式缓存条件时也可以缓存,但实践中较少 |
| 安全性 | URL 更容易出现在历史、日志和 Referer 中 | 请求体不等于加密;两者都必须使用 HTTPS,敏感信息还需服务端妥善处理 |
六、HTTP 与 HTTPS 的区别
七、HTTP/2 的核心改进
- 二进制分帧:把 HTTP 报文拆成二进制帧(HEADERS 帧、DATA 帧等),便于解析和压缩
- 多路复用:同一个 TCP 连接上并发多个流(stream),每个请求分配一个 stream id
- 头部压缩(HPACK):维护一张静态表 + 动态表 + Huffman 编码,头部体积大幅减小
- 服务端推送:服务端主动推资源(如推送 CSS 配合 HTML)
- 流量控制:每流单独控制,避免慢客户端拖累
- 请求优先级:客户端声明资源优先级
八、HTTP 常见请求头和响应头
请求头
Host:目标主机(虚拟主机必备)User-Agent:浏览器标识Accept:可接受的响应类型(如application/json)Accept-Encoding:可接受的编码(如gzip)Content-Type:请求体格式Authorization:认证信息Cookie:携带 CookieIf-None-Match:协商缓存标记
响应头
Content-Type:响应体格式Content-Length:响应体长度Set-Cookie:服务端设置 CookieCache-Control:缓存策略ETag:协商缓存标记Location:重定向地址
十二、浏览器渲染流程
详细理解
🧩 1️⃣ HTML → DOM Tree
浏览器解析 HTML 字符串,生成 DOM(Document Object Model)树。
解析过程中如果遇到 <script> 标签:
- 默认同步加载,暂停 HTML 解析,等 JS 执行完才继续
- 加
async:下载完立即执行(不保证顺序) - 加
defer:等 HTML 解析完再执行(保证顺序)
🎨 2️⃣ CSS → CSSOM Tree
浏览器解析 CSS(包括内联 style、外部 CSS、<style> 标签),生成 CSSOM(CSS Object Model)树。
🌳 3️⃣ Render Tree(关键)
DOM + CSSOM 合并生成 Render Tree(渲染树)。
注意:只包含可见节点(display: none 的不包含,visibility: hidden 的包含)。
📐 4️⃣ Layout(重排)
根据 Render Tree 计算每个节点的几何信息(位置、尺寸)。
🎨 5️⃣ Paint(重绘)
将 Render Tree 的每个节点转换为实际像素(颜色、阴影、文字等)。
🧩 6️⃣ Composite(合成)
将多个层(如 transform、opacity、will-change 创建的层)合并,输出到屏幕。
加分点:reflow vs repaint
🔹 Reflow(重排)
- 触发:DOM 几何属性变化(宽高、位置、显示隐藏)
- 影响:Layout → Paint → Composite 全流程
- 代价:高(可能导致整棵树重新计算)
// 触发 reflow
el.style.width = '100px'
el.style.display = 'none'
el.classList.add('active')🔹 Repaint(重绘)
- 触发:外观变化但不影响布局(颜色、背景、阴影)
- 影响:Paint → Composite(跳过 Layout)
- 代价:中等
// 触发 repaint(不触发 reflow)
el.style.color = 'red'
el.style.backgroundColor = 'blue'🚀 为什么 transform 更快?
transform / opacity 只会触发 Composite(合成),跳过 Layout 和 Paint!因为 GPU 可以单独把它们合成到独立层。
一句话总结(面试收尾)
浏览器渲染流程:HTML → DOM → CSSOM → Render Tree → Layout → Paint → Composite。
- 重排(reflow) 代价最高,影响布局
- 重绘(repaint) 代价中等,影响外观
- 合成(composite) 代价最低,transform / opacity 是最优解
十三、浏览器缓存机制详解
一、定义
浏览器在本地保存一份资源副本,下次访问相同资源时直接读取,避免重复下载。
二、缓存存储位置优先级
Service Worker(手动控制,最灵活)
↓
Memory Cache(内存缓存,浏览器自动管理,关闭标签页失效)
↓
Disk Cache(磁盘缓存,持久化)
↓
Push Cache(HTTP/2 推送缓存,会话级)三、强缓存(无需与服务器通信)
强缓存:浏览器直接使用本地副本,不发请求到服务器。
1. Expires(HTTP/1.0)
Expires: Wed, 21 Oct 2026 07:28:00 GMT- 绝对时间(服务器返回资源时的过期时间)
- 缺点:依赖客户端和服务器时钟一致
2. Cache-Control(HTTP/1.1,优先级更高)
Cache-Control: max-age=3600
Cache-Control: no-cache # 缓存但每次都验证(协商缓存)
Cache-Control: no-store # 完全不缓存
Cache-Control: public # 任何环境都可缓存
Cache-Control: private # 仅浏览器可缓存,代理服务器不能缓存max-age:相对时间(多少秒内用缓存)- 优先级:Cache-Control > Expires
四、协商缓存(需与服务器通信验证)
协商缓存:浏览器先发请求到服务器问"资源变了吗?",没变就用缓存(304),变了返回新资源(200)。
1. Last-Modified / If-Modified-Since
# 首次响应
Last-Modified: Wed, 21 Oct 2026 07:28:00 GMT
# 后续请求携带
If-Modified-Since: Wed, 21 Oct 2026 07:28:00 GMT- 服务器比对时间,没变就返回
304 Not Modified - 缺点:精度只到秒、可能被"假修改"(文件内容没变但时间变了)
2. ETag / If-None-Match(优先级更高)
# 首次响应
ETag: "abc123"
# 后续请求携带
If-None-Match: "abc123"- ETag 是资源的唯一标识(一般用文件内容哈希)
- 比 Last-Modified 更精确(内容变了才更新)
五、完整缓存决策流程
浏览器发起请求
↓
是否有 Cache-Control: no-store?
├─ 是 → 直接请求服务器
└─ 否 ↓
强缓存(Cache-Control / Expires)是否过期?
├─ 未过期 → 直接用缓存(不发请求)
└─ 已过期 ↓
携带 If-None-Match / If-Modified-Since 发请求
↓
服务器比对
├─ 没变 → 返回 304(用缓存)
└─ 变了 → 返回 200 + 新资源六、GET vs POST 的缓存行为
- GET 默认可被缓存(幂等)
- POST 默认不缓存
七、常见追问解答
no-cache 和 no-store 的区别
no-cache:缓存但每次都必须验证(走协商缓存)no-store:完全不缓存任何内容
如何控制缓存策略?
- HTML:通常
Cache-Control: no-cache(保证更新及时) - 静态资源(JS/CSS/图片):
Cache-Control: max-age=31536000+ 文件名 hash 化(app.a3f2b.js)
用户行为对缓存的影响
- 地址栏回车:走强缓存 / 协商缓存
- F5 刷新:跳过强缓存,走协商缓存
- Ctrl+F5 强制刷新:跳过所有缓存(Cache-Control: no-cache + Pragma: no-cache)
八、最佳实践总结
| 资源类型 | 推荐缓存策略 |
|---|---|
| HTML | no-cache |
| JS / CSS(带 hash) | max-age=31536000 |
| 图片 / 字体(带 hash) | max-age=31536000 |
| API 数据 | 不用浏览器缓存(用 Service Worker) |
十四、Cookie / localStorage / sessionStorage 大小与适用场景
Cookie(单个 Cookie 通常只有约 4KB 的兼容预算)
- 用户代理通常至少支持约 4096 字节的单个 Cookie,但精确计算、每域数量、总量和淘汰策略由实现决定;不能说成“每个域名总共 4KB”
- 只有满足 Domain、Path、Secure、SameSite 等条件时才会随请求携带;这也会增加相应请求流量
- 可设置过期时间(
Expires/Max-Age) - 适用:会话状态、用户偏好(语言、主题)
localStorage(配额由浏览器与存储策略决定)
- 配额和超限行为由浏览器实现、站点存储策略及用户设置决定
- 通常跨会话保留,但可能被用户清除、隐私模式隔离或按存储策略回收,不能视为永久可靠存储
- 不随请求发送(同源)
- API 简单:
setItem/getItem/removeItem - 适用:用户偏好、长期缓存(如 token,但有 XSS 风险)
sessionStorage(配额由浏览器与存储策略决定)
- 配额由浏览器实现决定
- 按 origin 和顶级浏览上下文的页面会话隔离;刷新会保留,关闭对应标签页/窗口通常结束该会话
- 不像 localStorage 那样在同源标签页之间共享;从现有页面打开新页面时,初始值可能被复制,之后彼此独立
- 适用:单次会话的临时数据(如表单草稿)
选择建议
- 会话凭据:在合适架构中优先考虑
Secure、HttpOnly、合适SameSite的 Cookie,并配套 CSRF 防护;HttpOnly 只能阻止脚本直接读取,不能“防住 XSS” - 长期偏好:localStorage
- 临时数据:sessionStorage
- 小数据且需随请求:Cookie
localStorage 存对象的正确方式
localStorage 只能存字符串,对象需要序列化:
// 存
const user = { name: '张三', age: 25 }
localStorage.setItem('user', JSON.stringify(user))
// 取
const data = JSON.parse(localStorage.getItem('user'))
// 安全的取(带默认值)
const getUser = () => {
try {
return JSON.parse(localStorage.getItem('user'))
} catch {
return null
}
}十五、CORS(跨域)深度讲解
为什么会有跨域问题?
同源策略:浏览器要求协议、域名、端口三者完全一致,否则视为跨域。
http://a.com/index.html 访问:
✅ http://a.com/api 同源
❌ https://a.com/api 协议不同
❌ http://b.com/api 域名不同
❌ http://a.com:8080/api 端口不同跨域限制主要保护:
- 防止 CSRF(伪造请求)
- 防止 XSS(窃取数据)
解决方案
1. CORS(服务端)
服务端在响应头里加:
Access-Control-Allow-Origin: http://a.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, AuthorizationAllow-Origin: *:允许所有源(不能与Allow-Credentials: true共存)Allow-Credentials: true:允许携带 Cookie- 浏览器发现跨域请求,先发 OPTIONS 预检请求,服务端返回上面的头后,浏览器才发真实请求。
2. JSONP(GET only,已淘汰)
利用 <script> 标签没有跨域限制:
// 动态创建 script
const script = document.createElement('script')
script.src = 'http://api.x.com/data?callback=handleData'
document.body.appendChild(script)
// 全局回调
function handleData(data) {
console.log(data)
}服务端返回 handleData({...})。只支持 GET,已被 CORS 取代。
3. Nginx 反向代理
前端请求同源 /api,Nginx 转发到后端:
location /api {
proxy_pass http://real-api.com;
}浏览器以为是同源请求,绕开跨域限制。
4. postMessage(跨窗口)
不同源的 iframe / 弹窗之间通信:
// 父窗口
iframe.contentWindow.postMessage('hello', 'http://child.com')
// 子窗口
window.addEventListener('message', e => {
if (e.origin === 'http://parent.com') {
console.log(e.data)
}
})5. WebSocket(无跨域限制)
WebSocket 协议不受同源策略限制(但服务端会校验 Origin)。
哪些请求会触发 OPTIONS(预检请求)?
✅ 触发预检:
- 非简单方法(PUT / DELETE / PATCH)
- 带自定义 Header(如
Authorization) - Content-Type 是
application/json
❌ 不触发(简单请求):
- GET / POST / HEAD
- 只用浏览器默认 Header
- Content-Type 是
text/plain/multipart/form-data/application/x-www-form-urlencoded
服务端环境下有没有跨域问题?
没有! 跨域是浏览器的安全限制。服务端用 axios / request / node-fetch 等发请求,没有跨域问题(除非服务端也设置 CORS 检查 IP 等)。
微信小程序有跨域问题吗?
没有! 小程序运行在微信客户端,不受浏览器同源策略限制。请求是直接发给微信代理服务器,由代理转发到真实后端。
但小程序开发期间需要在开发者工具里设置"不校验合法域名"(生产必须 HTTPS + 配置白名单)。