Appearance
Ajax、CORS 与浏览器缓存
问题
Ajax 是什么?如何创建 Ajax?Ajax 如何处理跨域和缓存?
结论
Ajax 指页面不刷新,通过脚本向服务器请求数据并更新局部 UI 的技术模式。传统实现使用 XMLHttpRequest,现代项目通常使用 fetch。
js
const response = await fetch('/api/users', {
method: 'GET',
headers: {
Accept: 'application/json'
}
});
const users = await response.json();Ajax 跨域受同源策略限制。标准方案是 CORS,由服务端返回允许的响应头;前端代理只是在开发或服务端层面转发请求,不能让浏览器忽略安全模型。
http
Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Credentials: true携带 Cookie 时,不能使用 Access-Control-Allow-Origin: *,还需要前端设置:
js
fetch('https://api.example.com/profile', {
credentials: 'include'
});同源策略的三种限制
浏览器同源策略要求协议、域名、端口三者全部相同。限制分三类:
| 限制类型 | 说明 |
|---|---|
| DOM 限制 | 不能通过脚本读取不同源 iframe 的 DOM |
| Web 数据限制 | localStorage、IndexedDB、Cookie 按域名隔离 |
| 网络限制 | XHR / fetch 只能访问同源资源(浏览器拦截响应) |
注意:同源策略拦截的是响应,不一定阻止请求发出。简单请求会先发出再被浏览器拦截;非简单请求先发 OPTIONS 预检,通过后才发真实请求。
简单请求与预检请求(POST 为什么发两次请求)
"POST 发两次请求"是 CORS 预检机制导致的——第一次是浏览器自动发出的 OPTIONS 预检,第二次才是真正的 POST。
简单请求条件(以下全部满足才是简单请求,直接发出、不预检):
- 方法为
GET、HEAD、POST之一 Content-Type仅限:text/plain、multipart/form-data、application/x-www-form-urlencoded- 没有自定义请求头(如
Authorization、X-Custom-Header) - 不涉及
ReadableStream或 XHR Upload 事件监听
不满足上述任一条件,都会先触发 OPTIONS 预检:
http
OPTIONS /api/data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization服务端返回允许信息后,浏览器才发出真实请求:
http
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400Access-Control-Max-Age 指定预检结果的缓存秒数,有效期内相同请求不再预检。
携带凭证时不能用通配符 *:带 Cookie 的跨域请求,Allow-Origin、Allow-Headers、Allow-Methods 必须明确指定值:
http
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: trueWebpack devServer proxy 原理
开发时的 proxy 不改变浏览器安全模型,而是绕过它:
- 浏览器请求
localhost:3000/api/data(同源,不触发 CORS) - 开发服务器以服务端身份转发给目标 API
- 服务端间通信不受同源策略约束
- 响应原样返回给浏览器
生产环境需要真正的 CORS 响应头或反向代理配置。
Ajax 缓存问题优先由 HTTP 缓存头控制,例如 Cache-Control、ETag、Last-Modified。临时绕过缓存可以给 URL 添加版本号或时间戳,但长期方案应该是正确缓存策略。
js
fetch(`/api/list?v=${buildVersion}`);旧题常问 Ajax 与 Flash。现代回答可以说明 Flash 已退出主流 Web 平台,不适合作为新项目方案;Ajax/fetch 是标准 Web 能力。
Demo
XMLHttpRequest 版本:
js
function request(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = () => resolve(xhr.response);
xhr.onerror = () => reject(new Error('Network error'));
xhr.send();
});
}面试回答:
Ajax 是不刷新页面发起异步请求并局部更新 UI 的模式,传统用 XHR,现代多用 fetch。跨域由浏览器同源策略限制(协议/域名/端口三同),标准方案是服务端配置 CORS。POST 带自定义头或 JSON 类型会触发 OPTIONS 预检,这就是"发两次请求"的原因。带 Cookie 时 Allow-Origin 等不能用通配
*。开发代理只是在服务端转发,绕过了浏览器限制,生产仍需真实 CORS。缓存问题应优先用 HTTP 缓存头解决,时间戳只能算临时绕过。