一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
三 基本实现 |
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);
},
});
以上,就是手写部分的内容。
五 参考文献
返回目录
对于本文影响较大的是呆呆大佬的文章:
- [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/ 处获得。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。