前言
在学习JS的过程中,渐渐意识到基础知识真的太重要了。在今天这篇文章中,我想站在前端的角度,跟大家聊一聊客户端浏览器和服务器之间的通信问题。这个问题涉及到两个概念,ajax和XHR。这篇文章除了会介绍这两个概念之外,我们还会聊一聊FormData相关的一些话题。
ajax
在维基百科中,关于ajax的定义是这样的:
AJAX,即"Asynchronous JavaScript and XML"(异步的JavaScript和XML技术),指的是一套综合了多项技术的浏览器端网页开发技术。AJAX应用可以仅向服务器发送并取回必须的数据,并在客户端采用JavaScript处理来自服务器的回应。
我不知道大家伙儿是怎么想的,但是我在拿到这个概念之后,心里有一个疑问:这项技术和XML有什么关系呢?明明它是一项关于浏览器和服务器之间交互的解决方案。JavaScript还是可以理解的,毕竟在浏览器收到来自服务器的响应时会用它来进行数据处理。完全看不出来这关XML什么事啊,它名字里为什么会有个XML呢?这个问题的答案,我在另一个和他名字很像的技术中似乎找到了。那边是讲这些名字只是XML流行时的遗迹。简单来说,ajax就是使用XMLHttpRequest对象与服务器通信。到这里,我们的第二个主角也登场了。说到通信,无非就是发送和接受了。鉴于我们这里的通信是在网络环境下,利用HTTP协议进行的通信,那么ajax要做的事情自然呼之欲出了:
- 在不重新加载页面的情况下发送请求给服务器
- 接受并使用从服务器发来的数据
如上图所示,在AJAX出现之前,传统的web应用都只能在提交表单时向服务器发送request请求,而服务器的响应也是新的HTML页面。最尴尬的问题是,新的HTML页面和原本的页面大部分都相同。这就使得这种方式极大的占用了带宽。
从这个角度来看,AJAX的异步性所带来的在不刷新页面的前提下维护数据就变得很喜人。
XHR
XMLHttpRequest(XHR)是所有浏览器都支持的对象,用于与服务器的交互,它定义了用脚本操纵HTTP的API。除了常用的GET请求,这个API还包含实现POST请求的能力,同时它能用文本或Document对象的形式返回服务器的响应。
XMLHttpRequest这个对象的继承关系如下:
这样的继承关系表明了属于父类(EventTarget、XMLHttpRequestTarget)的方法和属性也可以被应用到子类(XMLHttpRequest)上。
1. 发送HTTP请求
利用XHR发送HTTP请求的第一步是创建一个新的XMLHttpRequest对象:
var xhr = new XMLHttpRequest()
第二步是利用这个对象的方法.open()方法和.send()方法打开一个HTTP请求和向服务器发送请求数据。
xhr.open("GET",url)
xhr.send()
在这里,需要注意几个问题:
- HTTP请求的组成:
- HTTP请求方法或动作:POST、GET这些参数
- 正在请求的URL
- 一个可选的请求头集合,可能包括身份验证信息
- 一个可选的请求主体
- POST和GET的区别:
GET用于常规请求,它适用于当URL完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存时。GET请求绝对没有主体,所以在使用.send()方法时,应该传递null或者省略这个参数。POST常用于提交表单。它在请求主体中包含额外数据(表单数据)且这些数据常存储到服务器上的数据库中(副作用)。相同URL的重复POST请求从服务器得到的响应可能不同,同时不应该缓存使用这个方法的请求。POST请求通常拥有主体,同时它应该匹配使用setRequestHeader()指定Content-Type头。
.open()方法的参数:- method:要使用的HTTP方法
- url: 发送请求的URL
- async(可选): 一个可选的布尔参数,表示是否执行异步操作,默认为
true。如果值为false,send()方法直到收到答复前不会返回。如果true,已完成事务的通知可供事件监听器使用。 - user(可选): 可选的用户名用于认证
- password(可选): 可选的密码用于认证
.send()方法:用于发送 HTTP 请求。如果是异步请求(默认为异步请求),则此方法会在请求发送后立即返回;如果是同步请求,则此方法直到响应到达后才会返回。XMLHttpRequest.send() 方法接受一个可选的参数,其作为请求主体;如果请求方法是 GET 或者 HEAD,则应将请求主体设置为 null。需要注意的是:发送HTTP请求时一定要调用这个方法。- 添加请求头:该方法时设置HTTP请求头部的方法。它必须在
.open()和send()之间调用。它的使用语法如下:
xhr.setRequestHeader(header, value);
2. 处理服务器响应
服务器返回的HTTP响应包含三部分:
- 一个数字和文字组成的状态码,用来显示请求的成功和失败
- 一个响应头集合
- 响应主体 利用XMLHTTPResponse对于服务器返回的响应的处理就是对这三个部分的处理。由于在编码时不知道响应何时能够传回到客户端浏览器,需要一个标识来指示响应的阶段。这就是XMLHttpRequest的readyState属性。服务器返回的相应内容也是由XMLHttpRequest属性来存储的。与服务器响应相关的属性有:
- readyState:readyState是一个整数,它指定了HTTP请求的状态。它的取值如下
- 0:代表"UNSENT"状态,表示代理被创建,但是未调用
open()方法 - 1:代表"OPENED"状态,表示
open()方法已经被调用 - 2:代表"HEADERS_RECEIVED"状态,表示
send()方法已经被调用,并且都不和状态已经可获得。 - 3:代表"LOADING"状态,表示下载中,responseText已经获得部分数据
- 4:代表"DONE"状态,表示下载操作已完成
理论上,每次readyState属性改变都会触发
onreadyStateChange事件。在实际中,当readyState改编为0或1时可能没有触发这个时间。当调用send()时,即使readystate仍处于OPEN状态,也通常触发它。某些浏览器在LOADING状态时能触发多吃时间来给出进度反馈。当readyState值改变为4或服务器响应完成时,所有的浏览器都触onreadyStateChange事件。我们在处理服务器返回的响应数据时,需要监听onreadyStateChange事件,并且判断readyState的值,检验服务器相应是否完成。在完成之后,处理响应数据。
- 0:代表"UNSENT"状态,表示代理被创建,但是未调用
xhr.onreadyStateChange = () => {
if(xhr.readyState === 4){
// 处理服务器返回的响应数据
}
}
-
status:
XMLHTTPRequest.status返回了XMLHttpRequset响应中的数字状态码,是一个只读属性。在请求完成之前,status的值为0。它是标准的HTTP状态码。 -
statusText:以文字的形式表示HTTP状态码。例如:200对应的
statusText属性为'OK' -
responseXML:该属性是一个只读值。它返回一个包含请求检索的HTML或XML的Document。如果请求未成功、尚未发送、或者检索的数据无法正确解析为XML或HTML,则为null。
-
responseText:以文本的形式返回的响应主体。当处理一个异步的request时,尽管当前请求并没有结束,responseText的返回值时当前从后端接收到的内容。当请求状态readyState变为
XMLHttpRequest.DONE(4),且status值为200("OK")时,responseText是全部后端的返回数据。
最后,完整的处理后端服务器返回数据的代码如下:
xhr.onreadyStateChange = () => {
if(xhr.readyState === 4){
if(XPathResult.status === 200) {
// 处理服务器返回的响应数据
} else {
// 处理相应失败的结果
}
}
}
FormData
在发送HTTP的request请求的时候,有时候需要传递数据。当然,我们可以直接将传递的参数直接写到JavaScript对象里面。但是在提交表单的时候,一个一个获取数据,会比较麻烦。因此,FormData提供了一种表示表单数据的键值对key/value的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send()发送出去。
构造函数
FormData()构造函数用于创建一个新的FormData对象。
var formData = new FormData(form)
其中,参数form是可选的,表示一个HTML上的<form>表单元素。当它被指定的时候,这种方式创建的FormData对象会自动将form中的表单值也包含进去,包括文件内容也会比编码之后包含进去。
方法
FormData.append():添加新的属性值,并且不会覆盖原有属性值FormData.delete():删除一个键值对FormData.entries():返回一个包含所有键值对的iterator对象FormData.get():返回给定键的第一个值FormData.getAll():返回所有给定键关联的值的数组FormData.has():返回一个布尔值表示该对象中是否包含这个键FormData.keys():返回所有键的iterator对象FormData.set():设置属性值,并且覆盖原有属性FormData.values():返回包含所有值的iterator对象