1. 同源
1.1 什么是同源
协议、域名、端口号相同
1.2 为什么有同源政策
同源政策是为了保护用户信息的安全,放置恶意的网站窃取数据。最初的同源政策是指A网站再客户端设置的Cookie,B网站是不能访问的.
随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送Ajax请求,如果请求,浏览器就会报错
2. jsonp
- jsonp是json with padding(将JSON数据作为填充内容)的缩写,它不属于Ajax请求,但它可以模拟Ajax请求。
- 核心:利用了script标签可以向非同源端的服务器发送请求的特性,加载完毕后,相当于把服务器返回的内容当作JavaScript代码执行
2.1 准备工作
- 准备2个服务器,一个监听3000端口(作为客户端),一个监听3001端口(作为服务端)
- 两个服务器都暴露静态资源,3000端口服务器代码如下(3001端口类似):
const express = require('express')
const path = require('path')
const app = express()app.use(express.static(path.join(__dirname, 'public')))app.listen(3000, () => {console.log('http://localhost:3000')
})
2.2 JSONP的实现
1.在3001端口服务器中添加如下代码:
app.get('/jsonp',(req,res)=>{const data = `fn({name:'Marron', age:18, remarks:'JSONP测试'})`;res.send(data)
})
2.先定义一个全局函数,在使用script标签加载S2服务器的资源
<script>// 假设服务器返回的数据存储在data中function fn(data) {console.log(data);}
</script>
<!-- 使用jsonp加载资源 -->
<script src="http://localhost:3001/jsonp"></script>
2.3 JSONP代码优化
2.3.1 动态创建script标签发送jsonp请求
- 实际项目中,不可能将script标签静态的写死…因此需要动态的创建script标签,用script标签的src的跨浏览器性,来发送请求.
1.客户端将函数名称传递到服务器端
<script src="http://localhost:3001/jsonp?cb=fn"></script>
2.将script请求的发送变成动态请求
<script>var srcipt = document.creatElement('script');script.src="http://localhost:3001/jsonp?cb=fn"document.body.appendChild(script);
</script>
3.发送请求后,删除掉script标签
<script>var srcript = document.createElement('script');script.src="http://localhost:3001/jsonp?cb=fn";document.body.appendChild(script);script.onload = function (){document.body.removeChild(script)}
</script>
4.封装jsonp
function jsonp(url) {var script = document.createElement('script')script.src = urldocument.body.appendChild(script)script.onload = function() {document.body.removeChild(script)}
}
2.3.2 将回调函数封装进的JSONP
由于上面封装的JSONP它调用的函数和JSONP函数是分离的,破坏了JSONP的封装性,下面尝试将JSONP的回调函数封装如JSONP中,请求参数如下:
jsonp({url: 'http://localhost:3001/better?cb=fn',success: function(){}
})
产生了如下2个问题:
- 回调函数不再是一个全局变量,即当使用script标签加载完毕只会,找不到回调函数
- success是一个匿名函数,传递到后端是没有名称(如 fn)传递
// 将回调函数挂在到全局对象上
function jsonp(options) {var script = document.createElement('script');window.fn = options.success;script.src = options.url + '?cb=fn';document.body.appendChild(script)script.onload = function(){document.body.removeChild(script);window.fn = null;}
}
以上代码,当服务器端的返回的数据很慢时,如果连续发送多个不同的JSONP请求时,会导致多个请求的回调函数是同一个.
解决办法是,每次调用回调函数时,名字不能相同(即随机产生一个名字)
// 每次随机产生一个名字
function jsonp(options) {var script = document.createElement('script')var fn = 'fn'+ Math.random().toString().replace('.','')window[fn] = options.successscript.src = options.url + '?cb=' + fndocument.body.appendChild(script)script.onload = function() {document.body.removeChild(script)// 用完后释放内存delete window[fn]}
}
2.3.3 将get请求参数封装进jsonp
function jsonp(options) {var script = document.createElement('script')var fn ='fn' +Math.random().toString().replace('.', '')window[fn] = options.successvar params = []params.push('callback=' + fn)for (let attr in options.data) {params.push(attr + '=' + options.data[attr])}params = params.join('&')script.src = options.url + '?' + paramsdocument.body.appendChild(script)script.onload = function() {document.body.removeChild(script)delete window[fn]}
}
3. json实战
- 使用上面封装好的jsonp去获取腾讯天气的代码
3.1 寻找api
- 浏览器打开腾讯天气
- 按F12打开调试工具,选择NetWork -> JS
- 可以看到此处域名(tianqi.qq.com)使用到了域名(wis.qq.com)的数据,产生了跨域,并且它传过去的参数含有callback字段.可以假设使用了JSONP技术进行跨域获取数据
3.2 使用封装的JSONP函数获取数据.
- 在3000端口的public目录下新建
01.html
,贴上如下代码.
<script>
jsonp({url: 'http://wis.qq.com/weather/common',data: {source: 'pc',weather_type: 'forecast_1h',province: '湖北省',city: '武汉市'},success: function(data) {if (data.status == 200 && data.message) {console.log(data);}}
})
</script>
- 打开浏览器,输入
localhost:3000/01.html
.
访问成功…嘻嘻.
4. 参考
源代码