JSONP原理总结

329 阅读5分钟

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)
    }
})

介绍一下上面代码的逻辑

  1. 首先定义一个$对象,这个对象有个ajax属性,是个函数
  2. 调用$.ajax函数时,会先读取传入的对象,取出url,type,dataType等属性
  3. 定义一个targetProtocol和targetHost变量,之后用来判断是否是同源
  4. 判断url是否为http://或者https://开头的,如果是,取出目标域的协议,域名,端口号,注意,域名和端口号都在targetHost里面,如果是根路径,就是同源
  5. 判断dataType是否为jsonp,如果不是,或者如果是jsonp,但是为同源,直接按常规的ajax请求处理即可
  6. 如果是jsonp,且不是同源,则生成一个script标签,接下来生成一个随机的callback,并定义这个callback函数,函数体就是调用$.ajax时传入的success函数,并将url和callback拼接到script标签的src属性上,详见上面代码,当然,你可以和后端商量,让后端配合返回给你满足你需求的代码
  7. 将这个script标签添加到document.head中
  8. 由于后台做了特殊处理,返回的数据刚好是调用我们的这个随机callback函数的代码,比如这儿返回的代码是:
cb125623({status:1,message:"success"})

而我们的随机callback函数的函数体正好是调用$.ajax时传入的success函数,所以我们定义的success函数会被调用,而我们的success函数又是我们自己定义的函数,可以写我们自己的业务逻辑

这儿提一个疑问,数据格式即dataType为jsonp时,是不是只能使用get方式请求?

答疑:

  1. 得看是不是同源,如果是同源,其实还是正常的ajax请求,即get方式还是get,post方式还是post
  2. 如果不是同源,其实是通过script标签的src属性来发送请求的,而script标签只能发送get请求,所以不管是get请求,还是post请求,都只会发送get请求

以上内容是在学习JSONP时的一些笔记整理,你有什么想法欢迎一起讨论