JSONP原理及实现
文章目录
- 基本原理
- 执行过程
- 优缺点
- 案例分析
- 前期准备
- 后端代码
- 前端简单实现
- jQuery中ajax实现
- 封装一个JSONP方法
- 简易版
- 同时多个请求
- 最终版JSONP方法
基本原理
基本原理: 主要就是利用了 script 标签的src没有跨域限制来完成的。
执行过程
- 前端定义一个解析函数(如: jsonpCallback = function (res) {})
- 通过params的形式包装script标签的请求参数,并且声明执行函数(如cb=jsonpCallback)
- 后端获取到前端声明的执行函数(jsonpCallback),并以带上参数且调用执行函数的方式传递给前端
- 前端在script标签返回资源的时候就会去执行jsonpCallback并通过回调函数的方式拿到数据了。
优缺点
缺点:
- 只能进行GET请求
优点:
- 兼容性好,在一些古老的浏览器中都可以运行
案例分析
先来看看我们要实现一个什么效果:
在一个叫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>
然后我本地有一个文件server.js它会使用node提供一个服务,来模拟服务器。
并且定义一个接口/api/jsonp来查询id对应的数据。
当我打开index.html的时候就会加载script标签,并执行了此次跨域请求。
前期准备
- 我在本地新建一个文件夹node-cors
- 并在此目录下npm init,初始化package.json
- 安装koa(node的一个轻量级框架)
- 新建文件夹jsonp,并新建index.html和server.js,一个写前端代码,一个写后端
mkdir node-cors && cd node-cors
npm init
cnpm i --save-dev koa
mkdir jsonp && cd jsonp
touch index.html
touch server.js
后端代码
由于JSONP的实现需要前后端配合,先来写一下后端的实现:
(看不懂没关系,下面的前端简单实现会做解释)
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);
写完之后,保存。
并在jsonp这个文件夹下执行:
node server.js
来启动服务,可以看到编辑器的控制台中会打印出"listen 8080…"
前端简单实现
后端已经实现了,现在让我们来看看前端最简单的一种实现方式,也就是写死一个script并发送请求:
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>
这两个script的意思是:
- 第一个,创建一个jsonpCallback函数。但是它还没有被调用
- 第二个,加载src中的资源,并等待请求的内容返回
整个过程就是:
当执行到第二个script的时候,由于请求了我们的8080端口,并且把id和cb这两个参数放到URL里。那么后台就可以拿到URL里的这两个参数。
也就是在后端代码中的const { id, cb } = ctx.query这里获取到了。
那么后端在拿到这两个参数之后,可能就会根据id来进行一些查询,当然,我这里只是模拟的查询,用了一个简单的find来进行一个查找。查找到id为1的那项并且取title。
第二个参数cb,拿到的就是"jsonpCallback"了,这里也就是告诉后端,前端那里是会有一个叫做jsonpCallback的函数来接收后端想要返回的数据,而后端你只需要在返回体中写入jsonpCallback()就可以了。
前端在得到了后端返回的内容jsonpCallback({“title”:“title1”}),发现里面是一段执行函数的语句,因此就会去执行第一个script中的jsonpCallback方法了,并且又是带了参数的,所以此时浏览器控制台会打印出{ title: ‘title1’ }
以此来达到一个简单的跨域的效果。
其实你想想,如果我们把第二个script标签换成以下代码,是不是也能达到同样的效果呢?
<!-- <script src='http://localhost:8080/api/jsonp?id=1&cb=jsonpCallback' type='text/javascript'></script> -->
<script type="text/javascript">jsonpCallback({ title: 'title1' })
</script>
jQuery中ajax实现
上面我们介绍了用script标签来实现,在jQuery的$.ajax()方法其实也提供了jsonp。
让我们一起来看看:
<script src="https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js"></script>
<script>$.ajax({url: "http://localhost:8080/api/jsonp",dataType: "jsonp",type: "get",data: {id: 1},jsonp: "cb",success: function (data) {console.log(data);}});
</script>
在success回调中同样可以拿到数据。
封装一个JSONP方法
(此章会一步一步教你如何封装一个比较完美的JSONP方法)
简易版
先看下我们要实现的功能
定义一个JSONP方法,它接收四个参数:
- url
- params
- callbackKey:与后台约定的回调函数是用哪个字段(如cb)
- callback:拿到数据之后执行的回调函数
<script>function JSONP({url,params = {},callbackKey = 'cb',callback}) {// 定义本地的一个callback的名称const callbackName = 'jsonpCallback';// 把这个名称加入到参数中: 'cb=jsonpCallback'params[callbackKey] = callbackName;// 把这个callback加入到window对象中,这样就能执行这个回调了window[callbackName] = callback;// 得到'id=1&cb=jsonpCallback'const paramString = Object.keys(params).map(key => {return `${key}=${params[key]}`}).join('&')// 创建 script 标签const script = document.createElement('script');script.setAttribute('src', `${url}?${paramString}`);document.body.appendChild(script);}JSONP({url: 'http://localhost:8080/api/jsonp',params: { id: 1 },callbackKey: 'cb',callback (res) {console.log(res)}})
</script>
这样写打开页面也可是可以看到效果的。
同时多个请求
上面我们虽然实现了JSONP,但有一个问题,那就是如果我同时多次调用JSONP:
JSONP({url: 'http://localhost:8080/api/jsonp',params: { id: 1 },callbackKey: 'cb',callback (res) {console.log(res) // No.1}
})
JSONP({url: 'http://localhost:8080/api/jsonp',params: { id: 2 },callbackKey: 'cb',callback (res) {console.log(res) // No.2}
})
可以看到这里我调用了两次JSONP,只是传递的参数不同。但是并不会按我们预期的在No.1和No.2中分别打印,而是都会在No.2中打印出结果。这是因为后面一个callback把JSONP里封装的第一个callback给覆盖了,它们都是共用的同一个callbackName,也就是jsonpCallback。如下所示:
两次结果都是从76行打印出来的。
所以我们得改造一下上面的JSONP方法:
- 让callbackName是一个唯一的,可以使用递增
- 不要把回调定义在window中这样会污染全局变量,可以把它扔到JSON.xxx中
来看看改造之后的代码:
<script>function JSONP({url,params = {},callbackKey = 'cb',callback}) {// 定义本地的唯一callbackId,若是没有的话则初始化为1JSONP.callbackId = JSONP.callbackId || 1;let callbackId = JSONP.callbackId;// 把要执行的回调加入到JSON对象中,避免污染windowJSONP.callbacks = JSONP.callbacks || [];JSONP.callbacks[callbackId] = callback;// 把这个名称加入到参数中: 'cb=JSONP.callbacks[1]'params[callbackKey] = `JSONP.callbacks[${callbackId}]`;// 得到'id=1&cb=JSONP.callbacks[1]'const paramString = Object.keys(params).map(key => {return `${key}=${params[key]}`}).join('&')// 创建 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)}})
</script>
可以看到现在调用了两次回调,但是会分别执行JSONP.callbacks[1]和JSONP.callbacks[2]:
最终版JSONP方法
其实上面已经算比较完美了,但是还会有一个小问题,比如下面这种情况:
我改一下后端的代码
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;}if (ctx.path === '/api/jsonps') {const { cb, a, b } = ctx.query;ctx.body = `${cb}(${JSON.stringify({ a, b })})`;return;}
})
console.log('listen 8080...')
app.listen(8080);
增加了一个/api/jsonps的接口。
然后前端代码增加了一个这样的请求:
JSONP({url: 'http://localhost:8080/api/jsonps',params: {a: '2&b=3',b: '4'},callbackKey: 'cb',callback (res) {console.log(res)}
})
可以看到,参数的a中也会有b这个字符串,这样就导致我们获取到的数据不对了:
后台并不知道a的参数是一个字符串,它只会按照&来截取参数。
所以为了解决这个问题,可以使用URI编码。
也就是使用:
encodeURIComponent('2&b=3')// 结果为
"2%26b%3D3"
只需要改一下JSONP方法中参数的生成:
// 得到'id=1&cb=JSONP.callbacks[1]'
const paramString = Object.keys(params).map(key => {return `${key}=${encodeURIComponent(params[key])}`
}).join('&')
来看一下完整版的JSONP方法:
<script>function JSONP({url,params = {},callbackKey = 'cb',callback}) {// 定义本地的唯一callbackId,若是没有的话则初始化为1JSONP.callbackId = JSONP.callbackId || 1;let callbackId = JSONP.callbackId;// 把要执行的回调加入到JSON对象中,避免污染windowJSONP.callbacks = JSONP.callbacks || [];JSONP.callbacks[callbackId] = callback;// 把这个名称加入到参数中: 'cb=JSONP.callbacks[1]'params[callbackKey] = `JSONP.callbacks[${callbackId}]`;// 得到'id=1&cb=JSONP.callbacks[1]'const paramString = Object.keys(params).map(key => {return `${key}=${encodeURIComponent(params[key])}`}).join('&')// 创建 script 标签const script = document.createElement('script');script.setAttribute('src', `${url}?${paramString}`);document.body.appendChild(script);// id自增,保证唯一JSONP.callbackId++;}JSONP({url: 'http://localhost:8080/api/jsonps',params: {a: '2&b=3',b: '4'},callbackKey: 'cb',callback (res) {console.log(res)}})JSONP({url: 'http://localhost:8080/api/jsonp',params: {id: 1},callbackKey: 'cb',callback (res) {console.log(res)}})
</script>
注意:
encodeURI和encodeURIComponent的区别:
- encodeURI()不会对本身属于URI的特殊字符进行编码,例如冒号、正斜杠、问号和井字号;
- 而encodeURIComponent()则会对它发现的任何非标准字符进行编码
例如:
var url = 'https://lindaidai.wang'encodeURI(url) // "https://lindaidai.wang"encodeURIComponent(url) // "https%3A%2F%2Flindaidai.wang"
另外,可以使用decodeURIComponent来解码。
decodeURIComponent("https%3A%2F%2Flindaidai.wang")
// 'https://lindaidai.wang'
JSONP原理及实现相关推荐
- Ajax跨域:Jsonp原理解析
推荐先看下这篇文章:JS跨域(ajax跨域.iframe跨域)解决方法及原理详解(jsonp) JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重 ...
- JQuery实现Ajax跨域访问--Jsonp原理
JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为"Same-Origin Policy"(同源策略). ...
- JSONP原理以及安全问题
JSONP介绍 JSONP全称是JSON with Padding ,是基于JSON格式的为解决跨域请求资源而产生的解决方案.他实现的基本原理是利用了 HTML 里 元素标签没有跨域限制 JSONP原 ...
- php json 原理,基于JSONP原理详解
本文主要为大家推荐一篇基于JSONP原理解析,具有很好的参考价值,希望对大家有所帮助.一起跟随小编过来看看吧,希望能帮助到大家. 前言 我工作以来接触的第一个项目就是前后端分离的,前端静态文件有自己独 ...
- 跨域解决方法——jsonp原理
跨域解决方法--jsonp原理 一个域名地址的组成: 当协议.子域名.主域名.端口号任意一个不相同时,都算作不同域,不同域之间相互请求资源,就算做"跨域".由于浏览器同源策略的限制 ...
- jsonp原理-node篇
jsonp-原理node篇 在学习kerwin老师讲的nodejs的课程其中的jsonp原理这节课,使我理解的更加透彻,加深了我对jsonp理解,所以我打算写一篇文章用来记录一下,jsonp的学习成果 ...
- jsonp原理和详解
在介绍JSONP之前,先简单的介绍一些JSON.JSON是JavaScript Object Notation的缩写,是一种轻量的.可读的基于文本的数据交换开放标准.源于JavsScript编程语言中 ...
- js跨域请求方式 ---- JSONP原理解析
这篇文章主要介绍了js跨域请求的5中解决方式的相关资料,需要的朋友可以参考下 跨域请求数据解决方案主要有如下解决方法: 1 2 3 4 5 JSONP方式 表单POST方式 服务器代理 Html5的X ...
- “约见”面试官系列之常见面试题之第六十七篇之jsonp原理和实现(建议收藏)
一. 同源策略 所有支持Javascript的浏览器都会使用同源策略这个安全策略.看看百度的解释: 同源策略,它是由Netscape提出的一个著名的安全策略. 现在所有支持JavaScript 的浏览 ...
- 彻底弄懂jsonp原理及实现方法
一. 同源策略 所有支持Javascript的浏览器都会使用同源策略这个安全策略.看看百度的解释: 同源策略,它是由Netscape提出的一个著名的安全策略. 现在所有支持JavaScript 的浏览 ...
最新文章
- Python3中的类和实例
- 小清新简约风个人简历PPT模板
- EMOS 1.5安装和配置
- Java高并发编程(五):Java中的锁Lock
- java 文件分隔_java 实现大文件分隔成多个小文件
- OnDraw与OnPaint有什么区别
- LeetCode 685. 冗余连接 II(并查集)
- linux上dig命令,Linux dig命令(示例代码)
- Android 系统(215)---Android O_GO后台启动服务改动
- flink其他可选api
- echarts柱状图的数据差距过大影响美观
- jQuery..1..基本使用..选择
- python脚本实现QQ自动发送消息
- 创业工场麦刚:不要把创业美化
- Hero image网站转化这么高?21个最佳案例给你参考
- 【计算机图形学】着色简介
- 基于html+css的音乐网站网页设计
- Linux进程信号(产生、保存、处理)/可重入函数概念/volatile理解/SIGCHLD信号
- 全国计算机一级考试理论部分,全国计算机等级考试一级理论题
- win10笔记本识别不到蓝牙鼠标的解决办法