浏览器只对网络请求有同源限制,同源就是协议、域名和端口号一致,不同源的客户端脚本在没有明确授权的情况下,不能读写对方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
3
import 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
this.$http.jsonp(
"https://api.douban.com//v2/movie/in_theaters",
{ jsonp: "callback", jsonpCallback: "success_jsonpCallback" },
{
async: false,
headers: { "Content-Type": "application/json; charset=utf-8" },
emulateJSON: true
}
)
.then(response => {
this.biglist=response.body.subjects;
})
.catch(response => {
console.log("123" + response);
});

可以看到jsonp请求是有个回掉函数的,不设置回掉函数则会自动生成一个,写不写取决于api接口

Jsonp原理

同源情况下,有时候会自己做简单的api,最简单的话,就是自己写一个json文件,里面代码即是一个json数据对象,项目js再通过ajax请求这个文件。

我在学习了jsonp的时候发现它的原理跟上面的方式是差不多了,这次不用json文件,而是直接用js文件,把json数据存放到一个函数的调用里当作参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun({
"result":[
{
"name":"小明",
"age":"16",
"sex":"男"
},
{
"name":"小红",
"age":"17",
"sex":"女"
},
{
"name":"小可",
"age":"18",
"sex":"男"
},
.....
]
})

在项目js脚本中:

1
2
3
function fun(obj){
console.log(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-routervuexvue-cli。在看看axios,哪来的野孩子!

后来偶然间接触到了axios才知道,axiosVUE家庭的正牌成员,不仅这样,vue2.0之后,就不再对vue-resource更新,而是推荐使用axiosaxios可以自动转换 JSON 数据。

axios基于 Promise 的 HTTP 请求客户端,可同时在浏览器和 Node.js 中使用。

使用

首先依然是npm install axios

axios也可以用引入

一般在vue项目的入口文件写入:

1
2
import Vue from 'vue'
import axios from 'axios'

一般用法:

1
2
3
4
5
6
7
axios.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
2
3
4
5
6
7
'/api': {         //名字自取,可以不同,但要跟后面得接口名一致
target: "https://api.douban.com/v2",
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}

step2

在入口文件main.js 配置如下:

1
2
3
4
5
6
import Vue from 'vue'
import Axios from 'axios'

Vue.prototype.$axios = Axios
Axios.defaults.baseURL = '/api' //base url /api的前缀
Axios.defaults.headers.post['Content-Type'] = 'application/json';

step3

在对应得方法里请求数据:

1
2
3
4
5
6
7
this.$axios.get("/movie/top250")    //设置了 接口前缀,不需要写前缀了
.then(function(res){
console.log(res)
})
.catch(function(err){
console.log(err)
})

然后就可以看到数据请求成功了

可以对比一下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
2
3
4
5
6
7
this.$axios.get("/movie/top250")
.then(function(response){
console.log(this); //这里 this = undefined
})
.catch(function(error){
console.log(error);
});

之前在用JQuery的$.ajax时候也遇到过这个问题,可预先先用一个变量保存当前的this。

1
2
3
4
5
6
7
8
var _this= this;
this.$axios.get("/movie/top250")
.then(function(response){
console.log(_this); //这里 _this 指向vue
})
.catch(function(error){
console.log(error);
});

还有一种解决方法是:

1
2
3
4
5
6
7
this.$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
2
3
4
5
6
7
this.$axios.get("/movie/top250")
.then(res=>{
console.log(this) //这里 this 指向vue
})
.catch(err=>{
console.log(err)
})

两者相比

跨域的解决方法:

  • 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的前途之大。