在前端开发中,跨域是绕不开的高频问题。新手遇到跨域报错往往一头雾水,老手也可能在不同场景下选错解决方案。本文结合实战代码和项目经验,把同源策略、6 种跨域解决方案的原理、实现、优缺点和适用场景讲透,看完就能落地解决跨域问题。
一、先搞懂:什么是同源策略?
跨域的本质是浏览器的同源策略限制,这是浏览器自带的安全机制,目的是防止恶意网站窃取其他网站的数据。
1. 同源的判断标准
一个 URL 由「协议、域名(IP)、端口、路径」组成,只有前三者完全一致,才被视为 “同源”:
https://192.168.1.10:8080/home
├── 协议:https
├── 域名/IP:192.168.1.10
├── 端口:8080
└── 路径:/home
-
协议不同:
https://www.baidu.com与http://www.baidu.com; -
域名不同:
https://www.baidu.com与https://map.baidu.com; -
端口不同:
https://www.baidu.com:80与https://www.baidu.com:8080。
2. 跨域的常见场景
- 前端运行于
127.0.0.1:5500,后端接口部署在localhost:3000,端口不一致,引发跨域; - 本地开发环境直接请求线上服务接口,域名不同,引发跨域;
- 同一主域名下的不同子域名之间相互访问(如
a.xxx.com请求b.xxx.com),同样触发跨域。
二、6 种跨域解决方案:原理 + 实战
1. JSONP:利用 script 标签突破限制
核心原理
<script>标签的 src 属性不受同源策略约束,可跨域加载资源。通过动态创建 script 标签,拼接回调函数名向后端请求,后端将数据包裹在回调函数中返回,前端执行函数获取数据。
前端实现(jsonp/index.html)
function jsonp(url, cb) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = `${url}?cb=${cb}` // 拼接回调函数参数
// 挂载全局回调函数
window[cb] = function(data) {
resolve(data)
document.body.removeChild(script) // 清理标签
}
document.body.appendChild(script)
})
}
// 调用示例
jsonp('http://localhost:3000', 'callback').then(res => {
console.log('JSONP获取数据:', res);
})
后端实现(jsonp/server.js)
const http = require('http')
const server = http.createServer((req, res) => {
const query = new URL(req.url, 'http://localhost:3000').searchParams
if (query.get('cb')) {
const cb = query.get('cb')
const data = 'Hello JSONP'
// 拼接回调函数执行语句
const result = `${cb}("${data}")`
res.end(result)
}
})
server.listen(3000, () => console.log('JSONP服务启动:localhost:3000'))
优缺点
✅ 优点:兼容性好,支持老旧浏览器; ❌ 缺点:仅支持 GET 请求,需后端配合,存在 XSS 安全风险。
2. CORS:后端配置响应头(最常用)
核心原理
CORS(跨域资源共享)是 W3C 标准,通过后端在响应头中设置允许跨域的规则,告诉浏览器 “该域名可以访问资源”,彻底解决跨域问题。
后端实现(cors/server.js)
const http = require('http')
const server = http.createServer((req, res) => {
// 允许指定源跨域(* 表示允许所有源,生产环境建议指定具体域名)
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
// 允许所有请求方法(GET/POST/PUT等)
res.setHeader('Access-Control-Allow-Methods', '*')
// 允许自定义请求头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
// 返回数据
res.end(JSON.stringify({ name: '张三', age: 18 }))
})
server.listen(3000, () => console.log('CORS服务启动:localhost:3000'))
前端调用(cors/index.html)
// 普通fetch请求即可,无需额外处理
fetch('http://localhost:3000').then(res => res.json()).then(data => {
console.log('CORS获取数据:', data);
})
优缺点
✅ 优点:支持所有 HTTP 请求方法,前端无需额外处理,适配现代浏览器;
❌ 缺点:需后端配合配置,复杂场景(如带 Cookie)需额外配置withCredentials。
3. 代理跨域:前端 “曲线救国”
核心原理
跨域是浏览器的限制,服务器之间请求无跨域问题。通过搭建代理服务器,前端请求代理服务器,代理服务器转发请求到后端,再将结果返回给前端,绕开浏览器的同源策略。
方案 1:Node 代理(proxy/server.js)
const http = require('http')
const server = http.createServer((req, res) => {
// 允许前端跨域访问代理服务器
res.setHeader('Access-Control-Allow-Origin', '*')
// 代理转发请求到目标后端
http.request({
hostname: '192.168.31.221', // 后端真实地址
port: 3000,
path: '/',
method: 'GET'
}, (response) => {
response.on('data', (chunk) => {
res.end(chunk.toString()) // 将后端数据返回给前端
})
}).end()
})
server.listen(3000, () => console.log('Node代理服务启动:localhost:3000'))
方案 2:Vite 代理(开发环境首选)
在vite\.config\.js中配置代理,无需搭建独立 Node 服务:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
// 匹配以/api开头的请求
'/api': {
target: 'http://192.168.31.221:3000', // 后端地址
changeOrigin: true, // 开启跨域模拟
rewrite: (path) => path.replace(/^\/api/, '') // 去除/api前缀
}
}
}
})
方案 3:Nginx 代理(生产环境)
Nginx 配置示例(核心逻辑):
server {
listen 80;
server_name localhost;
# 代理前端请求到后端
location /api {
proxy_pass http://192.168.31.221:3000; # 后端地址
add_header Access-Control-Allow-Origin *;
}
}
优缺点
✅ 优点:前端可控,无需后端改代码,开发 / 生产环境均适用; ❌ 缺点:仅转发请求,需维护代理服务器(Nginx/Node)。
4. WebSocket:不受同源策略约束的全双工通信
核心原理
WebSocket 是独立的协议,不属于 HTTP,天生不受同源策略限制,可实现前端和后端的全双工实时通信(前端发请求、后端主动推送)。
后端实现(socket/server.js)
const websocket = require('ws')
const ws = new websocket.Server({ port: 3000 })
let count = 0
ws.on('connection', (socket) => {
console.log('客户端连接成功');
// 接收客户端消息
socket.on('message', (data) => {
console.log('客户端发送:', data.toString());
// 定时向前端推送数据
setInterval(() => {
socket.send(JSON.stringify({ count: count++ }))
}, 2000)
})
})
前端实现(socket/index.html)
function myWebSocket(url, params) {
return new Promise((resolve, reject) => {
const socket = new WebSocket(url)
// 连接成功
socket.onopen = function() {
console.log('WebSocket连接成功');
socket.send(JSON.stringify(params)) // 发送参数
}
// 接收后端推送的消息
socket.onmessage = function(e) {
console.log('后端推送:', e.data);
resolve(e.data)
}
})
}
// 调用示例
myWebSocket('ws://localhost:3000', { age: 18 })
适用场景
实时性要求高的业务:滴滴司机位置更新、聊天消息推送、烟感报警器实时报警等。
优缺点
✅ 优点:实时性强,全双工通信,无跨域限制; ❌ 缺点:服务端性能开销大,需维护长连接。
5. postMessage:跨窗口 /iframe 通信
核心原理
HTML5 新增的postMessage方法,允许不同窗口、不同域的页面之间安全地传递数据,适用于 iframe 嵌套、多窗口通信场景。
父页面(postMessage/index.html)
const iframe = document.querySelector('iframe')
const data = { name: '探长', age: 18 }
// 等待iframe加载完成
iframe.onload = function() {
// 向子页面发送消息(指定目标域)
this.contentWindow.postMessage(data, 'http://127.0.0.1:8080/')
// 接收子页面回传的消息
window.onmessage = function(e) {
console.log('子页面回传:', e.data);
}
}
子页面(postMessage/index2.html)
// 监听父页面消息
window.onmessage = function(e) {
console.log('父页面发送:', e.data);
document.getElementById('name').innerHTML = e.data.name
// 3秒后回传消息
setTimeout(() => {
const newData = { ...e.data, age: e.data.age + 1 }
e.source.postMessage(newData, e.origin)
}, 3000)
}
优缺点
✅ 优点:适配多窗口 /iframe 场景,不受域名限制; ❌ 缺点:仅适用于浏览器环境,需处理消息来源校验。
6. document.domain:子域名跨域(已废弃)
核心原理
通过设置document\.domain将不同子域名的页面统一为相同域名,实现跨域访问。注意:Chrome 115 版本已废弃该特性,仅作了解。
// 父页面(a.xxx.com)和子页面(b.xxx.com)都设置
document.domain = 'xxx.com'
// 父页面可直接访问子页面的window数据
console.log(iframe.contentWindow.data)
优缺点
✅ 优点:简单易实现(仅适用于子域名); ❌ 缺点:Chrome 已废弃,兼容性差,仅支持子域名。
三、解决方案选型指南
| 解决方案 | 适用场景 | 核心优势 | 注意事项 |
|---|---|---|---|
| JSONP | 老旧浏览器、仅需 GET 请求 | 兼容性好 | 仅支持 GET,有 XSS 风险 |
| CORS | 现代浏览器、前后端协同开发 | 支持所有请求方法,前端无侵入 | 需后端配置响应头 |
| 代理(Node/Vite/Nginx) | 前后端分离项目、生产 / 开发环境 | 前端可控,无需后端改代码 | 需维护代理服务 |
| WebSocket | 实时通信(聊天、推送) | 全双工、实时性强 | 服务端性能开销大 |
| postMessage | 跨窗口 /iframe 通信 | 跨域传递数据灵活 | 需校验消息来源 |
| document.domain | 子域名跨域(仅历史项目) | 实现简单 | Chrome 已废弃,不推荐 |
四、总结
跨域问题的本质是浏览器的同源策略限制,解决思路主要分三类:
-
绕开限制(JSONP、WebSocket);
-
后端放行(CORS);
-
中间层转发(代理)。
实际开发中,CORS 是最常用的方案(前后端协同),Vite/Node 代理 是开发环境首选(前端自主解决),WebSocket 适配实时通信场景,其他方案根据业务兼容性、场景需求灵活选择即可。