由于浏览器接收同源策略的限制,网页无法通过Ajax请求非同源的接口数据,但是script标签不受浏览器同源策略的影响,可以通过src属性请求非同源js脚本。简而言之,JSONP的实现原理就是通过script标签的src属性,请求跨域的数据接口,并通过函数调用的形式,接收跨域接口响应回来的数据。本文将由浅到深,分析jsonp的实现原理和实现方法。
1.深度解析jsonp的实现原理
首先来看最简单的一段代码:
<script> window.jsonp = function(res) { console.log(res); } </script> <script> let par = { name: 'ningmengmaoyu', pass: 123456 }; jsonp(par); </script>
在这段代码中,第一个script里面定义了一个全局的名为jsonp的函数,在第二个script中调用了这个函数,控制台输出结果如下:
从上述结果可以看出,我们只要通过调用jsonp这个全局函数并传入指定参数,就能够在jsonp函数中获取到这个参数并输出。那么我们能否通过后端调用这个jsonp函数并将想要的数据作为参数传递给前端呢?答案是肯定的。我们再来看一段简单的代码:
// 前端 <script src="http://127.0.0.1:1314/test"></script> // 后端 router.get('/test', async ctx => { ctx.body = "console.log('后端传来的数据'+123456)"; })
这里以node后端为例,将“console.log(‘后端传来的数据’+123456)”响应给前端,但我们发现,前端竟然将这段内容以js代码执行了一遍:
看到这里,我们可以确定:通过script调用后端的请求所返回的字符串将会以js代码在前端执行。因此我们也就知道应该如何通过后端调用前端的jsonp函数了,只需要将函数的调用与参数以字符串的形式响应给前端,前端就会执行jsonp函数并拿到后端传来的数据:
// 前端 <script> window.jsonp = function(res) { console.log(res); } </script> <script src="http://127.0.0.1:1314/test"></script> // 后端 router.get('/test', async ctx => { ctx.body = "jsonp('我是后端传来的数据'+12345678)"; })
那么,我们前端要如何给后端传递查询参数、后端如何知道前端应该调用哪个函数呢?
// 前端 <script> window.jsonp = function(res) { console.log(res); } </script> <script src="http://127.0.0.1:1314/test?name=maoyu&pass=123456&callback=jsonp"></script> //后端 router.get('/test', async ctx => { let {callback,name,pass} = ctx.query; let par = JSON.stringify({name,pass}) ctx.body = `${callback}(${par})`; })
注意:
前端传递查询参数需要通过‘?’和‘&’拼接而成,且一定要将回调函数的名称一同传递给后端,后端将要传递给前端的数据作为回调函数的参数。一定别忘了使用JSON.stringify将数据转化为字符串。
到这里,基本上已经实现了jsonp的基本使用方法。但在实际的开发环境中,我们往往需要动态的去发起请求,而不是往html中去写script标签:
// 前端 <script> window.jsonp = function(res) { console.log(res); } </script> <script> let script = document.createElement('script'); script.src = 'http://127.0.0.1:1314/test?name=maoyu&pass=12345678&callback=jsonp'; document.body.append(script); </script> // 后端 router.get('/test', async ctx => { let {callback,name,pass} = ctx.query; let par = JSON.stringify({name,pass}) ctx.body = `${callback}(${par})`; })
2.封装jsonp函数
在一个项目中,往往需要在多处使用到jsonp方法,不可能每次使用都去声明一个回调函数。这里我们在前端对jsonp函数进行封装:
// 前端 <script> //传入请求的地址 回调函数的名称 function getJSONP(url, callback) { let script = document.createElement('script'); script.src = url + '&callback=' + callback; document.body.append(script); //声明一个名称为‘callback’的回调函数 window[callback] = function(res) { console.log(res); //请求完成之后删除script标签 document.body.removeChild(script) //销毁callback函数 window.callback = null; } } //使用时直接调用即可 getJSONP('http://127.0.0.1:1314/test?name=maoyu&pass=1234', 'jsonp'); </script>
3.使用ajax函数发起jsonp请求
ajax()是Jquery库提供的函数,可以直接发起jsonp数据请求:
//引入Jquery库 <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script> <script> $.ajax({ url: 'http://127.0.0.1:1314/test?name=maoyu&pass=123456789', dataType: 'jsonp', //jsonp请求类型 jsonpCallback: 'jsonp', //指定回调函数名称 success: function(res) { console.log(res); } }) </script>
4.总结jsonp的注意事项
- 1.ajax和jsonp本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加script标签来调用服务器提供的js脚本。
- 2.jsonp方案属于客户端直接请求,不存在二次请求的问题,但jsonp只能发送get请求,因为script只能发送get请求。
- 3.jsonp需要后台配合,此种请求方式应该前后端配合,将返回结果包装成callback(result)的形式。
- 4.在`${callback}(${par})`中,callback是回调函数名,需要前端传来,且前端有定义这个函数。par则为后端向前端传递的参数。
。