JSONP

你写的原生极简写法,确实会造成内存泄漏,而且有三处泄漏隐患。

一、为什么会内存泄漏

1. <script> 标签一直留在 DOM 里

复制代码
const script = document.createElement('script');
script.src = 'xxx?callback=getData';
document.body.appendChild(script);

请求完成后,这个 script 元素还挂在 body 里,不会自动删除 ,多次调用就会堆积大量无用 script 节点 → DOM 节点泄漏。

2. 全局回调函数 getData 常驻全局

window.getData 定义后一直存在,不会自动销毁。多次 JSONP 请求、复用同一个回调名,容易变量污染、回调错乱,且全局引用不释放 → 内存占用累积。

3. 没有事件解绑

如果给 script 绑了 onload/onerror,不手动置空解绑,也会形成引用链无法回收


二、怎么彻底解决(防泄漏完整版)

核心三件事:

  1. 请求完成 / 失败后 立即移除 script 标签

  2. 删除全局回调函数

  3. 清空事件、解除引用

    // 全局回调
    window.getData = function(res) {
    console.log('拿到数据', res);
    // 关键:业务处理完,立刻清理
    cleanUp();
    };

    let script;
    let timer;

    // 发起JSONP
    function startJsonp() {
    script = document.createElement('script');
    script.src = 'https://api.example.com/data?callback=getData';

    // 超时兜底
    timer = setTimeout(() => {
    console.log('请求超时');
    cleanUp();
    }, 5000);

    // 加载失败
    script.onerror = function() {
    console.log('请求失败');
    cleanUp();
    };

    document.body.appendChild(script);
    }

    // 统一清理:杜绝内存泄漏
    function cleanUp() {
    // 1. 清除超时定时器
    clearTimeout(timer);

    // 2. 移除DOM中的script节点
    if (script && script.parentNode) {
    script.parentNode.removeChild(script);
    }

    // 3. 解绑事件,切断引用
    script.onload = null;
    script.onerror = null;

    // 4. 删除全局回调函数
    delete window.getData;

    // 5. 变量置空,方便GC回收
    script = null;
    }

    // 调用
    startJsonp();


三、最佳实践(彻底避免全局污染 + 内存泄漏)

用随机唯一回调名,自动清理,不污染全局

复制代码
function jsonp(url, timeout = 5000) {
  return new Promise((resolve, reject) => {
    // 随机唯一回调名,防止冲突
    const cbName = 'jsonp_cb_' + Date.now() + '_' + Math.random().toString(36);

    // 挂载全局回调
    window[cbName] = (data) => {
      resolve(data);
      clean();
    };

    const script = document.createElement('script');
    script.src = `${url}?callback=${cbName}`;

    let timer = setTimeout(() => {
      reject(new Error('jsonp超时'));
      clean();
    }, timeout);

    script.onerror = () => {
      reject(new Error('jsonp请求失败'));
      clean();
    };

    function clean() {
      clearTimeout(timer);
      // 移除节点
      script.parentNode && script.parentNode.removeChild(script);
      // 解绑事件
      script.onerror = null;
      // 删除全局回调
      delete window[cbName];
      // 置空引用
      script = null;
    }

    document.body.appendChild(script);
  });
}

// 使用
jsonp('https://api.example.com/data')
  .then(res => console.log(res))
  .catch(err => console.error(err));

四、总结

  1. 你原来的写法:会内存泄漏,DOM 节点堆积 + 全局函数不回收;
  2. 解决方案必须做:删 script 节点 + 删全局回调 + 清定时器 + 解绑事件 + 变量置空
  3. 项目里直接用上面 Promise 封装版,零泄漏、无全局污染、可复用
  • JSONP 只支持 GET 请求,不支持 POST/PUT/DELETE

  • 后端必须配合 :返回格式为 回调函数名(数据),例如:

    复制代码
    getData({ name: "测试", age: 20 })
  • 相比 CORS,JSONP 兼容性更好(支持 IE 低版本)

  • 封装版使用 Promise ,支持 then/catch 异步处理

JSONP 不是 AJAX,它就是一段可执行的 JS 代码。 浏览器拿到后端返回的内容,会直接当作 JS 执行


1. 你前端写的代码

复制代码
// 前端告诉后端:我的回调函数叫 getData
const script = document.createElement('script');
script.src = "https://api.com/data?callback=getData";
document.body.appendChild(script);

你定义了一个全局函数:

复制代码
function getData(data) {
  console.log(data);
}

2. 后端必须返回什么?

**后端不能返回 JSON!**不能返回:

复制代码
{ "name": "小明", "age": 20 }

必须返回一段 JS 函数调用代码:

复制代码
getData({ "name": "小明", "age": 20 });

3. 为什么必须这样?

因为 <script src="..."> 拿到内容后,会直接执行

浏览器拿到后端返回的内容后,相当于:

复制代码
<script>
getData({ "name": "小明", "age": 20 });
</script>

它会立刻执行你定义的 getData 函数,并把数据传进去!

这就是 JSONP 的原理。


4. 用生活例子理解

  • 你(前端)给快递员(后端)说:"送到后请喊我的名字:getData"
  • 快递员(后端)必须喊:"getData,你的快递到了!"
  • 你听到名字,就知道快递来了(拿到数据)

如果后端不喊你的名字,直接扔快递,你根本不知道是给谁的。


5. 后端返回错误会发生什么?

如果后端返回普通 JSON:

复制代码
{ "name": "小明" }

浏览器会报错:

复制代码
Uncaught SyntaxError: Unexpected token ':'

因为它把 JSON 当 JS 执行,语法不合法。


6. 后端怎么写?(示例)

Node.js 后端

复制代码
app.get('/data', (req, res) => {
  const callbackName = req.query.callback; // 拿到前端传的 getData
  const data = { name: "小明", age: 20 };
  
  // 返回:getData({...})
  res.send(`${callbackName}(${JSON.stringify(data)})`);
});

PHP 后端

复制代码
$callback = $_GET['callback'];
$data = array("name"=>"小明","age"=>20);
echo $callback . '(' . json_encode($data) . ')';

Java 后端

复制代码
String callback = request.getParameter("callback");
out.print(callback + "({name:'小明',age:20})");

全部都是:回调名 + (+ 数据 +)


7. 最终总结(最关键)

  1. JSONP = 动态创建 script 标签
  2. 后端返回的不是数据,是一段 JS 函数调用代码
  3. 格式必须是:回调函数名 (数据)
  4. 浏览器执行这段代码,你就拿到数据了
相关推荐
gogoing1 小时前
ESLint 配置字段说明
前端·javascript
gogoing1 小时前
CSS 属性值计算过程(Computed Value)
前端·css
gogoing1 小时前
webpack 的性能优化
前端·javascript
桃花键神1 小时前
Bright Data Web Scraping指南 2026: 使用 MCP + Dify 自动采集海外社交媒体数据
大数据·前端·人工智能
gogoing1 小时前
await fetch() 的两阶段设计
前端·javascript
gogoing1 小时前
前端首屏加载优化
前端·javascript
gogoing2 小时前
重排与重绘
前端·javascript
打小就很皮...2 小时前
基于Python + LangChain + 通义千问的聊天机器人实战
前端·langchain·机器人·千问
REDcker2 小时前
个人博客网站建设指南 Markdown资产化与静态站选型部署
前端·后端·博客·markdown·网站·资产·建站