摘要:導(dǎo)讀全稱即異步與它最早在中被使用然后由推廣開來(lái)典型的代表應(yīng)用有以及現(xiàn)代網(wǎng)頁(yè)中幾乎無(wú)不歡前后端分離也正是建立在異步通信的基礎(chǔ)之上瀏覽器為做了什么現(xiàn)代瀏覽器中雖然幾乎全部支持但它們的技術(shù)方案卻分為兩種標(biāo)準(zhǔn)瀏覽器通過(guò)對(duì)象實(shí)現(xiàn)了的功能只需要通過(guò)一行
導(dǎo)讀
Ajax 全稱 Asynchronous JavaScript and XML, 即異步JS與XML. 它最早在IE5中被使用, 然后由Mozilla, Apple, Google推廣開來(lái). 典型的代表應(yīng)用有 Outlook Web Access, 以及 GMail. 現(xiàn)代網(wǎng)頁(yè)中幾乎無(wú)ajax不歡. 前后端分離也正是建立在ajax異步通信的基礎(chǔ)之上.
瀏覽器為ajax做了什么現(xiàn)代瀏覽器中, 雖然幾乎全部支持ajax, 但它們的技術(shù)方案卻分為兩種:
① 標(biāo)準(zhǔn)瀏覽器通過(guò) XMLHttpRequest 對(duì)象實(shí)現(xiàn)了ajax的功能. 只需要通過(guò)一行語(yǔ)句便可創(chuàng)建一個(gè)用于發(fā)送ajax請(qǐng)求的對(duì)象.
var xhr = new XMLHttpRequest();
② IE瀏覽器通過(guò) XMLHttpRequest 或者 ActiveXObject 對(duì)象同樣實(shí)現(xiàn)了ajax的功能.
MSXML鑒于IE系列各種 "神級(jí)" 表現(xiàn), 我們先來(lái)看看IE瀏覽器風(fēng)騷的走位.
IE下的使用環(huán)境略顯復(fù)雜, IE7及更高版本瀏覽器可以直接使用BOM的 XMLHttpRequest 對(duì)象. MSDN傳送門: Native XMLHTTPRequest object. IE6及更低版本瀏覽器只能使用 ActiveXObject 對(duì)象來(lái)創(chuàng)建 XMLHttpRequest 對(duì)象實(shí)例. 創(chuàng)建時(shí)需要指明一個(gè)類似"Microsoft.XMLHTTP"這樣的ProgID. 而實(shí)際呢, windows系統(tǒng)環(huán)境下, 以下ProgID都應(yīng)該可以創(chuàng)建XMLHTTP對(duì)象:
Microsoft.XMLHTTP Microsoft.XMLHTTP.1.0 Msxml2.ServerXMLHTTP Msxml2.ServerXMLHTTP.3.0 Msxml2.ServerXMLHTTP.4.0 Msxml2.ServerXMLHTTP.5.0 Msxml2.ServerXMLHTTP.6.0 Msxml2.XMLHTTP Msxml2.XMLHTTP.3.0 Msxml2.XMLHTTP.4.0 Msxml2.XMLHTTP.5.0 Msxml2.XMLHTTP.6.0
簡(jiǎn)言之, Microsoft.XMLHTTP 已經(jīng)非常老了, 主要用于提供對(duì)歷史遺留版本的支持, 不建議使用.對(duì)于 MSXML4, 它已被 MSXML6 替代; 而 MSXML5 又是專門針對(duì)office辦公場(chǎng)景, 在沒有安裝 Microsoft Office 2003 及更高版本辦公軟件的情況下, MSXML5 未必可用. 相比之下, MSXML6 具有比 MSXML3 更穩(wěn)定, 更高性能, 更安全的優(yōu)勢(shì), 同時(shí)它也提供了一些 MSXML3 中沒有的功能, 比如說(shuō) XSD schema. 唯一遺憾的是, MSXML6 只在 vista 系統(tǒng)及以上才是默認(rèn)支持的; 而 MSXML3 在 Win2k SP4及以上系統(tǒng)就是可用的. 因此一般情況下, MSXML3 可以作為 MSXML6 的優(yōu)雅降級(jí)方案, 我們通過(guò)指定 PorgID 為 Msxml2.XMLHTTP 即可自動(dòng)映射到 Msxml2.XMLHTTP.3.0. 如下所示:
var xhr = new ActiveXObject("Msxml2.XMLHTTP");// 即MSXML3,等同于如下語(yǔ)句 var xhr = new ActiveXObject("MSXML2.XMLHTTP.3.0");
MSDN有篇文章專門講解了各個(gè)版本的MSXML. 傳送門: Using the right version of MSXML in Internet Explorer.
親測(cè)了 IE5, IE5.5, IE6, IE7, IE8, IE9, IE10, IE edge等瀏覽器, IE5及之后的瀏覽器均可以通過(guò)如下語(yǔ)句獲取xhr對(duì)象:
var xhr = new ActiveXObject("Msxml2.XMLHTTP");// 即MSXML3 var xhr = new ActiveXObject("Microsoft.XMLHTTP");// 很老的api,雖然瀏覽器支持,功能可能不完善,故不建議使用
以上, 思路已經(jīng)很清晰了, 下面給出個(gè)全兼容的方法.
全平臺(tái)兼容的XMLHttpRequest對(duì)象function getXHR(){ var xhr = null; if(window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { alert("您的瀏覽器暫不支持Ajax!"); } } } return xhr; }ajax有沒有破壞js單線程機(jī)制
對(duì)于這個(gè)問(wèn)題, 我們先看下瀏覽器線程機(jī)制. 一般情況下, 瀏覽器有如下四種線程:
GUI渲染線程
javascript引擎線程
瀏覽器事件觸發(fā)線程
HTTP請(qǐng)求線程
那么這么多線程, 它們究竟是怎么同js引擎線程交互的呢?
通常, 它們的線程間交互以事件的方式發(fā)生, 通過(guò)事件回調(diào)的方式予以通知. 而事件回調(diào), 又是以先進(jìn)先出的方式添加到任務(wù)隊(duì)列 的末尾 , 等到j(luò)s引擎空閑時(shí), 任務(wù)隊(duì)列 中排隊(duì)的任務(wù)將會(huì)依次被執(zhí)行. 這些事件回調(diào)包括 setTimeout, setInterval, click, ajax異步請(qǐng)求等回調(diào).
瀏覽器中, js引擎線程會(huì)循環(huán)從 任務(wù)隊(duì)列 中讀取事件并且執(zhí)行, 這種運(yùn)行機(jī)制稱作 Event Loop (事件循環(huán)).
對(duì)于一個(gè)ajax請(qǐng)求, js引擎首先生成 XMLHttpRequest 實(shí)例對(duì)象, open過(guò)后再調(diào)用send方法. 至此, 所有的語(yǔ)句都是同步執(zhí)行. 但從send方法內(nèi)部開始, 瀏覽器為將要發(fā)生的網(wǎng)絡(luò)請(qǐng)求創(chuàng)建了新的http請(qǐng)求線程, 這個(gè)線程獨(dú)立于js引擎線程, 于是網(wǎng)絡(luò)請(qǐng)求異步被發(fā)送出去了. 另一方面, js引擎并不會(huì)等待 ajax 發(fā)起的http請(qǐng)求收到結(jié)果, 而是直接順序往下執(zhí)行.
當(dāng)ajax請(qǐng)求被服務(wù)器響應(yīng)并且收到response后, 瀏覽器事件觸發(fā)線程捕獲到了ajax的回調(diào)事件 onreadystatechange (當(dāng)然也可能觸發(fā)onload, 或者 onerror等等) . 該回調(diào)事件并沒有被立即執(zhí)行, 而是被添加到 任務(wù)隊(duì)列 的末尾. 直到j(luò)s引擎空閑了, 任務(wù)隊(duì)列 的任務(wù)才被撈出來(lái), 按照添加順序, 挨個(gè)執(zhí)行, 當(dāng)然也包括剛剛append到隊(duì)列末尾的 onreadystatechange 事件.
在 onreadystatechange 事件內(nèi)部, 有可能對(duì)dom進(jìn)行操作. 此時(shí)瀏覽器便會(huì)掛起js引擎線程, 轉(zhuǎn)而執(zhí)行GUI渲染線程, 進(jìn)行UI重繪(repaint)或者回流(reflow). 當(dāng)js引擎重新執(zhí)行時(shí), GUI渲染線程又會(huì)被掛起, GUI更新將被保存起來(lái), 等到j(luò)s引擎空閑時(shí)立即被執(zhí)行.
以上整個(gè)ajax請(qǐng)求過(guò)程中, 有涉及到瀏覽器的4種線程. 其中除了 GUI渲染線程 和 js引擎線程 是互斥的. 其他線程相互之間, 都是可以并行執(zhí)行的. 通過(guò)這樣的一種方式, ajax并沒有破壞js的單線程機(jī)制.
ajax與setTimeout排隊(duì)問(wèn)題通常, ajax 和 setTimeout 的事件回調(diào)都被同等的對(duì)待, 按照順序自動(dòng)的被添加到 任務(wù)隊(duì)列 的末尾, 等待js引擎空閑時(shí)執(zhí)行. 但請(qǐng)注意, 并非xhr的所有回調(diào)執(zhí)行都滯后于setTImeout的回調(diào). 請(qǐng)看如下代碼:
function ajax(url, method){ var xhr = getXHR(); xhr.onreadystatechange = function(){ console.log("xhr.readyState:" + this.readyState); } xhr.onloadstart = function(){ console.log("onloadStart"); } xhr.onload = function(){ console.log("onload"); } xhr.open(method, url, true); xhr.setRequestHeader("Cache-Control",3600); xhr.send(); } var timer = setTimeout(function(){ console.log("setTimeout"); },0); ajax("http://louiszhai.github.io/docImages/ajax01.png","GET");
上述代碼執(zhí)行結(jié)果如下圖:
由于ajax異步, setTimeout回調(diào)本應(yīng)該最先被執(zhí)行, 然而實(shí)際上, 一次ajax請(qǐng)求, 并非所有的部分都是異步的, 至少"readyState==1"的 onreadystatechange 回調(diào)以及 onloadstart 回調(diào)就是同步執(zhí)行的. 因此它們的輸出排在最前面.
XMLHttpRequest 屬性解讀首先在Chrome console下創(chuàng)建一個(gè) XMLHttpRequest 實(shí)例對(duì)象xhr. 如下所示:
inherit試運(yùn)行以下代碼.
var xhr = new XMLHttpRequest(), i=0; for(var key in xhr){ if(xhr.hasOwnProperty(key)){ i++; } } console.log(i);//0 console.log(XMLHttpRequest.prototype.hasOwnProperty("timeout"));//true
可見, XMLHttpRequest 實(shí)例對(duì)象沒有自有屬性. 實(shí)際上, 它的所有屬性均來(lái)自于 XMLHttpRequest.prototype .
追根溯源, XMLHttpRequest 實(shí)例對(duì)象具有如下的繼承關(guān)系. (下面以a<
xhr << XMLHttpRequest.prototype << XMLHttpRequestEventTarget.prototype << EventTarget.prototype << Object.prototype
由上, xhr也具有Object等原型中的所有方法. 如toString方法.
xhr.toString();//"[object XMLHttpRequest]"
通常, 一個(gè)xhr實(shí)例對(duì)象擁有10個(gè)普通屬性+9個(gè)方法.
readyState只讀屬性, readyState屬性記錄了ajax調(diào)用過(guò)程中所有可能的狀態(tài). 它的取值簡(jiǎn)單明了, 如下:
readyState | 對(duì)應(yīng)常量 | 描述 |
---|---|---|
0 (未初始化) | xhr.UNSENT | 請(qǐng)求已建立, 但未初始化(此時(shí)未調(diào)用open方法) |
1 (初始化) | xhr.OPENED | 請(qǐng)求已建立, 但未發(fā)送 (已調(diào)用open方法, 但未調(diào)用send方法) |
2 (發(fā)送數(shù)據(jù)) | xhr.HEADERS_RECEIVED | 請(qǐng)求已發(fā)送 (send方法已調(diào)用, 已收到響應(yīng)頭) |
3 (數(shù)據(jù)傳送中) | xhr.LOADING | 請(qǐng)求處理中, 因響應(yīng)內(nèi)容不全, 這時(shí)通過(guò)responseBody和responseText獲取可能會(huì)出現(xiàn)錯(cuò)誤 |
4 (完成) | xhr.DONE | 數(shù)據(jù)接收完畢, 此時(shí)可以通過(guò)通過(guò)responseBody和responseText獲取完整的響應(yīng)數(shù)據(jù) |
注意, readyState 是一個(gè)只讀屬性, 想要改變它的值是不可行的.
onreadystatechangeonreadystatechange事件回調(diào)方法在readystate狀態(tài)改變時(shí)觸發(fā), 在一個(gè)收到響應(yīng)的ajax請(qǐng)求周期中, onreadystatechange 方法會(huì)被觸發(fā)4次. 因此可以在 onreadystatechange 方法中綁定一些事件回調(diào), 比如:
xhr.onreadystatechange = function(e){ if(xhr.readystate==4){ var s = xhr.status; if((s >= 200 && s < 300) || s == 304){ var resp = xhr.responseText; //TODO ... } } }
注意: onreadystatechange回調(diào)中默認(rèn)會(huì)傳入Event實(shí)例, 如下:
status只讀屬性, status表示http請(qǐng)求的狀態(tài), 初始值為0. 如果服務(wù)器沒有顯式地指定狀態(tài)碼, 那么status將被設(shè)置為默認(rèn)值, 即200.
statusText只讀屬性, statusText表示服務(wù)器的響應(yīng)狀態(tài)信息, 它是一個(gè) UTF-16 的字符串, 請(qǐng)求成功且status==20X時(shí), 返回大寫的 OK . 請(qǐng)求失敗時(shí)返回空字符串. 其他情況下返回相應(yīng)的狀態(tài)描述. 比如: 301的 Moved Permanently , 302的 Found , 303的 See Other , 307 的 Temporary Redirect , 400的 Bad Request , 401的 Unauthorized 等等.
onloadstartonloadstart事件回調(diào)方法在ajax請(qǐng)求發(fā)送之前觸發(fā), 觸發(fā)時(shí)機(jī)在 readyState==1 狀態(tài)之后, readyState==2 狀態(tài)之前.
onloadstart方法中默認(rèn)將傳入一個(gè)ProgressEvent事件進(jìn)度對(duì)象. 如下:
ProgressEvent對(duì)象具有三個(gè)重要的Read only屬性.
lengthComputable 表示長(zhǎng)度是否可計(jì)算, 它是一個(gè)布爾值, 初始值為false.
loaded 表示已加載資源的大小, 如果使用http下載資源, 它僅僅表示已下載內(nèi)容的大小, 而不包括http headers等. 它是一個(gè)無(wú)符號(hào)長(zhǎng)整型, 初始值為0.
total 表示資源總大小, 如果使用http下載資源, 它僅僅表示內(nèi)容的總大小, 而不包括http headers等, 它同樣是一個(gè)無(wú)符號(hào)長(zhǎng)整型, 初始值為0.
onprogressonprogress事件回調(diào)方法在 readyState==3 狀態(tài)時(shí)開始觸發(fā), 默認(rèn)傳入 ProgressEvent 對(duì)象, 可通過(guò) e.loaded/e.total 來(lái)計(jì)算加載資源的進(jìn)度, 該方法用于獲取資源的下載進(jìn)度.
注意: 該方法適用于 IE10+ 及其他現(xiàn)代瀏覽器.
xhr.onprogress = function(e){ console.log("progress:", e.loaded/e.total); }onload
onload事件回調(diào)方法在ajax請(qǐng)求成功后觸發(fā), 觸發(fā)時(shí)機(jī)在 readyState==4 狀態(tài)之后.
想要捕捉到一個(gè)ajax異步請(qǐng)求的成功狀態(tài), 并且執(zhí)行回調(diào), 一般下面的語(yǔ)句就足夠了:
xhr.onload = function(){ var s = xhr.status; if((s >= 200 && s < 300) || s == 304){ var resp = xhr.responseText; //TODO ... } }onloadend
onloadend事件回調(diào)方法在ajax請(qǐng)求完成后觸發(fā), 觸發(fā)時(shí)機(jī)在 readyState==4 狀態(tài)之后(收到響應(yīng)時(shí)) 或者 readyState==2 狀態(tài)之后(未收到響應(yīng)時(shí)).
onloadend方法中默認(rèn)將傳入一個(gè)ProgressEvent事件進(jìn)度對(duì)象.
timeouttimeout屬性用于指定ajax的超時(shí)時(shí)長(zhǎng). 通過(guò)它可以靈活地控制ajax請(qǐng)求時(shí)間的上限. timeout的值滿足如下規(guī)則:
通常設(shè)置為0時(shí)不生效.
設(shè)置為字符串時(shí), 如果字符串中全部為數(shù)字, 它會(huì)自動(dòng)將字符串轉(zhuǎn)化為數(shù)字, 反之該設(shè)置不生效.
設(shè)置為對(duì)象時(shí), 如果該對(duì)象能夠轉(zhuǎn)化為數(shù)字, 那么將設(shè)置為轉(zhuǎn)化后的數(shù)字.
xhr.timeout = 0; //不生效 xhr.timeout = "123"; //生效, 值為123 xhr.timeout = "123s"; //不生效 xhr.timeout = ["123"]; //生效, 值為123 xhr.timeout = {a:123}; //不生效ontimeout
ontimeout方法在ajax請(qǐng)求超時(shí)時(shí)觸發(fā), 通過(guò)它可以在ajax請(qǐng)求超時(shí)時(shí)做一些后續(xù)處理.
xhr.ontimeout = function(e) { console.error("請(qǐng)求超時(shí)!!!") }response responseText
均為只讀屬性, response表示服務(wù)器的響應(yīng)內(nèi)容, 相應(yīng)的, responseText表示服務(wù)器響應(yīng)內(nèi)容的文本形式.
responseXML只讀屬性, responseXML表示xml形式的響應(yīng)數(shù)據(jù), 缺省為null, 若數(shù)據(jù)不是有效的xml, 則會(huì)報(bào)錯(cuò).
responseTyperesponseType表示響應(yīng)的類型, 缺省為空字符串, 可取 "arraybuffer" , "blob" , "document" , "json" , and "text" 共五種類型.
responseURLresponseURL返回ajax請(qǐng)求最終的URL, 如果請(qǐng)求中存在重定向, 那么responseURL表示重定向之后的URL.
withCredentialswithCredentials是一個(gè)布爾值, 默認(rèn)為false, 表示跨域請(qǐng)求中不發(fā)送cookies等信息. 當(dāng)它設(shè)置為true時(shí), cookies , authorization headers 或者 TLS客戶端證書 都可以正常發(fā)送和接收. 顯然它的值對(duì)同域請(qǐng)求沒有影響.
注意: 該屬性適用于 IE10+, opera12+及其他現(xiàn)代瀏覽器.
abortabort方法用于取消ajax請(qǐng)求, 取消后, readyState 狀態(tài)將被設(shè)置為?0?(UNSENT). 如下, 調(diào)用abort 方法后, 請(qǐng)求將被取消.
getResponseHeadergetResponseHeader方法用于獲取ajax響應(yīng)頭中指定name的值. 如果response headers中存在相同的name, 那么它們的值將自動(dòng)以字符串的形式連接在一起.
console.log(xhr.getResponseHeader("Content-Type"));//"text/html"getAllResponseHeaders
getAllResponseHeaders方法用于獲取所有安全的ajax響應(yīng)頭, 響應(yīng)頭以字符串形式返回. 每個(gè)HTTP報(bào)頭名稱和值用冒號(hào)分隔, 如key:value, 并以rn結(jié)束.
xhr.onreadystatechange = function() { if(this.readyState == this.HEADERS_RECEIVED) { console.log(this.getAllResponseHeaders()); } } //Content-Type: text/html"
以上, readyState === 2 狀態(tài)時(shí), 就意味著響應(yīng)頭已接受完整. 此時(shí)便可以打印出完整的 response headers.
setRequestHeader既然可以獲取響應(yīng)頭, 那么自然也可以設(shè)置請(qǐng)求頭, setRequestHeader就是干這個(gè)的. 如下:
//指定請(qǐng)求的type為json格式 xhr.setRequestHeader("Content-type", "application/json"); //除此之外, 還可以設(shè)置其他的請(qǐng)求頭 xhr.setRequestHeader("x-requested-with", "123456");onerror
onerror方法用于在ajax請(qǐng)求出錯(cuò)后執(zhí)行. 通常只在網(wǎng)絡(luò)出現(xiàn)問(wèn)題時(shí)或者ERR_CONNECTION_RESET時(shí)觸發(fā)(如果請(qǐng)求返回的是407狀態(tài)碼, chrome下也會(huì)觸發(fā)onerror).
uploadupload屬性默認(rèn)返回一個(gè) XMLHttpRequestUpload 對(duì)象, 用于上傳資源. 該對(duì)象具有如下方法:
onloadstart
onprogress
onabort
onerror
onload
ontimeout
onloadend
上述方法功能同 xhr 對(duì)象中同名方法一致. 其中, onprogress 事件回調(diào)方法可用于跟蹤資源上傳的進(jìn)度.
xhr.upload.onprogress = function(e){ var percent = 100 * e.loaded / e.total |0; console.log("upload: " + precent + "%"); }overrideMimeType
overrideMimeType方法用于強(qiáng)制指定response 的 MIME 類型, 即強(qiáng)制修改response的 Content-Type . 如下, 服務(wù)器返回的response的 MIME 類型為 text/plain .
xhr.getResponseHeader("Content-Type");//"text/plain" xhr.responseXML;//null
通過(guò)overrideMimeType方法將response的MIME類型設(shè)置為 text/xml;charset=utf-8 , 如下所示:
xhr.overrideMimeType("text/xml; charset = utf-8"); xhr.send();
此時(shí)雖然 response headers 如上圖, 沒有變化, 但 Content-Type 已替換為新值.
xhr.getResponseHeader("Content-Type");//"text/xml; charset = utf-8"
此時(shí), xhr.responseXML 也將返回DOM對(duì)象, 如下圖.
XHR一級(jí)XHR1 即 XMLHttpRequest Level 1. XHR1時(shí), xhr對(duì)象具有如下缺點(diǎn):
僅支持文本數(shù)據(jù)傳輸, 無(wú)法傳輸二進(jìn)制數(shù)據(jù).
傳輸數(shù)據(jù)時(shí), 沒有進(jìn)度信息提示, 只能提示是否完成.
受瀏覽器 同源策略 限制, 只能請(qǐng)求同域資源.
沒有超時(shí)機(jī)制, 不方便掌控ajax請(qǐng)求節(jié)奏.
XHR二級(jí)XHR2 即 XMLHttpRequest Level 2. XHR2針對(duì)XHR1的上述缺點(diǎn)做了如下改進(jìn):
支持二進(jìn)制數(shù)據(jù), 可以上傳文件, 可以使用FormData對(duì)象管理表單.
提供進(jìn)度提示, 可通過(guò) xhr.upload.onprogress 事件回調(diào)方法獲取傳輸進(jìn)度.
依然受 同源策略 限制, 這個(gè)安全機(jī)制不會(huì)變. XHR2新提供 Access-Control-Allow-Origin 等headers, 設(shè)置為 * 時(shí)表示允許任何域名請(qǐng)求, 從而實(shí)現(xiàn)跨域CORS訪問(wèn)(有關(guān)CORS詳細(xì)介紹請(qǐng)耐心往下讀).
可以設(shè)置timeout 及 ontimeout, 方便設(shè)置超時(shí)時(shí)長(zhǎng)和超時(shí)后續(xù)處理.
這里就H5新增的FormData對(duì)象舉個(gè)例.
//可直接創(chuàng)建FormData實(shí)例 var data = new FormData(); data.append("name", "louis"); xhr.send(data); //還可以通過(guò)傳入表單DOM對(duì)象來(lái)創(chuàng)建FormData實(shí)例 var form = document.getElementById("form"); var data = new FormData(form); data.append("password", "123456"); xhr.send(data);
目前, 主流瀏覽器基本上都支持XHR2, 除了IE系列需要IE10及更高版本. 因此IE10以下是不支持XHR2的.
那么問(wèn)題來(lái)了, IE7, 8,9的用戶怎么辦? 很遺憾, 這些用戶是比較尷尬的. 對(duì)于IE8,9而言, 只有一個(gè)閹割版的 XDomainRequest 可用,IE7則沒有. 估計(jì)IE7用戶只能哭暈在廁所了.
XDomainRequestXDomainRequest 對(duì)象是IE8,9折騰出來(lái)的, 用于支持CORS請(qǐng)求非成熟的解決方案. 以至于IE10中直接移除了它, 并重新回到了 XMLHttpRequest 的懷抱.
XDomainRequest 僅可用于發(fā)送 GET 和 POST 請(qǐng)求. 如下即創(chuàng)建過(guò)程.
var xdr = new XDomainRequest();
xdr具有如下屬性:
timeout
responseText
如下方法:
open: 只能接收Method,和url兩個(gè)參數(shù). 只能發(fā)送異步請(qǐng)求.
send
abort
如下事件回調(diào):
onprogress
ontimeout
onerror
onload
除了缺少一些方法外, XDomainRequest 基本上就和 XMLHttpRequest 的使用方式保持一致.
必須要明確的是:
XDomainRequest 不支持跨域傳輸cookie.
只能設(shè)置請(qǐng)求頭的Content-Type字段, 且不能訪問(wèn)響應(yīng)頭信息.
$.ajax$.ajax是jquery對(duì)原生ajax的一次封裝. 通過(guò)封裝ajax, jquery抹平了不同版本瀏覽器異步http的差異性, 取而代之的是高度統(tǒng)一的api. jquery作為js類庫(kù)時(shí)代的先驅(qū), 對(duì)前端發(fā)展有著深遠(yuǎn)的影響. 了解并熟悉其ajax方法, 不可謂不重要.
參數(shù)列表$.ajax() 只有一個(gè)參數(shù), 該參數(shù)為key-value設(shè)置對(duì)象. 實(shí)際上, jq發(fā)送的所有ajax請(qǐng)求, 都是通過(guò)調(diào)用該ajax方法實(shí)現(xiàn)的. 它的詳細(xì)參數(shù)如下表:
序號(hào) | 參數(shù) | 類型 | 描述 |
---|---|---|---|
1 | accepts | PlainObject | 用于通知服務(wù)器該請(qǐng)求需要接收何種類型的返回結(jié)果. 如有必要, 推薦在 $.ajaxSetup()?方法中設(shè)置一次. |
2 | async | Boolean | 默認(rèn)為true, 即異步. |
3 | beforeSend | Function | 請(qǐng)求發(fā)送前的回調(diào), 默認(rèn)傳入?yún)?shù)jqXHR和settings. 函數(shù)內(nèi)顯式返回false將取消本次請(qǐng)求. |
4 | cache | Boolean | 請(qǐng)求是否開啟緩存, 默認(rèn)為true, 如不需要緩存請(qǐng)?jiān)O(shè)置為false. 不過(guò), dataType為"script"和"jsonp"時(shí)默認(rèn)為false. |
5 | complete | Function | 請(qǐng)求完成后的回調(diào)(請(qǐng)求success?和?error之后均調(diào)用), 默認(rèn)傳入?yún)?shù)jqXHR和textStatus(請(qǐng)求狀態(tài), 取值為 "success","notmodified","error","timeout","abort","parsererror"之一). 從jq1.5開始, complete可以設(shè)置為一個(gè)包含函數(shù)的數(shù)組. 如此每個(gè)函數(shù)將依次被調(diào)用. |
6 | contents | PlainObject | 一個(gè)以"{字符串/正則表達(dá)式}"配對(duì)的對(duì)象, 根據(jù)給定的內(nèi)容類型, 解析請(qǐng)求的返回結(jié)果. |
7 | contentType | String | 編碼類型, 相對(duì)應(yīng)于http請(qǐng)求頭域的"Content-Type"字段. 默認(rèn)值為"application/x-www-form-urlencoded; charset=UTF-8". |
8 | context | Object | 設(shè)置ajax回調(diào)函數(shù)的上下文. 默認(rèn)上下文為ajax請(qǐng)求傳入的參數(shù)設(shè)置對(duì)象. 如設(shè)置為document.body, 那么所有ajax回調(diào)函數(shù)中將以body為上下文. |
9 | converters | PlainObject | 一個(gè)數(shù)據(jù)類型到數(shù)據(jù)類型轉(zhuǎn)換器的對(duì)象. 默認(rèn)為 {"* text": window.String, "text html": true, "text json": jQuery.parseJSON, "text xml": jQuery.parseXML} . 如設(shè)置converters:{"json jsonp": function(msg){}} |
10 | crossDomain | Boolean | 默認(rèn)同域請(qǐng)求為false, 跨域請(qǐng)求為true. |
11 | data | Object, Array | 發(fā)送到服務(wù)器的數(shù)據(jù), 默認(rèn)data為鍵值對(duì)格式對(duì)象, 若data為數(shù)組則按照traditional參數(shù)的值, 自動(dòng)轉(zhuǎn)化為一個(gè)同名的多值查詢字符串. 如{a:1,b:2}將轉(zhuǎn)換為"&a=1&b=2". |
12 | dataFilter | Function | 處理XMLHttpRequest原始響應(yīng)數(shù)據(jù)的回調(diào), 默認(rèn)傳入data和type參數(shù), data是Ajax返回的原始數(shù)據(jù), type是調(diào)用$.ajax時(shí)提供的dataType參數(shù) |
13 | dataType | String | 預(yù)期服務(wù)器返回的數(shù)據(jù)類型, 可設(shè)置為"xml","html","script","json","jsonp","text"之一, 其中設(shè)置為"xml"或"text"類型時(shí), 數(shù)據(jù)不會(huì)經(jīng)過(guò)處理. |
14 | error | Function | 請(qǐng)求失敗時(shí)的回調(diào)函數(shù), 默認(rèn)傳入jqXHR(jq1.4以前為原生xhr對(duì)象),textStatus(請(qǐng)求狀態(tài),取值為null,"timeout","error","abort" 或 "parsererror"),errorString(錯(cuò)誤內(nèi)容), 當(dāng)一個(gè)HTTP錯(cuò)誤發(fā)生時(shí), errorThrown?接收HTTP狀態(tài)的文本部分,比如"Not Found"等. 從jq1.5開始, error可以設(shè)置為一個(gè)包含函數(shù)的數(shù)組. 如此每個(gè)函數(shù)將依次被調(diào)用.注意: 跨域腳本和JSONP請(qǐng)求時(shí)error不被調(diào)用. |
15 | global | Boolean | 表示是否觸發(fā)全局ajax事件, 默認(rèn)為true. 設(shè)為false將不再觸發(fā)ajaxStart,ajaxStop,ajaxSend,ajaxError等. 跨站腳本和jsonp請(qǐng)求, 該值自動(dòng)設(shè)置為false. |
16 | headers | PlainObject | 設(shè)置請(qǐng)求頭, 格式為k-v鍵值對(duì)對(duì)象. 由于該設(shè)置會(huì)在beforeSend函數(shù)被調(diào)用之前生效, 因此可在beforeSend函數(shù)內(nèi)覆蓋該對(duì)象. |
17 | ifModified | Boolean | 只有上次請(qǐng)求響應(yīng)改變時(shí), 才允許請(qǐng)求成功. 它使用HTTP包的Last-Modified 頭信息判斷, 默認(rèn)為false. 若設(shè)置為true, 且數(shù)據(jù)自從上次請(qǐng)求后沒有更改過(guò)就會(huì)報(bào)錯(cuò). |
18 | isLocal | Boolean | 運(yùn)行當(dāng)前環(huán)境設(shè)置為"本地",默認(rèn)為false, 若設(shè)置為true, 將影響請(qǐng)求發(fā)送時(shí)的協(xié)議. |
19 | jsonp | String | 顯式指定jsonp請(qǐng)求中的回調(diào)函數(shù)的名稱. 如jsonp:cb, jq會(huì)將cb代替callback, 以 "cb=?"傳給服務(wù)器. 從jq1.5開始, 若設(shè)置jsonp:false, 那么需要明確設(shè)置jsonpCallback:"callbackName". |
20 | jsonpCallback | String,Function | 為jsonp請(qǐng)求指定一個(gè)回調(diào)函數(shù)名, 以取代jq自動(dòng)生成的隨機(jī)函數(shù)名. 從jq1.5開始, 可以將該屬性設(shè)置為一個(gè)函數(shù), 函數(shù)的返回值就是jsonpCallback的結(jié)果. |
21 | mimeType | String | 設(shè)置一個(gè)MIME類型, 以覆蓋xhr的MIM類型(jq1.5新增) |
22 | password | String | 設(shè)置認(rèn)證請(qǐng)求中的密碼 |
23 | processData | Boolean | jq的ajax方法默認(rèn)會(huì)將傳入的data隱式轉(zhuǎn)換為查詢字符串(如"&a=1&b=2"), 以配合 默認(rèn)內(nèi)容類型 "application/x-www-form-urlencoded", 如果不希望轉(zhuǎn)換請(qǐng)?jiān)O(shè)置為false. angular中想要禁用默認(rèn)轉(zhuǎn)換, 需要重寫transformRequest方法. |
24 | scriptCharset | String | 僅在"script"請(qǐng)求中使用(如跨域jsonp, dataType為"script"類型). 顯式指定時(shí), 請(qǐng)求中將在script標(biāo)簽上設(shè)置charset屬性, 可在發(fā)現(xiàn)本地和遠(yuǎn)程編碼不一致時(shí)使用. |
25 | statusCode | PlainObject | 一組http狀態(tài)碼和回調(diào)函數(shù)對(duì)應(yīng)的鍵值對(duì)對(duì)象. 該對(duì)象以 {404:function(){}} 這種形式表示. 可用于根據(jù)不同的http狀態(tài)碼, 執(zhí)行不同的回調(diào).(jq1.5新增) |
26 | timeout | Number | 設(shè)置超時(shí)時(shí)間. |
27 | traditional | Boolean | 是否按照默認(rèn)方式序列化data對(duì)象, 默認(rèn)值為false. |
28 | type | String | 可以設(shè)置為8種http method之一, jq中不區(qū)分大小寫. |
29 | url | String | 請(qǐng)求的uri地址. |
30 | username | String | 設(shè)置認(rèn)證請(qǐng)求中的用戶名 |
31 | xhr | Function | 在回調(diào)內(nèi)創(chuàng)建并返回xhr對(duì)象 |
32 | xhrFields | PlainObject | 鍵值對(duì)對(duì)象, 用于設(shè)置原生的xhr對(duì)象, 如可用來(lái)設(shè)置withCredentials:true(jq1.5.1新增) |
$.ajax() 方法返回jqXHR對(duì)象(jq1.5起), 如果使用的不是XMLHttpRequest對(duì)象時(shí), 如jsonp請(qǐng)求, 返回的jqXHR對(duì)象將盡可能模擬原生的xhr. 從jq1.5起, 返回的jqXHR對(duì)象實(shí)現(xiàn)了promise接口, 具有如下新方法.
新方法 | 被替代的老方法(jq1.8起棄用) |
---|---|
done(function(data, textStatus, jqXHR) {}) | |
fail(function(jqXHR, textStatus, errorThrown) {}) | |
always(function(data or jqXHR, textStatus, jqXHR or errorThrown) {}) |
從jq1.6開始, done, fail, always按照FIFO隊(duì)列可以分配多個(gè)回調(diào).
使用轉(zhuǎn)換器$.ajax() 的轉(zhuǎn)換器可以將支持的數(shù)據(jù)類型映射到其它數(shù)據(jù)類型. 如果需要將自定義數(shù)據(jù)類型映射到已知的類型. 需要使用 contents 選項(xiàng)在響應(yīng)的 "Content-Type" 和實(shí)際數(shù)據(jù)類型之間添加一個(gè)轉(zhuǎn)換函數(shù).
$.ajaxSetup({ contents: { myContentType: /myContentType/ }, converters: { "myContentType json": function(data) { //TODO something return newData; } } });
轉(zhuǎn)換一個(gè)支持的類型為自定義類型, 然后再返回. 如 text—>myContentType—>json.
$.ajaxSetup({ contents: { myContentType: /myContentType/ }, converters: { "text myContentType": true, "myContentType json": function(data) { //TODO something return newData; } } });事件觸發(fā)順序
$.ajax()方法觸發(fā)的事件紛繁復(fù)雜, 有將近20個(gè)之多. 為了囊括最多的事件, 這里以一次成功的上傳請(qǐng)求為例, 以下是它們的調(diào)用順序(請(qǐng)求出現(xiàn)錯(cuò)誤時(shí)的順序, 請(qǐng)自行對(duì)應(yīng)).
序號(hào) | 事件名稱 | 是否全局事件 | 是否能關(guān)閉 | 默認(rèn)形參 |
---|---|---|---|---|
1 | $.ajaxPrefilter | ?? | ? | function(options, originalOptions, jqXHR){} |
2 | $(document).ajaxStar | ?? | ?? | function(){}(只在當(dāng)前無(wú)激活ajax時(shí)觸發(fā)) |
3 | beforeSend | ? | - | function(jqXHR, settings){} |
4 | $(document).ajaxSend | ?? | ?? | function(){} |
5 | xhr.onloadstart | - | - | ProgressEvent |
6 | xhr.upload.onloadstart | - | - | ProgressEvent |
7 | xhr.upload.onprogress | - | - | ProgressEvent |
8 | xhr.upload.onload | - | - | ProgressEvent |
9 | xhr.upload.onloadend | - | - | ProgressEvent |
10 | xhr.onprogress | - | - | ProgressEvent |
11 | xhr.onload | - | - | ProgressEvent |
12 |
|
? | - | function(data, textStatus, jqXHR){} |
13 | $(document).ajaxSuccess | ?? | ?? | function(event, jqXHR, options){} |
14 |
|
? | - | function(jqXHR, textStatus){} |
15 | $(document).ajaxComplete | ?? | ?? | function(event, jqXHR, textStatus) |
16 | $(document).ajaxStop | ?? | ?? | function(){} |
17 | xhr.onloadend | - | - | ProgressEvent |
從jq1.8起, 對(duì)于函數(shù) ajaxStart, ajaxSend, ajaxSuccess, ajaxComplete, ajaxStop , 只能為document對(duì)象綁定事件處理函數(shù), 為其他元素綁定的事件處理函數(shù)不會(huì)起作用.
Axios實(shí)際上, 如果你僅僅只是想要一個(gè)不錯(cuò)的http庫(kù), 相比于龐大臃腫的jquery, 短小精悍的Axios可能更加適合你. 原因如下:
Axios支持node, jquery并不支持.
Axios基于promise語(yǔ)法, jq3.0才開始全面支持.
Axios短小精悍, 更加適合http場(chǎng)景, jquery大而全, 加載較慢.
vue作者尤大放棄推薦vue-resource, 轉(zhuǎn)向推薦Axios. 以下為尤大原話.
"最近團(tuán)隊(duì)討論了一下, Ajax 本身跟 Vue 并沒有什么需要特別整合的地方, 使用 fetch polyfill 或是 axios、superagent 等等都可以起到同等的效果, vue-resource 提供的價(jià)值和其維護(hù)成本相比并不劃算, 所以決定在不久以后取消對(duì) vue-resource 的官方推薦."
Axios大小僅12k, 目前最新版本號(hào)為:
語(yǔ)法上Axios基本就和promise一樣, 在then方法中處理回調(diào), 在catch方法中處理異常. 如下:
axios.get("https://api.github.com/users/louiszhai") .then(function(response){ console.log(response); }) .catch(function (error) { console.log(error); });
除了get, 它還支持post, delete, head, put, patch, request請(qǐng)求. 具體使用攻略, 請(qǐng)戳這里: axios .
如需在網(wǎng)頁(yè)上引入 Axios, 可以鏈接CDN axios | Bootstrap中文網(wǎng)開源項(xiàng)目免費(fèi) CDN 服務(wù) 或者將其下載到本地.
Fetch說(shuō)到ajax, 就不得不提及fetch, 由于篇幅較長(zhǎng), fetch已從本文中獨(dú)立出來(lái), 請(qǐng)戳 Fetch進(jìn)階指南 .
ajax跨域請(qǐng)求 什么是CORSCORS是一個(gè)W3C(World Wide Web)標(biāo)準(zhǔn), 全稱是跨域資源共享(Cross-origin resource sharing).它允許瀏覽器向跨域服務(wù)器, 發(fā)出異步http請(qǐng)求, 從而克服了ajax受同源策略的限制. 實(shí)際上, 瀏覽器不會(huì)攔截不合法的跨域請(qǐng)求, 而是攔截了他們的響應(yīng), 因此即使請(qǐng)求不合法, 很多時(shí)候, 服務(wù)器依然收到了請(qǐng)求.(Chrome和Firefox下https網(wǎng)站不允許發(fā)送http異步請(qǐng)求除外)
通常, 一次跨域訪問(wèn)擁有如下流程:
移動(dòng)端CORS兼容性當(dāng)前幾乎所有的桌面瀏覽器(Internet Explorer 8+, Firefox 3.5+, Safari 4+和 Chrome 3+)都可通過(guò)名為跨域資源共享的協(xié)議支持ajax跨域調(diào)用.
那么移動(dòng)端兼容性又如何呢? 請(qǐng)看下圖:
可見, CORS的技術(shù)在IOS Safari7.1及Android webview2.3中就早已支持, 即使低版本下webview的canvas在使用跨域的video或圖片時(shí)會(huì)有問(wèn)題, 也絲毫不影響CORS的在移動(dòng)端的使用. 至此, 我們就可以放心大膽的去應(yīng)用CORS了.
CORS有關(guān)的headers1) HTTP Response Header(服務(wù)器提供):
Access-Control-Allow-Origin: 指定允許哪些源的網(wǎng)頁(yè)發(fā)送請(qǐng)求.
Access-Control-Allow-Credentials: 指定是否允許cookie發(fā)送.
Access-Control-Allow-Methods: 指定允許哪些請(qǐng)求方法.
Access-Control-Allow-Headers: 指定允許哪些常規(guī)的頭域字段, 比如說(shuō) Content-Type.
Access-Control-Expose-Headers: 指定允許哪些額外的頭域字段, 比如說(shuō) X-Custom-Header.
該字段可省略. CORS請(qǐng)求時(shí), xhr.getResponseHeader() 方法默認(rèn)只能獲取6個(gè)基本字段: Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma . 如果需要獲取其他字段, 就需要在Access-Control-Expose-Headers 中指定. 如上, 這樣xhr.getResponseHeader("X-Custom-Header") 才能返回X-Custom-Header字段的值.(該部分摘自阮一峰老師博客)
Access-Control-Max-Age: 指定preflight OPTIONS請(qǐng)求的有效期, 單位為秒.
2) HTTP Request Header(瀏覽器OPTIONS請(qǐng)求默認(rèn)自帶):
Access-Control-Request-Method: 告知服務(wù)器,瀏覽器將發(fā)送哪種請(qǐng)求, 比如說(shuō)POST.
Access-Control-Request-Headers: 告知服務(wù)器, 瀏覽器將包含哪些額外的頭域字段.
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請(qǐng)求CORS請(qǐng)求分為兩種, ① 簡(jiǎn)單請(qǐng)求; ② 非簡(jiǎn)單請(qǐng)求.
滿足如下兩個(gè)條件便是簡(jiǎn)單請(qǐng)求, 反之則為非簡(jiǎn)單請(qǐng)求.(CORS請(qǐng)求部分摘自阮一峰老師博客)
1) 請(qǐng)求是以下三種之一:
HEAD
GET
POST
2) http頭域不超出以下幾種字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type字段限三個(gè)值 application/x-www-form-urlencoded、multipart/form-data、text/plain
對(duì)于簡(jiǎn)單請(qǐng)求, 瀏覽器將發(fā)送一次http請(qǐng)求, 同時(shí)在Request頭域中增加 Origin 字段, 用來(lái)標(biāo)示請(qǐng)求發(fā)起的源, 服務(wù)器根據(jù)這個(gè)源采取不同的響應(yīng)策略. 若服務(wù)器認(rèn)為該請(qǐng)求合法, 那么需要往返回的 HTTP Response 中添加 Access-Control-* 等字段.( Access-Control-* 相關(guān)字段解析請(qǐng)閱讀我之前寫的CORS 跨域訪問(wèn) )
對(duì)于非簡(jiǎn)單請(qǐng)求, 比如Method為POST且Content-Type值為 application/json 的請(qǐng)求或者M(jìn)ethod為 PUT 或 DELETE 的請(qǐng)求, 瀏覽器將發(fā)送兩次http請(qǐng)求. 第一次為preflight預(yù)檢(Method: OPTIONS),主要驗(yàn)證來(lái)源是否合法. 值得注意的是:OPTION請(qǐng)求響應(yīng)頭同樣需要包含 Access-Control-* 字段等. 第二次才是真正的HTTP請(qǐng)求. 所以服務(wù)器必須處理OPTIONS應(yīng)答(通常需要返回20X的狀態(tài)碼, 否則xhr.onerror事件將被觸發(fā)).
以上請(qǐng)求流程圖為:
HTML啟用CORShttp-equiv 相當(dāng)于http的響應(yīng)頭, 它回應(yīng)給瀏覽器一些有用的信息,以幫助正確和精確地顯示網(wǎng)頁(yè)內(nèi)容. 如下html將允許任意域名下的網(wǎng)頁(yè)跨域訪問(wèn).
圖片啟用CORS通常, 圖片允許跨域訪問(wèn), 也可以在canvas中使用跨域的圖片, 但這樣做會(huì)污染畫布, 一旦畫布受污染, 將無(wú)法讀取其數(shù)據(jù). 比如無(wú)法調(diào)用 toBlob(), toDataURL() 或 getImageData()方法. 瀏覽器的這種安全機(jī)制規(guī)避了未經(jīng)許可的遠(yuǎn)程服務(wù)器圖片被濫用的風(fēng)險(xiǎn).(該部分內(nèi)容摘自 啟用了 CORS 的圖片 - HTML(超文本標(biāo)記語(yǔ)言) | MDN)
因此如需在canvas中使用跨域的圖片資源, 請(qǐng)參考如下apache配置片段(來(lái)自HTML5 Boilerplate Apache server configs).
ajax文件上傳SetEnvIf Origin ":" IS_CORS Header set Access-Control-Allow-Origin "*" env=IS_CORS
ajax實(shí)現(xiàn)文件上傳非常簡(jiǎn)單, 這里我選取原生js, jq, angular 分別來(lái)比較下, 并順便聊聊使用它們時(shí)的注意事項(xiàng).(ajax文件上傳的代碼已上傳至github, 請(qǐng)戳這里預(yù)覽效果: ajax 文件上傳 demo | louis)
1) 為了上傳文件, 我們得先選中一個(gè)文件. 一個(gè)type為file的input框就夠了.
2) 然后用FormData對(duì)象包裹?選中的文件.
var input = document.getElementById("input"), formData = new FormData(); formData.append("file",input.files[0]);//key可以隨意定義,只要后臺(tái)能理解就行
3) 定義上傳的URL, 以及方法. github上我搭建了一個(gè) node-webserver, 根據(jù)需要可以自行克隆下來(lái)npm start后便可調(diào)試本篇代碼.
var url = "http://localhost:10108/test", method = "POST";js文件上傳
4.1) 封裝一個(gè)用于發(fā)送ajax請(qǐng)求的方法.
function ajax(url, method, data){ var xhr = null; if(window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { alert("您的瀏覽器暫不支持Ajax!"); } } } xhr.onerror = function(e){ console.log(e); } xhr.open(method, url); try{ setTimeout(function(){ xhr.send(data); }); }catch(e){ console.log("error:",e); } return xhr; }
4.2) 上傳文件并綁定事件.
var xhr = ajax(url, method, formData); xhr.upload.onprogress = function(e){ console.log("upload progress:", e.loaded/e.total*100 + "%"); }; xhr.upload.onload = function(){ console.log("upload onload."); }; xhr.onload = function(){ console.log("onload."); }
上傳結(jié)果如下所示:
fetch上傳5) fetch只要發(fā)送一個(gè)post請(qǐng)求, 并且body屬性設(shè)置為formData即可. 遺憾的是, fetch無(wú)法跟蹤上傳的進(jìn)度信息.
fetch(url, { method: method, body: formData }).then(function(res){ console.log(res); }).catch(function(e){ console.log(e); });jquery文件上傳
jq提供了各式各樣的上傳插件, 其原理都是利用jq自身的ajax方法.
6) jq的ajax提供了xhr屬性用于自定義各種事件.
$.ajax({ type: method, url: url, data: formData, processData : false, contentType : false ,//必須false才會(huì)自動(dòng)加上正確的Content-Type xhr: function(){ var xhr = $.ajaxSettings.xhr();//實(shí)際上就是return new window.XMLHttpRequest()對(duì)象 if(xhr.upload) { xhr.upload.addEventListener("progress", function(e){ console.log("jq upload progress:", e.loaded/e.total*100 + "%"); }, false); xhr.upload.addEventListener("load", function(){ console.log("jq upload onload."); }); xhr.addEventListener("load", function(){ console.log("jq onload."); }); return xhr; } } });
jq上傳結(jié)果如下所示:
有關(guān)jq ajax更多的api, 請(qǐng)參考中文文檔 jQuery.ajax() | jQuery API 中文文檔 .
angular文件上傳7.1) angular提供了$http方法用于發(fā)送http請(qǐng)求, 該方法返回一個(gè)promise對(duì)象.
$http({ method: method, url: url, data: formData, }).success(function(res) { console.log(res); }).error(function(err, status) { console.log(err); });
angular文件上傳的代碼已上傳至github, 請(qǐng)戳這里預(yù)覽效果: angular 文件上傳 demo | louis.
低版本angular中文件上傳的功能并不完整, 直到angular1.5.5才在$http中加入了eventHandler和uploadEventHandlers等方法, 使得它支持上傳進(jìn)度信息. 如下:
$http({ method: method, url: url, eventHandlers: { progress: function(c) {//下載進(jìn)度 console.log("Progress -> " + c); } }, uploadEventHandlers: { progress: function(e) {//上傳進(jìn)度 console.log("UploadProgress -> " + e); } }, data: formData, }).success(function(res) { console.log(res); }).error(function(err, status) { console.log(err); });
angular1.5.5以下低版本中, 請(qǐng)參考成熟的實(shí)現(xiàn)方案 angular-file-upload 以及它提供的demo Simple example .
ajax請(qǐng)求二進(jìn)制文件 FileReader處理二進(jìn)制文件主要使用的是H5的FileReader.
PC支持性如下:
IE | Edge | Firefox | Chrome | Safari | Opera |
---|---|---|---|---|---|
10 | 12 | 3.6 | 6 | 6 | 11.5 |
Mobile支持性如下:
IOS Safari | Opera Mini | Android Browser | Chrome/Android | UC/Android |
---|---|---|---|---|
7.1 | - | 4 | 53 | 11 |
以下是其API:
屬性/方法名稱 | 描述 |
---|---|
error | 表示讀取文件期間發(fā)生的錯(cuò)誤. |
readyState | 表示讀取文件的狀態(tài).默認(rèn)有三個(gè)值:0表示文件還沒有加載;1表示文件正在讀取;2表示文件讀取完成. |
result | 讀取的文件內(nèi)容. |
abort() | 取消文件讀取操作, 此時(shí)readyState屬性將置為2. |
readAsArrayBuffer() | 讀取文件(或blob對(duì)象)為類型化數(shù)組(ArrayBuffer), 類型化數(shù)組允許開發(fā)者以數(shù)組下標(biāo)的方式, 直接操作內(nèi)存, 由于數(shù)據(jù)以二進(jìn)制形式傳遞, 效率非常高. |
讀取文件(或blob對(duì)象)為二進(jìn)制字符串, 該方法已移出標(biāo)準(zhǔn)api, 請(qǐng)謹(jǐn)慎使用. | |
readAsDataURL() | 讀取文件(或blob對(duì)象)為base64編碼的URL字符串, 與window.URL.createObjectURL方法效果類似. |
readAsText() | 讀取文件(或blob對(duì)象)為文本字符串. |
onload() | 文件讀取完成時(shí)的事件回調(diào), 默認(rèn)傳入event事件對(duì)象. 該回調(diào)內(nèi), 可通過(guò)this.result 或 event.target.result獲取讀取的文件內(nèi)容. |
var xhr = new XMLHttpRequest(), url = "http://louiszhai.github.io/docImages/ajax01.png"; xhr.open("GET", url); xhr.responseType = "blob"; xhr.onload = function(){ if(this.status == 200){ var blob = this.response; var img = document.createElement("img"); //方案一 img.src = window.URL.createObjectURL(blob);//這里blob依然占據(jù)著內(nèi)存 img.onload = function() { window.URL.revokeObjectURL(img.src);//釋放內(nèi)存 }; //方案二 /*var reader = new FileReader(); reader.readAsDataURL(blob);//FileReader將返回base64編碼的data-uri對(duì)象 reader.onload = function(){ img.src = this.result; }*/ //方案三 //img.src = url;//最簡(jiǎn)單方法 document.body.appendChild(img); } } xhr.send();ajax請(qǐng)求二進(jìn)制文本并展示
var xhr = new XMLHttpRequest(); xhr.open("GET","http://localhost:8080/Information/download.jsp?data=node-fetch.js"); xhr.responseType = "blob"; xhr.onload = function(){ if(this.status == 200){ var blob = this.response; var reader = new FileReader(); reader.readAsBinaryString(blob);//該方法已被移出標(biāo)準(zhǔn)api,建議使用reader.readAsText(blob); reader.onload=function(){ document.body.innerHTML = "" + this.result + ""; } } } xhr.send();
有關(guān)二進(jìn)制文件的讀取, 請(qǐng)移步這篇博客 HTML5新特性之文件和二進(jìn)制數(shù)據(jù)的操作 .
如何等待多個(gè)ajax請(qǐng)求完成原生js可以使用ES6新增的Promise. ES6的Promise基于 Promises/A+ 規(guī)范(該部分 Fetch入門指南 一文也有提及).
這里先提供一個(gè)解析responses的函數(shù).
function todo(responses){ responses.forEach(function(response){ response.json().then(function(res){ console.log(res); }); }); }
原生js使用 Promise.all 方法. 如下:
var p1 = fetch("http://localhost:10108/test1"), p2 = fetch("http://localhost:10108/test2"); Promise.all([p1, p2]).then(function(responses){ todo(responses); //TODO do somethings }); //"test1" //"test2"
jquery可以使用$.when方法. 該方法接受一個(gè)或多個(gè)Deferred對(duì)象作為參數(shù), 只有全部成功才調(diào)用resolved狀態(tài)的回調(diào)函數(shù), 但只要其中有一個(gè)失敗,就調(diào)用rejected狀態(tài)的回調(diào)函數(shù). 其實(shí), jq的Deferred是基于 Promises/A規(guī)范實(shí)現(xiàn), 但并非完全遵循. (傳送門: jQuery 中的 Deferred 和 Promises (2) ).
var p1 = $.ajax("http://localhost:10108/test1"), p2 = $.ajax("http://localhost:10108/test2"); $.when(p1, p2).then(function(res1, res2){ console.log(res1);//["test1", "success", Object] console.log(res2);//["test2", "success", Object] //TODO do somethings });
如上, $.when默認(rèn)返回一個(gè)jqXHR對(duì)象, 可以直接進(jìn)行鏈?zhǔn)秸{(diào)用. then方法的回調(diào)中默認(rèn)傳入相應(yīng)的請(qǐng)求結(jié)果, 每個(gè)請(qǐng)求結(jié)果的都是數(shù)組, 數(shù)組中依次是responseText, 請(qǐng)求狀態(tài), 請(qǐng)求的jqXHR對(duì)象.
angular中可以借助 $q.all() 來(lái)實(shí)現(xiàn). 別忘了, $q 需要在controller中注入. 此外, $q 相關(guān)講解可參考 AngularJS: ng.$q 或 Angular $q service學(xué)習(xí)筆記 .
var p1 = fetch("http://localhost:10108/test1"), p2 = fetch("http://localhost:10108/test2"); $q.all([p1, p2]).then(function(responses){ todo(responses); //TODO do somethings }); //"test1" //"test2"
$q.all() 實(shí)際上就是對(duì) Promise.all 的封裝.
ajax與history的兼容ajax的一大痛點(diǎn)就是無(wú)法支持瀏覽器前進(jìn)和后退操作. 因此早期的Gmail 采用 iframe, 來(lái)模擬ajax的前進(jìn)和后退.
如今, H5普及, pjax大行其道. pajax 就是 ajax+history.pushState 組合的一種技術(shù). 使用它便可以無(wú)刷新通過(guò)瀏覽器前進(jìn)和后退來(lái)改變頁(yè)面內(nèi)容.
先看下兼容性.
IE | Edge | Firefox | Chrome | Safari | Opera | iOS Safari | Android Browser | Chrome for Android | |
---|---|---|---|---|---|---|---|---|---|
pushState/replaceState | 10 | 12 | 4 | 5 | 6 | 11.5 | 7.1 | 4.3 | 53 |
history.state | 10 | 4 | 18 | 6 | 11.5 |
可見IE8,9并不能使用 H5的history. 需要使用墊片 HTML5 History API expansion for browsers not supporting pushState, replaceState .
pjaxpjax簡(jiǎn)單易用, 僅需要如下三個(gè)api:
history.pushState(obj, title, url) 表示往頁(yè)面history末尾新增一個(gè)歷史項(xiàng)(history entry), 此時(shí)history.length會(huì)+1.
history.replaceState(obj, title, url) 表示替換當(dāng)前歷史項(xiàng)為新的歷史項(xiàng). 此時(shí)history.length保持不變.
window.onpopstate 僅在瀏覽器前進(jìn)和后退時(shí)觸發(fā)(history.go(1), history.back() 及l(fā)ocation.href="xxx" 均會(huì)觸發(fā)), 此時(shí)可在history.state中拿到剛剛?cè)M(jìn)去的state, 即obj對(duì)象(其他數(shù)據(jù)類型亦可).
我們注意到, 首次進(jìn)入一個(gè)頁(yè)面, 此時(shí) history.length 值為1, history.state 為空. 如下:
1) 為了在onpopstate事件回調(diào)中每次都能拿到 history.state , 此時(shí)需要在頁(yè)面載入完成后, 自動(dòng)替換下當(dāng)前url.
history.replaceState("init", title, "xxx.html?state=0");
2) 每次發(fā)送ajax請(qǐng)求時(shí), 在請(qǐng)求完成后, 調(diào)用如下, 從而實(shí)現(xiàn)瀏覽器history往前進(jìn).
history.pushState("ajax請(qǐng)求相關(guān)參數(shù)", title, "xxx.html?state=標(biāo)識(shí)符");
3) 瀏覽器前進(jìn)和后退時(shí), popstate 事件會(huì)自動(dòng)觸發(fā), 此時(shí)我們手動(dòng)取出 history.state , 構(gòu)建參數(shù)并重新發(fā)送ajax請(qǐng)求或者直接取用state值, 從而實(shí)現(xiàn)無(wú)刷新還原頁(yè)面.
window.addEventListener("popstate", function(e) { var currentState = history.state; //TODO 拼接ajax請(qǐng)求參數(shù)并重新發(fā)送ajax請(qǐng)求, 從而回到歷史頁(yè)面 //TODO 或者從state中拿到關(guān)鍵值直接還原歷史頁(yè)面 });
popstate 事件觸發(fā)時(shí), 默認(rèn)會(huì)傳入 PopStateEvent 事件對(duì)象. 該對(duì)象具有如下屬性.
如有不懂, 更詳細(xì)講解請(qǐng)移步 : ajax與HTML5 history pushState/replaceState實(shí)例 ? 張?chǎng)涡?鑫空間-鑫生活 .
ajax緩存處理js中的http緩存沒有開關(guān), 受制于瀏覽器http緩存策略. 原生xhr請(qǐng)求中, 可通過(guò)如下設(shè)置關(guān)閉緩存.
xhr.setRequestHeader("If-Modified-Since","0"); xhr.setRequestHeader("Cache-Control","no-cache"); //或者 URL 參數(shù)后加上 "?timestamp=" + new Date().getTime()
jquery的http緩存是否開啟可通過(guò)在settings中指定cache.
$.ajax({ url : "url", dataType : "xml", cache: true,//true表示緩存開啟, false表示緩存不開啟 success : function(xml, status){ } });
同時(shí)jquery還可以全局設(shè)置是否緩存. 如下將全局關(guān)閉ajax緩存.
$.ajaxSetup({cache:false});
除此之外, 調(diào)試過(guò)程中出現(xiàn)的瀏覽器緩存尤為可惡. 建議開啟隱私瀏覽器或者勾選??控制臺(tái)的 Disable cache 選項(xiàng). (這里以Chrome舉例, 其他瀏覽器類似)
ajax的錯(cuò)誤處理前面已經(jīng)提過(guò), 通常只要是ajax請(qǐng)求收到了http狀態(tài)碼, 便不會(huì)進(jìn)入到錯(cuò)誤捕獲里.(Chrome中407響應(yīng)頭除外)
實(shí)際上, $.ajax 方法略有區(qū)別, jquery的ajax方法還會(huì)在類型解析出錯(cuò)時(shí)觸發(fā)error回調(diào). 最常見的便是: dataType設(shè)置為json, 但是返回的data并非json格式, 此時(shí) $.ajax 的error回調(diào)便會(huì)觸發(fā).
ajax調(diào)試技巧有關(guān)調(diào)試, 如果接口只是做小部分修改. 那么可以使用charles(Mac) 或者fiddler(Windows), 做代理, 將請(qǐng)求的資源替換為本地文件, 或者使用其斷點(diǎn)功能, 直接編輯response.
如果是新增接口的調(diào)試, 可以本地搭建node服務(wù). 利用hosts文件配置dns + nginx將http請(qǐng)求轉(zhuǎn)發(fā)到本地node服務(wù)器. 簡(jiǎn)易的node調(diào)試服務(wù)器可參考我的 node-webserver . 如下舉一個(gè)栗子?:
hosts+nginx+node-webserver假設(shè)我們要調(diào)試的是 www.test.com 的GET接口. 以下所有步驟以Mac為例, 其他系統(tǒng), 請(qǐng)自行搜索?文件路徑.
1) hosts配置.
sudo vim /etc/hosts #新增一行 127.0.0.1 www.test.com
2) nginx 配置
brew install nginx #安裝 #安裝成功后進(jìn)入目標(biāo)目錄 cd /usr/local/etc/nginx/ cd servers #默認(rèn)配置入口為nginx.conf.同時(shí)servers目錄下*.conf文件已自動(dòng)加入到配置文件列表中 vim test.conf #粘貼如下內(nèi)容 server { listen 80; server_name www.test.com; index index.html; error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } location / { proxy_pass http://localhost:10108/; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Read-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } #:wq保存并退出 #啟動(dòng)nginx sudo nginx -s reload #如果啟動(dòng)了只需重啟即可 sudo nginx #如果沒有啟動(dòng),便啟動(dòng)之
3) node-webServer 配置
參考 node-webserver . 啟動(dòng)服務(wù)前只需更改index.js, 在第9行后插入如下內(nèi)容:
"get": { "/": { getKey : "Welcome to Simple Node WebServer!" }, "接口api": "你的response內(nèi)容"http://插入的代碼 },
如需在nginx中配置CORS, 請(qǐng)看這里: Nginx通過(guò)CORS實(shí)現(xiàn)跨域.
編碼問(wèn)題XMLHttpRequest 返回的數(shù)據(jù)默認(rèn)的字符編碼是utf-8, post方法提交數(shù)據(jù)默認(rèn)的字符編碼也是utf-8. 若頁(yè)面編碼為gbk等中文編碼, 那么就會(huì)產(chǎn)生亂碼.
后端接口測(cè)試技巧通常, 如果后端接口開發(fā)OK了, 前端同學(xué)需要通過(guò)一些手段來(lái)確認(rèn)接口是能正常訪問(wèn)的.
使用命令測(cè)試OPTIONS請(qǐng)求curl -I -X OPTIONS -H "Origin: http://example.com" http://localhost:10108/ # response HTTP/1.1 200 OK X-Powered-By: Express Content-Type: text/json;charset=UTF-8 Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: x-requested-with,Content-Type Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS Access-Control-Allow-Origin: http://example.com Access-Control-Max-Age: 3600 Server: Node WebServer Website: https://github.com/Louiszhai/node-webserver Date: Fri, 21 Oct 2016 09:00:40 GMT Connection: keep-alive Transfer-Encoding: chunked
以上, http狀態(tài)碼為200, 表示允許OPTIONS請(qǐng)求.
GET, POST 請(qǐng)求與GET類似, 其他請(qǐng)求亦然.
curl -I -X GET -H "Origin: http://example.com" http://localhost:10108/ #HTTP/1.1 200 OK curl -I -X POST -H "Origin: http://example.com" http://localhost:10108/test #HTTP/1.1 200 OKpostman
除此之外, 我們還可以通過(guò)chrome的postman擴(kuò)展進(jìn)行測(cè)試. 請(qǐng)看postman素潔的界面:
postman支持所有類型的http請(qǐng)求, 由于其向chrome申請(qǐng)了cookie訪問(wèn)權(quán)限及所有http(s)網(wǎng)站的訪問(wèn)權(quán)限. 因此可以放心使用它進(jìn)行各種網(wǎng)站api的測(cè)試.
同時(shí), 強(qiáng)烈建議閱讀本文的你升級(jí)postman的使用技巧, 這里有篇: 基于Postman的API自動(dòng)化測(cè)試 , 拿走不謝.
ajax移動(dòng)端兼容性移動(dòng)端的支持性比較弱, 使用需謹(jǐn)慎. 看表.
IOS Safari | Opera Mini | Android Browser | Android Chrome | Android UC | |
---|---|---|---|---|---|
XMLHttpRequest | 8.4 | - | 4.4.4 | 53
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。 轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/81998.html 相關(guān)文章
發(fā)表評(píng)論0條評(píng)論Aomine男|高級(jí)講師TA的文章閱讀更多
閱讀需要支付1元查看
|