前言:這是筆者學習之後自己的理解與整理。如果有錯誤或者疑問的地方,請大家指正,我會持續更新! AJAX 是 asynchronous javascript and XML 的簡寫,就是非同步的 javascript 和 XML。這一技術能夠向伺服器請求額外的數據而無須刷新整個頁面,會帶來更好的用戶體驗 ...
前言:這是筆者學習之後自己的理解與整理。如果有錯誤或者疑問的地方,請大家指正,我會持續更新!
AJAX 是 asynchronous javascript and XML 的簡寫,就是非同步的 javascript 和 XML。這一技術能夠向伺服器請求額外的數據而無須刷新整個頁面,會帶來更好的用戶體驗。雖然名字中包含 XML,但是 ajax 通信與數據格式無關;
創建對象
因為 XMLHTTPRequest() 是一個構造函數,所以需要實例化一個 XMLHttpRequset 對象。下麵是創建XHR對象的相容寫法;
如果要建立多個不同的請求,就要實例化多個不同的 XMLHttpRequset 對象;
<script type="text/javascript"> var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } </script>
發送請求
要想把請求發送到伺服器,我們就需要使用 open() 方法和 send() 方法。
open()
open() 方法需要三個參數:
xhr.open("GET","test.json",true);
第一個參數定義發送請求所使用的方法("GET" 還是 "POST"),不區分大小寫,但通常使用大寫字母,記得帶引號。
"GET" 用於常規請求,它適用於當 URL 完全指定請求資源,當請求對伺服器沒有任何副作用以及當伺服器的響應是可緩存的情況下。
然而,在以下情況中,請使用 "POST" 請求:
- 無法使用緩存文件(更新伺服器上的文件或資料庫)
- 向伺服器發送大量數據("POST" 沒有數據量限制)
- 發送包含未知字元的用戶輸入時,"POST" 比 "GET"更穩定也更可靠
第二個參數規定伺服器端腳本的 URL(該文件可以是任何類型的文件,比如 .txt 和 .xml,或者伺服器腳本文件,比如 .asp 和 .php (在傳迴響應之前,能夠在伺服器上執行任務)。
第三個參數規定是否非同步發送請求的布爾值,如果不填寫,預設為 true,表示非同步發送。如果接受的是同步響應,則需要將 open()方法的第三個參數設置為 false,那麼 send()方法將阻塞直到請求完成。客戶端 javascript 是單線程的,當 send() 方法阻塞時,它通常會導致整個瀏覽器界面凍結。如果連接的伺服器響應慢,那麼用戶的瀏覽器將凍結,所以應該避免使用同步。
send()
send() 方法接收一個參數,即要作為請求主體發送的數據。調用 send() 方法後,請求被分派到伺服器。
如果是 "GET" 方法,send() 方法無參數,或參數為 null;如果是 "POST" 方法,send() 方法的參數為要發送的數據。
xhr.open("GET","test.json",false);
xhr.send(null);
GET
"GET" 用於常規請求,它適用於當 URL 完全指定請求資源,當請求對伺服器沒有任何副作用以及當伺服器的響應是可緩存的情況下。
【數據發送】
使用 "GET" 方式發送請求時,數據被追加到 open() 方法中 URL 的末尾,可以直接看到,存在安全隱患。
數據以問號開始,名和值之間用等號鏈接,名值對之間用和號(&)分隔。使用 "GET" 方式發送的數據常常被稱為查詢字元串。
【編碼】
由於 URL無法識別特殊字元,所以如果數據中包含特殊字元(如中文),則需要進行編碼,編碼的方式有很多種,其中 encodeURIComponent() 函數可把字元串作為 URI 組件進行編碼。該方法主要對 ;/?:@&=+$,# 等這些用於分隔URI組件的字元以及中文進行編碼。由於此方法對 :/ 都進行了編碼,所以不能用它來對網址進行編碼,而適合對 URI 中的參數進行編碼
在 "GET" 請求中,為了避免緩存的影響,可以向 URL 末尾添加一個隨機數或時間戳。
<script> var url = 'test.php' +'?name=' + encodeURIComponent("你好"); xhr.open('GET',url+'&'+Number(new Date()),true); xhr.send(null); </script>
POST
"POST" 請求通常用於向伺服器發送應該被保存的數據。"POST" 方法常用於 HTML 表單。它在請求主體中包含額外數據且這些數據常存儲到伺服器上的資料庫中。
在 open() 方法第一個參數的位置傳入"POST",就可以初始化一個 "POST" 請求。
【設置請求頭】
預設情況下,伺服器對 "POST" 請求和提交表單的請求並不會一視同仁。因此,伺服器端必須有程式來讀取發送過來的原始數據,並從中解析出有用的部分。不過,可以使用 XHR 來模仿表單提交:首先將 content-Type 頭部信息設置為 application/x-www-form-urlencoded,也就是表單提交時的內容類型;
使用 setRequestHeader() 方法可以設置自定義的請求頭部信息。這個方法接受兩個參數:頭部欄位的名稱頭部欄位的值。要成功發送請求頭部信息,必須在調用 open()方法之後且調用 send() 方法之前調用 setRequestHeader() 方法 。
在項目中,又是需要驗證用戶登錄,可以設置請求頭驗證。機制就是:在用戶首次登錄成功之後,伺服器發送token到客戶端,客戶端存入cookie。用戶做任何請求操作時,在 ajax的請求頭裡帶上 token,用以 server-end 做登錄狀態驗證。
【發送主體】
發送 "POST" 請求的第三步就是向 send() 方法中傳入某些數據,這一點和 "GET" 請求不一樣。由於 XHR 最初的設計主要是為了處理 XML,因此可以在此傳入 XML DOM 文檔,傳入的文檔經序列化之後將作為請求主體被提交到伺服器。當然,也可以在此傳入任何想發送到伺服器的字元串。
接下來要以適當的格式創建一個字元串,並使用 send() 方法發送。
"POST" 數據的格式與 "GET" 數據的格式相同,名和值之間用等號鏈接,名值對之間用和號(&)分隔。
【編碼】
由於使用 "POST" 方式傳遞數據時,需要設置請求頭 "content-type",這一步驟已經能夠自動對特殊字元(如中文)進行編碼,所以就不再需要使用 encodeURIComponent() 方法了。
"POST" 請求主要用於數據提交,相同 URL 的重覆 "POST" 請求從伺服器得到的響應可能不同,所以不應該緩存使用 "POST" 方法的請求。
"GET" 對所發送信息的數量有限制,一般在2000個字元。與 "GET" 請求相比,"POST" 請求消耗的資源會更多一些。從性能角度來看,以發送相同的數據計,"GET" 請求的速度最多可"POST"請求的兩倍。
<script> xhr.open('POST',url,true); //設置請求頭 xhr.setRequestHeader("content-type","application/x-www-form-urlencoded"); //拼接數據 var strData = 'name="abc"&num=123'; //發送請求 xhr.send(strData); </script>
接收響應
一個完整的 HTTP 響應由狀態碼、響應頭集合和響應主體組成。
在收到響應後,這些都可以通過 XMLHttpRequset 對象的屬性和方法使用,主要有以下4個屬性:
responseText | 作為響應主體被返回的文本(文本格式) |
responseXML | 如果響應的內容類型是 "text/xml" 或 "application/xml",這個屬性中將保存著響應數據的 XML DOM 文檔(document 格式) |
status | HTTP狀態碼(數字形式) |
statusText | HTTP狀態說明(文本格式) |
在接收到響應後,第一步是檢查 status 屬性,以確定響應已經成功返回。一般來說,可以將 HTTP 狀態碼為 200 作為響應成功的標誌。此時,responseText 屬性的內容已經就緒,而且在內容類型正確的情況下,responseXML 也可以訪問了。此外,狀態碼為 304 表示請求的資源並沒有被修改,可以直接使用瀏覽器中緩存的版本;當然,也意味著響應是有效的。
無論內容類型是什麼,響應主體的內容都會保存到 responseText 屬性中,而對於非 XML 數據而言,responseXML 屬性的值將為 null;
if((xhr.status >=200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert('請求失敗,響應代碼為:' + xhr.status);
}
非同步響應和同步響應
如果不設置 open() 方法的第三個參數(預設為true,即非同步響應)。
如果接收的是非同步響應,這就需要檢測 XMLHttpRequset 對象的 readyState 屬性,該屬性表示 請求/響應 過程的當前活動階段。這個屬性可取的值如下:
0(UNSENT) | 未初始化 | 還沒調用open() |
1(OPEND) | 啟動 | 已經調用open() ,但還沒調用 send() |
2(HEADERS_RECEIVED) | 發送 | 己經調用 send() 方法,且接收到頭信息。 |
3(LOADING) | 正在接收 | 已經接收到部分響應主體信息。 |
4(DONE) | 完成 | 已經接收到全部響應數據,而且已經可以在客戶端使用了。 |
理論上,只要 readyState 屬性值由一個值變成另一個值,都會觸發一次 readystatechange 事件。可以利用這個事件來檢測每次狀態變化後 readyState 的值。通常,我們對readyState 值為4的階段感興趣,因為這時所有數據都已就緒。
必須在調用 open() 之前指定 onreadystatechange 事件處理程式才能確保跨瀏覽器相容性,否則將無法接收 readyState 屬性為0和1的情況 。
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status == 200){
alert(xhr.responseText);
}
}
}
如果將 open() 方法的第三個參數設置為 false,接收的就是同步響應,那麼 send() 方法將阻塞直到請求完成。一旦 send() 返回,僅需要檢查 XHR對象的 status 和responseText 屬性即可。
應該避免使用同步請求。客戶端 javascript 是單線程的,當 send() 方法阻塞時,它通常會導致整個瀏覽器 UI 凍結。如果連接的伺服器響應慢,那麼用戶的瀏覽器將凍結,用戶體驗非常不好。
進度事件
一般地,使用 readystatechange 事件探測 HTTP 請求的完成。XHR2 規範草案定義了進度事件 Progress Events 規範,XMLHttpRequest 對象在請求的不同階段觸發不同類型的事件,所以它不再需要檢査 readyState 屬性。
有以下6個進度事件:
- loadstart: 在接收到響應數據的第一個位元組時觸發
- progress: 在接收響應期間持續不斷地觸
- error: 在請求發生錯誤時觸發
- abort: 在因為調用abort()方法而終止連接時觸發
- load: 在接收到完整的響應數據時觸發
- loadend: 在通信完成或者觸發error、abort或load事件後觸發
- timeout: 超時發生時觸發
每個請求都從觸發 loadstart 事件開始,接下來,通常每隔50毫秒左右觸發一次 progress 事件,然後觸發 load、error、abort 或 timeout 事件中的一個,最後以觸發 loadend 事件結束
對於任何具體請求,瀏覽器將只會觸發 load、abort、timeout 和 error 事件中的一個。XHR2規範草案指出一旦這些事件中的一個發生後,瀏覽器應該觸發 loadend 事件。
load
響應接收完畢後將觸發 load 事件,因此也就沒有必要去檢查 readyState 屬性了。但一個完成的請求不一定是成功的請求,例如,onload 事件的處理程式應該檢查 XMLHttpRequest 對象的 status 狀態碼來確定收到的是“200 OK”而不是“404 Not Found”的HTTP響應
progress
progress 事件會在瀏覽器接收新數據期間周期性地觸發。而 onprogress 事件處理程式會接收到一個 event 對象,其 target 屬性是 XHR 對象,但包含著三個額外的屬性:lengthComputable、loaded 和t otal。其中,lengthComputable 是一個表示進度信息是否可用的布爾值,loaded 表示已經接收的位元組數,total 表示根據 Content-Length 響應頭部確定的預期位元組數。有了這些信息,就可以為用戶創建一個進度指示器了。
上傳進度upload
除了為監控 HTTP 響應的載入定義的這些有用的事件外,XHR2 也給出了用於監控 HTTP 請求上傳的事件。在實現這些特性的瀏覽器中,XMLHttpRequest 對象將有 upload 屬性。upload 屬性值是一個對象,它定義了 addEventListener() 方法和整個 progress 事件集合,比如 onprogress 和 onload。但 upload 對象沒有定義 onreadystatechange 屬性,upload 僅能觸發新的事件類型。
<input type="file" name="file1" id="file1" style="display:none">
<button id="btn">上傳文件</button>
<div id="pro"></div>
<div id="result"></div>
<script>
btn.onclick = function(){
file1.click();
pro.innerHTML = result.innerHTML = '';
}
file1.onchange = function(){
//創建xhr對象
var xhr = new XMLHttpRequest();
var data = file1.files[0];
//上傳事件
xhr.upload.onprogress = function(e){
e = e || event;
if (e.lengthComputable){
pro.innerHTML = "上傳進度為:" + e.loaded + " of " + e.total + " bytes" + ';百分比為:' + e.loaded/e.total;
}
}
xhr.onload = function(e){
var data = xhr.responseText;
e = e || event;
if(xhr.status == 200){
result.innerHTML = data;
}
};
//發送請求
xhr.open('post','pp.php',true);
xhr.setRequestHeader("content-type",data.type);
xhr.send(data);
}
</script>
超時、中止、錯誤事件
HTTP 請求無法完成有3種情況。如果請求超時,會觸發 timeout 事件。如果請求中止,會觸發 abort 事件。最後,像太多重定向這樣的網路錯誤會阻止請求完成,但這些情況發生時會觸發 error 事件。
可以通過調用 XMLHttpRequest 對象的 abort() 方法來取消正在進行的 HTTP 請求。調用 abort() 的主要原因是完成取消或超時請求消耗的時間太長或當響應變得無關時。
XHR對象的 timeout 屬性等於一個整數,表示多少毫秒後,如果請求仍然沒有得到結果,就會自動終止。該屬性預設等於0,表示沒有時間限制。如果請求超時,將觸發ontimeout 事件。
<script>
var xhr = new XMLHttpRequest();
btn.onclick = function(){
xhr.abort();
}
xhr.onabort = function(){
console.log("請求已終止");
}
xhr.ontimeout = function(){
console.log('請求超時');
}
xhr.timeout = 3000;
xhr.onerror = function(){
console.log("請求報錯");
}
xhr.onloadend = function(){
console.log("請求結束");
}
</script>