你是不是也被这些报错折磨过?
Access to XMLHttpRequest has been blocked by CORS policy...跨域几乎是每一个前端工程师都会遇到的第一道坎。本文将用 面试 + 实战 + 原理 + 场景 的方式,带你彻底吃透跨域,并掌握五种主流解决方案:
JSONP、CORS、Nginx 反向代理、WebSocket、postMessage
一、什么是跨域?(一句话讲清楚)
跨域 = 浏览器的同源策略不允许一个源去读取另一个源的响应结果
同源策略(Same-Origin Policy)
只有当以下三者完全一致时,才是同源:
协议 + 域名 + 端口
| URL | 是否同源 | 原因 |
|---|---|---|
| http://localhost:3000 | — | 基准 |
| http://localhost:3000/api | ✅ | 路径不同不影响 |
| https://localhost:3000 | ❌ | 协议不同 |
| http://127.0.0.1:3000 | ❌ | 域名不同 |
| http://localhost:8080 | ❌ | 端口不同 |
本质:请求可以发出去,但浏览器不让你读响应结果。
二、JSONP:最古老的跨域方案(利用 script 标签不受同源限制)
1. 原理
<script> 标签的 src 不受同源策略限制,可以加载任意域名的资源。
浏览器:
<script src="http://api.xxx.com/user?callback=cb"></script>
服务器返回:
cb({ name: 'Tom', age: 18 })
前端定义:
function cb(data) {
console.log(data)
}
2. 特点
| 优点 | 缺点 |
|---|---|
| 实现简单 | 只支持 GET |
| 兼容性好 | 安全性差 |
| 老项目常见 | 无法处理复杂请求 |
3. 适用场景
- 老项目兼容方案
- 第三方接口只支持 JSONP
三、CORS:最主流、最标准的跨域方案 ⭐⭐⭐⭐⭐
跨资源共享(Cross-Origin Resource Sharing)
1. 原理
服务器通过响应头告诉浏览器:
哪些域名可以访问我
哪些请求方式可以用
哪些请求头是被允许的
也就是说:
跨域不是前端解决的,而是后端“放行”的结果
当浏览器发起跨域请求时:
- 浏览器先检查响应头
- 如果看到这些头:
Access-Control-Allow-Origin: http://localhost:3000
就会理解为:
「服务器允许 http://localhost:3000 这个网站访问它」
于是浏览器才会把响应结果交给前端 JS 使用。
2. 简单请求 vs 预检请求
简单请求
满足以下条件时,浏览器会直接发送请求,不会多做检查:
-
请求方法是:
- GET
- POST
- HEAD
-
请求头是“普通的 header”,比如:
- Content-Type
- Accept
-
Content-Type 只能是:
- text/plain
- application/x-www-form-urlencoded
- multipart/form-data
满足这些条件:
浏览器认为:这个请求是安全的 → 直接放行
所以你看到的效果是:
一次请求就成功返回数据
预检请求(OPTIONS)
当你做了“更危险”的事情时,比如:
- 使用 PUT / DELETE
- 携带 Authorization
- 自定义请求头
浏览器会变得非常谨慎:
先不发真正请求,而是先问服务器一句话:
OPTIONS /login
意思是:
「我想用 Authorization 头来访问你,你允许吗?」
服务器返回:
Access-Control-Allow-Headers: Authorization
浏览器看到后:
「允许的,那我才真正发送 POST /login」
所以你会看到:
一次接口调用,其实发了两次请求(OPTIONS + 真正请求)
3. Node 实战
app.use((req, res, next) => {
// 允许哪个前端域名访问我
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
// 允许哪些请求方式
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
// 允许前端携带哪些请求头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// 是否允许携带 cookie
res.setHeader('Access-Control-Allow-Credentials', 'true');
next();
});
这一段的意思是:
后端明确告诉浏览器:
- 哪个前端能访问
- 用什么方式访问
- 能带什么信息访问
- 能不能带登录态访问
浏览器看到后:
「安全,放行!」
四、Nginx 反向代理:开发环境最舒服的方案
1. 原理
浏览器只和 Nginx 通信 → Nginx 再请求后端 → 浏览器以为是同源
浏览器 → Nginx(前端域名) → 后端服务器
2. 配置示例
server {
listen 80;
server_name www.front.com;
location /api/ {
proxy_pass http://api.back.com/;
proxy_set_header Host $host;
}
}
前端请求:
axios.get('/api/user/list')
五、WebSocket:天生跨域的通信协议
1. 原理
WebSocket 不受同源策略限制:
const ws = new WebSocket('ws://api.xxx.com:8080');
建立长连接后即可双向通信。
2. 特点
| 优点 | 缺点 |
|---|---|
| 天生跨域 | 不适合普通接口 |
| 实时通信 | 维护成本高 |
3. 场景
- 聊天室
- 实时推送
- 股票行情
六、postMessage:iframe 跨域通信方案
1. 原理
// 父页面
iframe.contentWindow.postMessage('hello', 'http://child.com');
// 子页面
window.addEventListener('message', e => {
console.log(e.data);
});
2. 特点
| 优点 | 缺点 |
|---|---|
| 安全可控 | 使用复杂 |
| 支持双向通信 | 仅限页面通信 |
3. 场景
- 微前端
- 第三方嵌入页面
- 登录中台系统
七、五种方案对比总结(面试必背)
| 方案 | 原理 | 使用场景 | 面试评价 |
|---|---|---|---|
| JSONP | script 无跨域限制 | 老项目 | ⭐ |
| CORS | 响应头授权 | 正式生产 | ⭐⭐⭐⭐⭐ |
| Nginx | 服务端转发 | 开发环境 | ⭐⭐⭐⭐ |
| WebSocket | 天生跨域协议 | 实时通信 | ⭐⭐⭐ |
| postMessage | 页面通信 | 微前端 | ⭐⭐ |
八、面试标准回答模板
跨域是浏览器同源策略导致的安全限制。实际项目中主要通过 CORS 在服务端设置响应头解决,区分简单请求和预检请求。本地开发阶段使用 Nginx 或 Vite Proxy 代理避免跨域。JSONP 只支持 GET 已逐渐淘汰,WebSocket 天生跨域用于实时通信,postMessage 常用于 iframe 或微前端页面通信。
九、结语
跨域不是 Bug,而是浏览器的安全设计。
真正的高手不是背方案,而是:
知道什么时候用哪种方案,为什么这样用。
如果你觉得这篇文章对你有帮助,欢迎点赞 + 收藏 + 关注,后续我会持续输出:
- 前端面试高频知识点深度解析
- React/Vue 项目实战拆解
- 大厂面试通关路线图