JSONP跨域请求原理详解与实战实现方案
1. 跨域请求的痛点与 JSONP 的诞生逻辑前后端分离开发的日常里跨域几乎是绕不开的一道坎。前端页面跑在http://localhost:8080后端接口部署在http://api.example.com浏览器控制台立刻甩出一行红字Access-Control-Allow-Origin缺失请求被拦截。这个场景在十年前比现在更常见因为那时候 CORS跨域资源共享还没有被所有浏览器和服务器广泛支持开发者急需一种能绕过同源策略、又能拿到数据的方案。JSONP 就是在这个背景下被广泛采用的。JSONP 全称 JSON with Padding直译过来就是“带填充的 JSON”。它的核心思路非常朴素浏览器虽然有同源策略限制 XHR 请求但script标签的src属性不受同源策略约束。也就是说你可以从任意域名加载一个 JavaScript 文件浏览器不会拦截。JSONP 正是利用了这个“漏洞”把后端返回的数据包装成一段可执行的 JavaScript 代码前端通过动态创建script标签来加载从而拿到数据。这个方案能解决什么问题简单说它让前端在 CORS 尚未普及的年代能够跨域获取 JSON 数据。适合谁学习如果你正在维护老项目、对接第三方接口、或者面试中被问到跨域方案JSONP 都是必须掌握的基础知识点。即便现在 CORS 已经是主流理解 JSONP 的原理依然有助于你更深入地理解浏览器的同源策略和脚本加载机制。我第一次接触 JSONP 是在一个对接第三方天气接口的项目里。对方只提供 JSONP 形式的接口文档里写着“callback 参数必传”当时还觉得奇怪后来才明白这就是 JSONP 的标准约定。踩过几次坑之后我对这套机制的理解才算真正落地。2. JSONP 核心原理深度拆解2.1 同源策略与 script 标签的“特权”要理解 JSONP必须先搞清楚浏览器的同源策略。同源策略要求协议、域名、端口三者完全一致否则就视为跨域。跨域情况下浏览器会限制 XHR/Fetch 请求读取响应内容但有一个例外script、img、link这类标签可以跨域加载资源。这不是设计缺陷而是历史遗留的“合理放行”——早期网页需要从 CDN 加载脚本和图片如果这些都被拦截互联网就没法正常运转了。JSONP 正是抓住了script标签这个特性。前端动态创建一个script元素把src指向后端接口地址同时带上一个callback参数。后端收到请求后不返回纯 JSON而是返回一段形如callbackName({...数据...})的 JavaScript 代码。浏览器加载这段脚本后会立即执行它而callbackName正是前端预先定义好的全局函数数据就通过函数参数传了进来。注意JSONP 只能用于 GET 请求因为script标签的加载本质就是 GET。这一点在实际开发中经常被忽略导致有人试图用 JSONP 发 POST 请求结果怎么调都不通。2.2 JSONP 的完整通信流程把整个流程拆开来看大致分为五步前端提前定义一个全局函数比如window.handleResponse用于接收数据。前端动态创建script标签src设置为http://api.example.com/data?callbackhandleResponse。浏览器向该地址发起 GET 请求。后端接收到callback参数后把数据包装成handleResponse({name:张三,age:25})这样的字符串返回。浏览器把返回内容当作 JavaScript 执行handleResponse被调用前端拿到数据。整个过程没有任何 XHR 参与完全是脚本加载机制在起作用。这也是为什么 JSONP 能绕过同源策略——它压根就没触发 XHR 的跨域检查。2.3 为什么 JSONP 逐渐被 CORS 取代JSONP 虽然巧妙但缺点也很明显。首先它只支持 GET无法满足 RESTful 风格中 POST、PUT、DELETE 的需求。其次错误处理非常困难script标签加载失败只会触发onerror拿不到具体的 HTTP 状态码和错误信息。再者安全性堪忧因为返回的是可执行脚本如果后端被劫持攻击者可以注入任意代码。最后回调函数名是全局的多个请求之间容易冲突需要额外管理。CORS 出现后服务器只需设置几个响应头就能支持所有 HTTP 方法还能精细控制哪些域名可以访问。所以现在新项目基本都用 CORSJSONP 更多出现在老系统和第三方接口对接中。但理解 JSONP 依然有价值因为它能帮你更深刻地理解浏览器的安全模型。3. 手把手实现一个完整的 JSONP 方案3.1 前端封装从零写一个 JSONP 函数先来看前端怎么封装一个通用的 JSONP 请求函数。核心要点有三个生成唯一回调名、动态创建 script 标签、请求完成后清理现场。function jsonp(url, params {}, timeout 5000) { return new Promise((resolve, reject) { // 生成唯一回调名避免多个请求冲突 const callbackName jsonp_cb_ Date.now() _ Math.floor(Math.random() * 1000); // 把 callback 参数拼接到 URL 上 const query new URLSearchParams({ ...params, callback: callbackName }); const fullUrl url (url.includes(?) ? : ?) query.toString(); // 创建 script 标签 const script document.createElement(script); script.src fullUrl; // 定义全局回调函数 window[callbackName] function(data) { resolve(data); cleanup(); }; // 超时处理 const timer setTimeout(() { reject(new Error(JSONP request timeout)); cleanup(); }, timeout); // 清理函数移除 script 标签、删除全局函数、清除定时器 function cleanup() { clearTimeout(timer); if (script.parentNode) { script.parentNode.removeChild(script); } delete window[callbackName]; } // 加载失败处理 script.onerror function() { reject(new Error(JSONP script load error)); cleanup(); }; document.head.appendChild(script); }); }这段代码有几个细节值得展开说。第一回调名用时间戳加随机数确保唯一性避免并发请求时互相覆盖。第二用Promise包装调用方可以用async/await写起来更顺手。第三cleanup函数负责移除 script 标签和删除全局函数防止内存泄漏。第四超时时间设了 5 秒实际项目中可以根据接口响应速度调整。实操心得delete window[callbackName]这一步很多人会漏掉。如果不删页面运行久了全局变量会越来越多虽然不至于立刻出问题但在单页应用里频繁发 JSONP 请求内存占用会明显上升。3.2 后端配合PHP 跨域 JSONP 接口实现热词里提到了“php跨域jsonp”说明很多老项目是 PHP 后端。PHP 实现 JSONP 接口非常简单核心就是读取callback参数然后把数据包一层。?php // 设置响应类型为 JavaScript header(Content-Type: application/javascript; charsetutf-8); // 获取回调函数名做安全过滤 $callback isset($_GET[callback]) ? $_GET[callback] : callback; // 严格校验回调名只允许字母、数字、下划线、点 if (!preg_match(/^[a-zA-Z_][a-zA-Z0-9_\.]*$/, $callback)) { http_response_code(400); echo invalid callback; exit; } // 准备数据 $data [ code 0, msg success, data [ name 张三, age 25, city 杭州 ] ]; // 输出 JSONP 格式 echo $callback . ( . json_encode($data, JSON_UNESCAPED_UNICODE) . );;这里最关键的是回调名的安全校验。如果不做过滤攻击者可以传入恶意字符串导致 XSS 漏洞。正则/^[a-zA-Z_][a-zA-Z0-9_\.]*$/限制了回调名只能以字母或下划线开头后面跟字母、数字、下划线或点。这样既能支持cb、myCallback这种常见命名也能支持obj.method这种命名空间形式。JSON_UNESCAPED_UNICODE这个参数也值得注意。默认情况下json_encode会把中文转成\uXXXX形式加上这个参数后中文会原样输出调试时更直观。3.3 前后端联调完整请求示例前端调用刚才封装的函数async function fetchUserInfo() { try { const result await jsonp(http://api.example.com/user.php, { userId: 123 }); console.log(拿到数据, result); } catch (err) { console.error(请求失败, err.message); } } fetchUserInfo();后端收到的请求大概是http://api.example.com/user.php?userId123callbackjsonp_cb_1699999999_456返回内容为jsonp_cb_1699999999_456({code:0,msg:success,data:{name:张三,age:25,city:杭州}});浏览器执行这段脚本后window.jsonp_cb_1699999999_456被调用Promise 的resolve触发前端就拿到了数据。整个过程行云流水没有任何跨域报错。注意后端返回的 Content-Type 建议设为application/javascript虽然浏览器对 script 标签的响应类型不严格校验但设对了更规范也方便调试。4. 常见问题排查与避坑指南4.1 回调函数未定义或未执行这是 JSONP 最常见的问题。现象是请求发出去了后端也返回了但前端回调就是不执行。原因通常有三个回调名拼写不一致、全局函数被覆盖、或者后端返回的格式不对。排查步骤可以按这个顺序来先在浏览器 Network 面板找到那个 script 请求看 Response 内容是不是callbackName({...})格式然后检查前端定义的全局函数名和后端返回的是否完全一致大小写都要对最后确认没有其他地方覆盖了同名全局变量。我遇到过一次特别隐蔽的情况两个模块都用了callback这个默认名结果后发的请求把先发的回调覆盖了导致先发的请求永远拿不到数据。后来改成动态生成唯一回调名就解决了。4.2 超时与错误处理失效script标签的onerror只在网络层面加载失败时触发比如 404、DNS 解析失败。如果后端返回了 500 错误但内容是一段 HTML浏览器会尝试执行这段 HTML通常不会触发onerror而是抛出语法错误。这种情况下 Promise 既不会 resolve 也不会 reject请求就“悬”在那里了。解决办法是加超时机制就像前面代码里那样用setTimeout。超时时间设多少合适一般接口 3 到 5 秒足够慢查询接口可以放宽到 10 秒。超时后主动 reject并清理 script 标签。4.3 安全性问题与防范JSONP 的安全风险主要有两个一是回调名注入二是返回内容被篡改。回调名注入前面已经讲了用正则严格校验就能防住。返回内容被篡改则比较麻烦因为 JSONP 本质是加载远程脚本如果中间人攻击或者后端被入侵攻击者可以返回任意 JavaScript 代码在用户浏览器里执行。防范措施包括只对接可信的第三方接口、尽量用 HTTPS、后端对返回数据做严格过滤。如果接口涉及敏感操作建议改用 CORS 加 Token 鉴权的方式不要用 JSONP。4.4 常见问题速查表问题现象可能原因排查方法解决方案回调不执行回调名不一致对比 Network 返回内容与前端函数名统一命名用动态唯一名请求无响应超时未处理检查是否有 setTimeout加超时 reject 逻辑控制台报语法错误后端返回非 JS 内容查看 Response 内容后端确保返回合法 JS并发请求数据错乱回调名冲突检查是否用了固定回调名每次请求生成唯一回调名内存泄漏全局函数未删除检查 cleanup 逻辑请求完成后 delete 全局函数中文乱码编码不一致检查 Content-Type设为 utf-8用 JSON_UNESCAPED_UNICODE5. JSONP 在现代开发中的定位与替代方案5.1 JSONP 与 CORS 的对比选型现在新项目基本不会主动选 JSONPCORS 是更现代、更安全的方案。但两者并不是完全替代关系有些场景下 JSONP 反而更合适。比如对接一些老旧的第三方接口对方只提供 JSONP 形式你没得选。再比如需要加载跨域的静态脚本资源这本身就是 script 标签的天然能力跟 JSONP 思路一致。从实现成本看CORS 需要后端配置响应头前端用标准 Fetch 即可代码更简洁。JSONP 需要前后端约定回调参数前端还要手动管理 script 标签和全局函数维护成本更高。从安全性看CORS 支持精细的域名白名单和凭证控制JSONP 几乎没有安全边界。所以只要条件允许优先选 CORS。5.2 用 CORS 替代 JSONP 的迁移思路如果你手上有老项目还在用 JSONP想迁移到 CORS可以按这个思路来。后端先加上 CORS 响应头比如Access-Control-Allow-Origin设为具体域名Access-Control-Allow-Methods加上需要的 HTTP 方法。然后前端把 JSONP 调用改成 Fetch 或 Axios去掉 callback 参数。最后灰度发布观察一段时间确认没问题再全量切换。迁移过程中要注意预检请求。如果请求带了自定义头或者用了 PUT/DELETE 方法浏览器会先发一个 OPTIONS 请求做预检后端需要正确处理 OPTIONS 并返回 204。这一点在 JSONP 时代是不存在的迁移时容易漏掉。5.3 其他跨域方案简述除了 JSONP 和 CORS还有几种跨域方案值得一提。一是 Nginx 反向代理把前端和后端放在同一个域名下从根上避免跨域。二是 postMessage适合 iframe 之间的通信。三是 WebSocket本身不受同源策略限制。每种方案都有适用场景实际项目中往往是组合使用。实操心得如果项目里既有 JSONP 又有 CORS建议统一封装一个请求层根据接口配置自动选择用哪种方式。这样业务代码不用关心底层细节迁移时也只需要改配置不用动业务逻辑。JSONP 这个技术点看起来简单但真正写好、用好、排查好问题还是需要不少实战经验的。我在实际项目中最大的体会是不要因为它“老”就轻视它很多老系统的稳定性恰恰依赖于这些看似过时的方案。理解它的原理和边界比单纯会用更重要。后续如果遇到需要兼容极老浏览器的场景JSONP 依然是一个可靠的备选方案。

相关新闻

HTTP为何能靠TCP“躺赢”?从协议栈分工到工业排障全解析

HTTP为何能靠TCP“躺赢”?从协议栈分工到工业排障全解析

把Wireshark打开,盯一条HTTP请求的完整生命周期走一遍,你大概率会得出跟我一样的结论:HTTP跑起来实在是太省心了。丢包重传、乱序重组、连接建立与释放、流量与拥塞控制,这些又脏又累的活,统统被TCP协议扛在了自己肩上…

2026/10/9 12:45:12 阅读更多 →
TX1串口调试实战:ttyTHS1与ttyTHS2区别、配置与避坑指南

TX1串口调试实战:ttyTHS1与ttyTHS2区别、配置与避坑指南

1. 从一块开发板的调试口说起:tx1串口到底特殊在哪很多人第一次接触tx1的串口,都是因为系统起不来、网络不通、或者想进控制台看看内核打印。这块板子的串口不像普通单片机那样随便找个UART就能用,它的调试串口和通用串口在设备树里是分开配置…

2026/10/9 12:44:11 阅读更多 →
EmbeddingGemma浏览器原生推理:纯前端文本向量化实战

EmbeddingGemma浏览器原生推理:纯前端文本向量化实战

1. 项目概述:为什么“EmbeddingGemma 浏览器直接试用”这件事值得认真对待最近在多个技术社区和开发者群聊里,频繁刷到“EmbeddingGemma 浏览器直接试用”这个短语——它不像传统模型部署那样强调服务器、GPU或Docker,而是把“浏览器”和“直…

2026/10/9 12:44:11 阅读更多 →

最新新闻

HarmonyOS 7 Core Vision Kit:文搜图短别名索引与回查契约【鸿蒙心迹】

HarmonyOS 7 Core Vision Kit:文搜图短别名索引与回查契约【鸿蒙心迹】

李游 把“文本搜照片”接进相册类应用时,第一反应往往是模型是否足够准确、结果能否排到用户想看的那一张。但真正接入产品数据之后,还有一道更靠前的门槛:供视觉服务建立索引的究竟是哪条文件路径?图库里的资源可能来自相机、文件…

2026/10/9 13:21:08 阅读更多 →
HarmonyOS 7 PanGesture:图片对比滑块手势裁决与滚动让行【鸿蒙心迹】

HarmonyOS 7 PanGesture:图片对比滑块手势裁决与滚动让行【鸿蒙心迹】

李游 一个很小的交互,到了大屏双栏里,反而更容易露出边界。左侧是商品清单,右侧是详情。详情页面纵向滚动,某个图片对比区域又放了一个可以左右拖动的分割滑块。用户的手指刚接触图片,想上下看下一段描述,滑…

2026/10/9 13:21:08 阅读更多 →
2026大厂AI Agent高频面试题Top50:题目+参考答案+追问陷阱(TaoToken版)

2026大厂AI Agent高频面试题Top50:题目+参考答案+追问陷阱(TaoToken版)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 13:21:08 阅读更多 →
dnSpy:支持调试与重编译的.NET反编译器实战指南

dnSpy:支持调试与重编译的.NET反编译器实战指南

简介:本资源为C#开发者与逆向工程学习者必备的dnSpy反编译工具完整安装包,涵盖反编译、调试、代码编辑与热替换等核心能力,适用于.NET程序分析、Bug修复、安全审计及源码级学习等实战场景。压缩包共399个文件,包含304个dll&#x…

2026/10/9 13:21:08 阅读更多 →
Reflector反编译实战:FileDisassembler与FileGenerator插件还原.NET程序集

Reflector反编译实战:FileDisassembler与FileGenerator插件还原.NET程序集

简介:一款面向.NET开发者的Reflector 7.4.1.179绿色注册版,截至2011年11月为官方最新版本,已内置注册信息并真正集成FileDisassembler与FileGenerator两大流行插件;与网上标注更高版号的假版本不同,该版本号可在官网核…

2026/10/9 13:21:08 阅读更多 →
Access 2007 免费版 zip 靠不靠谱?一张图看懂 accdb 与正规获取法

Access 2007 免费版 zip 靠不靠谱?一张图看懂 accdb 与正规获取法

简介:Access 2007 免费精简版安装包,是面向办公软件场景的 Access 2007 SP3 独立精简版本,适合需要快速部署数据库环境、不愿安装完整 Office 套件的办公人员、数据库初学者或教学场景使用。该包基于官方 SP3 深度定制,重点解决了…

2026/10/9 13:20:05 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →