Skip to content

第 08 章 · 面试题集(网络与存储)


Q1:fetch 和 XHR 有什么区别?

考察点:网络 API

标准答案

维度XHRfetch
写法回调(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 协议细节

标准答案

简单请求满足三条:

  1. 方法:GET / POST / HEAD
  2. Content-Type:text/plainmultipart/form-dataapplication/x-www-form-urlencoded
  3. 没有自定义请求头

→ 浏览器直接发,看响应头放行。

预检请求:不满足上面任一条。 → 浏览器先发 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)

考察点:跨域 + 凭证

标准答案: 默认情况下,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)

考察点:客户端存储对比

标准答案

维度CookieLocalStorageSessionStorageIndexedDB
容量~4KB~5MB~5MB几百 MB+
生命周期自定义永久tab 关闭即清永久
自动随请求发送
同步/异步同步同步同步异步
存储类型字符串字符串字符串任意结构化数据
跨标签页共享
适用场景登录态用户偏好、缓存临时草稿离线应用、大数据

追问

  • localStorage 怎么做跨标签页通信?(监听 storage 事件,同源跨 tab)
  • localStorage 满了怎么办?(QuotaExceededError,try/catch)

考察点:Cookie 安全

标准答案

属性含义防护
Secure仅 HTTPS 才发送防中间人
HttpOnlyJS 读不到(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

对比

维度XSSCSRF
攻击面你的网站被注入脚本利用你已登录的状态
是否需要恶意脚本
主要防护转义 + CSPSameSite + 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' });

加分回答

  • 推荐用 idbDexie.js 包装库
  • 注意版本管理:升级 schema 要在 onupgradeneeded 里
  • 事务超出 microtask 边界会失效

Q11(加餐):缓存策略 (HTTP 缓存) 简介

考察点:HTTP 缓存

标准答案

强缓存(不发请求,直接用本地):

  • Cache-Control: max-age=3600(推荐)
  • Expires: 时间(旧)

协商缓存(发请求,服务端判断 304):

  • ETag / If-None-Match
  • Last-Modified / If-Modified-Since

流程

请求 → 强缓存命中?─ 是 → 直接用(200, from disk/memory cache)
              ├─ 否 → 发请求 → 服务端比对 ETag/Last-Modified
                        ├─ 没变 → 304 Not Modified(用本地)
                        └─ 变了 → 200 + 新内容

Cache-Control 常用值

  • no-store:完全不缓存
  • no-cache:缓存但每次都校验
  • private / public:仅浏览器 / 含 CDN
  • max-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 登录了但没权限)