一、前言:当你的请求被“拒之门外”
想象一下,你正在开发一个天气预报网站,你的服务器在 yourdomain.com,而天气数据来自一个公共的API,比如 weather-api.com。当你用jQuery的$.ajax去获取数据时,浏览器会毫不留情地抛出一个错误,告诉你“跨域请求被阻止”。
这就是浏览器的“同源策略”在起作用,它就像一个严格的门卫,只允许网页与同协议、同域名、同端口的服务器通信,目的是为了保护用户安全。但现实中,我们需要从不同地方获取数据,这就催生了两种经典的“通行证”方案:JSONP 和 CORS。今天,我们就来彻底聊聊它们,并用实战代码看看怎么选。
技术栈声明: 本文所有示例均基于 jQuery 3.x 与 原生JavaScript/HTML 环境。
二、方案一:JSONP - 巧用“脚本”标签的智慧
JSONP,全称是“JSON with Padding”。它的核心思想非常巧妙:浏览器对<script>标签的src属性没有跨域限制。JSONP就是利用这一点,动态创建一个<script>标签,其src指向目标API,并附带一个回调函数名作为参数。服务器收到请求后,不是返回纯JSON,而是返回一段调用这个回调函数的JavaScript代码,数据作为参数传入。
应用场景: 主要用于向老旧的、或不支持CORS的第三方服务(如一些公开的公共API)发起GET请求。
实战示例:用jQuery发起JSONP请求
<!-- 示例:获取模拟的天气数据 -->
<!DOCTYPE html>
<html>
<head>
<title>JSONP示例</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<button id="getWeather">获取天气</button>
<div id="weatherResult"></div>
<script>
$(document).ready(function() {
$('#getWeather').click(function() {
// 假设 weather-api.com 提供了一个支持JSONP的接口
// 它需要接收一个名为 `callback` 的参数,值就是我们本地定义的函数名
$.ajax({
url: 'https://weather-api.com/data', // 模拟的第三方API地址
type: 'GET', // JSONP只支持GET方法
dataType: 'jsonp', // 关键!告诉jQuery这是JSONP请求
jsonp: 'callback', // 指定回调函数参数名,通常为'callback'
jsonpCallback: 'handleWeatherData', // 明确指定全局回调函数名(可选)
success: function(data) {
// 当服务器返回的JS代码执行后,会调用这个success函数
// 实际上,jQuery内部处理了回调,我们在这里拿到的是解析好的数据对象
$('#weatherResult').html(`城市:${data.city},温度:${data.temp}°C`);
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('请求失败:', textStatus, errorThrown);
}
});
// 如果不使用jQuery,手动实现JSONP是这样的:
// 1. 定义一个全局函数
// window.handleWeatherData = function(data) { console.log(data); };
// 2. 动态创建script标签
// var script = document.createElement('script');
// script.src = 'https://weather-api.com/data?callback=handleWeatherData';
// document.body.appendChild(script);
});
});
</script>
</body>
</html>
服务器端(模拟)响应内容:
服务器不会返回 {"city": "北京", "temp": 25},而是返回:
handleWeatherData({"city": "北京", "temp": 25});
这段JS代码到达浏览器后立即执行,就相当于调用了我们页面上定义的 handleWeatherData 函数,并传入了数据。
优缺点分析:
- 优点: 兼容性极佳,支持老版本浏览器;实现简单,对服务器改造要求低(只需包装JSON)。
- 缺点: 只支持GET请求,安全性较差(完全信任第三方脚本);错误处理机制不完善(难以捕获404等HTTP错误);需要服务器端配合特定格式。
三、方案二:CORS - 官方标准的跨域协议
CORS,全称“跨源资源共享”,是一个W3C标准。它才是现代浏览器处理跨域问题的“正规军”。其原理是:浏览器在发送真正的跨域请求(如POST,带特定头部的GET)前,会先发送一个“预检”请求(OPTIONS方法),询问目标服务器是否允许当前源进行跨域访问。服务器通过响应头(如Access-Control-Allow-Origin)来声明允许的源、方法、头部等。只有预检通过,真正的请求才会发出。
应用场景: 现代Web开发的首选,尤其适用于自己可控的前后端分离项目(前端localhost:8080,后端api.yourdomain.com),或需要安全地进行POST、PUT等非简单请求的场景。
实战示例:jQuery发起CORS请求
<!-- 示例:向自己的后端API提交数据 -->
<!DOCTYPE html>
<html>
<head>
<title>CORS示例</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<form id="userForm">
<input type="text" id="username" placeholder="用户名">
<input type="email" id="email" placeholder="邮箱">
<button type="submit">提交</button>
</form>
<div id="result"></div>
<script>
$(document).ready(function() {
$('#userForm').submit(function(event) {
event.preventDefault(); // 阻止表单默认提交
var userData = {
username: $('#username').val(),
email: $('#email').val()
};
// 假设我们自己的后端API地址是 https://api.yourdomain.com
$.ajax({
url: 'https://api.yourdomain.com/user', // 跨域的API地址
type: 'POST', // CORS支持所有HTTP方法
contentType: 'application/json', // 发送JSON数据
data: JSON.stringify(userData), // 将对象转为JSON字符串
// dataType: 'json', // 期望服务器返回JSON(jQuery会自动解析)
// 对于CORS请求,jQuery的ajax用法和同源请求完全一样!
success: function(data, textStatus, jqXHR) {
$('#result').html(`用户 ${data.username} 创建成功!`);
},
error: function(jqXHR, textStatus, errorThrown) {
// CORS请求有完善的错误处理,可以获取HTTP状态码
if (jqXHR.status === 0) {
$('#result').html('网络错误或请求被CORS策略阻止。');
} else {
$('#result').html(`错误 ${jqXHR.status}: ${errorThrown}`);
}
}
});
});
});
</script>
</body>
</html>
服务器端(以Node.js Express为例)关键配置:
// 服务器端代码片段(Node.js + Express)
const express = require('express');
const app = express();
// 使用CORS中间件(最简单的方式)
const cors = require('cors');
app.use(cors()); // 默认允许所有源访问
// 或者,进行更精细的控制
app.use((req, res, next) => {
// 允许指定的源访问
res.setHeader('Access-Control-Allow-Origin', 'https://yourfrontend.com');
// 允许携带认证信息(如Cookies)
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 允许的HTTP方法
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
// 允许的请求头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// 处理预检请求
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
app.post('/user', (req, res) => {
// 处理业务逻辑...
res.json({ username: req.body.username, id: 123 });
});
app.listen(3000);
优缺点分析:
- 优点: 支持所有HTTP方法(GET, POST, PUT, DELETE等);安全性高,由浏览器和服务器通过头部协商控制;有完善的错误处理机制;是W3C推荐的标准方案。
- 缺点: 需要服务器端明确配置支持;对于“非简单请求”(如带自定义头、Content-Type非
application/x-www-form-urlencoded等)会多发一次OPTIONS预检请求,增加延迟;IE10以下不支持。
四、JSONP vs CORS:如何选择与注意事项
核心对比表:
| 特性 | JSONP | CORS |
| :--- | :--- | :--- |
| 原理 | 利用<script>标签无跨域限制 | 通过HTTP头部进行跨域协商 |
| 支持方法 | 仅GET | 所有HTTP方法 |
| 安全性 | 较低(执行外部JS) | 较高(浏览器严格检查) |
| 错误处理 | 困难 | 完善(基于HTTP状态码) |
| 服务器改造 | 需将JSON包装为JS函数调用 | 需设置正确的CORS响应头 |
| 浏览器兼容 | 几乎所有浏览器 | IE10+,现代浏览器全支持 |
选择指南:
- 如果你完全控制后端API:毫无疑问,选择 CORS。它是现代Web开发的标配,安全、强大、标准。
- 如果你调用不支持CORS的第三方老接口:且只需要GET数据,那么 JSONP 是唯一可行的浏览器端方案。
- 如果你的项目需要支持古董浏览器(如IE9):并且需要复杂操作,可能需要考虑JSONP,或者搭配服务端代理(在自家服务器上请求第三方数据,再转给前端,从而避免浏览器跨域)。
重要注意事项:
- CORS与凭证:如果跨域请求需要携带Cookies或HTTP认证信息,必须在ajax设置中加上
xhrFields: { withCredentials: true },同时服务器响应头必须包含Access-Control-Allow-Credentials: true,并且Access-Control-Allow-Origin不能为通配符*,必须是明确的源。 - JSONP的安全风险:由于JSONP本质是加载并执行一段外部JS,务必确保你信任该第三方服务器,否则有被注入恶意代码的风险。
- 预检请求的优化:对于频繁的CORS非简单请求,可以通过服务器设置
Access-Control-Max-Age头来缓存预检请求结果,减少OPTIONS请求的次数。
五、总结
跨域请求是前端开发者必闯的一关。JSONP像是一位机智的“穿越者”,用巧妙的技巧绕过了限制,但只适合简单的、只读的场景。CORS则是手握“官方文书”的“外交官”,通过正式的协商流程,安全、全面地打通了跨域通信的道路。
在现代开发中,随着前后端分离架构的普及和浏览器标准的统一,CORS已经成为绝对的主流和首选。理解JSONP有助于你处理遗留系统或特殊接口,但将精力投入到掌握CORS的原理和服务器配置上,无疑会带来更稳健、更安全的开发体验。下次当你的ajax调用被浏览器拦截时,不妨先检查一下,是时候给服务器配上正确的CORS头部了。
Comments