乐趣区

关于前端:jsliang-求职系列-13-JSONP

一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 前言
三 根本实现
 3.1 步骤一:前端局部
 3.2 步骤二:后端局部
 3.3 步骤三:前端局部
四 手写 JSONP
 4.1 封装 JSONP
 4.2 简略实现
 4.3 欠缺版本
五 参考文献

二 前言

返回目录

基本原理 :利用 script 标签的 src 没有跨域限度来实现跨域目标。

执行过程

  1. 前端定义一个解析函数:jsonpCallBack = function (res) {}
  2. 通过 params 的模式包装 script 标签的申请参数,并且申明执行函数,如:cb = jsonpCallBack
  3. 后端获取前端申明的执行函数(jsonpCallBack),并且带上参数且调用函数的形式传递给前端
  4. 前端再 script 标签返回资源的时候就会执行 jsonpCallBack 并通过回调函数的形式拿到数据。

优缺点

  1. 【毛病】只能进行 GET 申请
  2. 【长处】兼容性好,在一些古老的浏览器中都能够运行。

注:本篇文章绝大部分参考 LinDaiDai_霖呆呆 大佬的手写内容,基于集体了解进行的再创作。呆呆大佬的文章在参考文献已给出

三 根本实现

返回目录

上面咱们通过 3 步骤来理解如何应用 JSONP

3.1 步骤一:前端局部

返回目录

前端代码:

index.html

<script type='text/javascript'>
  window.jsonpCallBack = function(res) {console.log(res);
  }
</script>
<script src='http://localhost:8080/api/jsonp?id=1&cb=jsonpCallBack' type='text/javascript'></script>
  1. 创立一个 jsonpCallBack 函数,然而还没有被调用。
  2. 加载 src 中的资源,调用 localhost:8080 端口的 API:api/jsonp,传递的参数是 id = 1 以及 cb = jsonpCallBack

3.2 步骤二:后端局部

返回目录

后端代码:

const Koa = require('koa');
const app = new Koa();
const items = [{id: 1, title: 'title1'}, {id: 2, title: 'title2'}]

app.use(async (ctx, next) => {if (ctx.path === '/api/jsonp') {const { cb, id} = ctx.query;
    const title = items.find(item => item.id == id)['title']
    ctx.body = `${cb}(${JSON.stringify({title})})`;
    return;
  }
})
console.log('listen 8080...')
app.listen(8080);
  1. 解析 ctx.query,将 idcb 获取到。
  2. 查找 title
  3. 返回一个字符串,该字符串调用 cb 办法,并将 title 转成字符串,返回到内容 ctx.body 中。

3.3 步骤三:前端局部

返回目录

这时候前端接管到 Node.js 的返回,间接调用了 cb(title),办法,最终执行了它的回调,从而执行:

window.jsonpCallBack = function(res) {console.log(res);
}

输入:{title: 'title1'}

四 手写 JSONP

返回目录

4.1 封装 JSONP

返回目录

定义一个 JSONP 办法,接管 4 个参数:

  • urlapi 接口
  • paramsapi 接口参数
  • callbackKey:与后端约定回调函数用哪个字段
  • callback:拿到数据之后前端执行的回调函数
JSONP({
  url: 'https://www.baidu.com/s',
  params: {wd: 'jsliang'},
  callBackkey: 'cb',
  callback(res) {console.log(res);
  }
})

4.2 简略实现

返回目录

咱们看看简略实现:

const JSONP = ({
  url,
  params = {},
  callBackkey = 'cb',
  callback,
}) => {params[callBackkey] = callBackkey;

  window[callBackkey] = callback;

  const newParam = Object.keys(params).map((key) => `${key}=${params[key]}`).join('&');

  const script = document.createElement('script');
  script.setAttribute('src', `${url}?${newParam}`);
  document.body.appendChild(script);
}

JSONP({
  url: 'https://www.baidu.com/s',
  params: {wd: 'jsliang'},
  callBackkey: 'cb',
  callback(res) {console.log(res);
  }
})

4.3 欠缺版本

返回目录

优化屡次调用时候的一个问题:

function JSONP({
  url,
  params = {},
  callbackKey = "cb",
  callback,
}) {
  // 定义本地的惟一 callBackId,避免屡次调用的时候出问题
  JSONP.callBackId = JSONP.callBackId || 1; // 默认为 1

  // 拿到这个 id
  const callBackId = JSONP.callBackId;

  // 将要执行的回调如果到 JSONP 对象中,防止净化 window
  JSONP.callbacks = JSONP.callbacks || [];
  JSONP.callbacks[callBackId] = callback;

  // 把这个名称退出到参数中:`cb = JSONP.callbacks[1]`
  params[callbackKey] = `JSONP.callbacks[${callBackId}]`;

  // 组合 params:'id=1&cb=JSONP.callbacks[1]'
  const paramString = Object.keys(params).map((key) => `${key}=${params[key]}`);

  // 动态创建 script 标签
  const script = document.createElement("script");
  script.setAttribute("src", `${url}?${paramString}`);
  document.body.appendChild(script);

  // id 自增,保障惟一
  JSONP.callBackId++;
}

JSONP({
  url: "http://localhost:8080/api/jsonp",
  params: {id: 1},
  callbackKey: "cb",
  callback(res) {console.log(res);
  },
});
JSONP({
  url: "http://localhost:8080/api/jsonp",
  params: {id: 2},
  callbackKey: "cb",
  callback(res) {console.log(res);
  },
});

以上,就是手写局部的内容。

五 参考文献

返回目录

对于本文影响较大的是呆呆大佬的文章:

  • [x] JSONP 原理及实现【浏览倡议:30min】

外面还有最终降级加强版,jsliang 就不颤抖了,面试问手写 JSONP 还没有碰到。


jsliang 的文档库由 梁峻荣 采纳 常识共享 署名 - 非商业性应用 - 雷同形式共享 4.0 国内 许可协定 进行许可。<br/> 基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/> 本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。

退出移动版