JSONP跨域请求背后隐藏的安全风险与防范策略
优化前:卡得不行
最近在做一个项目,里面有个功能需要通过 JSONP 调用第三方接口来获取数据。本来以为挺简单的,结果上线后发现性能差到离谱。尤其是在低网速环境下,页面加载时间直接飙到了 5 秒以上,用户都快把浏览器关了还没看到数据。
更头疼的是,JSONP 的请求一旦出问题,整个页面的交互都会受影响,甚至有时候还会出现跨域报错。试了好几种方案,最后终于找到了一个靠谱的优化方法,今天就来聊聊这个踩坑经历。
找到瓶颈了!
先说下定位问题的过程吧。一开始我以为是网络延迟的问题,毕竟第三方接口确实有点远(比如 https://jztheme.com/api/data)。后来用 Chrome DevTools 的 Network 面板抓了一下请求,发现 JSONP 请求的时间占了整个加载时间的大头。
接着我又用了 Lighthouse 测了一下性能,结果发现 TTI(Time to Interactive)被拖得很长,主要就是因为 JSONP 的回调函数阻塞了主线程。再加上 JSONP 本身的实现方式比较老土,靠动态插入 script 标签来完成跨域请求,这种机制本身就容易导致性能问题。
另外,我还发现一个问题:如果 JSONP 请求失败了,整个页面都没有降级处理,用户体验直接崩了。所以这次优化的目标有两个:提升加载速度和增强容错能力。
优化后:流畅多了
经过一番折腾,最终我选择了以下几种优化方法:
- 减少 JSONP 的使用场景,尽量用 CORS 替代
- 给 JSONP 请求加超时控制
- 优化回调函数的执行逻辑
- 增加降级方案
下面具体讲讲我是怎么做的。
核心代码就这几行
首先,JSONP 本质上就是通过动态创建 script 标签发起请求,然后通过全局回调函数处理返回的数据。优化前的代码大概是这样的:
function jsonp(url, callback) {
const script = document.createElement('script');
const callbackName = 'jsonpCallback_' + Math.random().toString(36).substr(2);
window[callbackName] = function(data) {
callback(data);
delete window[callbackName];
document.body.removeChild(script);
};
script.src = ${url}?callback=${callbackName};
document.body.appendChild(script);
}
jsonp('https://jztheme.com/api/data', function(data) {
console.log('Data loaded:', data);
});
这段代码看起来没啥问题,但实际运行中会遇到几个坑:
- 没有超时控制,如果请求挂了,页面就一直等
- 回调函数直接绑在全局对象上,容易污染全局命名空间
- 脚本加载失败时没有任何错误提示
于是我对代码做了如下优化:
function jsonp(url, callback, timeout = 5000) {
const script = document.createElement('script');
const callbackName = 'jsonpCallback_' + Math.random().toString(36).substr(2);
// 设置超时处理
const timer = setTimeout(() => {
cleanup();
callback(new Error('Request timed out'));
}, timeout);
function cleanup() {
delete window[callbackName];
document.body.removeChild(script);
clearTimeout(timer);
}
window[callbackName] = function(data) {
cleanup();
callback(null, data);
};
script.src = ${url}?callback=${callbackName};
script.onerror = function() {
cleanup();
callback(new Error('Script load failed'));
};
document.body.appendChild(script);
}
// 使用示例
jsonp('https://jztheme.com/api/data', function(err, data) {
if (err) {
console.error('Error:', err);
return;
}
console.log('Data loaded:', data);
}, 3000); // 超时时间为 3 秒
优化后的代码有几个关键点:
- 超时控制:通过 setTimeout 给请求设置了一个最大等待时间,避免页面无限等待。
- 错误处理:增加了 onerror 事件监听,防止脚本加载失败时没有任何反馈。
- 清理逻辑:无论是成功还是失败,都会调用 cleanup 函数清理全局变量和 DOM 元素。
踩坑提醒:这三点一定注意
在优化过程中,我踩了几个坑,这里给大家提个醒:
- 回调函数冲突:如果多个 JSONP 请求同时进行,可能会覆盖同一个全局回调函数。解决方法是每次生成唯一的回调函数名(如上面代码中的随机字符串)。
- 超时时间设置:超时时间不能太短,否则在网络稍慢的情况下就会触发超时;也不能太长,否则会影响用户体验。我试了几种方案,最后觉得 3-5 秒比较合适。
- 降级方案:JSONP 失败后,最好提供一个备用数据源或展示默认内容,这样即使请求失败,用户也不会完全看不到东西。
性能数据对比
优化后的效果还是很明显的。我在本地模拟了不同网络环境下的测试:
- 优化前:普通 3G 网络下,页面加载时间平均 5.2 秒,TTFB(Time to First Byte)约 4 秒。
- 优化后:同样条件下,页面加载时间降到 800 毫秒左右,TTFB 缩短到 300 毫秒。
特别是在弱网环境下,超时控制和降级方案的作用非常明显,用户至少能看到默认内容,而不是一片空白。
总结一下
以上是我对 JSONP 性能优化的一些实战经验,主要是通过超时控制、错误处理和降级方案来提升用户体验。当然,JSONP 本身是一个比较老旧的技术,现在大部分场景都可以用 CORS 或者 Fetch 来替代,建议大家尽量减少 JSONP 的使用。
如果你有更好的优化方案或者踩过类似的坑,欢迎在评论区交流!后续我还会分享更多关于性能优化的文章,敬请期待。

暂无评论