引言

前端是一个快速发展的领域,而在前端的技术栈当中,前端请求又是最见的一个领域,通过请求接口数据,才能将一个静态的页面动态化。本文将以前端发展的时间轴来逐一分析前端请求的技术演变及其优劣,针对这一课题,作者查阅了相关资料加以自己的理解,如有错误,烦请指出。

一、XMLHttpRequest

AJAX:Asynchronous JavaScript and XML,意思是用JavaScript执行异步网络请求。

现代浏览器上,ajax的实现主要依靠 XMLHttpRequest 对象,该对象用于和服务器交换数据。有了XMLHttpRequest,开发者终于可以在不重新加载页面的情况下更新网页,可以在页面加载后请求接受以及发送数据。

1. 所有浏览器均可以获取XMLHttpRequest对象:

1
var xhr = new XMLHttpRequest(); // 获取xhr对象

但是XMLHttpRequest是个比较粗燥的底层对象,各个浏览器对其的创建方法也不同,如果需要兼容老版本的 IE (IE5, IE6) 浏览器,则需要使用 ActiveX 对象:

1
2
3
4
5
6
7
8
9
10
11
12
var xhr;
if (window.XMLHttpRequest) { // Mozilla, Safari...
xhr = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
try {
xhr = new ActiveXObject('Msxml2.XMLHTTP');
} catch (e) {
try {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
}
}

2. 使用XMLHttpRequest发起一个get请求

1
2
3
// 对于get请求可以通过向URL添加唯一的ID,来避免首先获取缓存结果
xhr.open("GET","test1.txt",true);
xhr.send();

3. 完整的post请求代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var xhr;
if (window.XMLHttpRequest) { // Mozilla, Safari...
xhr = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
try {
xhr = new ActiveXObject('Msxml2.XMLHTTP');
} catch (e) {
try {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
}
}
if (xhr) {
xhr.onreadystatechange = onReadyStateChange;
// open("请求类型", "请求文件在服务器上的位置", 是否异步)
xhr.open('POST', '/api', true);
// 设置 Content-Type 为 application/x-www-form-urlencoded
// 以表单的形式传递数据
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('username=admin&password=root');
}
/** XMLHttpRequest 提供 onreadystatechange() 方法、readyState 属性、status 属性来监听响应状态。
readyState的取值如下:
0 (未初始化)
1 (正在装载)
2 (装载完毕)
3 (交互中)
4 (完成)
*/
function onReadyStateChange() {
if (xhr.readyState === 4 && xhr.status === 200) {
// XMLHttpRequest对象的 responseText 和 responseXML 属性来处理响应数据。
console.log('执行成功',xhr.responseText);
} else {
console.log('执行出错');
}
}

4. XMLHttpRequest对象有 open(“请求类型”, “请求文件在服务器上的位置”,是否异步) 和 send() 方法。

(1)对于get请求可以通过向URL添加唯一的ID,来避免首先获取缓存结果。

(2)对于post请求:使用setRequestHeader()添加HTTP头,然后在send()方法中规定发送数据:

1
2
3
xmlhttp.open("POST","/try/ajax/demo_post2.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("fname=Henry&lname=Ford");

5. XMLHttpRequest对象的 responseText 和 responseXML 属性来处理响应数据。

6. XMLHttpRequest 提供 onreadystatechange() 方法、readyState 属性、status 属性来监听响应状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>AJAX</title>
</head>
<body>
<div id="myDiv"><h2>使用AJAX修改文本内容</h2></div>
<button type="button" onclick="loadXMLDoc()">修改内容</button>
</body>
<script>
function loadXMLDoc() {
var xmlhttp;
if (window.XMLHttpRequest) {
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行
xmlhttp = new XMLHttpRequest();
} else {
// IE6, IE5 浏览器执行代码
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
document.getElementById("myDiv").innerHTML = xmlhttp.responseText;
}
}
xmlhttp.open("GET","/try/demo_get.php?t=" + Math.random(), true);
xmlhttp.send();
}
</script>
</html>

参考:廖雪峰官网AJAX

二、Jquery Ajax

说到Jquery,这是一个时代,几乎统治了前端10年有余,彻底解决了UI层与数据层交互的问题,直到三大框架(Angular/React/Vue)的出现,前端进入MVVM浪潮。而Ajax将XHR进行封装,让开发者可以更加便捷方便进行使用。

1
2
3
4
5
6
7
8
9
10
$.ajax({   //标准写法
type: 'POST',
url: url,
data: data,
dataType: dataType,
success: function () {},
error: function () {}
});
$.get(url,function(){}); //get请求
$.post(url,body,function(){}); //post请求

优点: - 对原生XHR的封装 - 针对MVC的编程 - 完美的兼容性 - 支持jsonp

缺点: - 不符合MVVM - 异步模型不够现代,不支持链式,代码可读性差 - 整个Jquery太大,引入成本过高

三、Fetch

Fetch其实是一个新世界,脱离的XHR,完全是基于Promise的异步处理机制,使用起来会比起ajax更加简单。

使用fetch的代码会相比xhr来说更具有条理性

1
2
3
4
5
6
7
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(e) {
console.log("Oops, error");
});

在使用ES6的箭头函数后

1
2
3
fetch(url).then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))

优点: - 更加底层,提供的API丰富(request, response) - 语法简单,脱离了XHR,基于ES新的Promise设计

看到以上,或许你会觉得fetch真的很美好,但是请了解,fetch本身是一个 low-level 的 API,它注定不会像你习惯的 $.ajax 或是 axios 等库帮你封装各种各样的功能或实现。

所以它是存在一定的缺点: - 兼容性比较凄惨,低级别浏览器均不支持,需要实现fetch的polyfill了。思路其实很简单,就是判断浏览器是否支持原生的fetch,不支持的话,就仍然使用XMLHttpRequest的方式实现,同时结合Promise来进行封装。常见的polyfill就有:es6-promise,babel-polyfill,fetch-ie8等

  • 不支持jsonp,可以引入fetch-jsonp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 安装
    npm install fetch-jsonp --save-dev
    // 使用
    fetchJsonp(url, {
    timeout: 3000,
    jsonpCallback: 'callback'
    }).then(function(response) {
    console.log(response.json());
    }).catch(function(e) {
    console.log(e)
    });
  • 没有拦截器,需要额外再封装一层或者fetch-interceptor

  • 默认不带cookie,需要添加配置

    1
    2
    3
    fetch(url,{
    credentials: 'include' //include表示cookie既可同域,也可跨域,‘same-origin’表示只可同域
    });
  • 没有abort,不支持timeout超时处理

可以用Promise.race()实现,Promise.race(iterable) 方法返回一个Promise对象,只要 iterable 中任意一个Promise 被 resolve 或者 reject 后,外部的Promise 就会以相同的值被 resolve 或者 reject。 - 无法获取progress状态

四、axios

axios也是比较新的网络请求的类库,并且被尤雨溪尤大推荐,已成为VUE的网络请求标配,也是十分的火爆。它本身也是对原生XHR的封装。 - 支持node,创建http请求 - 支持Promise API - 客户端防止CSRF:每个请求带一个cookie拿到的key - 拦截请求和响应 - 可取消请求

兼容性上虽然axios本质上也是对原生XHR的封装,但是它也依赖原生ES6 Promise的实现,和fetch一样需要polyfill的兼容。

1. 安装

1
2
3
4
// npm
npm install axios
// cdn
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

基本使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
axios({
method: 'GET',
url: url,
})
.then(res => {console.log(res)})
.catch(err => {console.log(err)})
// get请求
axios.get(url)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// post请求
axios.post(‘/user’, {
name: 'Jerry',
lastName: 'Liang'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

2. 特殊场景的处理

在开发过程中,经常会遇到比较尴尬的场景就是多请求的串行与并发,并发比较容易解决,不存在回调地狱,但是代码可读性就会容易变得很渣,而串行问题对于前端是绝望的,最好的办法是后端来做合并,如果后端不做这块的处理,前端就必须来面对回调地狱。

(1)多请求串行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ajax
$.ajax({
url: '',
data: '',
success: function (data) {
$.ajax({
url: '',
data: '',
success: function (data) {
$.ajax({
// 如此一层嵌套一层
})
}
})
}
})
//axios
axios.get(url)
.then(res => {
return axios.get(url,{
{name:result.name}
});
}).then(res => {
//如此一层层嵌套
});

(2)多请求并行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// ajax 通过计数器实现(虽然Jquery支持$.when的方式,但此处不做案例)
var num = 0;
function all(){
num++;
if(n>=3)console.log('三个请求全部完成');
}
$.ajax({
url: '',
data: '',
success: function (data) {
console.log("ajax请求1 完成");
all();
}
})
$.ajax({
url: '',
data: '',
success: function (data) {
console.log("ajax请求2 完成");
all();
}
})
$.ajax({
url: '',
data: '',
success: function (data) {
console.log("ajax请求3 完成");
all();
}
})
//axios
function getInfo() {
return axios.get(url);
}
function getUser() {
return axios.get(url);
}
axios.all([getInfo(), getUser()])
.then(axios.spread(function (info, user) {
// 两个请求现在都执行完成
}));

五、如何选择

(1)首先可以肯定的是,如果你的代码依旧是基于Jquery,那毫无疑问,ajax就是你最好的选择。

(2)如果你使用的是任意MVVM框架,建议无脑使用axios,fetch在实际项目使用中,需要各种的封装和异常处理,并非开箱即用,而axios可以做到直接替换$.ajax。

(3)如果就是要使用fetch,那相信你也一定能封装成自己的一套最佳实践。

参考:

前端请求大比拼:Fetch、Axios、Ajax、XHR

AJAX和Fetch的区别