JSONP使用场景
由于浏览器同源策略的影响,当我们向服务器请求不同源的资源时,会在浏览器接收到数据后,进行拦截,不让我们进一步处理。
同源策略的介绍
既然提到了同源策略,那么简单介绍一下,什么是同源策略,我们平时请求网页,地址栏通常都是http://xxx:80/... 格式的,就以百度官网的地址为例:www.baidu.com 这个地址分为三个部分:
- http://或https:// 这表示请求的网络协议,简称协议
- www.baidu.com 这部分表示域名
- :80 这个表示端口号,这儿没有端口号,一般端口号默认为80,比如我们平时本地启的服务localhost:8080这儿的端口号就是8080
只有协议,域名,端口号都完全一致,才符合同源策略,否则就是不同源策略,不同源策略的情况下,我们的ajax请求就会收到影响,但是影响只是在接收到服务器返回的数据后,浏览器进行判断处理的,换句话说就是:
1.我们的请求可以正常发送给服务器
2.服务器可以正常接收到我们的请求
3.服务器可以正常处理我们的请求
4.服务器可以正常返回数据
5.我们的浏览器可以正常接收到后端返回的数据
当我们的浏览器正常接收到数据后,会判断是不是同源,如果不是,就会进行拦截,阻止我们后续操作 ,这就是我们常说的跨域现象
解决跨域问题的方法
后端工程师配合的情况下
- 需要后端工程师配置 Access-Control-Allow-Origin 属性,配置好了,我们就可以正常跨域访问了
- JSONP 需要后端工程师配合处理,接下来我们会重点介绍
后端工程师不配合的情况
- iframe(只能显示,不能控制)
- 需要我们配置服务器代理,webpack中有个proxy属性,可以配置代理,这里就不多做介绍了
前面介绍了JSONP的使用场景,接下来就重点介绍一下JSONP
JSONP的原理
我们平时向服务器发起请求会有以下几种方式:
- 浏览器地址栏里直接敲服务器的地址,这种方式不可控,没法用代码控制
- 使用BOM里的location.href = "url",这会打开一个页面,也没法用代码继续控制
- 使用带有src属性的标签,比如img,video,audio...
- 使用带有href属性的标签,这个属性一般返回的是样式,所以这儿也不会用
- 使用带有action属性的标签,比如form表单,但是form表单会打开一个新页面
- ajax,可以用代码控制,但是受到同源策略的影响
综上分析,似乎只有带src属性的标签有可能,但是带src属性的标签又有很多,比如img,video,audio,iframe,script...
img,video,audio分别是用来接收图片、视频、音频的,没法用代码控制,iframe只能用来展示,也没法用代码来控制,似乎只有script标签有可能性了,script标签的src属性就是用来接收script代码,并执行的,而且不受同源策略的影响
而JSONP的原理,恰恰就是使用script的这个特性来实现的,利用script不受同源策略的影响,它的src属性可以向服务端发起请求,由服务端配合处理,返回js代码,浏览器接收到js代码后,会立即执行返回的js代码
接下来我们用代码来演示一下
var $ = {
ajax: function(options) {
// 取出传入的配置对象
var url = options.url;
var type = options.type;
var dataType = options.dataType
// 判断是否为同源(协议,域名,端口号)
var targetProtocol = ""
var targetHost = ""
// 如果url不是一http://或者https://开头的,而是有根路径开头的肯定是同源
if (url.indexOf('http://') == 0 || url.indexOf('https://') == 0) {
var target = new URL(url);
// 域名和端口号都在targetHost里
targetProtocol = target.protocol;
targetHost = target.host;
} else {
targetProtocol = location.protocol;
targetHost = location.host;
}
// 首先判断是否为jsonp,如果不是jsonp,不用做其他判断,直接发送ajax就可以了
if (dataType == 'jsonp') {
// 判断是不是同源
if (location.protocol == targetProtocol && location.host == targetHost) {
// 这种情况是同源,同源这个不做处理
} else {
// 不是同源做处理
var script = document.createElement('script');
// 动态生成一个callback,并创建callback函数
var callback = 'cb' + Math.floor(Math.random() * 1000000)
// callback函数的函数体是我们调用时传入的success函数
window[callback] = options.success
// 拼接script标签的src属性
if (url.indexOf('?') > 0) {
script.src = url + "&callback=" + callback
} else {
script.src = url + "?callback=" + callback
}
script.id = callback
document.head.appendChild(script)
}
}
}
}
$.ajax({
url: 'https://developer.duyiedu.com/edu/testJsonp',
type: 'get',
dataType: 'jsonp',
success: function(data){
// 这里可以写业务逻辑,我们这儿只打印一下data
console.log(data)
}
})
介绍一下上面代码的逻辑
- 首先定义一个$对象,这个对象有个ajax属性,是个函数
- 调用$.ajax函数时,会先读取传入的对象,取出url,type,dataType等属性
- 定义一个targetProtocol和targetHost变量,之后用来判断是否是同源
- 判断url是否为http://或者https://开头的,如果是,取出目标域的协议,域名,端口号,注意,域名和端口号都在targetHost里面,如果是根路径,就是同源
- 判断dataType是否为jsonp,如果不是,或者如果是jsonp,但是为同源,直接按常规的ajax请求处理即可
- 如果是jsonp,且不是同源,则生成一个script标签,接下来生成一个随机的callback,并定义这个callback函数,函数体就是调用$.ajax时传入的success函数,并将url和callback拼接到script标签的src属性上,详见上面代码,当然,你可以和后端商量,让后端配合返回给你满足你需求的代码
- 将这个script标签添加到document.head中
- 由于后台做了特殊处理,返回的数据刚好是调用我们的这个随机callback函数的代码,比如这儿返回的代码是:
cb125623({status:1,message:"success"})
而我们的随机callback函数的函数体正好是调用$.ajax时传入的success函数,所以我们定义的success函数会被调用,而我们的success函数又是我们自己定义的函数,可以写我们自己的业务逻辑
这儿提一个疑问,数据格式即dataType为jsonp时,是不是只能使用get方式请求?
答疑:
- 得看是不是同源,如果是同源,其实还是正常的ajax请求,即get方式还是get,post方式还是post
- 如果不是同源,其实是通过script标签的src属性来发送请求的,而script标签只能发送get请求,所以不管是get请求,还是post请求,都只会发送get请求
以上内容是在学习JSONP时的一些笔记整理,你有什么想法欢迎一起讨论