手写 JSONP:深入理解跨域数据获取的经典方案
在 Web 开发中,由于浏览器的 同源策略(Same-Origin Policy),前端 JavaScript 默认无法直接请求不同域名下的接口数据。
早期的解决方案是 JSONP(JSON with Padding),它巧妙利用 <script> 标签不受同源策略限制的特性,实现跨域数据获取。虽然现代 Web 开发更推荐 CORS(跨域资源共享),但理解 JSONP 仍然有助于深入掌握浏览器安全机制和跨域技术。
一、JSONP 的核心原理
JSONP 的核心思想是:
-
前端动态创建
<script>标签,其src指向目标 API,并附带一个回调函数名(如?callback=handleData)。 -
服务器返回的数据被包裹在该回调函数中,例如:
handleData({ "name": "Jack", "age": 25 }) -
浏览器执行这段脚本,自动调用回调函数并处理数据。
由于 <script> 标签可以跨域加载资源,因此绕过了同源策略的限制。
二、手写 JSONP 实现
前端实现
我们需要编写一个函数,动态创建 <script> 标签并处理返回的数据:
function jsonp({url,param, callbackName, successCallback, errorCallback}) {
// 1. 创建全局回调函数
window[callbackName] = function (data) {
successCallback(data);
// 清理 script 标签和全局函数
document.body.removeChild(script);
delete window[callbackName];
};
const arr = []
for(let key in param){
arr.push(`${key}=${param[key]}`)
}
// 2. 创建 script 标签
const script = document.createElement("script");
script.src = `${url}?callback=${callbackName}&${arr.join('&')}`;
// 3. 错误处理(如请求失败)
script.onerror = function () {
errorCallback(new Error("JSONP request failed"));
document.body.removeChild(script);
delete window[callbackName];
};
// 4. 插入 DOM,发起请求
document.body.appendChild(script);
}
使用方法:
jsonp({
url:"http://localhost:3000/getData",
param: {
userId:"6484316841",
userName:"Nick"
},
callbackName:"handleUserData",
successCallback:(data) => console.log("成功:", data),
errorCallback:(err) => console.error("失败:", err),
});
后端实现(Node.js 示例)
服务器需要解析 callback 参数,并返回包裹在回调函数中的 JSON 数据:
// server.js - 原生 Node.js 版本
const http = require("http");
const server = http.createServer((req, res) => {
// 匹配 GET 请求 /say
if (req.url.startsWith("/getData")) {
// 解析url 创建url实例 形成一个完成的url对象 因为req.url 是没有前面的域名之类的
const url = new URL(req.url, `http://${req.headers.host}`);
const userId = url.searchParams.get("userId");
const userName = url.searchParams.get("userName");
const callback = url.searchParams.get("callback");
// 返回 JSONP 格式响应
res.writeHead(200, {
"Content-Type": "application/javascript",
});
const data = {
code: 0,
msg: "success",
data: {
userId,
userName
age: 18,
},
};
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");
});
请求示例:
http://localhost:3000/getData?callback=handleUserData&usrId=6484316841&userName=Nick
```Nick
**返回数据:**
```javascript
handleUserData({
code:0,
msg:'success',
data:{ "userId": "6484316841", "userName": "Nick" ,"age": 18,}
})
三、JSONP 的优缺点
优点在于:兼容性好:支持所有浏览器,包括 IE6/7/8 等老旧浏览器; 无需 CORS:适用于无法修改服务器配置的场景。
缺点
- 仅支持 GET 请求,无法使用 POST、PUT、DELETE 等方法。
- 安全性问题:如果服务器被攻击,可能返回恶意代码(XSS 攻击)。
- 错误处理困难:无法像
fetch或axios那样捕获 HTTP 404/500 错误。 - 回调函数污染全局作用域,可能与其他脚本冲突。
四、SONP vs CORS
| 对比项 | JSONP | CORS |
|---|---|---|
| 实现方式 | 利用 <script> 标签 | 使用 Access-Control-Allow-Origin 头 |
| 请求方法 | 仅 GET | 支持 GET/POST/PUT/DELETE 等 |
| 安全性 | 较低(可能 XSS) | 较高(可限制来源) |
| 兼容性 | 所有浏览器(包括 IE6+) | 现代浏览器(IE10+) |
| 适用场景 | 旧系统兼容、简单数据获取 | 现代 Web 开发 |
总结
- JSONP 是一种利用
<script>标签绕过同源策略的跨域方案。 - 手写 JSONP 需要动态创建
<script>并定义全局回调函数。 - JSONP 仅适用于 GET 请求,且存在安全风险,现代开发推荐 CORS。