
这篇文章不说空泛的理论,只给你能直接落地的实战干货:jsonp的回调函数怎么写才不会“失效”?老浏览器兼容要避开哪些坑?cors的服务端响应头有哪些“必加项”?带cookie的跨域请求该怎么配置?甚至连“什么时候选jsonp、什么时候用cors”的场景判断都帮你理得清清楚楚——比如旧项目要兼容IE,jsonp是“过渡神器”;现代项目追求规范,cors才是“长期解法”。
不管你是刚入门的新人,还是想快速搞定问题的老开发,看完这篇就能摸透跨域的底层逻辑。下次遇到跨域报错,不用再对着文档发呆,照着文中的技巧一步步操作,90%的跨域难题都能迎刃而解。
你有没有过这样的经历?写了个Ajax请求调后端接口,控制台突然红了一片,提示“Access-Control-Allow-Origin”错误,明明接口地址复制粘贴没错,数据就是拿不到——这就是跨域在搞鬼。我去年帮朋友的生鲜电商小程序调支付接口时,就踩过这个坑:前端项目部署在微信的开发环境(localhost:8080),支付接口在商家的服务器(api.shop.com),第一次调就报错,我当时还以为是接口参数传错了,对着文档核对了三遍,最后才反应过来是跨域的问题。今天就把我亲测有效的jsonp和cors技巧掰开揉碎讲给你听,帮你少走点弯路。
先搞懂:为什么会出现Ajax跨域?
要解决跨域问题,得先明白同源策略——这是浏览器的“安全 guard”。简单说就是:如果你的网页是从a.com
加载的,那它只能访问a.com
域名下的接口;要是访问b.com
或者api.a.com
(子域名不同)、localhost:8080
(端口不同),甚至https://a.com
(协议不同),都会被浏览器拦下来。这规则是为了防止恶意网站偷数据,比如你在银行网页登录后,要是没有同源策略,别的网站就能偷偷调银行的接口转你的钱——想想都可怕。
但实际开发中,跨域太常见了:比如前端项目跑在localhost:3000
,后端接口在localhost:8080
(端口不同);或者公司官网的前端在www.company.com
,要调api.company.com
的接口(子域名不同);再或者要调用第三方接口(比如微信支付、高德地图),这些都属于跨域。我之前做公司的招聘官网时,就因为前端和后端端口不同,第一次调“获取职位列表”接口就报错,当时还以为是后端接口没启动,后来查了半天才知道是跨域的问题。
下常见的跨域场景:
a.com
vs b.com
api.a.com
vs www.a.com
localhost:3000
vs localhost:8080
http://a.com
vs https://a.com
记住:只要“协议、域名、端口”有一个不一样,就是跨域——这是解决问题的前提。
jsonp:老项目兼容的“过渡神器”,但别用错了
如果你的项目要兼容IE8、IE9这些老浏览器,或者对接的第三方接口只支持jsonp,那这个“老古董”技巧还能用。我去年帮一个做了5年的企业官网调新闻接口时,就用了jsonp——因为那个接口是PHP写的,只支持jsonp,没法改cors。
jsonp的原理:借script标签“绕开”同源策略
你有没有想过:为什么我们在网页里引外部JS文件(比如jQuery的CDN)从来不会报错?因为script标签没有跨域限制。jsonp就是利用这个“漏洞”:前端定义一个回调函数,然后用script标签请求接口,接口返回这个函数的调用,把数据当参数传回来。
举个我实际用过的例子:
handleNews
函数,用来处理返回的数据:function handleNews(data) {
callbackconsole.log('拿到新闻数据了:', data);
// 这里渲染新闻列表
}
创建script标签,请求接口:用JS动态创建一个script标签,src指向接口地址,并且带一个 参数(告诉接口“我要调用handleNews函数”):
javascript
const script = document.createElement('script');
script.src = 'http://api.news.com/getNews?callback=handleNews';
document.body.appendChild(script);
handleNews({ newsList: [...] })
接口返回函数调用:接口收到请求后,会返回 这样的字符串——本质是调用我们定义的函数,把数据当参数传进去。
newsList执行回调函数:浏览器加载script标签时,会执行这个函数,我们就能拿到 数据了。
handleData这些坑,我踩过你别踩
jsonp虽然好用,但有几个“硬伤”得注意:
只能发GET请求:因为script标签只能发GET请求,要是你要发POST(比如提交表单),jsonp就不管用了。我之前想用水晶报表,发现无法实现post请求,最后还是改用了其他方法。 安全性低:接口返回的是JS代码,如果接口被篡改,可能会注入恶意代码(比如XSS攻击)。所以别用jsonp调不可信的接口——比如第三方接口要是没做安全防护,最好别用。 回调函数名容易冲突:如果页面里有多个jsonp请求,都用 当回调函数名,就会覆盖。我之前做新闻列表+广告列表两个jsonp请求时,就因为函数名重复,结果广告数据覆盖了新闻数据,后来改成
handleNews_162345(加随机字符串)才解决。
setTimeout(() => { alert('请求超时'); }, 5000)无法捕获错误:script标签加载失败时,不会触发Ajax的error事件,只能靠超时或者监听script的onerror事件。我之前调一个不稳定的接口,经常加载失败,后来加了个超时器: ,才给用户提示。
http://localhost:3000什么时候用jsonp?
一下:
老浏览器兼容(IE8+); 对接只支持jsonp的第三方接口; 只需要发GET请求的场景。 要是你做的是新项目,我 直接跳过jsonp——用cors更安全、更规范。
cors:现代项目的“标准解法”,这些细节决定成败
现在前端项目基本都用Vue、React、Angular这些框架,后端用Spring Boot、Express、Django这些,cors是解决跨域的首选方案——MDN文档里明确说“cors是跨域资源共享的标准方案”,我最近3年做的项目全用的cors,没踩过大坑。
cors的原理:服务端“允许”你访问
cors的核心是服务端设置响应头,告诉浏览器“这个域名的请求我允许”。比如你的前端在
,后端接口在
http://localhost:8080,后端只要在响应头里加
Access-Control-Allow-Origin: http://localhost:3000,浏览器就会放行这个请求。
我举个我用Vue+Express做的项目例子:
前端Ajax请求(用axios):
javascript
axios.get('http://localhost:8080/api/users')
.then(res => console.log(res.data))
.catch(err => console.log(err));
后端设置响应头(Express中间件):
javascript
app.use((req, res, next) => {
// 允许前端域名访问
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
// 允许的请求方法
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
// 允许的请求头(比如Content-Type、Authorization)
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
withCredentials
这样设置后,前端就能正常调接口了——是不是很简单?
实战必看:cors的“细节杀”
cors看似简单,但几个细节没处理好,就会掉坑:
带cookie的请求:这三点必须做 如果你的请求需要带cookie(比如用户登录后,接口需要验证身份),得注意:
前端设置 :用axios的话,要加
withCredentials: true;用fetch的话,加
credentials: 'include':
javascript
// axios例子
axios.get('http://localhost:8080/api/me', { withCredentials: true })
Access-Control-Allow-Credentials: true
服务端设置 :告诉浏览器“允许带cookie”;
Access-Control-Allow-Origin不能设为
:必须写具体的域名(比如
http://localhost:3000)——因为
代表允许所有域名,但带cookie的请求不允许用
(安全限制)。
Access-Control-Allow-Origin我去年做用户中心项目时,就因为把
设成了
,结果带cookie的请求一直报错,后来改成具体域名才解决。
Content-Type预检请求(OPTIONS):别漏了处理 有些请求会先发一个OPTIONS请求(叫“预检”),问问服务端“我能发这个请求吗?”——比如:
POST请求,且 是
application/json(不是默认的
application/x-www-form-urlencoded);
Authorization带自定义头的请求(比如 头,用来传token);
请求方法是PUT、DELETE这些“非简单方法”。 服务端得处理这个OPTIONS请求,返回允许的方法和头,否则浏览器会拦截真正的请求。比如用Express的话,可以加个中间件:
javascript
app.use((req, res, next) => {
res.header(‘Access-Control-Allow-Methods’, ‘GET, POST, PUT, DELETE’);
res.header(‘Access-Control-Allow-Headers’, ‘Content-Type, Authorization’);
// 处理OPTIONS请求,直接返回200
if (req.method === ‘OPTIONS’) {
res.sendStatus(200);
} else {
next();
}
});
我之前用Spring Boot做后端时,一开始没处理OPTIONS请求,结果前端发
application/json的POST请求一直报错,后来加了
@CrossOrigin注解(Spring Boot的cors注解)才好——这个注解会自动处理OPTIONS请求。
X-Request-Id跨域带自定义头:得告诉服务端 如果你在请求里加了自定义头(比如
用来追踪请求),那服务端得在
Access-Control-Allow-Headers里加上这个头,否则浏览器会拦截。比如前端请求加了
X-Request-Id: 123,服务端得设:
javascript
res.header(‘Access-Control-Allow-Headers’, ‘Content-Type, Authorization, X-Request-Id’);
我之前做日志追踪时,就因为没加这个头,结果请求一直被拦,后来查了MDN文档才知道要加。
用表格对比:jsonp vs cors,选哪个?
为了让你更清楚,我做了个对比表——都是我实际项目中遇到的场景:
对比项 | jsonp | cors |
---|---|---|
支持的请求方法 | 仅GET | 所有HTTP方法(GET/POST/PUT等) |
安全性 | 低(返回JS代码,有XSS风险) | 高(浏览器验证响应头) |
兼容性 | 支持IE8+ | 支持IE10+、现代浏览器 |
是否支持带cookie | 不支持 | 支持(需配置) |
推荐场景 | 老浏览器、只支持jsonp的接口 | 现代项目、需要POST/带cookie的场景 |
最后:这3个 帮你少踩90%的坑
Access-Control-Allow-Origin对不对;
withCredentials、服务端
Allow-Credentials、
Allow-Origin设具体域名。
如果你按这些方法试了,还是解决不了,可以把报错信息发在评论区——我帮你看看,毕竟跨域的坑我踩过太多,说不定能帮你省点时间。
为什么Ajax请求会出现跨域错误啊?
因为浏览器有个“同源策略”的安全规则,简单说就是你网页从哪个协议、域名、端口加载的,就只能访问同样协议、域名、端口下的接口。比如你前端在localhost:8080,调商家服务器api.shop.com的接口,域名不一样,浏览器就会拦下来,报“Access-Control-Allow-Origin”错误——我去年帮朋友调生鲜小程序支付接口时就踩过这坑,当时还以为参数错了,核对三遍才反应过来是跨域的问题。
同源策略是为了防恶意网站偷数据,但实际开发中跨域太常见了,比如前端和后端端口不同、调第三方接口都属于这种情况。
jsonp只能用在什么场景啊?
jsonp主要是老项目兼容的“过渡神器”,比如你的项目要兼容IE8、IE9这些老浏览器,或者对接的第三方接口(比如某些老PHP接口)只支持jsonp,再或者你只需要发GET请求的场景,都可以用jsonp。
我之前帮一个做了5年的企业官网调新闻接口,那个接口是PHP写的,只能用jsonp,没法改cors,就靠这方法解决了跨域问题。
cors带cookie的跨域请求要注意什么?
带cookie的话得注意三点:首先前端要用axios的话,得加个withCredentials: true的配置,用fetch就加credentials: ‘include’;然后服务端要设置Access-Control-Allow-Credentials: true,告诉浏览器允许带cookie;最后Access-Control-Allow-Origin不能设成,得写具体的域名,比如http://localhost:3000——我去年做用户中心项目时,一开始把Allow-Origin设成,结果带cookie的请求一直报错,改成具体域名才好。
jsonp和cors选哪个更合适啊?
要是新项目肯定优先选cors,安全又规范,支持POST、PUT这些方法,还能带你cookie;要是老项目要兼容IE8、IE9,或者对接的接口只支持jsonp(比如某些第三方老接口),就只能用jsonp了。
比如现在用Vue、React做的现代项目,直接用cors就行;但要是对接那种只能用jsonp的老接口,就没办法只能选它。
cors的预检请求(OPTIONS)要怎么处理啊?
有些请求会先发个OPTIONS请求“探路”,比如POST请求是application/json格式,或者带自定义头(比如Authorization)、用PUT/DELETE方法的,服务端得处理这个请求,返回允许的方法和头。
比如用Express的话,可以加个中间件,设置Access-Control-Allow-Methods(比如GET、POST、PUT、DELETE)和Access-Control-Allow-Headers(比如Content-Type、Authorization),然后如果是OPTIONS请求就直接返回200,不然继续走下一步。我之前用Spring Boot时没处理这个,结果前端发POST请求一直报错,后来加了@CrossOrigin注解才解决。