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