主题
第 08 章 · 面试题集(网络与存储)
Q1:fetch 和 XHR 有什么区别?
考察点:网络 API
标准答案:
| 维度 | XHR | fetch |
|---|---|---|
| 写法 | 回调(onload/onerror) | Promise / async-await |
| 兼容性 | 全部浏览器(含老 IE) | 现代浏览器 |
| 默认带 cookie | ✅ | ❌ 需 credentials |
| 进度监听 | ✅ onprogress | ❌(要用 ReadableStream) |
| 取消 | xhr.abort() | AbortController |
| HTTP 错误自动 reject | ❌ | ❌(也不!只判网络错误) |
坑: fetch 不会自动把 4xx / 5xx 当错误:
js
const res = await fetch('/404');
if (!res.ok) throw new Error(res.statusText); // 必须自己判断追问:
- 为什么 fetch 不自动 reject 4xx?(设计哲学:HTTP 错误不是网络错误)
- 怎么实现请求进度?(XHR onprogress 或 fetch 的 ReadableStream)
Q2:什么是同源策略?为什么需要它?
考察点:浏览器安全
标准答案: 同源 = 协议 + 域名 + 端口完全相同。同源策略限制:
- 不能 AJAX 请求不同源
- 不能读取不同源 iframe 的 DOM
- 不能读取不同源的 cookie / storage
为什么:防止恶意站点窃取你在其他站点的数据。如果没有它,你登录银行后访问 evil.com,evil.com 就能读你的银行 cookie 把你的钱转走。
追问:
- 哪些资源不受同源限制?(
<script><img><link>等都能跨域加载,但 JS 读不到内容)
Q3:跨域有哪些解决方案?
考察点:CORS / JSONP / 代理
标准答案:
| 方案 | 原理 | 适用 |
|---|---|---|
| CORS | 服务端响应头 Access-Control-Allow-Origin | 主流方案 |
| JSONP | <script> 不受限制,后端返回回调函数调用 | 老项目,仅 GET |
| 代理(开发期) | webpack/vite proxy,前端请同源后端,由后端转发 | 开发期 |
| Nginx 反向代理 | 配置同域路径转发 | 生产部署 |
| postMessage | 父子 iframe 跨域通信 | 第三方嵌入 |
| document.domain | 同主域不同子域 | 已废弃 |
追问:
- JSONP 怎么实现?(动态
<script>+ 回调函数名) - JSONP 和 CORS 的区别?(JSONP 仅 GET、不安全;CORS 通用、安全)
Q4:CORS 简单请求和预检请求的区别?
考察点:CORS 协议细节
标准答案:
简单请求满足三条:
- 方法:GET / POST / HEAD
- Content-Type:
text/plain、multipart/form-data、application/x-www-form-urlencoded - 没有自定义请求头
→ 浏览器直接发,看响应头放行。
预检请求:不满足上面任一条。 → 浏览器先发 OPTIONS 请求询问服务端允许的方法、头部、源,通过后才发真实请求。
预检关键响应头:
http
Access-Control-Allow-Origin: https://a.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, X-Token
Access-Control-Max-Age: 86400 # 缓存预检结果(秒)追问:
- application/json 的 POST 是简单请求吗?(不是,会触发预检)
- 怎么减少预检的影响?(设置 Max-Age)
Q5:CORS 怎么携带 Cookie?
考察点:跨域 + 凭证
标准答案: 默认情况下,CORS 请求不带 cookie。要带需要:
前端:
js
fetch(url, { credentials: 'include' });
// 或 axios: { withCredentials: true }后端:
http
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://a.com # 必须明确,不能是 *坑:
Allow-Origin: *和Allow-Credentials: true不能共存- cookie 还要满足自身的 Domain / SameSite 限制
追问:
- SameSite=None 的 cookie 必须有什么?(必须 Secure,仅 HTTPS)
Q6:Cookie / LocalStorage / SessionStorage / IndexedDB 区别?
考察点:客户端存储对比
标准答案:
| 维度 | Cookie | LocalStorage | SessionStorage | IndexedDB |
|---|---|---|---|---|
| 容量 | ~4KB | ~5MB | ~5MB | 几百 MB+ |
| 生命周期 | 自定义 | 永久 | tab 关闭即清 | 永久 |
| 自动随请求发送 | ✅ | ❌ | ❌ | ❌ |
| 同步/异步 | 同步 | 同步 | 同步 | 异步 |
| 存储类型 | 字符串 | 字符串 | 字符串 | 任意结构化数据 |
| 跨标签页共享 | ✅ | ✅ | ❌ | ✅ |
| 适用场景 | 登录态 | 用户偏好、缓存 | 临时草稿 | 离线应用、大数据 |
追问:
- localStorage 怎么做跨标签页通信?(监听 storage 事件,同源跨 tab)
- localStorage 满了怎么办?(QuotaExceededError,try/catch)
Q7:Cookie 的 Secure / HttpOnly / SameSite 各是什么?
考察点:Cookie 安全
标准答案:
| 属性 | 含义 | 防护 |
|---|---|---|
| Secure | 仅 HTTPS 才发送 | 防中间人 |
| HttpOnly | JS 读不到(document.cookie 拿不到) | 防 XSS 偷 cookie |
| SameSite | 跨站发送策略:Strict/Lax/None | 防 CSRF |
| Domain | 哪些域名共享 | 子域控制 |
| Path | 哪些路径共享 | 路径隔离 |
SameSite 三档:
Strict:完全禁止跨站 cookie(最严,有时影响体验)Lax:默认值,导航请求允许(如点链接),其他禁止None:允许,必须配 Secure
追问:
- HttpOnly 能完全防 XSS 吗?(不能,只是防 cookie 被偷,不防其他攻击)
Q8:什么是 XSS 和 CSRF?怎么防御?
考察点:Web 安全
XSS(跨站脚本):
- 本质:恶意 JS 注入到页面执行
- 类型:存储型 / 反射型 / DOM 型
- 防御:
- 输入校验、输出转义
- cookie 加 HttpOnly
- CSP 头限制脚本源
- 用 textContent 不用 innerHTML
CSRF(跨站请求伪造):
- 本质:诱导用户在已登录状态下访问恶意网站,自动发请求到目标站
- 防御:
- cookie 加 SameSite=Lax/Strict
- 表单加 CSRF Token(一次性令牌)
- 关键操作二次验证
- 校验 Referer / Origin
对比:
| 维度 | XSS | CSRF |
|---|---|---|
| 攻击面 | 你的网站被注入脚本 | 利用你已登录的状态 |
| 是否需要恶意脚本 | ✅ | ❌ |
| 主要防护 | 转义 + CSP | SameSite + Token |
Q9:怎么取消一个 fetch 请求?
考察点:AbortController
标准答案: 用 AbortController:
js
const ctrl = new AbortController();
fetch('/api', { signal: ctrl.signal })
.then(r => r.json())
.catch(err => {
if (err.name === 'AbortError') console.log('已取消');
});
// 触发取消
setTimeout(() => ctrl.abort(), 3000);应用场景:
- 搜索框联想(新输入到达时取消上一次请求)
- 组件卸载时取消(React useEffect 清理)
- 实现请求超时
js
function fetchWithTimeout(url, ms) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), ms);
return fetch(url, { signal: ctrl.signal })
.finally(() => clearTimeout(timer));
}追问:
- AbortController 还能用在哪?(addEventListener、Promise.race、setTimeout 等任何接受 signal 的 API)
Q10:怎么实现一个简易的 IndexedDB 增删改查?
考察点:IndexedDB API
简化代码:
js
function openDB(name, version, upgrade) {
return new Promise((resolve, reject) => {
const req = indexedDB.open(name, version);
req.onupgradeneeded = (e) => upgrade(e.target.result);
req.onsuccess = (e) => resolve(e.target.result);
req.onerror = (e) => reject(e.target.error);
});
}
const db = await openDB('myDB', 1, (db) => {
if (!db.objectStoreNames.contains('notes')) {
db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
}
});
function tx(store, mode) {
return db.transaction(store, mode).objectStore(store);
}
function add(store, data) {
return new Promise((resolve, reject) => {
const req = tx(store, 'readwrite').add(data);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
await add('notes', { title: 'hi', content: 'hello' });加分回答:
- 推荐用
idb或Dexie.js包装库 - 注意版本管理:升级 schema 要在 onupgradeneeded 里
- 事务超出 microtask 边界会失效
Q11(加餐):缓存策略 (HTTP 缓存) 简介
考察点:HTTP 缓存
标准答案:
强缓存(不发请求,直接用本地):
Cache-Control: max-age=3600(推荐)Expires: 时间(旧)
协商缓存(发请求,服务端判断 304):
ETag/If-None-MatchLast-Modified/If-Modified-Since
流程:
请求 → 强缓存命中?─ 是 → 直接用(200, from disk/memory cache)
├─ 否 → 发请求 → 服务端比对 ETag/Last-Modified
├─ 没变 → 304 Not Modified(用本地)
└─ 变了 → 200 + 新内容Cache-Control 常用值:
no-store:完全不缓存no-cache:缓存但每次都校验private/public:仅浏览器 / 含 CDNmax-age=N:N 秒强缓存
Q12(加餐):HTTP 状态码常见的有哪些?
考察点:HTTP 基础
标准答案:
| 类别 | 含义 | 常见 |
|---|---|---|
| 1xx | 信息性 | 100 Continue |
| 2xx | 成功 | 200 OK、201 Created、204 No Content |
| 3xx | 重定向 | 301 永久、302 临时、304 Not Modified |
| 4xx | 客户端错误 | 400 参数错、401 未登录、403 无权限、404 找不到、429 限流 |
| 5xx | 服务端错误 | 500 内部错误、502 网关错、503 不可用、504 网关超时 |
追问:
- 301 和 302 的区别?(301 永久 SEO 转移;302 临时)
- 401 和 403 的区别?(401 没登录;403 登录了但没权限)