最近需要读取本地json文件,找到了原生js方式和ajax方式,都会报跨域的问题。于是研究了下什么是跨域,为什么会跨域,以及JSONP解决方案的运用。
一、我是怎么遇到跨域问题的?
因为要读取本地json文件(test.json),分别使用了原生js方式和ajax方式(代码如下)。
// 原生方式 - google会报跨域
window.onload = function () {
var url = "./test.json";
var request = new XMLHttpRequest();
request.open("get", url); // 设置请求方法与路径
request.send(null);// 读取本地,就不发送数据到服务器
request.onload = function () { // XHR对象获取到返回信息后执行
if (request.status == 200) { // 返回状态为200,即为数据获取成功
var json = JSON.parse(request.responseText);
console.log(json);
}
}
}
// ajax请求json文件 - google会报跨域
// 要引入jq库文件
$.ajax({
url: "./test.json",// json文件位置
type: "GET",
dataType: "json",
success: function(data) {
console.log(name);
}
})
用谷歌浏览器和IE浏览器打开,都获取不到json数据,控制台报错:
谷歌浏览器访问本地文件的跨域报错提示
二、什么是跨域?
1. 首先,什么是域?
协议、域名、端口这三者相同,视为同一个域。
所以,只要协议,域名,端口有一个不同,就是跨域。
2. 为什么浏览器会报跨域的错?
这是因为浏览器有一个安全机制,叫做 同源策略(CROS),不同域的客户端脚本在无明确授权的情况下,是不能读取对方资源的。它保证了一个域的脚本只能读写本域内的资源,而无法访问其他域的资源。
所以,可以说跨域就是不同源。
但需要注意,并不是所有浏览器都用同源策略,比如火狐浏览器,就允许跨域。而且,即使是谷歌浏览器,也可以通过浏览器设置项改成允许跨域。
3. 本地html页面读取本地json文件是跨域?
按照上面我们分析的跨域场景是:协议,域名,端口有一个不同。
但看起来本地页面html的地址,和本地json文件的地址是在同一个域的感觉:
file:///Z:/celine/test/jsonp/demo.html
file:///Z:/celine/test/jsonp/test.json
而实际上,让我们仔细看看谷歌浏览器的报错内容,就可以发现,跨源请求仅支持协议方案。 :http, data, chrome, chrome-extension, https.
,不支持file
协议。
(谷歌报错内容:Access to XMLHttpRequest at 'file:///Z:/celine/test/jsonp/test.json' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
)
三、怎么解决跨域问题?
解决跨域目前有以下几种:
- 纯前端方式:采用JSONP;
- 后端配合方式:使用nginx反向代理;
- 小白选手方式:修改谷歌浏览器的配置;
- 本地调试方式:本地搭建一个服务。
针对这一次案例:本地脚本读取本地json文件。其实只要案例项目放到服务器中,避免掉使用file:///
协议访问页面,就不是跨域了。
如果一定要在本地打开,要么是方案4-本地起一个服务;要么就是方案1-jsonp。
此处我们着重看下怎么使用jsonp解决跨域。
四、JSONP方式解决跨域问题
1. 什么是jsonp?
JSONP是一种非正式传输协议,目的就是便于客户端使用数据。它的具体概念和优点,请查看参考文章3.
要注意区分json
和 jsonp
两个概念:
- json : 是一种数据格式。
- jsonp: 是一种数据调用方式。
JSONP方式具有一定的局限性:
- 仅适用于GET请求;
- 读取本地json文件的话,json文件里的数据要包含在一个函数名里(这个往后看就知道是什么意思了)。
1. 实现原理
我们知道,<script>
标签是不受同源策略的限制的,它可以载入任意地方的 JavaScript 文件,而并不要求同源(回忆下,我们可以通过<script>
载入官方服务器(或者cdn)上的一些库文件,比如jq库文件或者boostrap库文件)。
所以,我们可以利用<script>
标签的这个特点,用它来载入json文件。
载入json文件后,我们还需要获取到文件里面的json数据,这时候我们可以借用函数调用方式,把json数据作为函数实参,从而在js代码中取到数据。
2. 实现步骤
step1: 定义一个函数getJson()
,这个函数将会在.json
文件里被调用,得到json数据。可以在函数内部对json数据处理。
step2: 通过<script>
标签引入test.json
文件。
step3: test.json
文件中,要把json数据作为实参放在函数getJson()
中,即调用函数。
<!-- demo.html -->
<script type="text/javascript">
// 1. 定义接收数据的函数
function getJson(data){
// data 就是要取的json数据
console.log(data);
alert(data.name);
// 可以在函数内部对json数据进行处理
}
</script>
<!-- 2. 引入json文件 -->
<script type="text/javascript" src="test.json"></script>
// test.json
// 3. 真正json数据需要放在函数getJson()里,相当于作为调用函数getJson的实参。
getJson({
"name": "celine",
"nickName": "bling",
"likes": [
{
"name": "Alex",
"age": "18"
},
{
"name": "Didi",
"age": "9"
}
]
})
网络上有些提到在引入json文件时候,需要在链接后面加上回调函数,如下:
<script type="text/javascript" src="test.json?callback=getJson">
这种一般是向服务器请求json文件时,允许客户端传递一个callback参数(此处就是getJson
)给服务端,然后服务端返回数据时会将这个callback参数(即getJson
)作为函数名来包裹住JSON数据,这样客户端就可以随意定义自己的函数来处理返回数据了。——这也是JSONP协议的要点。
也就是说,json文件不需要手动去给它包裹上一个函数名了(即不需要上面的step3,这个步骤由后端返回数据时完成)。
五、结语
以上就是一个jsonp方式解决跨域问题的方案。
但这种需要去修改json文件里面的原始数据结构,其实也不是很好。
希望有更好方案,小伙伴可以积极提供。
思考:vue-cli项目中,因为有一个本地服务器概念,如果使用axios
去请求json文件,不知道是不是就没有跨域问题了。有待尝试!
甚至有可能直接用import
或者require
就可以获取到呢?
参考文章:
-
谷歌通过ajax获取本地JSON文件,为什么会提示跨域?
杂糅了一大堆的资料,后面的例子反而不是和清晰了。 -
解决ajax不能访问本地文件(利用js跨域原理)
通过截图,很清晰说明了jsonp应用过程。 -
jsonp 读取本地文件
这篇的主要贡献是:介绍了jsonp的概念、提出jsonp的有点,与ajax的区别。很建议一读!
网友评论