浏览器只对网络请求有同源限制,同源就是协议、域名和端口号一致,不同源的客户端脚本在没有明确授权的情况下,不能读写对方XHR资源,反之不同源脚本读取对方XHR资源就是跨域。本笔记通过vue项目中请求豆瓣api来说说我与跨域那些事。
Vue-Resource
vue-resource是Vue.js的一款插件,他在vue家庭的中充当ajax请求(异步请求)的功能(在我遇到axios之前认为vue-resource就是vue家庭唯一的请求使)。它可以通过XMLHttpRequest或JSONP发起请求并处理响应。
不用vue做项目时,大家更可能熟知的或许是$.ajax,JQuery的异步请求方法,方便,兼容性好;而一般$.ajax能做的事情,vue-resource一样也能做到,不仅如此,vue-resource体积更加小巧,其api更加简洁,兼容性也不错。
使用
先npm install Vue-Resource
然后是引入方法,一种是直接通过方式引入,不多讲。
另一种是1
2
3import Vue from 'vue'
import VueResource from 'vue-resource'
Vue.use(VueResource)
接着就可以在相应的位置使用了1
2
3
4
5
6// 写法
this.$http.get('/someUrl', [options]).then(function(response){
// 响应成功回调
}, function(response){
// 响应错误回调
});
跨域
Vue-Resource的跨域请求跟$.ajax类似,JSONP请求
jsonp是一种只支持GET,不支持POST请求,不安全XSS的跨域解决方案
我在vue项目中请求豆瓣api的用法是这样的:
1 | this.$http.jsonp( |
可以看到jsonp请求是有个回掉函数的,不设置回掉函数则会自动生成一个,写不写取决于api接口
Jsonp原理
同源情况下,有时候会自己做简单的api,最简单的话,就是自己写一个json文件,里面代码即是一个json数据对象,项目js再通过ajax请求这个文件。
我在学习了jsonp的时候发现它的原理跟上面的方式是差不多了,这次不用json文件,而是直接用js文件,把json数据存放到一个函数的调用里当作参数:
1 | fun({ |
在项目js脚本中:
1 | function fun(obj){ |
前后两个函数名必须一致,该函数即为后来的jsonp回掉函数
这里有些和一般套路不一样,本来一般是外wenj脚本里写函数,项目js脚本里调用该外js脚本里的函数,可上面原理中走的却是不寻常的路,在外文件(之所以不说js文件,是因为该文件类型并不重要,重要在内容)里调用本js脚本里的函数,却都能实现函数作用,甚至后者的实参还能充当数据源,更出乎意料,却又在情理之中的是script标签不仅能引入本地文件,还能引入外部文件,所以只要引入的文件符合当前函数的调用(把数据对象当作实参),,内部js脚本即能获取该数据,当引入外部文件的时候,不就是跨域了么,这就演变了后来的jsonp;
jsonp请求流程:定义函数——>创建script标签——>设置script标签的src属性——>获取数据,回掉函数
然而使用Jsonp,要求接口上数据是jsonp格式的,如果api提供的不是jsonp,哪怕是json,也不能通过jsonp方式跨域,这该怎么办呢?
- “后端兄,会偷数据不?/滑稽”
- “前端老弟,对方有服务器限制吗?/滑稽”
……
博主当时的后端技术还没写过返回json数据的接口,其中的问题一时没有办法解决,好歹豆瓣api还是比较友好的,只是一般的免费api都有的共性——限制次数。
还有个好之处,Vue-Resource的jsonp的请求方式不管开发环境还是在生产环境下都能使用,而且不用修改任何其他配置。这跟下面要提到的老兄是非常有联系的
axios
之前做vue项目时,一直都在用Vue-Resource来进行ajax请求,偶尔也听说过axios,但一直觉得axios是一个其他不相关的插件,一直没深入了解,有这样的想法,可能是因为Vue-Resource有一个vue-头衔,更像是VUE家庭的一员,像其他成员一样:vue-router、vuex、vue-cli。在看看axios,哪来的野孩子!
后来偶然间接触到了axios才知道,axios是VUE家庭的正牌成员,不仅这样,vue2.0之后,就不再对vue-resource更新,而是推荐使用axios。axios可以自动转换 JSON 数据。
axios基于 Promise 的 HTTP 请求客户端,可同时在浏览器和 Node.js 中使用。
使用
首先依然是npm install axios
axios也可以用引入
一般在vue项目的入口文件写入:1
2import Vue from 'vue'
import axios from 'axios'
一般用法:1
2
3
4
5
6
7axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
跨域
axios没有像$.ajax和Vue-Resource那样有直接的jsonp方法,其中的用法要比Vue-Resource麻烦一些
同样以豆瓣api为例
step1
首先得修改一下配置
修改config文件夹下的index.js文件,在proxyTable中加上如下代码:
1 | '/api': { //名字自取,可以不同,但要跟后面得接口名一致 |
step2
在入口文件main.js 配置如下:
1 | import Vue from 'vue' |
step3
在对应得方法里请求数据:
1 | this.$axios.get("/movie/top250") //设置了 接口前缀,不需要写前缀了 |
然后就可以看到数据请求成功了
可以对比一下Vue-Resource请求得结果 对比一下不同之处。
可能不是非常理解axios得跨域处理,这里我引用一位网友的理解:
因为我们给url加上了前缀/api,我们访问/movie/top250就当于访问了:localhost:8080/api/movie/top250(其中localhost:8080是默认的IP和端口)。
在index.js中的proxyTable中拦截了/api,并把/api及其前面的所有替换成了target中的内容,因此实际访问Url是http://api.douban.com/v2/movie/top250。
this指向
axios的请求的方法的回掉函数里是无法识别this的,即
1 | this.$axios.get("/movie/top250") |
之前在用JQuery的$.ajax时候也遇到过这个问题,可预先先用一个变量保存当前的this。
1 | var _this= this; |
还有一种解决方法是:1
2
3
4
5
6
7this.$axios.get("/movie/top250")
.then(function(response){
console.log(this); //这里 this 指向vue
}.bind(this)) //bind(this)来改变匿名函数的this指向
.catch(function(error){
console.log(error);
});
更好的方法是ES6的箭头函数
ES6中的 箭头函数 “=>” 内部的this是词法作用域,由上下文确定(也就是由外层调用者vue来确定)
1 | this.$axios.get("/movie/top250") |
两者相比
跨域的解决方法:
- jsonp:只支持GET,不支持POST请求,不安全XSS
- postMessage:配合使用iframe,需要兼容IE6、7、8、9
- document.domain:仅限于同一域名下的子域
- cors:需要后台配合进行相关的设置
- websocket:需要后台配合修改协议,不兼容,需要使用socket.io
- proxy:使用代理去避开跨域请求,需要修改nginx、apache等的配置
综上的个人感觉,还是vue-resource不管是在用法上,还是环境问题上都要好的多,用法不要过多配置,开发环境能正常跨域请求到api数据,生产环境也能正常跨域请求api数据。
axios用法上需要加一些配置,虽然在开发环境下能正常跨域请求到api数据,可是在生产环境下,跨域请求的地址是不正常的,即使是修改了生产环境下的api接口,能使请求地址正常,也无法实现跨域,这部分实现涉及到拦截器技术。
博主对拦截器接触的不多,就不多1313了
可惜的是vue2.0之后,就不再对vue-resource更新,而是推荐使用axios,
个人理解是:
之前提到的jsonp:只支持GET,不支持POST请求,不安全XSS;而axios弥补了这个缺陷,不仅支持GET,也支持POST。
不仅如此,axios在一些复杂的项目中能够发挥的作用更加强大。目前的开发方式,前后端分离开发越来越流行,Node.js的用处越来越大,而axios能够配合node.js,在 node.js 中发送 http请求,足以见得axios的前途之大。