一文带你掌握 JSONP:从 Script 标签到手写实现
一、JSONP 是什么?用来做什么?
JSONP(JSON with Padding)诞生于 CORS 尚未普及的年代,是前端解决 “跨域 GET 请求” 的鼻祖级方案。核心思想:
利用
<script>标签没有同源限制的特性,让服务器把数据“包”成一段 JavaScript 函数调用返回,浏览器执行后即可拿到数据。
- 只能发 GET
- 兼容 IE6+
- 无需任何浏览器插件或 CORS 配置
在现代前端,JSONP 已逐渐被 CORS 取代,但仍在 老旧系统、第三方统计脚本、CDN 回调 等场景活跃,同时也是 面试常考题。
二、Script 标签及其属性回顾
| 属性 | 作用 | 对 JSONP 的影响 |
|---|---|---|
src |
发起 GET 请求加载外部 JS | 核心字段,承载接口地址 + 查询参数 |
async |
异步加载,不保证执行顺序 | 默认行为,JSONP 无需顺序 |
defer |
异步但 DOM 后再执行 | 一般不用,防止延迟 |
crossorigin |
开启 CORS 错误详情 | JSONP 不需要,否则报错 |
onload / onerror
|
监听加载成功/失败 | 可用来做 超时/异常 处理 |
关键特性:
-
<script src="xxx">不受同源限制 - 下载完成后立即在全局作用域执行
- 不会把响应文本暴露给 JS,只能靠“执行后的副作用”拿数据
三、Callback 是怎么传递与执行的?
① 传递:前端 → 后端
- 前端生成全局唯一函数名(如
jsonp_1710000000000) - 把函数名作为 GET 查询参数拼到 script 的 src:
https://api.example.com/jsonp?callback=jsonp_1710000000000&id=123 - 在 window 上挂同名函数:
window[jsonp_1710000000000] = function (data) { /* 处理数据 */ };
② 执行:后端 → 浏览器
- 服务器读取
req.query.callback(即jsonp_1710000000000) - 把数据包进该函数名,返回一段可执行 JS:
响应体:Content-Type: text/javascriptjsonp_1710000000000({"name": "jsonp-demo"}); - 浏览器下载完后立即在全局作用域执行上述代码 →
函数被调用,参数即为数据,副作用完成。
③ 清理:前端自己
执行完立即 delete window[jsonp_1710000000000] 并移除 <script>,防止堆积。
四、手写一个简洁版 JSONP(含超时 + 错误)
function jsonp(url, data = {}, timeout = 7000) {
return new Promise((resolve, reject) => {
const cb = `jp_${Date.now()}`;
const script = document.createElement('script');
const timer = setTimeout(() => cleanup(reject('timeout')), timeout);
window[cb] = (data) => cleanup(resolve(data));
function cleanup(fn) {
clearTimeout(timer);
script.remove();
delete window[cb];
fn();
}
script.onerror = () => cleanup(reject('script error'));
script.src = `${url}${url.includes('?') ? '&' : '?'}callback=${cb}&${new URLSearchParams(data)}`;
document.head.appendChild(script);
});
}
/* 使用 */
jsonp('https://api.example.com/jsonp', { id: 123 })
.then(console.log) // { id: '123', name: 'jsonp-demo' }
.catch(console.error);
五、常见问题与坑
| 问题 | 原因 | 解决 |
|---|---|---|
| 返回纯 JSON 报语法错 |
<script> 期望 JS 而非 JSON |
服务器务必返回 callback(JSON);
|
| 无法捕捉 HTTP 状态码 |
<script> 只有 onload/onerror
|
靠 onerror + 超时做模糊失败处理 |
| 只能 GET |
<script> 天生 GET |
换 CORS 或代理 |
| 回调名冲突 | 全局变量重名 | 使用时间戳+随机数唯一化 |
六、今天还用 JSONP 吗?
- 新项目:优先 CORS,简单、标准、支持所有 HTTP 方法
- 老系统/统计脚本/CDN:JSONP 仍活跃,零配置跨域不可替代
- 面试:手写 JSONP 是高频手写题,考察 Promise + Script 加载 + 全局回调 综合功底
七、一句话总结
JSONP = <script> 无同源限制 + 服务器包成 JS 函数调用 + 全局回调收数据
“下载即执行,执行即回调”——掌握它,跨域历史就懂了一半!