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. 浏览器执行这段代码,你就拿到数据了
相关推荐
红辣椒...13 小时前
codex+第三方模型
java·服务器·前端
木子雨廷13 小时前
Flutter 使用 flutter_flavorizr 多渠道打包
前端·flutter
环境工程笔记13 小时前
浏览器自动化跑成功了,为什么结果还是不对?
前端
东风破_13 小时前
一文搞懂 JavaScript 变量声明:var、let、const 到底有什么区别?
前端·javascript
问心无愧051313 小时前
ctf show web入门261
android·前端·笔记
触底反弹13 小时前
你真的理解 JavaScript 变量提升(Hoisting)吗?从 V8 引擎编译原理深入剖析
前端·面试
蜡台13 小时前
Vue2 使用 typescript 教程
前端·vue.js·typescript
光影少年13 小时前
Redux Toolkit 用法、解决原生Redux 冗余问题
开发语言·前端·javascript·react.js·中间件·前端框架·ecmascript
云水一下13 小时前
JavaScript 从零基础到精通系列:DOM 操作与事件驱动编程
前端·javascript