yliu

时来天地皆同力,运去英雄不自由

jsonp实现原理


简单说一下存在的历史,浏览器存在同源政策,即域名+端口+协议必须一致,很多时候我们需要跨域访问,当然在现在我们可以借助 webpack 的反向代理配合服务器设置 cors 实现访问,但是在之前工程化还没出现的时候就需要有一种方法可以访问所以 jsonp 就出现了。 jsonp 的原理就是浏览器的 script 标签可以加载不同源的资源,配合后端通过执行函数传递参数,这也是 jsonp 只支持 get 的原因,同理的标签还有img,我们可以用它来实现访问站点次数的统计,根据上面的原理我们来实现一个模拟的版本

第一版

<script>
  function callback(v) {
    console.log("参数是:");
    console.log(v);
  }
</script>
<script>
  callback({
    name: "zhangsan",
    age: 18
  });
</script>

可以看到就是这个思路,后面的脚本调用这个参数即可。

简单实现一个 jsonp 的函数

interface Iparams {
  [propName: string]: any;
}
interface Ioptions {
  params?: Iparams;
  timeout?: number;
  name?: string;
}
let uid = 0;
const jsonp = function jsonp(this: any, url: string, option: Ioptions = {}) {
  return new Promise((resolve, reject) => {
    // 注意,这个id不能重复
    const id = `__uid${uid++}`;
    Reflect.set(window, id, (...rest: any[]) => {
      clear();
      return resolve.call(this, ...rest);
    });
    const { name = "callback", params = {}, timeout = 6000 } = option;
    // 清理
    const clear = () => {
      Reflect.set(window, id, () => {});
      script.parentNode.removeChild(script);
      if (timer) {
        clearTimeout(timer);
      }
    };
    // 创建超时任务
    let timer: NodeJS.Timeout;
    if (typeof timeout === "number") {
      timer = setTimeout(() => {
        clear();
        return reject(new Error(`Task timeout`));
      }, timeout);
    }
    const script: HTMLScriptElement = document.createElement("script");
    // 处理一下参数最终合并
    const par = new URLSearchParams(params);
    par.append(name, id);
    const href = `${url}?${par}`;
    // 编码一下防止出现中文之类的
    script.src = encodeURI(href);
    document.body.appendChild(script);
    script.addEventListener("error", () => {
      clear();
      return reject(
        new Error(`Failed to create jsonp, request target address is:${url}`)
      );
    });
  });
};
export default jsonp;

这里为了方便演示使用了URLSearchParams对象,它是浏览器的原生对象,用来构造、解析和处理 URL 的查询字符串,当然在生产中还需要polyfill,如果不满足兼容性建议手写一个工具处理。

最后

代码我已经放置到了仓库,欢迎来 Star

参考: