版权声明:本文为博主原创文章,未经博主允许不得转载。
目录(?)[+]
Ajax全称Asynchronous JavaScript and XML,也就是异步的js和XML技术。现在网页中Ajax几乎无所不在,前后端的分离也离不开Ajax的异步通信技术。
现在的浏览器当中,虽然几乎都支持了Ajax,但他们的技术方案都分为两种:
1,标准浏览器是通过XMLHttpRequest
对象来实现Ajax的功能。
var xhr = new XMLHttpRequest();
2,IE浏览器是通过XMLHttpRequest或者ActiveXObjet对象来实现Ajax的功能。
但是由于IE版本众多,实现的方法也不尽相同。如果想要采取全兼容方案的话:
对于这个问题,我们可以需要知道浏览器的线程:
HTTP请求线程
那么这些线程,又是怎么和js引擎线程进行交互呢?
通常来说,线程间的交互是以事件的方式来发生的,通过事件回调的方式予以通知其他线程。但是事件回调,又是以先进先出的队列方式添加到任务队列
的末尾,等到js引擎空闲的时候,任务队列
中排队的任务就会依次被执行。
浏览器中,js引擎线程会循环从任务队列中读取事件并且执行,这种运行机制成为Event Loop(事件循环)。
对于一个Ajax请求,js引擎会首先生成XMLHttpRequest实例对象,然后完成open方法,调用send方法。到这里,所有的语句还是同步执行的。但是从send方法内部开始,浏览器要为将要发生的网络请求创建新的http请求线程,这个线程独立于js引擎线程,于是乎网络请求被异步发送出去了。但是另一方面,js引擎并不会等待Ajax发起的Http请求的Response,而是按顺序依次往下执行。
当Ajax请求被服务器响应并且User Agent收到response后,浏览器事件触发线程就会捕获到Ajax的回调事件onreadystatechange
(当然也可能触发onload或者onerror事件)。该回调事件并没有被立即执行,而是被添加到了任务队列
的末尾。直到js引擎空闲了,任务队列
的任务才被捞出来,按照添加的顺序,挨个执行,当然也包括刚刚append到队列末尾的onreadystatechange
事件。
在onreadystatechange事件内部,有可能对DOM进行操作,此时浏览器便会挂起js引擎线程,转而执行GUI渲染线程,进而UI重绘(repaint)或者回流(reflow)。当js引擎重新执行时,GUI渲染线程又会被挂起,GUI更新将被保存起来,等到js引擎空闲时立即被执行。
以上整个Ajax请求过程中,有涉及到浏览器的4种线程,其中除了GUI渲染线程
和js引擎线程
是互斥的。其他线程相互之间都是可以并行执行的。通过这样的一种方式,Ajax并没有破坏js的单线程机制。
通常来说,Ajax和setTimeout的事件回调都被同等的对待,那么就会按照顺序自动地被添加到任务队列
的末尾,等待js引擎空闲的时候,但请注意,并非xhr的所有回调执行都滞后于setTimeout的回调。
由于Ajax异步,所以setTimeout回调本应该最先被执行,然后实际上,一次ajax请求,并非所有的部分都是异步的。至少在readyState == 1 的onreadystatechange
回调以及onloadstart
回调就是同步执行的,因此它们的输出排在最前面。
首先在chrome console下创建一个XMLHttpRequest实例对象xhr,如下所示:
运行以下代码:
就会发现XMLHttpRequest实例对象其实并没有一个自有属性!这是因为它是BOM对象(Browser Object Model)
通常来说,一个xhr实例对象拥有10个普通属性和9个方法。
readyState
只读属性,readyState属性记录了ajax调用过程中所有可能的状态,它的取值简单明了,如下:
注意,readyState是一个只读属性,想要改变它的值是不可行的。
onreadystatechange
onreadystatechange事件回调方法是在readystate状态发生改变的时候触发的,在一个收到响应的ajax请求周期中,onreadystatechange方法会被触发4次。因此可以在onreadystatechange方法中绑定一些事件回调,比如:
注意: onreadystatechange回调中默认会传入Event实例,如下:
status
只读属性,status表示http请求的状态,初始值为0。就是状态码。
statusText
只读属性,statusText表示服务器的响应状态信息,它是一个UTF-16的字符串,请求成功且status == 20X时,返回的大写OK。请求失败的返回空字符串。其他情况下返回响应的状态描述。
onloadstart
onloadstart事件回调方法在ajax请求发送之前触发,触发的时机是在readyState == 1状态之后,readyState == 2 状态之前。也就是在open()方法调用之后,send()方法调用之前。
onprogress
onprogress事件回调方法在readystate==3
状态时触发,默认传入了ProgressEvent对象,可以通过e.loaded/e.total
来计算加载资源的进度,该方法用于获取资源的下载进度。
注意:该方法适用于IE10+及其他现代浏览器。
onload
onload事件回调方法在ajax请求成功后触发,触发时机在readyState == 4
状态孩子后,所以说要想捕捉到一个ajax异步请求的成功状态,并且执行回调,一般下面的语句就足够了:
onload
onloadend 事件回调方法在ajax请求完成时触发,触发时机在readyState == 4
状态之后(收到响应时)或者readyState ==2
状态之后(未收到响应时)。
注意:onloadend方法也会默认传入一个ProgressEvent事件进度对象。
timeout
timeout属性用于指定ajax的超时时长,通过它可以灵活地控制ajax的请求时间上限。timeout的值满足如下规则:
ontimeout
ontimeout方法在ajax请求超时时触发,通过它可以在ajax请求超时时做一些后续处理。
response responseText
均为只读属性,response表示服务器的响应内容,相应的,responseText表示服务器响应内容的文本形式。
responseXML
只读属性,responseXML表示xml形式的响应数据,缺省为null。如果数据不是xml格式,就会报错。
responseType
responseType表示响应的类型,缺省的话为空字符串,其值可取的''arraybuffer''
,''blob''
,''document''
,''json''
,and ''text''
共五种类型。
responseURL
reponseURL 返回 ajax 请求最终的URL。
withCredentials
withCredentials 是一个布尔值,默认为false,表示跨域请求中不发送cookie等信息。当它设置为true时,cookie
,authorization headers
或者TLS客户端证书
都可以正常发送和接收,显然它的值对同域请求没有影响。
注意: 该属性适用于IE10+, opera 12+ 及其他现代浏览器。
abort
abort方法用于取消ajax请求,取消后,readyState状态被设置为0(UNSENT)。如下,调用abort方法后,请求将被取消。
getResponseHeader
getResponseHeader 方法用于获取ajax 响应头中指定name 的值。 如果response header 中存在相同的name ,那么它们的值将自动以字符串的形式连接在一下。
getAllResponseHeaders
getAllRequestHeaders方法用于获取所有安全的ajax响应头,响应头以字符串形式返回。每个HTTP报头名称和值用冒号分隔,如key:value , 并以\r\n结束。
setRequestHeader
既然可以获取响应头,那么自然也也可以设置请求头,setRequestHeader就是干这个的,如下:
onerror
onerror 方法用于在ajax 请求出错后执行,通常只在网络出现问题时或者ERR_CONNECTION_RESET时触发(如果请求返回的是407状态码,chrome下也会触发onerror)
upload
upload 属性默认返回一个XMLHttpRequestUpload
对象,用于上传资源,该对象具有如下方法:
onprogress
事件回调方法可用于跟踪资源上传的进度。XHR1 即 XMLHttpRequest Level 1. XHR1时,xhr对象具有如下缺点:
同源策略
限制,只能请求同域资源。XHR2 即XHMHttpRequest Level 2。XHR2针对XHR1 的上述缺点做了如下改进:
同源策略
限制,这个安全机制是不会改变的,但是XHR2 提供了 Access-Control-Allow-Origin
等headers,设置为*
时表示允许任何域名请求,从而实现跨域CORS
访问。可以设置timeout及ontimeout,方便设置超时时长和超时后续处理。
这里就H5新增的FormData对象举个例子。
目前,主流浏览器都支持了XHR2,除了IE系列需要IE10及更高版本,因此IE10以下是不支持XHR2的。
而对于IE 8,9用户来说,可以使用一个阉割版的XDomainRequest
可用,IE7则没有。
$.ajax 是jQuery对原生ajax的一次封装。通过封装ajax,jQuery抹平了不同版本浏览器异步http的差异性,取而代之的是高度统一的api。
实际上,如果你仅仅只是想要一个不错的http库,相比于庞大臃肿的jQuery,短小精悍的Axios可能更加适合你,原因如下:
除了get, 它还支持post, delete, head, put, patch, request请求
说到ajax,就不得不提及fetch,可以点击:http://louiszhai.github.io/2016/11/02/fetch/
什么是CORS
CORS是一个W3C(World Wide Web 标准),全称是跨域资源共享(Cross-origin resource sharing)。它允许浏览器向跨域服务器,发出异步Http请求,从而克服了ajax受同源策略的限制。实际上,浏览器不会拦截不合法的跨域请求,而是拦截了他们的响应,因此即使请求不合法,很多时候,服务器依然接收到了请求(Chrome和Firefox 下https 网站不允许发送http异步请求除外)
移动端CORS兼容性
当前几乎所有的桌面浏览器(IE8+,Firefox 3.5+ ,Safari 4+ 和 Chrome 3+)都可以通过名为跨域资源共享的协议支持ajax跨域调用。
可见,CORS的技术在iOS Safari 7.1 及 Android webview 2.3 中就早已支持。
CORS 有关的headers
1)HTTP Request Header (服务器提供):
2)HTTP Request Header(浏览器OPTIONS请求默认自带):
3) 以下所有的header name 是被拒绝的:
- Accept-Charset
- Accept-Encoding
- Access-Control-Request-Headers
- Access-Control-Request-Method
- Connection
- Content-Length
- Cookie
- Cookie2
- Date
- DNT
- Expect
- Host
- Keep-Alive
- Origin
- Referer
- TE
- Trailer
- Transfer-Encoding
- Upgrade
- Via
- 包含以Proxy- 或 Sec- 开头的header name
CORS请求
CORS请求分为两种,1,简单请求;2,非简单请求。
满足如下两个条件的便是简单请求,反之则为非简单请求。
1,请求是以下三种之一:
2,http头域不超出以下几种字段:
application/x-www-form-urlencoded
,multipart/form-data
, text/plain
对于简单的请求,浏览器将发送一次http请求,同时在Request头域中增加Origin字段,用来标示请求发起的源,服务器根据这个源采取不同的响应策略,如服务器认为请求合法,那么需要往返会得HTTP Respinse
中添加 Access-Control-*
等字段。
对于非简单请求, 比如Method为POST且Content-Type值为 application/json 的请求或者Method为 PUT 或 DELETE 的请求, 浏览器将发送两次http请求. 第一次为preflight预检(Method: OPTIONS),主要验证来源是否合法. 值得注意的是:OPTION请求响应头同样需要包含 Access-Control-* 字段等. 第二次才是真正的HTTP请求. 所以服务器必须处理OPTIONS应答(通常需要返回20X的状态码, 否则xhr.onerror事件将被触发).
以上请求流程图为:
HTML启用CORS
http-equiv相当于http的响应头,它回应给浏览器一写有用的信息,以确保正确和精确地显示网页内容。
如下html将允许任意域名下的网页跨域访问。
图片启用CORS
通常,图片允许跨域访问,也可以在canvas中使用跨域的图片,但这样做会污染画布,一旦画布受污染,将无法读取其数据。
Ajax实现文件上传非常简单,这里我选取原生js,jq分别来比较下,并顺便聊聊使用它们时的注意事项。
1) 为了上传文件,我们得先选中一个文件,一个type为file 的input框就够了。
2) 然后用FormData 对象包裹选中的文件。
3) 定义上传的URL。
js文件上传
4.2 )上传文件并绑定事件.
fetch 上传
5) fetch 上传只要发送一个post请求,并且body属性设置为formData即可。遗憾的是,fetch无法跟踪上传的进度信息。
jquery文件上传
jq提供了各式各样的上传插件,其原理都是利用jq自身的ajax方法。
6) jq的ajax提供了xhr属性用于自定义各种事件
FileReader
ajax请求二进制图片并预览
ajax请求二进制文本并展示
有关二进制文件的读取, 请移步这篇博客http://www.cnblogs.com/jscode/archive/2013/04/27/3572239.html
如何等待多个ajax请求完成
原生js可以使用ES6新增的普若迷斯(Promise). ES6的Promise基于Promises/A+规范(该部分 Fetch入门指南 一文也有提及).
这里先提供一个解析responses的函数.
原生js使用 Promise.all 方法. 如下:
jquery可以使用$.when方法. 该方法接受一个或多个Deferred对象作为参数, 只有全部成功才调用resolved状态的回调函数, 但只要其中有一个失败,就调用rejected状态的回调函数. 其实, jq的Deferred是基于 Promises/A规范实现, 但并非完全遵循. (传送门: jQuery 中的 Deferred 和 Promises (2) ).
如上, $.when默认返回一个jqXHR对象, 可以直接进行链式调用. then方法的回调中默认传入相应的请求结果, 每个请求结果的都是数组, 数组中依次是responseText, 请求状态, 请求的jqXHR对象.
ajax与history的兼容