技术虽然很老,但是我们也要知道他是怎么一回事

前言

看到这篇文章时,你或许在项目中将axios用的如虎添翼,体验到了axios大法好,确实不能否认用axios能够大幅度提高了项目开发效率,无论是方便的Promise还是拦截器先进理念,它都理所当然地成为项目开发的首选,但是这一切的起点,都是基于XHR对象,也就是我们平常说的ajax,本篇文章是我在使用axios几个月后再次回归本质,探索web请求的基石。

概述

ajax是asynchronous javascript and XML的简写,中文翻译是异步的javascript和XML,这一技术能够向服务器请求额外的数据而无须卸载页面,会带来更好的用户体验。虽然名字中包含XML,但ajax通信与数据格式无关,它可以请求任意格式的数据,比如常用的json,ajax包括以下几步骤:

  1. 创建AJAX对象
  2. 发出HTTP请求
  3. 接收服务器传回的数据
  4. 更新网页数据

概括起来,就是一句话,ajax通过原生的XMLHttpRequest对象发出HTTP请求,得到服务器返回的数据后,再进行处理。

步骤一:创建XHR对象

由于ajax的核心XMLHttpRequest对象(XHR)是微软首先引进的(IE5、IE6时期),并且它用的是自己的MSXML库中的一个ActiveX对象实现的,而IE7+及其他标准浏览器都支持原生的XHR对象,所以这就造成了一个兼容性问题,虽然现在地球上也没几个人用IE5、6,但是作为规范就必须要写兼容性,要不然就会被认为不够严谨,anyway,就一行代码的事情嘛~

1
2
3
4
5
6
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP'); //兼容IE5、6
}

创建一个XHR对象,也叫实例化一个XHR对象,因为XMLHTTPRequest()是一个构造函数,用new将它实例化。

步骤二:发送请求

open()

在使用XHR对象发送请求时,要调用的第一个方法是open(),如下所示,该方法接受3个参数:

xhr.open(“get”,”url”, false);

我们分别详细讲解这三个参数的具体含义:

  1. 第一个参数用于指定发送请求的方式(method),这个字符串,不区分大小写,但通常使用大写字母。通常是”GET”和”POST”就够满足基本要求,但是你也可以写PUT、PATCH、DELETE都是没问题的,这里又引出一道很基础的概念:

  “GET”用于常规请求,它适用于当URL完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存的情况下使用。

  “POST”方法常用于HTML表单。它在请求主体中包含额外数据且这些数据常存储到服务器上的数据库中。相同URL的重复POST请求从服务器得到的响应可能不同,同时不应该缓存使用这个方法的请求。

  1. open()方法的第二个参数是URL,该URL相对于执行代码的当前页面,向同一个域中使用相同端口和协议的URL发送请求(同源策略),当然也是能跨域的。

  2. open()方法的第三个参数是表示是否异步发送请求的布尔值,如果不填写,默认为true,表示异步发送,如果是false,那就是万恶的同步请求,如果没有极其特殊的要求,还是默认为异步。

  3. 如果请求一个受密码保护的URL,把用于认证的用户名和密码作为第4和第5个参数传递给open()方法,虽然不常用,但是写出来让大家了解一下。

send()

上面一步我们已经创建了一个请求,单单创建是不够的,这时候我们还发出去,也就是send(),这是个非常容易遗漏的(特别是写get请求),如果是get方法,参数可以写null或者直接省略,如果是post等方法,参数就是要发送出去的数据。

  • GET:

    1
    2
    xhr.open("get","/test",false)
    xhr.send()
  • POST:

1
2
3
xhr.open("post","/posttest",false)
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
xhr.send("name=huajinbo&age=12")

这样数据就发送出去,那么我们怎么才能获得返回的数据呢?接着往下看:

步骤三:接收响应:

一个完整的HTTP响应由状态码、响应头集合和响应主体组成。在收到响应后,这些都可以通过XHR对象的属性和方法使用,主要有以下4个属性:

  • responseText: 作为响应主体被返回的文本(文本形式)
  • responseXML: 如果响应的内容类型是’text/xml’或’application/xml’,这个属性中将保存着响应数据的XML - - DOM文档(document形式)
  • status: HTTP状态码(数字形式)
  • statusText: HTTP状态说明(文本形式)

注意: 有点需要说明白,我们上面的例子都是使用ajax的同步方式,后面会具体介绍。

在接收到响应后,第一步是检查status属性,以确定响应已经成功返回。一般来说,可以将HTTP状态码为2XX作为成功的标志。此时,responseText属性的内容已经就绪,而且在内容类型正确的情况下,responseXML也可以访问了。此外,状态码为304表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本;当然,也意味着响应是有效的。

我们通过以下代码来获取到返回的数据:

1
2
3
4
5
if((xhr.status >=200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert('request was unsuccessful:' + xhr.status);
}

到这里,我们整个ajax就发送完成并且收到响应数据,为了具体阐述出整个ajax请求,因此以上例子都是基于ajax同步请求,但是在实际开发中,大部分是用异步ajax来请求的,下面我将会说明这两种的区别:

同步请求

新人往往喜欢一切按照步骤来,所以由衷地喜欢同步,如果接受的是同步响应,则需要将open()方法的第三个参数设置为false,那么send()方法将阻塞直到请求完成。一旦send()返回,仅需要检查XHR对象的status和responseText属性即可,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<button id="btn">获取信息</button>
<div id="result"></div>
<script>
btn.onclick = function(){
//创建xhr对象
var xhr;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
//发送请求
xhr.open('get','/test',false);
xhr.send();
//同步接受响应
if((xhr.status >=200 && xhr.status < 300) || xhr.status == 304){
//实际操作
result.innerHTML += xhr.responseText;
}
}
</script>

这是一种头绪清晰的请求过程,但是客户端javascript是单线程的,当send()方法阻塞时,也就是说请求未返回的这段时间,它通常会导致整个浏览器冻结,无法执行其他任务,如果连接的服务器响应慢,那么用户的浏览器将冻结,因此,这里极度不推荐这么做,因为我们有更佳的事件方案。

异步请求

假如你有一些ajax基础,你或许会疑问问什么一直没有提出readyState,这就是我们讲解异步请求的基础,readyState表示请求/响应过程的当前活动阶段,它是时刻监控请求的xhr变量,当请求的状态发生变化,readyState也会自动变化,这个时候我们就可以监听readyState的阶段码来实现异步请求,readyState的具体值如下:

  • 0(UNSENT):未初始化。尚未调用open()方法
  • 1(OPENED):启动。已经调用open()方法,但尚未调用send()方法
  • 2(HEADERS_RECEIVED):发送。己经调用send()方法,且接收到头信息
  • 3(LOADING):接收。已经接收到部分响应主体信息
  • 4(DONE):完成。已经接收到全部响应数据,而且已经可以在客户端使用了

通过以下监听函数来执行不同阶段的任务:

1
2
3
4
5
xhr.onreadystatechange = function(){
if(xhr.readyState === XXX){
// something
}
}

但是我们一般只关系readyState值为4的阶段,因为这个时候数据请求已经成功就绪状态:

1
2
3
4
5
6
7
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status == 200){
alert(xhr.responseText);
}
}
}

注意: 必须在调用open()之前指定onreadystatechange 事件处理程序才能确保跨浏览器兼容性,否则将无法接收readyState属性为0和1的情况

完整例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
btn.onclick = function(){
//创建xhr对象
var xhr;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
//异步接受响应
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
//实际操作
result.innerHTML += xhr.responseText;
}
}
}
//发送请求
xhr.open('get','/test',true);
xhr.send();
}
</script>

超时

我们在使用axios的时候,初始化前一般都会设置超时参数,以便我们弹出错误提醒和重新请求,其实也就是基于ajax的超时设置,如下:

1
2
3
4
5
6
xhr.open('post','/test',true);
xhr.ontimeout = function(){
// 超时提醒或者再次请求
}
xhr.timeout = 1000;
xhr.send();

总结

通过以上分析,ajax并不复杂,无非是三个步骤四个状态,使用起来也是很人性化的,但是,由于ajax涉及到一些后端及网络的知识,比如各种请求方式、头部信息,使得初学者学起来不是很容易。因此axios将它的步骤和状态封装成语法糖,使用起来更简单,也减少了代码量。

本篇文章通过回归ajax本质,有利于我们更清楚的理解浏览器请求过程以及原理,因为只是浅析,具体其他更深层次的知识,我将会在合适的时间,做一次全面总结。

Experience is the father of wisdom and memory the mother :)