不再學AJAX了!(二)使用AJAX

在上一篇文章中咱們知道,AJAX是一系列技術的統稱。在本篇中咱們將更進一步,詳細解釋如何使用Ajax技術在項目中獲取數據。而爲了解釋清楚,咱們首先要搞清楚咱們是從哪裏獲取數據的,其次咱們關注的纔是獲取數據的具體方式php

1、獲取數據

咱們知道AJAX用來在項目中以阻止頁面刷新的方式獲取數據,那麼數據從哪裏來呢?咱們又怎麼知道如何獲取這些數據?答案是咱們一般使用API與各式各樣的數據庫交互。ajax

「API」是「Application Programming Interface」(即:應用程序接口)的縮寫,你能夠想象一些數據是開放的而且在等待被使用,而咱們獲取這些數據的方式即是使用API。API一般的形式是一個URL,並提供指定的參數名和參數值用來幫助你定位所要獲取的數據。數據庫

還記得咱們提過AJAX須要服務器端的相應設置嗎?咱們以後會再來談這一點。瀏覽器


2、AJAX技術的核心 - XMLHttpRequest對象

讓咱們先把服務器端的設置拋在一邊,聚焦AJAX技術的核心環節:XMLHttpRequest對象。安全

XMLHttpRequest對象是瀏覽器提供的一個API,用來順暢地向服務器發送請求並解析服務器響應,固然整個過程當中,瀏覽器頁面不會被刷新。它將是本文接下來的主角,讓咱們先站在較高的層次,對該對象有一個全局的概覽:服務器

  1. XMLHttpRequest只是一個JavaScript對象,確切的說,是一個構造函數。換句話說,它一點也不神祕,它的特殊之處只在於它是由客戶端(即瀏覽器)提供的(而不是JavaScript原生的),除此以外,它有屬性,有方法,須要經過new關鍵字進行實例化,咱們只需掌握它們就好;
  2. 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/xmlapplication/xml,該屬性將保存包含着相應數據的XML DOM文檔;
  • .status:響應的HTTP狀態;
  • .statusText:HTTP狀態的說明;
  • .readyState:表示「請求」/「響應」過程的當前活動階段

另外,瀏覽器還爲該對象提供了一個onreadystatechange監聽事件,每當XML實例的readyState屬性變化時,就會觸發該事件的發生。異步

至此,關於XMLHttpRequest實例對象的屬性方法就所有羅列完畢了,接下來,咱們將更進一步的探究如何使用這些方法,屬性完成發送AJAX請求的流程。ide


3、準備AJAX請求

要想與服務器交互,咱們首先須要回答如下問題:

  • 咱們是要獲取數據仍是存儲數據? --表現爲請求方式的不一樣:GETPOST
  • 向哪裏發出請求? --即相應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()方法的參數最終被髮往服務器,該數據能夠是任意大小,任意類型。

這裏須要注意如下兩點:

  1. .send()方法的參數是不可爲空的,也就是說,對於不須要發送任何數據的GET請求,也須要在調用.send()方法時,向其傳入null值;
  2. 目前爲止,咱們知道了兩種向服務器發送數據的方式:表單提交以及發送POST請求,要注意服務器對待這兩種方式並不一視同仁,這意味着服務器須要有相應的代碼專門處理POST請求發送來的原始數據。

但好在咱們能夠經過POST請求模擬表單提交,只須要簡單兩步:

  1. 設置請求頭參數:Content-Type: application/x-www-form-urlencoded(表單提交時的內容類型);
  2. 將表單數據序列化爲查詢字符串形式,傳入.send()方法;

(二)請求URL地址

這裏須要注意若使用相對路徑,請求URL是相對於執行代碼的當前頁面

(三)同步請求與異步請求

人們一般認爲AJAX是異步的,實際上並不是如此,AJAX是避免頁面在獲取數據後刷新的一種技術,至於等待服務器響應的方式是同步仍是異步,須要開發人員結合業務需求進行配置(雖然一般是異步的)。

你可能會好奇,何時咱們須要使用同步的AJAX?就我我的經驗而言,彷佛很難找到相應的場景。Stack Overflow上有一個相似的問題,有興趣的不妨點擊查看

最後咱們再簡單解釋一下「同步」等待響應與「異步」等待響應的區別:「同步」意味着一旦請求發出,任何後續的JavaScript代碼不會再執行,「異步」則是當請求發出後,後續的JavaScript代碼會繼續執行,當請求成功後,會調用相應的回調函數。


4、設置請求頭

每一個HTTP請求和響應都會帶有相應的頭部信息,包含一些與數據,收發者網絡環境與狀態等相關信息。XMLHttpRequest對象提供的.setRequestHeader()方法爲開發者提供了一個操做這兩種頭部信息的方法,並容許開發者自定義請求頭的頭部信息。

默認狀況下,當發送AJAX請求時,會附帶如下頭部信息:

  • Accept:瀏覽器可以處理的內容類型;
  • Accept-Charset: 瀏覽器可以顯示的字符集;
  • Accept-Encoding:瀏覽器可以處理的壓縮編碼;
  • Accept-Language:瀏覽器當前設置的語言;
  • Connection:瀏覽器與服務器之間鏈接的類型;
  • Cookie:當前頁面設置的任何Cookie;
  • Host:發出請求的頁面所在的域;
  • Referer:發出請求的頁面URI;
  • User-Agent:瀏覽器的用戶代理字符串;

注意,部分瀏覽器不容許使用.setRequestHeader()方法重寫默認請求頭信息,所以自定義請求頭信息是更加安全的方法:

// 自定義請求頭
xhr.setRequestHeader("myHeader", "MyValue")

5、發送請求

到此爲止,咱們已經徹底作好了發送請求的全部準備:利用.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)

額..,總以爲仍是差點什麼?放輕鬆夥計,由於咱們只是發出了請求,尚未處理響應,咱們這就來看看它。


6、處理響應

讓咱們直接看看如何處理一個同步的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)

注意:爲了確保跨瀏覽器的兼容性,必需要在調用.open()方法以前指定事件處理程序,仔細想一想也有道理,畢竟.open()方法的執行也包含在該事件處理程序的監聽範圍以內對吧?


7、取消異步請求

有時候,你可能須要在接收到響應以前取消異步請求,這時候,你須要調用.abort()方法。

該方法會令XHR對象實例中止觸發事件,而且再也不容許訪問任何和響應有關的對象屬性。沒了監控器,咱們再也無法判斷響應了不是嗎?

可是須要注意的是,當終止AJAX請求後,你須要手動對XHR對象實例進行解綁以釋放內存空間。


?? 恭喜你!到這裏你已經學會了全部的AJAX基礎知識,你知道了AJAX是什麼,存在的意義以及如何真正發起一個AJAX請求並接收響應,你已是一個AJAX大師!祝賀你!太棒了!??











? 真棒,尊敬的AJAX大師,你竟然尚未離開,那麼我將傳授你最後一部分AJAX祕籍,幫助你成爲一個真正的AJAX忍者,這是你的堅持贏得的!

8、祕籍: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:在通訊完成或者觸發errorabortload事件後觸發;

    • 桌面端:全部瀏覽器不支持
    • 移動端:全部瀏覽器不支持

這裏咱們將着重展開講解如下兩個事件:

① 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!喜歡這篇文章嗎?別忘了在下方? 點贊讓我知道。

相關文章
相關標籤/搜索