JSONP方法解决跨域问题

吐槽君 分类:javascript

什么是同源策略

源:协议、域名、端口号。
在数据响应回来时浏览器会检测请求数据的源与发送请求的HTML页面的源是否一致。
维基百科的解释如下图,详细讲解可参阅维基百科-同源策略

1.png
需要注意的一点是,直接在浏览器中打开访问链接,相当于就是直接访问,是能看到数据。但是,当从一个页面发送请求访问时,会判断是不是跨域,如果是跨域,请求其实也是发送出去了的,服务器也响应了,但是响应回来后被浏览器拦截了,因为浏览器的安全策略中有个同源策略。

JSONP(JSON+Padding)方法及其原理

JSONP方法:利用script标签不受同源策略限制的特性,在HTML页面中动态生成script标签,利用script标签的src属性访问服务器端,服务器端会返回一个函数的调用(fuName(返回的数据)),并将响应数据放在回调函数的参数里,HTML页面通过回调函数的参数获取响应数据。
以下提供两个我看过的讲解jsonp的视频:
YouTube-跨域讲解了jsonp方法在HTML页面以及服务器端的数据交互,
YouTube-jsonp讲解了jsonp方法访问某外部接口时的函数调用。
注:img、link和script标签不受同源策略限制,允许跨域加载。
我的讲述若有不明确的地方,也可参考博客,下图就是该博客的部分内容截图。

3.png

代码实现

jsonp方法代码实现

以城市接口wis.qq.com/city/like 为例,该接口所需参数见下图,该图来源博客

2.png
第一步,要直接访问一次接口来查看回调函数名是什么;
这个接口是在腾讯天气的地址搜索栏里调用的,所以我们在搜索框中调用一次该接口,查看网络中的请求头信息,发现回调函数名称是callback,如下图:

4.png
我们就可以确定访问的地址应是:wis.qq.com/city/like?s…
第二步,此次调用以香港为例。

function getData(data) {
        let script = document.getElementById("jsonp")
        script.parentNode.removeChild(script)  //移除该新创的节点
        console.log(data)
    }

    // 创建script节点
    let script = document.createElement("script")
    script.id = "jsonp"
    script.src = "https://wis.qq.com/city/like?source=pc&city=香港&callback=getData"
    // 将script节点添加到DOM树中,否则该节点不会生效
    document.body.appendChild(script)
 

如下图,便是此次调用接口的请求头和响应数据。

5.png

6.png

jsonp函数封装

按照上面的办法,每次调用jsonp都要重复上述代码,很不方便,所以我们可以进一步地封装该方法。
按照上述思路,我们一共需要四个参数,访问url、访问参数、接口定义的回调函数参数名、回调函数参数体。
(注:不同接口的回调函数参数名不同,我见到的大部分接口回调函数参数名都是callback,也有例外,如百度网搜索框的回调函数参数名是cb,如下图
7.png
封装后的代码如下:

/*
    url: 访问链接
    params: 参数名/参数值组成的json格式的数据
    cbName: 访问接口时规定的回调函数参数名
    cbFunction: 回调函数具体函数体
     */
    function jsonp(url, params, cbName, cbFunction) {
        let strParams = ""
        // 遍历json格式的参数,组合为符合格式的字符串
        for (let key in params) {
            // 经过最后一次遍历时最后一个参数末尾后会多一个&,后接回调函数参数
            strParams += key + "=" + params[key] + "&"
        }
        // Math.random(): 返回 0 ~ 1 之间的随机数,降低同时调用多个jsonp 请求时回调函数名称的重复率
        let fnName = cbName + Math.random().toString().replace('.', '')
        // 创建script节点
        let script = document.createElement("script")
        script.src = url + "?" + strParams + cbName + "=" + fnName
        // 将script节点添加到DOM树中,否则该节点不会生效
        document.body.appendChild(script)
        // 将回调函数加入全局作用域
        window[fnName] = cbFunction
        // 当这个 script 标签加载完毕时将其移出
        script.onload = () => script.parentNode.removeChild(script)
    }
 

调用方式如下:

jsonp("https://wis.qq.com/city/like", {
            "source": "pc",
            "city": "香港",
        }, "callback",
        (data) => console.log(data))
 

需注意的问题

  1. 回调函数一定要放在全局作用域,因为服务端接收到回调函数后会返回页面中的script中去找,如果不写在全局作用域中根本找不到;
  2. 不同接口的回调函数参数名不同,封装函数的话这个地方需要单独作为参数。

回复

我来回复
  • 暂无回复内容