跨域问题:从同源策略到JSONP实战,前端必知必会
前端面试中几乎必问的跨域问题,你真的理解透彻了吗?今天我们就来彻底搞懂它!
什么是跨域?浏览器为何如此"小气"
想象一下,你正在本地开发一个React应用(运行在5173端口),需要调用后端API(运行在8080端口)。当你信心满满地发起请求时,浏览器却无情地抛出了错误:

csharp
Access to fetch at 'http://localhost:8080/api/data' from origin 'http://localhost:5173'
has been blocked by CORS policy
这就是经典的跨域问题!浏览器为何如此"小气"?这要从同源策略说起。
同源策略:浏览器的安全卫士
同源策略(Same-Origin Policy) 是浏览器最基本的安全机制。它规定:只有当两个URL的协议(protocol)、域名(domain)和端口(port) 完全相同时,才被认为是同源。
让我们看几个例子:
当前页面URL | 请求URL | 是否同源 | 原因 |
---|---|---|---|
http://a.com/index |
http://a.com/api |
✅ 是 | 协议、域名、端口都相同 |
http://a.com:8080 |
http://a.com:3000/api |
❌ 否 | 端口不同 |
https://a.com |
http://a.com/api |
❌ 否 | 协议不同 |
http://a.com |
http://b.com/api |
❌ 否 | 域名不同 |
为何要有同源策略?
没有同源策略的世界会多么可怕!想象一下:
- 你登录了银行网站(
bank.com
) - 然后访问了一个恶意网站(
evil.com
) - 恶意网站可以在后台悄悄请求
bank.com/transfer?to=attacker&amount=10000
- 因为浏览器会自动携带
bank.com
的cookie,转账可能成功!
同源策略就像浏览器的"边防检查",保护我们免受:
- CSRF攻击:跨站请求伪造
- 数据窃取:防止恶意网站读取用户敏感数据
- DOM泄露:阻止恶意网站操作其他网站的DOM
跨域解决方案:突破"边防"的多种方式
既然同源策略如此严格,我们如何在开发中解决跨域问题呢?下面是几种常用方法:
1. JSONP:巧用Script标签的"漏洞"
JSONP 是一种绕过同源策略限制的"古老"但有效的方法 ,它利用
<script>
标签不受同源策略限制的特点,通过动态创建<script>
标签来加载跨域数据。
⚠️ 注意:JSONP 只支持 GET 请求,不支持 POST 或其他方法。
实现原理
-
前端创建一个
<script>
标签,src指向跨域API并携带回调函数名html<script src="http://api.com/data?callback=handleData"></script>
-
后端返回的不是JSON,而是包裹在回调函数中的JSON数据
jsconst data = { code:0, msg:'字节,我来了' } res.end("callback("+JSON.stringify(data)+")")
因为我们毕竟是包裹在script标签内,要遵守script标签内容的规则,所以要用回调函数包裹,将返回的数据变成js格式
jscallback({ "name": "小明", "age": 25 });
3. 前端预先定义好回调函数
js
function callback(data) {
console.log(data); // 获取到跨域数据!
}
手写JSONP实现
一直像上面一样既要保证前端使用的是script标签去请求数据,还要让前后端共同使用一个在前端声明的使JSON文件变成JS的回调函数,还要在前后端不同的地方反复调用,使用方法非常复杂,于是我们便想将JSONP封装成一个函数,之后每次请求时,只要调用这个函数即可,调用这个函数就像使用可以跨域的fetch一样方便
前端封装:
JSONP函数包含的:
- 将跨域请求包装成一个script文件,并挂载到DOM上执行
- 定义callback全局函数,在这个函数可以接受到后端返回的数据
- 将回调函数,基础的URL和其他各种参数传入JSONP函数中,在函数里面将这些传入的参数拼凑成一个script的src地址(注意queryString对象之前要加上
?
) - 为了确保一定会传递callback函数,我们在JSONP函数里面再将callback函数和其他查询参数放到一起
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function getJSONP ({url,params={},callback}){
//`callback${Date.now()}`避免重复,覆盖别人的
//DOM
return new Promise((resolve,reject)=>{
let script = document.createElement('script')
params = {...params,callback}
// queryString的对象 ?callback=show&a=1&b-2
let arr = []
for(let key in params){
arr.push(`${key}=${params[key]}`)
}
console.log(arr)
// queryString 以?开始 查询字符串
script.src = `${url}?${arr.join('&')}`
window[callback] = (data) =>{
resolve(data)
}//在页面上声明了一个全局的函数
document.body.appendChild(script)
})
}
getJSONP({
url:'http://localhost:3000/say',
params:{//查询参数
wd:'iloveyou'
},
callback:'show'//必须的 show为实参
}).then(data =>{
console.log(data)
})
</script>
</body>
</html>
后端配合(Node.js): 后端将会接受到前端返回的url,在url里面可以接收到前端传递来的callback回调函数,在返回数据时调用callback函数,即可
js
// server.js - 原生 Node.js 版本
const http = require('http');
const server = http.createServer((req, res) => {
// 匹配 GET 请求 /say
if (req.url.startsWith('/say')) {
// 解析查询参数(简单处理)
const url = new URL(req.url, `http://${req.headers.host}`);
console.log(url.searchParams)
const wd = url.searchParams.get('wd');
const callback = url.searchParams.get('callback');
console.log(url)
console.log(wd); // Iloveyou
console.log(callback); // show
// 返回 JSONP 格式响应
res.writeHead(200, { 'Content-Type': 'application/javascript' });
const data = {
code:0,
msg:'字节,我来了'
}
res.end(`${callback}(${JSON.stringify(data)})`);
} else {
res.writeHead(404);
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
JSONP的优缺点
✅ 优点:
- 兼容性好(支持IE6+等老浏览器)
- 不需要后端特殊配置(CORS)
❌ 缺点:
- 只支持GET请求
- 错误处理困难
- 存在XSS风险(信任所有返回的JS)
- 需要前后端约定回调参数名
2. CORS:现代跨域的"正规军"
CORS(Cross-Origin Resource Sharing)是现代浏览器支持的官方跨域方案。
工作原理
从源头解决问题,让fetch可以使用,使得数据可以跨域
当浏览器检测到跨域请求时:
- 对于简单请求(GET/POST/HEAD + 特定Header),直接发送请求
- 对于复杂请求(PUT/DELETE等),先发送OPTIONS预检请求
- 服务器响应是否允许跨域
- 浏览器根据响应决定是否放行实际请求
简单请求
直接在后端的请求头设置:'Access-Control-Allow-Origin'
属性
'Access-Control-Allow-Origin': '*'
表示允许所有不同域的ip地址访问数据'Access-Control-Allow-Origin': 'http://localhost:3030'
表示只允许http://localhost:3030 这个地址访问
js
if(req.url === '/api/test' && req.method === 'GET') {
res.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
})
res.end(JSON.stringify({
msg: '跨域成功!'
}))
}
复杂请求
由于请求较为复杂,可能会修改我的数据,不仅仅是简单的读取,所以需要更高的权限,因此要先发送OPTIONS预检请求,看是否符合条件,再向服务器发送真实的请求

后端CORS配置
js
const http = require('http');
const server = http.createServer((req, res) => {
// 设置CORS头
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:5173');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 处理预检请求
if (req.method === 'OPTIONS') {
res.writeHead(204); // No Content
res.end();
return;
}
// 处理实际请求
if (req.url === '/api/data') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: '跨域成功!' }));
}
});
server.listen(8080, () => {
console.log('CORS服务器运行在 http://localhost:8080');
});
CORS核心响应头
响应头 | 作用 |
---|---|
Access-Control-Allow-Origin | 允许的源(* 或特定域名) |
Access-Control-Allow-Methods | 允许的HTTP方法 |
Access-Control-Allow-Headers | 允许的自定义请求头 |
Access-Control-Allow-Credentials | 是否允许发送Cookie(true/false) |
Access-Control-Max-Age | 预检请求缓存时间(秒) |
前端使用CORS
js
// 带凭证的请求(如Cookie)
fetch('http://api.com/data', {
credentials: 'include'
})
.then(response => response.json())
.then(data => console.log(data));
其他跨域方案
-
Nginx反向代理:
nginxserver { listen 80; server_name local.dev; location /api { proxy_pass http://localhost:8080; add_header Access-Control-Allow-Origin *; } }
-
WebSocket:天然支持跨域
jsconst socket = new WebSocket('ws://api.com');
-
postMessage:跨窗口通信
js// 发送方 otherWindow.postMessage('Hello!', 'https://target.com'); // 接收方 window.addEventListener('message', event => { if (event.origin !== 'https://trusted.com') return; console.log('收到消息:', event.data); });
总结:如何选择跨域方案
方案 | 适用场景 | 特点 |
---|---|---|
JSONP | 老项目兼容、简单GET请求 | 兼容性好但安全性低 |
CORS | 现代Web应用首选 | 官方标准、功能全面 |
反向代理 | 前端控制跨域、避免后端改动 | 部署时常用、配置灵活 |
WebSocket | 实时双向通信 | 不受同源策略限制 |
实际开发建议:现代项目优先使用CORS,老项目或特殊场景考虑JSONP,部署时可通过Nginx统一处理跨域。
理解了跨域的原理和各种解决方案,下次面试官再问跨域问题,你就可以从容应对了!在实际项目中,根据需求选择最适合的方案,让数据自由穿越浏览器的"边防"吧!🚀