手写 JSONP:深入理解跨域数据获取的经典方案

75 阅读2分钟

手写 JSONP:深入理解跨域数据获取的经典方案

在 Web 开发中,由于浏览器的 同源策略(Same-Origin Policy),前端 JavaScript 默认无法直接请求不同域名下的接口数据。

早期的解决方案是 JSONP(JSON with Padding),它巧妙利用 <script> 标签不受同源策略限制的特性,实现跨域数据获取。虽然现代 Web 开发更推荐 CORS(跨域资源共享),但理解 JSONP 仍然有助于深入掌握浏览器安全机制和跨域技术。

一、JSONP 的核心原理

JSONP 的核心思想是:

  1. 前端动态创建 <script> 标签,其 src 指向目标 API,并附带一个回调函数名(如 ?callback=handleData)。

  2. 服务器返回的数据被包裹在该回调函数中,例如:

    handleData({ "name": "Jack", "age": 25 })
    
  3. 浏览器执行这段脚本,自动调用回调函数并处理数据。

由于 <script> 标签可以跨域加载资源,因此绕过了同源策略的限制。

二、手写 JSONP 实现

前端实现

我们需要编写一个函数,动态创建 <script> 标签并处理返回的数据:

function jsonp({url,param, callbackName, successCallback, errorCallback}) {
  // 1. 创建全局回调函数
  window[callbackName] = function (data) {
    successCallback(data);
    // 清理 script 标签和全局函数
    document.body.removeChild(script);
    delete window[callbackName];
  };
   
  const arr = []
  for(let key in param){
      arr.push(`${key}=${param[key]}`)
  }

  // 2. 创建 script 标签
  const script = document.createElement("script");
  script.src = `${url}?callback=${callbackName}&${arr.join('&')}`;
  
  // 3. 错误处理(如请求失败)
  script.onerror = function () {
    errorCallback(new Error("JSONP request failed"));
    document.body.removeChild(script);
    delete window[callbackName];
  };

  // 4. 插入 DOM,发起请求
  document.body.appendChild(script);
}

使用方法:

jsonp({
    url:"http://localhost:3000/getData",
    param: {
        userId:"6484316841",
        userName:"Nick"
    },
    callbackName:"handleUserData",
    successCallback:(data) => console.log("成功:", data),
    errorCallback:(err) => console.error("失败:", err),
});

后端实现(Node.js 示例)

服务器需要解析 callback 参数,并返回包裹在回调函数中的 JSON 数据:

// server.js - 原生 Node.js 版本
const http = require("http");

const server = http.createServer((req, res) => {
  // 匹配 GET 请求 /say
  if (req.url.startsWith("/getData")) {
    // 解析url 创建url实例 形成一个完成的url对象  因为req.url 是没有前面的域名之类的
    const url = new URL(req.url, `http://${req.headers.host}`);

    const userId = url.searchParams.get("userId");
    const userName = url.searchParams.get("userName");
    const callback = url.searchParams.get("callback");

    // 返回 JSONP 格式响应
    res.writeHead(200, {
      "Content-Type": "application/javascript",
    });

    const data = {
      code: 0,
      msg: "success",
      data: {
        userId,
        userName
        age: 18,
      },
    };
    
    res.end(`${callback}(${JSON.stringify(data)})`);
  } else {
    res.writeHead(404);
    res.end("Not Found");
  }
});

server.listen(3000, () => {
  console.log("Server running at http://localhost:3000");
});

请求示例:

http://localhost:3000/getData?callback=handleUserData&usrId=6484316841&userName=Nick
```Nick
**返回数据:**

```javascript
handleUserData({
    code:0,
    msg:'success',
    data:{ "userId": "6484316841", "userName": "Nick" ,"age": 18,}
})

三、JSONP 的优缺点

优点在于:兼容性好:支持所有浏览器,包括 IE6/7/8 等老旧浏览器; 无需 CORS:适用于无法修改服务器配置的场景。

缺点

  1. 仅支持 GET 请求,无法使用 POST、PUT、DELETE 等方法。
  2. 安全性问题:如果服务器被攻击,可能返回恶意代码(XSS 攻击)。
  3. 错误处理困难:无法像 fetchaxios 那样捕获 HTTP 404/500 错误。
  4. 回调函数污染全局作用域,可能与其他脚本冲突。

四、SONP vs CORS

对比项JSONPCORS
实现方式利用 <script> 标签使用 Access-Control-Allow-Origin
请求方法仅 GET支持 GET/POST/PUT/DELETE 等
安全性较低(可能 XSS)较高(可限制来源)
兼容性所有浏览器(包括 IE6+)现代浏览器(IE10+)
适用场景旧系统兼容、简单数据获取现代 Web 开发

总结

  • JSONP 是一种利用 <script> 标签绕过同源策略的跨域方案
  • 手写 JSONP 需要动态创建 <script> 并定义全局回调函数
  • JSONP 仅适用于 GET 请求,且存在安全风险,现代开发推荐 CORS。