2.AJAX
2.1 AJAX介绍
- Ajax即AsynchronousJavascriptAndXML(异步JavaScript和XML)
xml是基于SGML(通用标言)语法规范的文档模型,SGML依据规范指定了DTD中的元素,属性和内容实体。DTD整体学习成本大,不太适用
- 转发和重定向会改变整个页面
- 使用Ajax技术网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面,类似本地的原生应用。这使得程序能够更快地回应用户的操作。
- 不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序的技术。
- 使用 JavaScript 向服务器提出请求并处理响应(解析服务器响应提供接口)而不阻塞的用户核心对象XMLHttpRequest。通过这个对象,您的 JavaScript 可在不重载页面的情况与 Web 服务器交换数据,即在不需要刷新页面的情况下,就可以产生局部刷新的效果。
- Ajax 应用程序独立于浏览器和平台。增强BS的体验性。
- H5(手机)+网页+客户端(C)+手机端(android/IOS )+小程序
- 使用Ajax可以创建接近本地桌面应用的直接、高可用、更丰富、更动态的web用户界面。
2.2 伪造的AJAX
在js中所有能用到的变量,提前获取
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript">
window.onload = function () {
document.getElementById("currentTime").innerText = new Date().getDate();
}
function loadFunction() {
let urlTarght = document.getElementById("url").value;
document.getElementById("displayPage").src = urlTarght;
}
</script>
</head>
<body>
<h3>请输入网址:<span id="currentTime"></span></h3>
<div>
<input type="text" id="url">
<input type="button" value="链接" onclick="loadFunction()">
</div>
<div>
<iframe style="width: 100%;height: 400px" id="displayPage">
</iframe>
</div>
</body>
</html>- 整个伪类不是ajax的原因是:并没有发起XHR请求,只是点击事件时将链接赋给iframe的src,让iframe去加载。
2.3 XMLHttpRequest
在JavaScript 中,XMLHttpRequest 是客户端的一个 API,它为浏览器与服务器通信提供了一个便捷通道
XMLHttpRequest 用于在后台与服务器交换数据
2.3.1 XMLHttpRequest标准
XMLHttpRequest标准又分为Level 1和Level 2。XMLHttpRequest Level 1主要存在以下缺点:Level 2改进,
XMLHttpRequest Level 2中新增了以下功能:- 可以发送跨域请求,在服务端允许的情况下;
- 支持发送和接收二进制数据;
- 新增formData对象,支持发送表单数据;
- 发送和获取数据时,可以获取进度信息;
- 可以设置请求的超时时间;
2.3.2 XMLHttpRequest使用
- 使用
XMLHttpRequest发送Ajax请求
function sendAjax() {
//构造表单数据
var formData = new FormData();
formData.append('username', 'johndoe');
formData.append('id', 123456);
//创建xhr对象
var xhr = new XMLHttpRequest();
//设置xhr请求的超时时间
xhr.timeout = 3000;
//设置响应返回的数据格式
xhr.responseType = "text";
//创建一个 post 请求,采用异步
xhr.open('POST', '/server', true);
//注册相关事件回调处理函数
xhr.onload = function(e) {
if(this.status == 200||this.status == 304){
alert(this.responseText);
}
};
xhr.ontimeout = function(e) { ... };
xhr.onerror = function(e) { ... };
xhr.upload.onprogress = function(e) { ... };
//发送数据
xhr.send(formData);- 可以发送什么类型的数据
void send(data)
ArrayBufferBlobDocumentDOMStringFormDatanull- 如果是 GET/HEAD请求,
send()方法一般不传参或传null。不过即使你真传入了参数,参数也最终被忽略,xhr.send(data)中的data会被置为null. xhr.send(data)中data参数的数据类型会影响请求头部content-type的默认值:
- 如果
data是Document类型,同时也是HTML Document类型,则content-type默认值为text/html;charset=UTF-8;否则为application/xml;charset=UTF-8;- 如果
data是DOMString类型,content-type默认值为text/plain;charset=UTF-8;- 如果
data是FormData类型,content-type默认值为multipart/form-data; boundary=[xxx]- 如果
data是其他类型,则不会设置content-type的默认值- 若在断网状态下调用
xhr.send(data)方法,则会抛错如果不 catch 就无法继续执行后面的代码,所以调用xhr.send(data)方法时,应该用try-catch捕捉错误
2.2.3 xhr.withCredentials与CORS 什么关系
我们都知道,在发同域请求时,浏览器会将
cookie自动加在request header中。但大家是否遇到过这样的场景:在发送跨域请求时,cookie并没有自动加在request header中。
造成这个问题的原因是:在
CORS标准中做了规定,默认情况下,浏览器在发送跨域请求时,不能发送任何认证信息(credentials)如"cookies"和"HTTP authentication schemes"。除非xhr.withCredentials为true(xhr对象有一个属性叫withCredentials,默认值为false)。所以根本原因是
cookies也是一种认证信息,在跨域请求中,client端必须手动设置xhr.withCredentials=true,且server端也必须允许request能携带认证信息(即response header中包含Access-Control-Allow-Credentials:true),这样浏览器才会自动将cookie加在request header中。另外,要特别注意一点,一旦跨域
request能够携带认证信息,server端一定不能将Access-Control-Allow-Origin设置为*,而必须设置为请求页面的域名。
2.3.4 xhr相关事件
- 事件分类
interface XMLHttpRequestEventTarget : EventTarget {
// event handlers
attribute EventHandler onloadstart;
attribute EventHandler onprogress;
attribute EventHandler onabort;
attribute EventHandler onerror;
attribute EventHandler onload;
attribute EventHandler ontimeout;
attribute EventHandler onloadend;
};
interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {
};
interface XMLHttpRequest : XMLHttpRequestEventTarget {
// event handler
attribute EventHandler onreadystatechange;
readonly attribute XMLHttpRequestUpload upload;- 事件触发条件
| 事件 | 触发条件 |
|---|---|
onreadystatechange | 每当xhr.readyState改变时触发;但xhr.readyState由非0值变为0时不触发。 |
onloadstart | 调用xhr.send()方法后立即触发,若xhr.send()未被调用则不会触发此事件。 |
onprogress | xhr.upload.onprogress在上传阶段(即xhr.send()之后,xhr.readystate=2之前)触发,每50ms触发一次;xhr.onprogress在下载阶段(即xhr.readystate=3时)触发,每50ms触发一次。 |
onload | 当请求成功完成时触发,此时xhr.readystate=4 |
onloadend | 当请求结束(包括请求成功和请求失败)时触发 |
onabort | 当调用xhr.abort()后触发 |
ontimeout | xhr.timeout不等于0,由请求开始即onloadstart开始算起,当到达xhr.timeout所设置时间请求还未结束即onloadend,则触发此事件。 |
onerror | 在请求过程中,若发生Network error则会触发此事件(若发生Network error时,上传还没有结束,则会先触发xhr.upload.onerror,再触发xhr.onerror;若发生Network error时,上传已经结束,则只会触发xhr.onerror)。注意,只有发生了网络层级别的异常才会触发此事件,对于应用层级别的异常,如响应返回的xhr.statusCode是4xx时,并不属于Network error,所以不会触发onerror事件,而是会触发onload事件。 |
- 事件触发顺序
- 当请求一切正常时,相关的事件触发顺序如下:
- 触发
xhr.onreadystatechange(之后每次readyState变化时,都会触发一次)- 触发
xhr.onloadstart
//上传阶段开始:- 触发
xhr.upload.onloadstart- 触发
xhr.upload.onprogress- 触发
xhr.upload.onload- 触发
xhr.upload.onloadend
//上传结束,下载阶段开始:- 触发
xhr.onprogress- 触发
xhr.onload- 触发
xhr.onloadend
- 发生abort/timeout/error异常的处理:在请求的过程中,有可能发生
abort/timeout/error这3种异常。那么一旦发生这些异常,xhr后续会进行哪些处理呢?后续处理如下
- 一旦发生
abort或timeout或error异常,先立即中止当前请求- 将
readystate置为4,并触发xhr.onreadystatechange事件- 如果上传阶段还没有结束,则依次触发以下事件:
xhr.upload.onprogressxhr.upload.[onabort或ontimeout或onerror]xhr.upload.onloadend- 触发
xhr.onprogress事件- 触发
xhr.[onabort或ontimeout或onerror]事件- 触发
xhr.onloadend事件
- 哪个xhr事件中注册成功回调
- 可以知道若
xhr请求成功,就会触发xhr.onreadystatechange和xhr.onload两个事件,最好是xhr.onload事件因为xhr.onreadystatechange是每次xhr.readyState变化时都会触发,而不是xhr.readyState=4时才触发。
xhr.onload = function () {
//如果请求成功
if(xhr.status == 200){
//do successCallback
}- 上面的示例代码是很常见的写法:先判断
http状态码是否是200,如果是,则认为请求是成功的,接着执行成功回调。这样的判断是有坑儿的,比如当返回的http状态码不是200,而是201时,请求虽然也是成功的,但并没有执行成功回调逻辑。所以更靠谱的判断方法应该是:当http状态码为2xx或304时才认为成功。
xhr.onload=function(){//如果请求成功if((xhr.status>=200&& xhr.status<300)|| xhr.status==304){//do successCallback}如何获取上传、下载的进度
在上传或者下载比较大的文件时,实时显示当前的上传、下载进度是很普遍的产品需求。
我们可以通过
onprogress事件来实时显示进度,默认情况下这个事件每50ms触发一次。需要注意的是,上传过程和下载过程触发的是不同对象的onprogress事件:上传触发的是
xhr.upload对象的onprogress事件下载触发的是
xhr对象的onprogress事件
xhr.onprogress= updateProgress;
xhr.upload.onprogress= updateProgress;functionupdateProgress(event){if(event.lengthComputable){var completedPercent= event.loaded/ event.total;}2.3.5 设置request header
- 在发送
Ajax请求(实质是一个HTTP请求)时,我们可能需要设置一些请求头部信息,比如content-type、connection、cookie、accept-xxx等。xhr提供了setRequestHeader来允许我们修改请求 header。
void setRequestHeader(DOMString header, DOMString value);
注意点:
方法的第一个参数 header 大小写不敏感,即可以写成
content-type,也可以写成Content-Type,甚至写成content-Type;Content-Type的默认值与具体发送的数据类型有关;setRequestHeader必须在open()方法之后,send()方法之前调用,否则会抛错;setRequestHeader可以调用多次,最终的值不会采用覆盖override的方式,而是采用追加append的方式。下面是一个示例代码:
var client=newXMLHttpRequest();
client.open('GET','demo.cgi');
client.setRequestHeader('X-Test','one');
client.setRequestHeader('X-Test','two');// 最终request header中"X-Test"为: one, two
client.send();2.3.6 获取response header
xhr提供了2个用来获取响应头部的方法:getAllResponseHeaders和getResponseHeader。前者是获取 response 中的所有header 字段,后者只是获取某个指定 header 字段的值。另外,getResponseHeader(header)的header参数不区分大小写。
DOMString getAllResponseHeaders();` `DOMString getResponseHeader(DOMString header);
使用
getAllResponseHeaders()看到的所有response header与实际在控制台Network中看到的response header不一样使用
getResponseHeader()获取某个header的值时,浏览器抛错Refused to get unsafe header "XXX"原因1:W3C的 xhr 标准中做了限制,规定客户端无法获取 response 中的
Set-Cookie、Set-Cookie2这2个字段,无论是同域还是跨域请求;原因2:W3C 的 cors 标准对于跨域请求也做了限制,规定对于跨域请求,客户端允许获取的response header字段只限于“
simple response header”和“Access-Control-Expose-Headers” (两个名词的解释见下方)。
"
simple response header"包括的 header 字段有:Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma;
“Access-Control-Expose-Headers”:首先得注意是"Access-Control-Expose-Headers"进行跨域请求时响应头部中的一个字段,对于同域请求,响应头部是没有这个字段的。这个字段中列举的 header 字段就是服务器允许暴露给客户端访问的字段。
- 所以
getAllResponseHeaders()只能拿到限制以外*(即被视为safe)的header字段,而不是全部字段;而调用getResponseHeader(header)方法时,header参数必须是**限制以外***的header字段,否则调用就会报Refused to get unsafe header的错误。
2.3.6 如何指定xhr.response的数据类型
- 有些时候我们希望
xhr.response返回的就是我们想要的数据类型。比如:响应返回的数据是纯JSON字符串,但我们期望最终通过xhr.response拿到的直接就是一个 js 对象,我们该怎么实现呢? - 有2种方法可以实现,一个是
level 1就提供的overrideMimeType()方法,另一个是level 2才提供的xhr.responseType属性。
xhr.overrideMimeType()
overrideMimeType是xhr level 1就有的方法,所以浏览器兼容性良好。这个方法的作用就是用来重写response的content-type,这样做有什么意义呢?比如:server 端给客户端返回了一份document或者是xml文档,我们希望最终通过xhr.response拿到的就是一个DOM对象,那么就可以用xhr.overrideMimeType('text/xml; charset = utf-8')来实现。再举一个使用场景,我们都知道
xhr level 1不支持直接传输blob二进制数据,那如果真要传输 blob 该怎么办呢?当时就是利用overrideMimeType方法来解决这个问题的。下面是一个获取图片文件的代码示例:
var xhr=newXMLHttpRequest();//向 server 端获取一张图片
xhr.open('GET','/path/to/image.png',true);// 这行是关键!//将响应数据按照纯文本格式来解析,字符集替换为用户自己定义的字符集
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange=function(e){if(this.readyState==4&&this.status==200){//通过 responseText 来获取图片文件对应的二进制字符串var binStr=this.responseText;//然后自己再想方法将逐个字节还原为二进制数据for(var i=0, len= binStr.length; i< len;++i){var c= binStr.charCodeAt(i);//String.fromCharCode(c & 0xff);var byte= c&0xff;}}};
xhr.send();- 代码示例中
xhr请求的是一张图片,通过将response的content-type改为’text/plain; charset=x-user-defined’,使得xhr以纯文本格式来解析接收到的blob 数据,最终用户通过this.responseText拿到的就是图片文件对应的二进制字符串,最后再将其转换为 blob 数据。
xhr.responseType
responseType是xhr level 2新增的属性,用来指定xhr.response的数据类型,目前还存在些兼容性问题。那么responseType可以设置为哪些格式呢,我简单做了一个表,如下:
| 值 | xhr.response 数据类型 | 说明 |
|---|---|---|
"" | String字符串 | 默认值(在不设置responseType时) |
"text" | String字符串 | |
"document" | Document对象 | 希望返回XML 格式数据时使用 |
"json" | javascript 对象 | 存在兼容性问题,IE10/IE11不支持 |
"blob" | Blob对象 | |
"arrayBuffer" | ArrayBuffer对象 |
- 下面是同样是获取一张图片的代码示例,相比
xhr.overrideMimeType,用xhr.response来实现简单得多。
var xhr=newXMLHttpRequest();
xhr.open('GET','/path/to/image.png',true);//可以将`xhr.responseType`设置为`"blob"`也可以设置为`" arrayBuffer"`//xhr.responseType = 'arrayBuffer';
xhr.responseType='blob';
xhr.onload=function(e){if(this.status==200){var blob=this.response;}};
xhr.send();小结
虽然在
xhr level 2中,2者是共同存在的。但其实不难发现,xhr.responseType就是用来取代xhr.overrideMimeType()的,xhr.responseType功能强大的多,xhr.overrideMimeType()能做到的xhr.responseType都能做到。所以我们现在完全可以摒弃使用xhr.overrideMimeType()了。
2.3.7 如何获取response数据
xhr`提供了3个属性来获取请求返回的数据,分别是:`xhr.response`、`xhr.responseText`、`xhr.responseXMLxhr.response- 默认值:空字符串
"" - 当请求完成时,此属性才有正确的值
- 请求未完成时,此属性的值可能是
""或者null,具体与xhr.responseType有关:当responseType为""或"text"时,值为"";responseType为其他值时,值为null
- 默认值:空字符串
xhr.responseText- 默认值为空字符串
"" - 只有当
responseType为"text"、""时,xhr对象上才有此属性,此时才能调用xhr.responseText,否则抛错 - 只有当请求成功时,才能拿到正确值。以下2种情况下值都为空字符串
"":请求未完成、请求失败
- 默认值为空字符串
xhr.responseXML- 默认值为
null - 只有当
responseType为"text"、""、"document"时,xhr对象上才有此属性,此时才能调用xhr.responseXML,否则抛错 - 只有当请求成功且返回数据被正确解析时,才能拿到正确值。以下3种情况下值都为
null:请求未完成、请求失败、请求成功但返回数据无法被正确解析时
- 默认值为
2.3.8 追踪ajax请求的当前状态
- 在发一个
ajax请求后,如果想追踪请求当前处于哪种状态,该怎么做呢? - 用
xhr.readyState这个属性即可追踪到。这个属性是只读属性,总共有5种可能值,分别对应xhr不同的不同阶段。每次xhr.readyState的值发生变化时,都会触发xhr.onreadystatechange事件,我们可以在这个事件中进行相关状态判断。
xhr.onreadystatechange=function(){switch(xhr.readyState){case1://OPENED//do somethingbreak;case2://HEADERS_RECEIVED//do somethingbreak;case3://LOADING//do somethingbreak;case4://DONE//do somethingbreak;}| 值 | 状态 | 描述 |
|---|---|---|
0 | UNSENT (初始状态,未打开) | 此时xhr对象被成功构造,open()方法还未被调用 |
1 | OPENED (已打开,未发送) | open()方法已被成功调用,send()方法还未被调用。注意:只有xhr处于OPENED状态,才能调用xhr.setRequestHeader()和xhr.send(),否则会报错 |
2 | HEADERS_RECEIVED(已获取响应头) | send()方法已经被调用, 响应头和响应状态已经返回 |
3 | LOADING (正在下载响应体) | 响应体(response entity body)正在下载中,此状态下通过xhr.response可能已经有了响应数据 |
4 | DONE (整个数据传输过程结束) | 整个数据传输过程结束,不管本次请求是成功还是失败 |
2.3.9 如何设置请求的超时时间
- 如果请求过了很久还没有成功,为了不会白白占用的网络资源,我们一般会主动终止请求。
XMLHttpRequest提供了timeout属性来允许设置请求的超时时间。
xhr.timeout
-单位:milliseconds 毫秒 默认值:0,即不设置超时
从*请求开始* 算起,若超过
timeout时间请求还没有结束(包括成功/失败),则会触发ontimeout事件,主动结束该请求。【那么到底什么时候才算是*请求开始* ?】——
xhr.onloadstart事件触发的时候,也就是你调用xhr.send()方法的时候。因为xhr.open()只是创建了一个连接,但并没有真正开始数据的传输,而xhr.send()才是真正开始了数据的传输过程。只有调用了xhr.send(),才会触发xhr.onloadstart。【那么什么时候才算是*请求结束* ?】——
xhr.loadend事件触发的时候。另外,还有2个需要注意的坑儿:
- 可以在
send()之后再设置此xhr.timeout,但计时起始点仍为调用xhr.send()方法的时刻。 - 当
xhr为一个sync同步请求时,xhr.timeout必须置为0,否则会抛错。
- 可以在
2.3.10 如何发一个同步请求
xhr默认发的是异步请求,但也支持发同步请求(当然实际开发中应该尽量避免使用)。到底是异步还是同步请求,由xhr.open()传入的async参数决定。
open(method, url [, async = true [, username = null [, password = null]]])
method: 请求的方式,如GET/POST/HEADER等,这个参数不区分大小写url: 请求的地址,可以是相对地址如example.php,这个相对是相对于当前网页的url路径;也可以是绝对地址如http://www.example.com/example.phpasync: 默认值为true,即为异步请求,若async=false,则为同步请求同步请求和异步请求不仅仅是阻塞和非阻塞的区别,其他什么事件触发、参数设置应该是一样的。
W3C 的 xhr标准中关于
open()方法有这样一段说明:
Throws an “InvalidAccessError” exception if async is false, the JavaScript global environment is a document environment, and either the timeout attribute is not zero, the withCredentials attribute is true, or the responseType attribute is not the empty string.
从上面一段说明可以知道,当
xhr为同步请求时,有如下限制:xhr.timeout必须为0xhr.withCredentials必须为falsexhr.responseType必须为""(注意置为"text"也不允许)若上面任何一个限制不满足,都会抛错,而对于异步请求,则没有这些参数设置上的限制。
之前说过页面中应该尽量避免使用
sync同步请求,为什么呢?因为我们无法设置请求超时时间(xhr.timeout为0,即不限时)。在不限制超时的情况下,有可能同步请求一直处于pending状态,服务端迟迟不返回响应,这样整个页面就会一直阻塞,无法响应用户的其他交互。另外,标准中并没有提及同步请求时事件触发的限制,但实际开发中我确实遇到过部分应该触发的事件并没有触发的现象。如在 chrome中,当
xhr为同步请求时,在xhr.readyState由2变成3时,并不会触发onreadystatechange事件,xhr.upload.onprogress和xhr.onprogress事件也不会触发。