一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
三 基本实现 |
3.1 步骤一:前端部分 |
3.2 步骤二:后端部分 |
3.3 步骤三:前端部分 |
四 手写 JSONP |
4.1 封装 JSONP |
4.2 简单实现 |
4.3 完善版本 |
五 参考文献 |
二 前言
返回目录
基本原理:利用 script
标签的 src
没有跨域限制来完成跨域目的。
执行过程:
- 前端定义一个解析函数:
jsonpCallBack = function (res) {}
- 通过
params
的形式包装script
标签的请求参数,并且声明执行函数,如:cb = jsonpCallBack
- 后端获取前端声明的执行函数(
jsonpCallBack
),并且带上参数且调用函数的方式传递给前端 - 前端再
script
标签返回资源的时候就会执行jsonpCallBack
并通过回调函数的方式拿到数据。
优缺点:
- 【缺点】只能进行
GET
请求 - 【优点】兼容性好,在一些古老的浏览器中都可以运行。
注:本篇文章绝大部分参考 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>
- 创建一个
jsonpCallBack
函数,但是还没有被调用。 - 加载
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);
- 解析
ctx.query
,将id
和cb
获取到。 - 查找
title
- 返回一个字符串,该字符串调用
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 个参数:
url
:api
接口params
:api
接口参数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); }, });
以上,就是手写部分的内容。
五 参考文献
返回目录
对于本文影响较大的是呆呆大佬的文章:
- JSONP 原理及实现【阅读建议:30min】
里面还有最终升级加强版,jsliang 就不哆嗦了,面试问手写 JSONP
还没有碰到。
jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。
基于 github.com/LiangJunron… 上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。