由于浏览器接收同源策略的限制,网页无法通过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则为后端向前端传递的参数。
。








