再也不学AJAX了!(二)使用AJAX
在上一篇文章中我們知道,AJAX是一系列技術的統稱。在本篇中我們將更進一步,詳細解釋如何使用Ajax技術在項目中獲取數據。而為了解釋清楚,我們首先要搞清楚我們是從哪里獲取數據的,其次我們關注的才是獲取數據的具體方式。
一、獲取數據
我們知道AJAX用來在項目中以阻止頁面刷新的方式獲取數據,那么數據從哪里來呢?我們又怎么知道如何獲取這些數據?答案是我們通常使用API與各式各樣的數據庫交互。
“API”是“Application Programming Interface”(即:應用程序接口)的縮寫,你可以想象一些數據是開放的并且在等待被使用,而我們獲取這些數據的方式便是使用API。API通常的形式是一個URL,并提供指定的參數名和參數值用來幫助你定位所要獲取的數據。
還記得我們提過AJAX需要服務器端的相應設置嗎?我們之后會再來談這一點。
二、AJAX技術的核心 - XMLHttpRequest對象
讓我們先把服務器端的設置拋在一邊,聚焦AJAX技術的核心環節:XMLHttpRequest對象。
XMLHttpRequest對象是瀏覽器提供的一個API,用來順暢地向服務器發送請求并解析服務器響應,當然整個過程中,瀏覽器頁面不會被刷新。它將是本文接下來的主角,讓我們先站在較高的層次,對該對象有一個全局的概覽:
讓我們先從剖析XMLHttpRequest實例的屬性和方法開始,先創建一個XML對象的實例:
const xhr = new XMLHttpRequest()該實例的屬性,方法有:
方法
- .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請求的流程。
三、準備AJAX請求
要想與服務器交互,我們首先需要回答以下問題:
- 我們是要獲取數據還是存儲數據? --表現為請求方式的不同:GET或POST;
- 向哪里發出請求? --即相應API地址;
- 以何種方式等待響應? --有“同步”和“異步”兩種選擇;(網絡傳輸是一個過程,請求和響應不是同時發生的。)
而XMLHttpRequest實例的.open()方法的作用就是用來回答以上三個問題。.open()方法接收三個參數:請求方式,請求URL地址和是否為異步請求的布爾值。
下面是一個.open()方法調用的例子:
// 該段代碼會啟動一個針對“example.php”的GET同步請求。 xhr.open("get", "example.php", false)相當于開始做飯前,將工具準備齊備,將菜洗好,.open()方法也同樣出色地完成了發送AJAX請求的準備工作。
現在,讓我們再深入聊聊一些準備工作的細節:
(一)GET請求 與 POST請求
- GET請求
GET請求用于獲取數據,有時候我們需要獲取的數據需要通過“查詢參數”進行定位,在這種情況下,我們會將查詢參數追加到URL的末尾,令服務器解析。
查詢參數是指一個由?號起始,由&符號分割的包含相應鍵值對的字符串。用來告知瀏覽器所要查詢的特定資源。
const query = "example.php?name=tom&age=24" // "?name=tom&age=24"即是一個查詢參數需要注意的是,查詢字符串中每個參數的名和值都必須使用encodeURIComponent()進行編碼(這是因為URL中有些字符會引起歧義,例如“&”)。
- POST請求
POST請求用于向服務器發送應該被保存的數據,因此POST請求天然比GET請求多需要一份需要被保存的數據。那么這些數據應該放在何處呢?畢竟,我們的.open()方法接收的三個參數都沒有合適的位置。
答案是需要發送的數據會作為.send()方法的參數最終被發往服務器,該數據可以是任意大小,任意類型。
這里需要注意以下兩點:
但好在我們可以通過POST請求模擬表單提交,只需要簡單兩步:
(二)請求URL地址
這里需要注意若使用相對路徑,請求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流程的推進而不斷累加,其可取的值如下:
- 0:未初始化 -- 尚未調用.open()方法;
- 1:啟動 -- 已經調用.open()方法,但尚未調用.send()方法;
- 2:發送 -- 已經調用.send()方法,但尚未接收到響應;
- 3:接收 -- 已經接收到部分響應數據;
- 4:完成 -- 已經接收到全部響應數據,而且已經可以在客戶端使用了;
有了這個時間處理程序對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)注意:為了確??鐬g覽器的兼容性,必須要在調用.open()方法之前指定事件處理程序,仔細想想也有道理,畢竟.open()方法的執行也包含在該事件處理程序的監聽范圍之內對吧?
七、取消異步請求
有時候,你可能需要在接收到響應之前取消異步請求,這時候,你需要調用.abort()方法。
該方法會令XHR對象實例停止觸發事件,并且不再允許訪問任何和響應有關的對象屬性。沒了監控器,我們再也沒法判斷響應了不是嗎?
但是需要注意的是,當終止AJAX請求后,你需要手動對XHR對象實例進行解綁以釋放內存空間。
?? 恭喜你!到這里你已經學會了所有的AJAX基礎知識,你知道了AJAX是什么,存在的意義以及如何真正發起一個AJAX請求并接收響應,你已經是一個AJAX大師!祝賀你!太棒了!??
? 真棒,尊敬的AJAX大師,你居然還沒有離開,那么我將傳授你最后一部分AJAX秘籍,幫助你成為一個真正的AJAX忍者,這是你的堅持贏得的!
八、秘籍:XMLHttpRequest 2級
還記得我們一開始有提到,W3C提出了XMLHttpRequest 2級規范嗎?雖然并非所有的瀏覽器都實現了該規范所規定的內容,但還是有一些內容被全部或大多數瀏覽器所實現。想成為AJAX忍者?往下看吧。
提示:在這一部分,你將會看到很多有關瀏覽器兼容性的文字,希望你不要覺得枯燥,畢竟這可是忍者的修行,對吧?
(一)FormData 類型
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的瀏覽器兼容性:
-  桌面端 - IE 10+ 與其他瀏覽器均支持
 
-  移動端 - Android,Firefox Mobile,OperaMobile均支持,其余瀏覽器未知
 
(二)超時設定
當我們發送一個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對象有了更多的控制。
瀏覽器兼容性:
-  桌面端 - IE 10+ 與其他瀏覽器均支持
 
-  移動端 - IE Mobile 10+ 與其他瀏覽器均支持
 
(三)overrideMimeType()方法
響應返回的響應頭里,描述了返回數據的MIME類型,瀏覽器通過識別該類型,告知XMLHttpRequest實例處理該數據的方式。然而有時候(例如將XML類型數據當做純文本處理),我們想要以我們想要的方式處理響應的數據,在XMLHttpRequest 2級規范中,我們可以使用.overrideMimeType()方法,從方法名也可以輕松猜出,該方法可以覆寫響應頭所描述數據的MIME類型。
其寫法如下:
const xhr = new XMLHttpRequest() xhr.open("get", "example.php", true) xhr.overrideMimeType("text/xml") // 強迫瀏覽器將響應數據以指定類型方式解讀 xhr.send(null)至此,我們掌控了響應數據的處理方式。
瀏覽器兼容性:
-  桌面端 - IE 7+ 與其他瀏覽器均支持
 
-  移動端 - Firefox Mobile,Chrome for Android 均支持,其余瀏覽器未知
 
(四)進度事件
Progress Events規范是W3C制定的一個工作草案。該規范定義了與客戶端與服務器通信相關的一系列事件,這些事件監聽了通信進程中的各個關鍵節點,使我們能夠以更細的顆粒度掌控數據傳輸過程中的細節。目前共有6個進度事件,他們會隨數據傳輸進展被順序觸發(除了error,abort事件),讓我們看看他們的定義和瀏覽器兼容情況:
-  loadstart:在接收到響應數據的第一個字節時觸發; - 桌面端:除 Safari Mobile 未知外,其他瀏覽器均支持
- 移動端:除 Safari Mobile 未知外,其他瀏覽器均支持
 
-  progress:在接收響應期間持續不斷地觸發; - 桌面端:IE10+ 與其他瀏覽器均支持
- 移動端:均支持
 
-  error:在請求發生錯誤時觸發; - 桌面端:所有瀏覽器均支持(信息來源)
- 移動端:除IE Mobile不支持外,其他瀏覽器均支持(信息來源)
 
-  abort:再因為調用abort()方法時觸發; - 桌面端:未知
- 移動端:未知
 
-  load:在接收到完整的響應數據時觸發; - 桌面端:IE7+ 與其他瀏覽器均支持
- 移動端:Chrome for Android,Edge,Firefox Mobile支持,其余瀏覽器未知
 
-  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!喜歡這篇文章嗎?別忘了在下方? 點贊讓我知道。
總結
以上是生活随笔為你收集整理的再也不学AJAX了!(二)使用AJAX的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: [Erlang 0097] TCP半开的
- 下一篇: FTP服务器搭建下的主动模式和被动模式
