一、什么是跨域与同源策略
1.1 同源策略
同源策略(Same-Origin Policy)是浏览器最核心的安全机制之一。它规定:协议、域名、端口三者完全相同的两个URL才属于同源。非同源的页面之间,脚本无法相互访问DOM、Cookie、LocalStorage等数据,也不能直接发起AJAX请求。
1.2 跨域产生的条件
只要协议、域名、端口有任何一个不同,就会产生跨域。
| 当前页面URL | 请求URL | 是否跨域 | 原因 |
|---|---|---|---|
http://a.com |
http://a.com/api |
否 | 完全同源 |
http://a.com |
https://a.com/api |
是 | 协议不同 |
http://a.com |
http://b.com/api |
是 | 域名不同 |
http://a.com:8080 |
http://a.com:3000 |
是 | 端口不同 |
http://www.a.com |
http://api.a.com |
是 | 子域名不同 |
二、通用跨域方案详解
方案一:JSONP
原理
JSONP(JSON with Padding)利用 <script> 标签不受同源策略限制的特性,通过动态创建script标签,将回调函数名作为参数传递给服务端,服务端将数据包裹在回调函数中返回,浏览器执行脚本从而拿到数据。
优点 :兼容性好,支持老式浏览器
缺点:只支持GET请求;存在XSS安全风险;需要服务端配合
示例代码
前端实现:
javascript
// 原生JS实现
function jsonp(url, params, callbackName) {
return new Promise((resolve, reject) => {
// 创建script标签
const script = document.createElement('script');
// 拼接参数
const queryString = Object.keys(params)
.map(key => `${key}=${params[key]}`)
.join('&');
script.src = `${url}?${queryString}&callback=${callbackName}`;
// 挂载全局回调函数
window[callbackName] = function(data) {
resolve(data);
// 清理
document.body.removeChild(script);
delete window[callbackName];
};
// 错误处理
script.onerror = function() {
reject(new Error('JSONP request failed'));
document.body.removeChild(script);
delete window[callbackName];
};
document.body.appendChild(script);
});
}
// 使用
jsonp('http://api.example.com/user', { id: 123 }, 'handleUser')
.then(data => console.log('用户数据:', data));
Node.js 服务端实现:
javascript
const express = require('express');
const app = express();
app.get('/user', (req, res) => {
const { callback } = req.query;
const data = { id: 123, name: '张三', age: 25 };
if (callback) {
// JSONP格式:用回调函数包裹数据
res.send(`${callback}(${JSON.stringify(data)})`);
} else {
res.json(data);
}
});
app.listen(3000);
方案二:CORS(跨域资源共享)
原理
CORS(Cross-Origin Resource Sharing)是W3C标准,通过在HTTP响应头中添加特定字段,告诉浏览器该资源允许被哪些源访问。浏览器根据响应头决定是否放行跨域请求。
CORS分为简单请求 和非简单请求(预检请求):
- 简单请求 :方法为GET/HEAD/POST,且Content-Type为
text/plain、multipart/form-data、application/x-www-form-urlencoded - 非简单请求:会先发送OPTIONS预检请求,确认服务端允许后再发送真实请求
优点 :支持所有HTTP方法;安全规范;前端无需特殊处理
缺点:不支持IE10以下;需要服务端配置
示例代码
前端(正常发请求即可):
javascript
// 原生fetch
fetch('http://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'test' }),
// 携带cookie
credentials: 'include'
})
.then(res => res.json())
.then(data => console.log(data));
// axios配置
axios.defaults.withCredentials = true; // 允许携带cookie
Node.js 服务端配置:
javascript
const express = require('express');
const app = express();
// 手动配置CORS
app.use((req, res, next) => {
// 允许的源,*表示所有源(但不能携带cookie)
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
// 允许的请求方法
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');
// 预检请求缓存时间
res.setHeader('Access-Control-Max-Age', 86400);
// 处理OPTIONS预检请求
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
app.get('/data', (req, res) => {
res.json({ code: 0, data: '跨域成功' });
});
app.listen(3000);
也可以直接使用 cors 中间件:
javascript
const cors = require('cors');
app.use(cors({
origin: 'http://localhost:8080',
credentials: true
}));
方案三:Nginx 反向代理
原理
利用Nginx作为反向代理服务器,将前端请求转发到目标后端服务。对于浏览器来说,请求的是同源的Nginx服务器,不存在跨域问题;Nginx内部再将请求转发到真实后端。
优点 :前端无需修改代码;性能好;适合生产环境
缺点:需要运维配置Nginx
配置示例
Nginx配置文件(nginx.conf):
nginx
server {
listen 80;
server_name www.example.com;
# 前端静态资源
location / {
root /usr/share/nginx/html;
index index.html;
}
# API接口反向代理
location /api/ {
# 转发到真实后端服务
proxy_pass http://backend-server.com/;
# 转发请求头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 处理cookie跨域
proxy_cookie_domain backend-server.com www.example.com;
}
}
前端请求:
javascript
// 前端直接请求同源的/api路径,由nginx转发到后端
fetch('/api/user/123')
.then(res => res.json())
.then(data => console.log(data));
方案四:Node 中间层接口转发
原理
在前端和后端之间搭建一个Node中间层服务,前端请求同源的Node服务,Node服务内部通过HTTP请求调用后端接口,再将结果返回给前端。服务端之间的调用不受浏览器同源策略限制。
优点 :灵活可控;可做数据加工、缓存、鉴权等
缺点:增加部署成本;多一次网络转发
示例代码
javascript
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// 方式一:使用http-proxy-middleware中间件
app.use('/api', createProxyMiddleware({
target: 'http://api.backend.com',
changeOrigin: true, // 修改请求头的host为目标地址
pathRewrite: {
'^/api': '' // 路径重写,去掉/api前缀
},
// 可选:修改请求头
onProxyReq(proxyReq, req, res) {
proxyReq.setHeader('Authorization', 'Bearer xxx');
}
}));
// 方式二:手动转发(适合需要处理数据的场景)
app.get('/api/user/:id', async (req, res) => {
try {
const response = await fetch(`http://api.backend.com/user/${req.params.id}`, {
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
// 可以在这里对数据做加工处理
res.json({ code: 0, data });
} catch (err) {
res.status(500).json({ code: -1, msg: '转发失败' });
}
});
app.listen(3000, () => {
console.log('中间层服务运行在3000端口');
});
方案五:Webpack DevServer Proxy(开发环境代理)
原理
在开发环境下,Webpack DevServer内置了http-proxy-middleware,可以将API请求代理到后端服务器。本质上也是中间层转发,只在开发环境使用。
优点 :配置简单;开发体验好
缺点:仅适用于开发环境
配置示例
webpack.config.js 配置:
javascript
module.exports = {
devServer: {
port: 8080,
proxy: {
// 匹配/api开头的请求
'/api': {
target: 'http://localhost:3000', // 目标后端地址
changeOrigin: true, // 改变源
pathRewrite: {
'^/api': '' // 重写路径
},
// 支持https
secure: false,
// 自定义代理逻辑
bypass: function(req, res, proxyOptions) {
if (req.headers.accept.indexOf('html') !== -1) {
return '/index.html';
}
}
},
// 多个代理配置
'/ws': {
target: 'ws://localhost:4000',
ws: true // 支持websocket
}
}
}
};
Vite 配置:
javascript
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
};
方案六:WebSocket
原理
WebSocket是HTML5的全双工通信协议,它本身不受同源策略限制 。WebSocket使用 ws://(非加密)和 wss://(加密)协议,建立连接后可以双向通信。
优点 :不受同源限制;双向通信;性能高
缺点:只适用于WebSocket场景,不能替代普通HTTP接口
示例代码
前端:
javascript
// 可以直接连接不同源的WebSocket服务
const ws = new WebSocket('ws://api.example.com:8080/chat');
ws.onopen = function() {
console.log('连接已建立');
ws.send('Hello Server!');
};
ws.onmessage = function(event) {
console.log('收到消息:', event.data);
};
ws.onerror = function(error) {
console.error('WebSocket错误:', error);
};
ws.onclose = function() {
console.log('连接已关闭');
};
Node.js 服务端(ws库):
javascript
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
console.log('新客户端连接');
ws.on('message', function incoming(message) {
console.log('收到:', message);
ws.send(`服务端回复: ${message}`);
});
ws.on('close', function() {
console.log('客户端断开连接');
});
});
方案七:document.domain
原理
对于主域名相同、子域名不同 的页面,可以通过设置 document.domain 为共同的主域名,实现跨子域通信。常用于iframe之间的交互。
适用场景 :a.example.com 和 b.example.com 之间通信
优点 :简单直接
缺点:只能用于主域名相同的场景;有安全风险
示例代码
页面A(http://a.example.com):
html
<iframe id="iframe" src="http://b.example.com/page.html"></iframe>
<script>
// 设置为共同的主域名
document.domain = 'example.com';
// 等待iframe加载完成
document.getElementById('iframe').onload = function() {
// 可以访问iframe的window对象
const iframeWin = document.getElementById('iframe').contentWindow;
console.log(iframeWin.getData()); // 调用iframe中的方法
};
// 暴露给iframe调用的方法
function parentMethod() {
return '来自父页面的数据';
}
</script>
**页面B(http://b.example.com/page.html)**:
html
<script>
// 同样设置主域名
document.domain = 'example.com';
function getData() {
return '来自子页面的数据';
}
// 访问父页面
console.log(window.parent.parentMethod());
</script>
方案八:postMessage
原理
postMessage 是HTML5提供的API,用于不同窗口/iframe之间的跨域通信。它可以安全地实现跨源通信,不受同源策略限制。
优点 :功能强大;安全可靠;支持各种跨窗口场景
缺点:只能用于窗口间通信,不能解决接口跨域
示例代码
父页面(http://a.com):
html
<iframe id="iframe" src="http://b.com/page.html" style="display:none"></iframe>
<script>
const iframe = document.getElementById('iframe');
iframe.onload = function() {
// 向iframe发送消息
// 参数:消息数据,目标源(*表示任意源,生产环境建议指定具体域名)
iframe.contentWindow.postMessage(
{ type: 'getData', data: { id: 123 } },
'http://b.com'
);
};
// 监听来自iframe的消息
window.addEventListener('message', function(event) {
// 安全校验:验证消息来源
if (event.origin !== 'http://b.com') return;
console.log('收到子页面消息:', event.data);
if (event.data.type === 'result') {
console.log('数据:', event.data.payload);
}
});
</script>
**子页面(http://b.com/page.html)**:
html
<script>
window.addEventListener('message', function(event) {
// 安全校验
if (event.origin !== 'http://a.com') return;
console.log('收到父页面消息:', event.data);
if (event.data.type === 'getData') {
// 处理数据后回复
const result = { name: '张三', age: 25 };
event.source.postMessage(
{ type: 'result', payload: result },
event.origin
);
}
});
</script>
方案九:window.name
原理
window.name 属性有一个特点:在同一个窗口中,即使页面跳转(跨域),name值也不会丢失 ,且容量可达约2MB。利用这个特性,可以通过iframe加载跨域页面,目标页面将数据写入 window.name,然后iframe跳转到同源页面,此时父页面就可以读取到name中的数据。
优点 :容量较大;兼容性好
缺点:传输过程繁琐;只能传字符串;有安全隐患
示例代码
**主页面(http://a.com/index.html)**:
html
<script>
function crossDomainGetData(url, callback) {
const iframe = document.createElement('iframe');
let state = 0;
iframe.style.display = 'none';
iframe.onload = function() {
if (state === 0) {
// 第一次加载:跨域页面已加载,数据写入了window.name
// 跳转到同源的代理页面
state = 1;
iframe.contentWindow.location = 'http://a.com/proxy.html';
} else if (state === 1) {
// 第二次加载:同源代理页面加载完成
// 读取window.name中的数据
const data = iframe.contentWindow.name;
callback(JSON.parse(data));
// 清理
document.body.removeChild(iframe);
iframe.contentWindow.document.write('');
iframe.src = '';
}
};
iframe.src = url;
document.body.appendChild(iframe);
}
// 使用
crossDomainGetData('http://b.com/data.html', function(data) {
console.log('跨域获取的数据:', data);
});
</script>
**数据页面(http://b.com/data.html)**:
html
<script>
// 将数据写入window.name
window.name = JSON.stringify({
id: 123,
name: '张三',
list: [1, 2, 3]
});
</script>
**代理页面(http://a.com/proxy.html)**:
html
<!-- 空页面即可,只需要和主页面同源 -->
三、典型业务场景的跨域实践
以上9种方案覆盖了前端接口请求、窗口通信、实时通信等通用跨域场景。在实际业务开发中,图片资源处理 和前端监控体系是两个跨域问题高频出现的细分场景,有其特殊的安全规则和针对性解决方案。
3.1 跨域与图片资源
3.1.1 图片加载的同源策略边界
图片是前端最常用的静态资源之一,它的跨域规则和接口请求有明显区别:图片本身的加载不受同源策略限制,但对图片数据的读取和操作会受同源策略约束。
核心行为边界:
- ✅ 允许:
<img>标签加载并渲染任意域名的图片 - ✅ 允许:CSS
background-image引用外域图片 - ❌ 禁止:Canvas绘制跨域图片后,调用
getImageData()读取像素 - ❌ 禁止:Canvas绘制跨域图片后,调用
toDataURL()/toBlob()导出图片 - ❌ 禁止:WebGL将跨域图片作为纹理进行读取处理
浏览器的这种限制是为了安全:防止恶意网站通过Canvas窃取用户的敏感图片(如登录态下的其他网站截图、本地隐私图片等)。
3.1.2 Canvas 画布污染问题
当Canvas绘制了未经过跨域授权的图片后,画布会被浏览器标记为**「已污染(Tainted Canvas)」**,此时任何读取画布像素数据的操作都会直接抛出安全异常。
错误复现代码:
javascript
const img = new Image();
// 加载跨域图片,未设置跨域属性
img.src = 'https://cross-domain.com/photo.jpg';
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 执行读取像素操作,直接报错
// Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D':
// The canvas has been tainted by cross-origin data.
const pixelData = ctx.getImageData(0, 0, canvas.width, canvas.height);
console.log(pixelData);
};
3.1.3 解决方案:crossorigin 属性 + 服务端 CORS 配置
解决画布污染需要前后端配合,两步即可完成:
- 前端 :给图片设置
crossorigin属性,告知浏览器以跨域模式加载该图片 - 服务端:图片所在的服务器配置CORS响应头,允许当前源访问图片资源
crossorigin 属性的两个取值:
| 取值 | 说明 | 适用场景 |
|---|---|---|
anonymous |
默认值,跨域请求不携带Cookie、HTTP认证等凭证 | 绝大多数公开静态图片场景 |
use-credentials |
跨域请求携带凭证 | 需要登录态才能访问的私有图片 |
前端修复代码:
javascript
const img = new Image();
// 关键:设置跨域属性,匿名模式
img.crossOrigin = 'anonymous';
img.src = 'https://cross-domain.com/photo.jpg';
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 正常读取像素数据,不再报错
const pixelData = ctx.getImageData(0, 0, canvas.width, canvas.height);
console.log('像素读取成功', pixelData);
// 也可以正常导出图片
const base64 = canvas.toDataURL('image/jpeg');
console.log('导出Base64成功', base64.slice(0, 50) + '...');
};
服务端 Nginx 配置示例(给静态图片资源开启CORS):
nginx
location ~* \.(jpg|jpeg|png|gif|webp|svg)$ {
root /data/static/images;
# 允许指定域名访问,生产环境不建议用 *
add_header Access-Control-Allow-Origin "https://www.your-site.com";
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Access-Control-Max-Age 86400;
# 处理预检请求
if ($request_method = OPTIONS) {
return 204;
}
}
如果使用阿里云OSS、腾讯云COS等云存储,可在控制台的「跨域设置」中直接配置规则,无需手动写配置。
3.1.4 图片防盗链与跨域的区别
很多站点的图片会出现「403 Forbidden」无法加载的情况,这通常是防盗链限制,而非浏览器同源策略的跨域拦截,二者有本质区别:
- 跨域:浏览器层面的安全限制,拦截的是JS对数据的读取,图片本身可以正常渲染
- 防盗链 :服务端层面的访问控制,通过校验请求头的
Referer字段判断请求来源,直接拒绝返回图片资源
前端应对方式:
- 正规业务:联系资源方将你的域名加入白名单,或使用自有/授权资源
- 代理转发:通过Nginx或Node中间层代理图片,转发时修改或清空Referer头
- 无Referer请求:给img标签设置
referrerPolicy="no-referrer",请求不携带Referer,可绕过部分宽松的防盗链规则
html
<!-- 不发送Referer请求头 -->
<img src="https://example.com/image.jpg" referrerPolicy="no-referrer">
3.2 跨域与前端监控
前端监控包含错误监控、性能监控、用户行为埋点三大核心模块,几乎所有监控体系都会遇到跨域问题------监控服务器通常独立部署,和业务站点不同源。
3.2.1 错误监控:跨域脚本的 Script Error
问题现象 :当页面引用了外域CDN上的JS脚本时,如果脚本内部抛出异常,通过window.onerror捕获到的错误只有一句Script error.,没有错误堆栈、行号列号,完全无法定位问题。
根本原因:这是浏览器的安全机制------故意隐藏跨域脚本的详细错误信息,防止恶意页面通过加载敏感站点的脚本,窃取用户的登录态、隐私数据等信息。
解决方案:和图片跨域逻辑一致,需要前后端双配置:
- 前端 :给跨域的
<script>标签添加crossorigin="anonymous"属性 - 服务端:JS资源所在的CDN/服务器配置CORS响应头
前端代码示例:
html
<!-- 跨域JS脚本添加crossorigin属性 -->
<script src="https://cdn.example.com/bundle.abc123.js" crossorigin="anonymous"></script>
<script>
// 全局错误捕获
window.onerror = function(message, source, lineno, colno, error) {
console.log('错误信息:', message);
console.log('错误文件:', source);
console.log('错误位置:', `${lineno}行${colno}列`);
console.log('错误堆栈:', error?.stack);
// 上报到监控系统
reportError({
message,
source,
line: lineno,
column: colno,
stack: error?.stack
});
return true; // 阻止错误冒泡到控制台
};
</script>
3.2.2 埋点上报:三种跨域上报方案对比
埋点数据需要上报到监控服务端,业界主流有三种上报方式,各自的跨域特性、适用场景完全不同:
方案1:IMG 图片打点上报(最常用)
原理 :利用<img>标签的src请求天然不受同源策略限制的特性,创建一个1x1像素的透明GIF图片,将上报数据拼接在URL查询参数中,浏览器加载图片时自动完成数据发送。
优点 :完全无跨域问题,无需服务端配置;兼容性拉满;异步加载不阻塞主线程
缺点:仅支持GET请求,数据量受URL长度限制;无法获取上报结果;页面跳转时可能中断
代码示例:
javascript
function reportByImg(eventData) {
const img = new Image();
const query = Object.entries(eventData)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
img.src = `https://monitor.example.com/pixel.gif?${query}`;
}
// 上报页面PV
reportByImg({
event: 'page_view',
page: '/product/detail',
userId: 'u_123456',
timestamp: Date.now()
});
方案2:XHR / Fetch 上报
原理:使用标准的AJAX请求上报数据,和普通接口请求一样,需要服务端配置CORS才能跨域。
优点 :支持POST请求,可上报大量结构化数据;能获取响应状态,支持失败重传
缺点:存在跨域限制,需要服务端配置CORS;页面卸载时容易被强制取消
代码示例:
javascript
function reportByFetch(data) {
fetch('https://monitor.example.com/api/report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
mode: 'cors',
credentials: 'omit'
}).catch(() => {
console.warn('上报失败,加入重传队列');
});
}
方案3:navigator.sendBeacon 上报(推荐)
原理 :W3C专门为数据上报设计的标准API,它会在浏览器空闲时异步发送请求,即使页面关闭、跳转也能保证请求完整发出,且天然遵循CORS跨域规范。
优点 :异步非阻塞,不影响页面性能;页面卸载时可靠发送;支持POST请求
缺点:不支持自定义请求头;无法获取响应结果;IE不支持
代码示例:
javascript
function reportByBeacon(data) {
// 优先使用sendBeacon,不支持则降级为img打点
if (navigator.sendBeacon) {
const blob = new Blob([JSON.stringify(data)], {
type: 'application/json'
});
navigator.sendBeacon('https://monitor.example.com/api/report', blob);
} else {
reportByImg(data);
}
}
// 页面离开时上报停留时长
window.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') {
reportByBeacon({
event: 'page_leave',
duration: Date.now() - pageEnterTime
});
}
});
3.2.3 性能监控:跨域资源的时序数据缺失
问题现象 :使用performance.getEntriesByType('resource')等Resource Timing API统计页面资源加载性能时,跨域的图片、JS、CSS等资源的很多关键字段都会返回0,比如duration(加载总耗时)、requestStart、responseStart、transferSize等,无法统计真实加载性能。
根本原因:同源策略限制------浏览器不允许页面读取跨域资源的详细网络时序信息,避免泄露敏感数据。
解决方案 :在资源所在的服务端添加Timing-Allow-Origin响应头,指定允许获取时序数据的域名即可,和CORS配置逻辑类似。
Nginx 配置示例:
nginx
location /static/ {
root /data;
# 允许所有源获取资源时序数据,生产环境建议指定具体域名
add_header Timing-Allow-Origin *;
add_header Access-Control-Allow-Origin *;
}
前端验证代码:
javascript
window.addEventListener('load', function() {
const resourceList = performance.getEntriesByType('resource');
resourceList.forEach(resource => {
console.log('资源地址:', resource.name);
console.log('加载总耗时:', resource.duration, 'ms'); // 配置后可拿到真实耗时
console.log('资源大小:', resource.transferSize, '字节');
console.log('---');
});
});
四、方案对比与选型建议
4.1 全方案对比表
| 方案分类 | 具体方案 | 核心适用场景 | 服务端是否需要改造 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 通用接口类 | CORS | 绝大多数业务接口场景 | 是 | 标准规范、功能完整、支持所有方法 | 需要服务端配置 |
| Nginx反向代理 | 生产环境部署 | 是 | 性能好、前端无感知 | 需要运维配置 | |
| Node中间层 | 需要数据加工、统一鉴权的场景 | 是 | 灵活可控、可扩展能力强 | 增加部署成本、多一次转发 | |
| DevServer Proxy | 本地开发环境 | 否 | 配置简单、开发体验好 | 仅适用于开发环境 | |
| JSONP | 兼容低版本浏览器的简单GET请求 | 是 | 兼容性极佳 | 只支持GET、有XSS风险 | |
| 双向通信 | WebSocket | 实时聊天、数据推送 | 是 | 不受同源限制、全双工高性能 | 仅适用于WS协议 |
| 窗口通信 | postMessage | 跨域iframe/窗口通信 | 否 | 功能强大、安全可靠 | 仅用于窗口间通信 |
| document.domain | 同主域不同子域的iframe通信 | 否 | 简单直接 | 适用范围极窄、有安全风险 | |
| window.name | 低版本浏览器跨iframe传数据 | 否 | 容量大、兼容性好 | 使用繁琐、体验差 | |
| 图片场景 | crossorigin+CORS | Canvas操作跨域图片 | 是 | 标准方案、实现简单 | 需要服务端配合 |
| 监控场景 | IMG打点 | 轻量级埋点上报 | 否 | 完全无跨域问题、兼容性强 | 仅支持GET、数据量有限 |
| sendBeacon | 页面卸载时的可靠上报 | 是 | 不阻塞页面、卸载可靠 | 无回调、IE不兼容 | |
| Fetch上报 | 大量结构化数据上报 | 是 | 支持POST、可重传 | 有跨域限制、卸载易中断 |
4.2 选型核心原则
- 接口类跨域首选CORS:作为W3C标准方案,功能最完整,前后端改造成本低,覆盖绝大多数业务场景;生产环境可配合Nginx反向代理统一管理。
- 开发环境直接用代理:无论是Webpack还是Vite,都内置了代理功能,配置简单,完全满足本地开发联调需求。
- 图片场景用标准方案 :涉及Canvas像素操作、图片导出的场景,
crossorigin属性+服务端CORS是唯一的标准解,不建议用hack方式绕过。 - 监控埋点按需选择:轻量级行为埋点优先用IMG打点,无跨域问题;页面离开时的必达上报用sendBeacon;大量结构化错误数据用Fetch+失败重传。
- iframe通信优先postMessage :功能最强大、安全性最高;只有主域名相同的场景,才考虑用
document.domain简化实现。
五、总结
跨域问题本质上是浏览器同源策略带来的安全限制,所有解决方案本质上都在做两件事:要么通过服务端配合(CORS、代理转发)获得浏览器授权,要么利用浏览器本身就放行的特性(script/img标签、WebSocket、postMessage)绕过限制。
从通用接口到细分业务场景,前端跨域已经形成了非常成熟的解决方案体系。实际开发中不需要追求"最全面"的方案,而是根据业务场景、技术栈、部署环境,选择成本最低、稳定性最高的方案即可。