在上一篇文章中咱們知道,AJAX是一系列技術的統稱。在本篇中咱們將更進一步,詳細解釋如何使用Ajax技術在項目中獲取數據。而爲了解釋清楚,咱們首先要搞清楚咱們是從哪裏獲取數據的,其次咱們關注的纔是獲取數據的具體方式。php
咱們知道AJAX用來在項目中以阻止頁面刷新的方式獲取數據,那麼數據從哪裏來呢?咱們又怎麼知道如何獲取這些數據?答案是咱們一般使用API與各式各樣的數據庫交互。ajax
「API」是「Application Programming Interface」(即:應用程序接口)的縮寫,你能夠想象一些數據是開放的而且在等待被使用,而咱們獲取這些數據的方式即是使用API。API一般的形式是一個URL,並提供指定的參數名和參數值用來幫助你定位所要獲取的數據。數據庫
還記得咱們提過AJAX須要服務器端的相應設置嗎?咱們以後會再來談這一點。瀏覽器
讓咱們先把服務器端的設置拋在一邊,聚焦AJAX技術的核心環節:XMLHttpRequest
對象。安全
XMLHttpRequest
對象是瀏覽器提供的一個API,用來順暢地向服務器發送請求並解析服務器響應,固然整個過程當中,瀏覽器頁面不會被刷新。它將是本文接下來的主角,讓咱們先站在較高的層次,對該對象有一個全局的概覽:服務器
XMLHttpRequest
只是一個JavaScript對象,確切的說,是一個構造函數。換句話說,它一點也不神祕,它的特殊之處只在於它是由客戶端(即瀏覽器)提供的(而不是JavaScript原生的),除此以外,它有屬性,有方法,須要經過new
關鍵字進行實例化,咱們只需掌握它們就好;XMLHttpRequest
對象是不斷被擴展的。隨着XML對象被普遍的接收,W3C也開始着手製定相應的標準來規範其行爲。目前,XMLHttpRequest
有兩個級別:1級提供了XML對象的實現細節,2級進一步發展了XML對象,額外添加了一些方法,屬性和數據類型。可是,並非全部瀏覽器都實現了XML對象2級的內容(並不意外,對吧?);讓咱們先從剖析XMLHttpRequest
實例的屬性和方法開始,先建立一個XML對象的實例:網絡
const xhr = new XMLHttpRequest()
該實例的屬性,方法有:app
.open()
:準備啓動一個AJAX請求;.setRequestHeader()
:設置請求頭部信息;.send()
:發送AJAX請求;.getResponseHeader()
: 得到響應頭部信息;.getAllResponseHeader()
:得到一個包含全部頭部信息的長字符串;.abort()
:取消異步請求;.responseText
:包含響應主體返回文本;.responseXML
:若是響應的內容類型時text/xml
或application/xml
,該屬性將保存包含着相應數據的XML DOM文檔;.status
:響應的HTTP狀態;.statusText
:HTTP狀態的說明;.readyState
:表示「請求」/「響應」過程的當前活動階段另外,瀏覽器還爲該對象提供了一個onreadystatechange
監聽事件,每當XML實例的readyState
屬性變化時,就會觸發該事件的發生。異步
至此,關於XMLHttpRequest實例對象的屬性方法就所有羅列完畢了,接下來,咱們將更進一步的探究如何使用這些方法,屬性完成發送AJAX請求的流程。ide
要想與服務器交互,咱們首先須要回答如下問題:
GET
或POST
;而XMLHttpRequest實例的.open()
方法的做用就是用來回答以上三個問題。.open()
方法接收三個參數:請求方式,請求URL地址和是否爲異步請求的布爾值。
下面是一個.open()
方法調用的例子:
// 該段代碼會啓動一個針對「example.php」的GET同步請求。 xhr.open("get", "example.php", false)
至關於開始作飯前,將工具準備齊備,將菜洗好,.open()
方法也一樣出色地完成了發送AJAX請求的準備工做。
如今,讓咱們再深刻聊聊一些準備工做的細節:
GET請求用於獲取數據,有時候咱們須要獲取的數據須要經過「查詢參數」進行定位,在這種狀況下,咱們會將查詢參數追加到URL的末尾,令服務器解析。
查詢參數是指一個由?
號起始,由&
符號分割的包含相應鍵值對的字符串。用來告知瀏覽器所要查詢的特定資源。
const query = "example.php?name=tom&age=24" // "?name=tom&age=24"便是一個查詢參數
須要注意的是,查詢字符串中每一個參數的名和值都必須使用encodeURIComponent()進行編碼(這是由於URL中有些字符會引發歧義,例如「&」)。
POST請求用於向服務器發送應該被保存的數據,所以POST請求自然比GET請求多須要一份須要被保存的數據。那麼這些數據應該放在何處呢?畢竟,咱們的.open()
方法接收的三個參數都沒有合適的位置。
答案是須要發送的數據會做爲.send()
方法的參數最終被髮往服務器,該數據能夠是任意大小,任意類型。
這裏須要注意如下兩點:
.send()
方法的參數是不可爲空的,也就是說,對於不須要發送任何數據的GET請求,也須要在調用.send()
方法時,向其傳入null
值;但好在咱們能夠經過POST請求模擬表單提交,只須要簡單兩步:
Content-Type: application/x-www-form-urlencoded
(表單提交時的內容類型);.send()
方法;這裏須要注意若使用相對路徑,請求URL是相對於執行代碼的當前頁面。
人們一般認爲AJAX是異步的,實際上並不是如此,AJAX是避免頁面在獲取數據後刷新的一種技術,至於等待服務器響應的方式是同步仍是異步,須要開發人員結合業務需求進行配置(雖然一般是異步的)。
你可能會好奇,何時咱們須要使用同步的AJAX?就我我的經驗而言,彷佛很難找到相應的場景。Stack Overflow上有一個相似的問題,有興趣的不妨點擊查看。
最後咱們再簡單解釋一下「同步」等待響應與「異步」等待響應的區別:「同步」意味着一旦請求發出,任何後續的JavaScript代碼不會再執行,「異步」則是當請求發出後,後續的JavaScript代碼會繼續執行,當請求成功後,會調用相應的回調函數。
每一個HTTP請求和響應都會帶有相應的頭部信息,包含一些與數據,收發者網絡環境與狀態等相關信息。XMLHttpRequest對象提供的.setRequestHeader()
方法爲開發者提供了一個操做這兩種頭部信息的方法,並容許開發者自定義請求頭的頭部信息。
默認狀況下,當發送AJAX請求時,會附帶如下頭部信息:
Accept
:瀏覽器可以處理的內容類型;Accept-Charset
: 瀏覽器可以顯示的字符集;Accept-Encoding
:瀏覽器可以處理的壓縮編碼;Accept-Language
:瀏覽器當前設置的語言;Connection
:瀏覽器與服務器之間鏈接的類型;Cookie
:當前頁面設置的任何Cookie;Host
:發出請求的頁面所在的域;Referer
:發出請求的頁面URI;User-Agent
:瀏覽器的用戶代理字符串;注意,部分瀏覽器不容許使用.setRequestHeader()
方法重寫默認請求頭信息,所以自定義請求頭信息是更加安全的方法:
// 自定義請求頭 xhr.setRequestHeader("myHeader", "MyValue")
到此爲止,咱們已經徹底作好了發送請求的全部準備:利用.open()
方法肯定了請求方式,等待響應的方式和請求地址,甚至還經過.setRequestHeader()
自定義了響應頭,接下來就到了最激動人心的時刻:使用.send()
方法,發送AJAX請求!
// 發送AJAX請求! const xhr = new XMLHttpRequest() xhr.open("get", "example.php", false) xhr.setRequestHeader("myHeader", "goodHeader") xhr.send(null)
呃,簡單的有些使人尷尬不是嗎?換個POST請求試試看:
// 發送AJAX請求! const xhr = new XMLHttpRequest() xhr.open("post", "example.php", false) xhr.setRequestHeader("myHeader", "bestHeader") xhr.send(some_data)
額..,總以爲仍是差點什麼?放輕鬆夥計,由於咱們只是發出了請求,尚未處理響應,咱們這就來看看它。
讓咱們直接看看如何處理一個同步的GET請求響應:
const xhr = new XMLHttpRequest() xhr.open("get", "example.php", false) xhr.setRequestHeader("myHeader", "goodHeader") xhr.send(null) // 因爲是同步的AJAX請求,所以只有當服務器響應後纔會繼續執行下面的代碼 // 所以xhr.status的值必定不爲默認值 if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { alert(xhr.responseText) } else { alert("Request was unsuccessful: " + xhr.status) }
上面的代碼不難理解,咱們經過以前提到的xhr.status
屬性(若是你忘記了,它存儲着響應的HTTP狀態)判斷請求是否成功,若是成功的話,咱們將讀取xhr.responseText
屬性中存儲的返回值。可是,當咱們的請求爲異步時,問題就稍微變得複雜了,因爲是異步的請求,在xhr.send(null)
語句被執行後,JavaScript引擎會緊接着執行下面的判斷語句,而這時因爲還沒有來得及響應,咱們註定會獲得一個默認的xhr.status值,所以,咱們永遠都不可能獲取請求的資源了。
如何解決這個問題?答案是經過爲XMLHTTPRequest實例添加onreadystatechange
事件處理程序(固然你也能夠直接使用DOM2級規範規定的.addEventListener()
方法,可是注意,IE8是不支持該方法的)。
xhr實例的readystatechange
事件會監聽xhr.readyState
屬性的變化,你能夠將這個屬性想象爲一個計數器,隨着AJAX流程的推動而不斷累加,其可取的值以下:
.open()
方法;.open()
方法,但還沒有調用.send()
方法;.send()
方法,但還沒有接收到響應;有了這個時間處理程序對AJAX進程作監聽,剩下的事就簡單多了,一個異步的GET請求代碼以下:
const xhr = new XMLHttpRequest() xhr.onreadystatechange = () => { if (xhr.readystate == 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { alert(xhr.responseText) } else { alert("Request was unsuccessful: " + xhr.status) } } } xhr.open("get", "example.php", true) xhr.send(null)
注意:爲了確保跨瀏覽器的兼容性,必需要在調用.open()
方法以前指定事件處理程序,仔細想一想也有道理,畢竟.open()
方法的執行也包含在該事件處理程序的監聽範圍以內對吧?
有時候,你可能須要在接收到響應以前取消異步請求,這時候,你須要調用.abort()
方法。
該方法會令XHR對象實例中止觸發事件,而且再也不容許訪問任何和響應有關的對象屬性。沒了監控器,咱們再也無法判斷響應了不是嗎?
可是須要注意的是,當終止AJAX請求後,你須要手動對XHR對象實例進行解綁以釋放內存空間。
?? 恭喜你!到這裏你已經學會了全部的AJAX基礎知識,你知道了AJAX是什麼,存在的意義以及如何真正發起一個AJAX請求並接收響應,你已是一個AJAX大師!祝賀你!太棒了!??
? 真棒,尊敬的AJAX大師,你竟然尚未離開,那麼我將傳授你最後一部分AJAX祕籍,幫助你成爲一個真正的AJAX忍者,這是你的堅持贏得的!
還記得咱們一開始有提到,W3C提出了XMLHttpRequest 2級規範嗎?雖然並不是全部的瀏覽器都實現了該規範所規定的內容,但仍是有一些內容被所有或大多數瀏覽器所實現。想成爲AJAX忍者?往下看吧。
提示:在這一部分,你將會看到不少有關瀏覽器兼容性的文字,但願你不要以爲枯燥,畢竟這但是忍者的修行,對吧?
FormData是XMLHttpRequest 2級爲咱們提供的新的數據類型(構造函數),還記的咱們以前是如何假裝一個POST請求爲一個表單提交嗎?FormData令這一過程變得更加輕鬆,由於XHR對象可以識別傳入的數據類型是FormData的實例,並自動配置適當的頭部信息。
FormData的使用方式以下:
// 添加數據 let data1 = new FormData() data1.append("name", "Tom") xhr.send(data1) // 提取表單數據 let data2 = new FormData(document.forms[0]) xhr.send(data2)
除此以外,FormData的另外一個好處是相較於傳統的AJAX請求,它容許咱們上傳二進制數據(圖片,視頻,音頻等),具體詳情可查看該連接。
FormData的瀏覽器兼容性:
桌面端
移動端
當咱們發送一個AJAX請求,卻遲遲得不到服務器響應,這種感受是很糟糕的。爲了緩解這種糟糕的感受,XMLHttpRequest 2級規範爲咱們提供了一個額外的屬性和事件監聽事件:
timeout
屬性:設置超時時間,單位爲毫秒;timeout
事件:當響應時間超出實例對象timeout屬性時被觸發;使用方式以下:
// 當響應時間超過1秒時,請求停止,彈出提示框 xhr.timeout = 1000 xhr.ontimeout = () => { alert("Request did not return in a second.") }
注意,當請求終止時,會調用ontimeout
事件處理程序,此時xhr的readyState
屬性的值可能已變爲4,這意味着會繼續調用onreadystatechange
事件處理程序,可是當超時停止請求後再訪問xhr的status
屬性會使瀏覽器拋出一個錯誤,所以須要將檢查status
屬性的語句放入try-catch
語句中。
雖然帶來了一些麻煩,可是咱們卻對XMLHttpRequest對象有了更多的控制。
瀏覽器兼容性:
桌面端
移動端
響應返回的響應頭裏,描述了返回數據的MIME類型,瀏覽器經過識別該類型,告知XMLHttpRequest實例處理該數據的方式。然而有時候(例如將XML類型數據當作純文本處理),咱們想要以咱們想要的方式處理響應的數據,在XMLHttpRequest 2級規範中,咱們可使用.overrideMimeType()
方法,從方法名也能夠輕鬆猜出,該方法能夠覆寫響應頭所描述數據的MIME類型。
其寫法以下:
const xhr = new XMLHttpRequest() xhr.open("get", "example.php", true) xhr.overrideMimeType("text/xml") // 強迫瀏覽器將響應數據以指定類型方式解讀 xhr.send(null)
至此,咱們掌控了響應數據的處理方式。
瀏覽器兼容性:
桌面端
移動端
Progress Events規範是W3C制定的一個工做草案。該規範定義了與客戶端與服務器通訊相關的一系列事件,這些事件監聽了通訊進程中的各個關鍵節點,使咱們可以以更細的顆粒度掌控數據傳輸過程當中的細節。目前共有6個進度事件,他們會隨數據傳輸進展被順序觸發(除了error,abort事件),讓咱們看看他們的定義和瀏覽器兼容狀況:
loadstart
:在接收到響應數據的第一個字節時觸發;
progress
:在接收響應期間持續不斷地觸發;
error
:在請求發生錯誤時觸發;
abort
:再由於調用abort()
方法時觸發;
load
:在接收到完整的響應數據時觸發;
loadend
:在通訊完成或者觸發error
,abort
或load
事件後觸發;
這裏咱們將着重展開講解如下兩個事件:
① load事件
該事件幫助咱們節省了readstatechange
事件,咱們沒必要在XHR對象實例上綁定該事件監聽函數以追蹤實例上readState
屬性的變化,而是能夠直接使用如下代碼:
const xhr = new XMLHttpRequest() xhr.onload = () => { if ((xhr.status >= 200 && xhr.status <300) || xhr.status == 304) { alert(xhr.responseText) } else { alert("Something wrong!") } } xhr.open("get", "example.php", true) xhr.send(null)
② progress事件
該事件令咱們能夠實現咱們求之不得的加載進度條效果。由於onprogress
事件處理程序會接收到一個event
對象,其target
屬性爲XHR對象實例,但卻額外包含着三個屬性:
lengthComputable
:表示進度信息是否可用的布爾值;position
:表示目前接收的字節數;totalSize
:表示根據Content-Length響應頭部肯定的預期字節數;很顯然,咱們的加載進度條所需的一切資源都準備就緒,咱們只需寫出下面的代碼:
const xhr = new XMLHttpRequest() xhr.onload = () => { if ((xhr.status >= 200 && xhr.status <300) || xhr.status == 304) { alert(xhr.responseText) } else { alert("Something wrong!") } } // 加載進度條 xhr.onprogress = function(event) { const divStatus = document.getElementById("status") if (event.lengthComputable) { divStatus.innerHTML = `Received ${event.postion} of ${event.totalSize} bytes` } } xhr.open("get", "example.php", true) xhr.send(null)
一切大功告成!不過還要記得注意,須要在.open()
方法前調用onprogress
事件處理程序。
太棒了,關於AJAX,我已經沒有什麼可說的了,若是你已經掌握了以上全部概念,那麼「AJAX忍者」的稱號你當之無愧。
我真的爲你感到驕傲,Great Work!?
? Hey!喜歡這篇文章嗎?別忘了在下方? 點贊讓我知道。