jsonp

465 阅读2分钟

JSONP

前言

在面试中经常会问到怎么解决跨域,面试题中经常会听到一个词 JSONP,利用 JSONP 跨域,闲暇之余就去看了看 JSONP 到底是什么东西,在此记录一下。

什么是 JSONP

JSONP 是解决跨域问题的一种方案,和 JSON 并没有关系,而是一种绕过跨域的技巧

实现原理

  1. 首先它的原理就是利用 script 标签不受同源策略的限制,将 src 指向服务端地址
  2. 将 js 方法名作为参数传递到服务端(www.xxx.com?jsonCallback=callbackFunction), 然后由服务端用这个方法名也就是callbackFunction来包裹数据(callbackFunction(data)),相当于在前端执行这个方法
  3. 由于 callbackFunction 在前端是没有这个方法的,所以要在 scirpt 的 src 访问这个 url 之前在 window 上注册这个方法,这样在服务端返回(callbackFunction(data))执行的时候就可以在 window 上定义的这个方法去接收数据

JSONP 实现

function jsonp(url, opts, fn) {
  // 因为opts是可选参数,所以先判断opts是不是一个function
  if ("function" == typeof opts) {
    fn = opts;
    opts = {};
  }
  if (!opts) opts = {};

  // String 处理响应的全局回调函数的前缀  默认为__jp
  var prefix = opts.prefix || "__jp",
    count = 0;

  // String 处理响应的全局回调函数的名称 如果没有传入name则默认 prefix + 递增计数器
  var id = opts.name || prefix + count++;

  // String 用于指定回调的查询字符串参数的名称
  var param = opts.param || "callback";
  // Number 响应超时,默认是60000
  var timeout = null != opts.timeout ? opts.timeout : 60000;
  var enc = encodeURIComponent;
  var target = document.getElementsByTagName("script")[0] || document.head;
  var script;
  var timer;

  // 超时时间
  if (timeout) {
    timer = setTimeout(function () {
      cleanup();
      // 如果超时抛出错误信息Timeout
      if (fn) fn(new Error("Timeout"));
    }, timeout);
  }

  // 清除定时器
  function cleanup() {
    if (script.parentNode) script.parentNode.removeChild(script);
    window[id] = noop;
    if (timer) clearTimeout(timer);
  }

  function cancel() {
    if (window[id]) {
      cleanup();
    }
  }

  // 在window顶层对象增加一个属性方法,接收参数为响应返回的数据
  window[id] = function (data) {
    debug("jsonp got", data);
    cleanup();
    if (fn) fn(null, data);
  };

  // 处理请求地址
  url += (~url.indexOf("?") ? "&" : "?") + param + "=" + enc(id);
  url = url.replace("?&", "?");

  script = document.createElement("script");
  script.src = url;
  target.parentNode.insertBefore(script, target);

  return cancel;
}

ajax 使用 jsonp

以调用百度位置方法为例

$.ajax({
  type: "get", // 设置请求类型
  timeout: 5000, // 设置超时时间
  url: "[请求url]",
  data: {
    ak: "[填写ak]",
    output: "json",
    coordtype: "bd09ll",
    location: `${lat},${lng}`,
    extensions_poi: 1,
  },
  dataType: "jsonp", // 指定类型为jsonp
  success: function (data) {
    console.log(data); // 接收数据
  },
  error: function (err) {
    alert("获取定位失败");
    console.error(err);
  },
});