1.概述
ajax是asynchronous javascript and XML的簡寫,中文翻譯是異步的javascript和XML,這一技術能夠向服務器請求額外的數據而無須卸載頁面,會帶來更好的用戶體驗。雖然名字中包含XML,但ajax通信與數據格式無關
ajax包括以下幾步驟:1、創建AJAX對象;2、發出HTTP請求;3、接收服務器傳回的數據;4、更新網頁數據
概括起來,就是一句話,ajax通過原生的XMLHttpRequest
對象發出HTTP請求,得到服務器返回的數據后,再進行處理
2.創建ajax對象
ajax技術的核心是XMLHttpRequest對象(簡稱XHR),這是由微軟首先引入的一個特性,其他瀏覽器提供商后來都提供了相同的實現。XHR為向服務器發送請求和解析服務器響應提供了流暢的接口,能夠以異步方式從服務器取得更多信息,意味着用戶單擊后,可以不必刷新頁面也能取得新數據。
IE5是第一款引入XHR對象的瀏覽器。在IE5中,XHR對象是通過MSXML庫中的一個ActiveX對象實現的,而IE7+及其他標准瀏覽器都支持原生的XHR對象
創建一個XHR對象,也叫實例化一個XHR對象,因為XMLHTTPRequest()是一個構造函數。下面是創建XHR對象的兼容寫法
var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); }
[注意]如果要建立N個不同的請求,就要使用N個不同的XHR對象。當然可以重用已存在的XHR對象,但這會終止之前通過該對象掛起的任何請求。
3.發送請求
open()
在使用XHR對象時,要調用的第一個方法是open(),如下所示,該方法接受3個參數
xhr.open("get","example.php", false);
1、open()方法的第一個參數用於指定發送請求的方式,這個字符串,不區分大小寫,但通常使用大寫字母。"GET"和"POST"是得到廣泛支持的
"GET"用於常規請求,它適用於當URL完全指定請求資源,當請求對服務器沒有任何副作用以及當服務器的響應是可緩存的情況下.
"POST"方法常用於HTML表單。它在請求主體中包含額外數據且這些數據常存儲到服務器上的數據庫中。相同URL的重復POST請求從服務器得到的響應可能不同,同時不應該緩存使用這個方法的請求.
除了"GET"和"POST"之外,參數還可以是"HEAD"、"OPTIONS"、"PUT"。而由於安全風險的原因,"CONNECT"、"TRACE"、"TRACK"被禁止使用.
2、open()方法的第二個參數是URL,該URL相對於執行代碼的當前頁面,且只能向同一個域中使用相同端口和協議的URL發送請求。如果URL與啟動請求的頁面有任何差別,都會引發安全錯誤
3、open()方法的第三個參數是表示是否異步發送請求的布爾值,如果不填寫,默認為true,表示異步發送
4、如果請求一個受密碼保護的URL,把用於認證的用戶名和密碼作為第4和第5個參數傳遞給open()方法
send()
send()方法接收一個參數,即要作為請求主體發送的數據。調用send()方法后,請求被分派到服務器
如果是GET方法,send()方法無參數,或參數為null;如果是POST方法,send()方法的參數為要發送的數據
xhr.open("get", "example.txt", false); xhr.send(null);
4.接收響應
一個完整的HTTP響應由狀態碼、響應頭集合和響應主體組成。在收到響應后,這些都可以通過XHR對象的屬性和方法使用,主要有以下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('request was unsuccessful:' + xhr.status); }
5.同步請求
如果接受的是同步響應,則需要將open()方法的第三個參數設置為false,那么send()方法將阻塞直到請求完成。一旦send()返回,僅需要檢查XHR對象的status和responseText屬性即可。
同步請求是吸引人的,但應該避免使用它們。客戶端javascript是單線程的,當send()方法阻塞時,它通常會導致整個瀏覽器UI凍結。如果連接的服務器響應慢,那么用戶的瀏覽器將凍結。
6.異步請求
如果需要接收的是異步響應,這就需要檢測XHR對象的readyState屬性,該屬性表示請求/響應過程的當前活動階段。這個屬性可取的值如下:
0(UNSENT):未初始化。尚未調用open()方法 1(OPENED):啟動。已經調用open()方法,但尚未調用send()方法 2(HEADERS_RECEIVED):發送。己經調用send()方法,且接收到頭信息 3(LOADING):接收。已經接收到部分響應主體信息 4(DONE):完成。已經接收到全部響應數據,而且已經可以在客戶端使用了
理論上,只要readyState屬性值由一個值變成另一個值,都會觸發一次readystatechange事件。可以利用這個事件來檢測每次狀態變化后readyState的值。通常,我們對readyState值為4的階段感興趣,因為這時所有數據都已就緒。
[注意]必須在調用open()之前指定onreadystatechange 事件處理程序才能確保跨瀏覽器兼容性,否則將無法接收readyState屬性為0和1的情況。
//創建xhr對象 var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } //異步接受響應 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ //實際操作 result.innerHTML += xhr.responseText; } } } //發送請求 xhr.open('get','message.xml',true); xhr.send();
7.請求超時
XHR對象的timeout屬性等於一個整數,表示多少毫秒后,如果請求仍然沒有得到結果,就會自動終止。該屬性默認等於0,表示沒有時間限制
如果請求超時,將觸發ontimeout事件
[注意]IE8-瀏覽器不支持該屬性
xhr.open('post','test.php',true); xhr.ontimeout = function(){ console.log('The request timed out.'); } xhr.timeout = 1000; xhr.send();
使用AJAX接收數據時,由於網絡和數據大小的原因,並不是立刻就可以在頁面中顯示出來。所以,更好的做法是,在接受數據的過程中,顯示一個類似loading的小圖片,並且禁用按鈕;當數據完全接收后,再隱藏該圖片,並啟用按鈕。
8.請求方式
GET
GET是最常見的請求類型,最常用於向服務器查詢某些信息,它適用於當URL完全指定請求資源,當請求對服務器沒有任何副作用以及當服務器的響應是可緩存的情況下。
【數據發送】
使用GET方式發送請求時,數據被追加到open()方法中URL的末尾
數據以問號開始,名和值之間用等號鏈接,名值對之間用和號(&)分隔。使用GET方式發送的數據常常被稱為查詢字符串
xhr.open("get","example.php?name1=value1&name2=value2",true)
【編碼】
由於URL無法識別特殊字符,所以如果數據中包含特殊字符(如中文),則需要使用 encodeURIComponent() 進行編碼
[注意] encodeURIComponent() 只是6種編解碼方法的一種。
var url = 'test.php' +'?name=' + encodeURIComponent("小火柴"); xhr.open('get',url,true);
上面的URL被編碼為
test.php?name=%E5%B0%8F%E7%81%AB%E6%9F%B4
【編碼函數】
下面這個函數可以輔助向現有URL的末尾添加查詢字符串參數
function addURLParam(url,name,value){ url += (url.indexOf("?") == -1 ? "?" : "&"); url +=encodeURIComponent(name) + "=" + encodeURIComponent(value); return url; }
【緩存】
在GET請求中,為了避免緩存的影響,可以向URL添加一個隨機數或時間戳
xhr.open('get',url+'&'+Number(new Date()),true); xhr.open('get',url+'&'+Math.random(),true);
POST
使用頻率僅次於GET的是POST請求,通常用於服務器發送應該被保存的數據。"POST"方法常用於HTML表單。它在請求主體中包含額外數據且這些數據常存儲到服務器上的數據庫中。相同URL的重復POST請求從服務器得到的響應可能不同,同時不應該緩存使用這個方法的請求
POST請求應該把數據作為請求的主體提交,而GET請求傳統上不是這樣。POST請求的主體可以包含非常多的數據,而且格式不限。在open()方法第一個參數的位置傳入"post",就可以初始化一個POST請求
xhr.open("post","example.php",true);
【設置請求頭】
發送POST請求的第二步就是向send()方法中傳入某些數據。由於XHR最初的設計主要是為了處理XML,因此可以在此傳入XML DOM文檔,傳入的文檔經序列化之后將作為請求主體被提交到服務器。當然,也可以在此傳入任何想發送到服務器的字符串
默認情況下,服務器對POST請求和提交Web表單的請求並不會一視同仁。因此,服務器端必須有程序來讀取發送過來的原始數據,並從中解析出有用的部分。不過,可以使用XHR來模仿表單提交:首先將Content-Type頭部信息設置為application/x-www-form-urlencoded,也就是表單提交時的內容類型
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
如果不設置Content-Type,發送給服務器的數據就不會出現在$_POSR超級全局變量中。這時要訪問同樣的數據,須借助$HTTP_RAW_POST_DATA
如果對相同的頭調用多次setReQuestHeader(),新值不會取代之前指定的值。相反,HTTP請求將包含這個頭的多個副本或這個頭將指定多個值
【發送主體】
接下來要以適當的格式創建一個字符串,並使用send()方法發送
POST數據的格式與查詢字符串格式相同,名和值之間用等號鏈接,名值對之間用和號(&)分隔,如下所示
xhr.send('name="abc"&num=123');
【編碼和緩存】
由於使用POST方式傳遞數據時,需要設置請求頭"content-type",這一步驟已經能夠自動對特殊字符(如中文)進行編碼,所以就不再需要使用encodeURIComponent()方法了
POST請求主要用於數據提交,相同URL的重復POST請求從服務器得到的響應可能不同,所以不應該緩存使用POST方法的請求
【性能】
GET對所發送信息的數量有限制,一般在2000個字符。與GET請求相比,POST請求消耗的資源會更多一些。從性能角度來看,以發送相同的數據計,GET請求的速度最多可POST請求的兩倍。
8.相應解碼
我們接收到的響應主體類型可以是多種形式的,包括字符串String、ArrayBuffer對象、二進制Blob對象、JSON對象、javascirpt文件及表示XML文檔的Document對象等。下面將針對不同的主體類型,進行相應的響應解碼。
在介紹響應解碼之前,要先了解XHR對象的屬性。一般地,如果接受的數據是字符串,使用responseText即可,這也是最常用的用於接收數據的屬性。但如果獲取了其他類型的數據,使用responseText可能就不太合適了。
【responseText】
responseText屬性返回從服務器接收到的字符串,該屬性為只讀。如果本次請求沒有成功或者數據不完整,該屬性就會等於null。
如果服務器返回的數據格式是JSON、字符串、javascript或XML,都可以使用responseText屬性
【response】
response屬性為只讀,返回接收到的數據體。它的類型可以是ArrayBuffer、Blob、Document、JSON對象、或者一個字符串,這由XMLHttpRequest.responseType屬性的值決定
如果本次請求沒有成功或者數據不完整,該屬性就會等於null
[注意]IE9-瀏覽器不支持
【responseType】
responseType屬性用來指定服務器返回數據(xhr.response)的類型
“”:字符串(默認值)
“arraybuffer”:ArrayBuffer對象
“blob”:Blob對象
“document”:Document對象
“json”:JSON對象
“text”:字符串
【responseXML】
responseXML屬性返回從服務器接收到的Document對象,該屬性為只讀。如果本次請求沒有成功,或者數據不完整,或者不能被解析為XML或HTML,該屬性等於null
【overrideMimeType()】
該方法用來指定服務器返回數據的MIME類型。該方法必須在 send() 之前調用
傳統上,如果希望從服務器取回二進制數據,就要使用這個方法,人為將數據類型偽裝成文本數據
但是,這種方法很麻煩,在 XMLHttpRequest 版本升級以后,一般采用指定 responseType 的方法
9.進度事件
一般地,使用 readystatechange 事件探測HTTP請求的完成。XHR2規范草案定義了進度事件 Progress Events 規范,XMLHttpRequest 對象在請求的不同階段觸發不同類型的事件,所以它不再需要檢査 readyState 屬性。
有以下6個進度事件:
loadstart: 在接收到響應數據的第一個字節時觸發。
progress: 在接收響應期間持續不斷地觸。
error: 在請求發生錯誤時觸發。
abort: 在因為調用 abort() 方法而終止連接時觸發。
load: 在接收到完整的響應數據時觸發。
loadend: 在通信完成或者觸發 error、abort 或 load 事件后觸發。
timeout: 超時發生時觸發。
[注意] IE9- 瀏覽器不支持以上事件(IE9瀏覽器僅支持 load 事件)
每個請求都從觸發 loadstart 事件開始,接下來,通常每隔50毫秒左右觸發一次 progress 事件,然后觸發 load、error、abort 或 timeout 事件中的一個,最后以觸發 loadend 事件結束。
對於任何具體請求,瀏覽器將只會觸發 load、abort、timeout 和 error 事件中的一個。XHR2規范草案指出一旦這些事件中的一個發生后,瀏覽器應該觸發 loadend 事件。
load
響應接收完畢后將觸發load事件,因此也就沒有必要去檢查readyState屬性了。但一個完成的請求不一定是成功的請求,
例如,load事件的處理程序應該檢查XMLHttpRequest對象的status狀態碼來確定收到的是“200 OK”而不是“404 Not Found”的HTTP響應
//創建xhr對象 var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } //進度事件 xhr.onload = function(){ if(xhr.status == 200){ result.innerHTML += xhr.responseText; } } //發送請求 xhr.open('get','message.xml',true); xhr.send();
progress
progress事件會在瀏覽器接收新數據期間周期性地觸發。而onprogress事件處理程序會接收到一個event對象,其target屬性是XHR對象,
但包含着三個額外的屬性:lengthComputable、loaded和total。
lengthComputable是一個表示進度信息是否可用的布爾值,
loaded表示已經接收的字節數,
total表示根據Content-Length響應頭部確定的預期字節數。有了這些信息,就可以為用戶創建一個進度指示器了
//創建xhr對象 var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } //進度事件 xhr.onprogress = function(e){ e = e || event; if (e.lengthComputable){ result.innerHTML = "Received " + e.loaded + " of " + e.total + " bytes"; } }; xhr.onload = function(e){ var data = xhr.response; e = e || event; if(xhr.status == 200){ var audio = document.createElement('audio'); audio.onload = function(){ URL.revokeObjectURL(audio.src); } audio.src = URL.createObjectURL(data); console.log(audio); audio.setAttribute('controls',''); if(!music.innerHTML){ music.appendChild(audio); } } }; //發送請求 xhr.open('get','myocean.mp3',true); xhr.responseType = 'blob'; xhr.send();
上傳進度
除了為監控HTTP響應的加載定義的這些有用的事件外,XHR2也給出了用於監控HTTP請求上傳的事件。在實現這些特性的瀏覽器中,XMLHttpRequest對象將有upload屬性。
upload屬性值是一個對象,它定義了addEventListener()方法和整個progress事件集合,比如onprogress和onload(但upload對象沒有定義onreadystatechange屬性,upload僅能觸發新的事件類型)。
能僅僅像使用常見的progress事件處理程序一樣使用upload事件處理程序。對於XMLHttpRequest對象,設置XHR.onprogress以監控響應的下載進度,並且設置XHR.upload.onprogress以監控請求的上傳進度。
<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; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } 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種情況,對應3種事件。如果請求超時,會觸發timeout事件。如果請求中止,會觸發abort事件。最后,像太多重定向這樣的網絡錯誤會阻止請求完成,但這些情況發生時會觸發error事件
可以通過調用XMLHttpRequest對象的abort()方法來取消正在進行的HTTP請求。調用abort()方法在這個對象上觸發abort事件
調用abort()的主要原因是完成取消或超時請求消耗的時間太長或當響應變得無關時。假如使用XMLHttpRequest為文本輸入域請求自動完成推薦。如果用戶在服務器的建議達到之前輸入了新字符,這時等待請求不再有用,應該中止
XHR對象的timeout屬性等於一個整數,表示多少毫秒后,如果請求仍然沒有得到結果,就會自動終止。該屬性默認等於0,表示沒有時間限制
如果請求超時,將觸發ontimeout事件
var xhr = new XMLHttpRequest(); btn.onclick = function(){ xhr.abort(); }; xhr.ontimeout = function(){ console.log('The request timed out.'); } xhr.timeout = 100; xhr.onabort = function(){ console.log("The transfer has been canceled by the user."); } xhr.onerror = function(){ console.log("An error occurred while transferring the file."); } xhr.onloadend = function(){ console.log("請求結束"); }
10.頭部信息
默認情況下,在發送XHR請求的同時,還會發送下列頭部信息
Accept: 瀏覽器能夠處理的內容類型 Accept-Charset: 瀏覽器能夠顯示的字符集 Accept-Encoding: 瀏覽器能夠處理的壓縮編碼 Accept-Language: 瀏覽器當前設置的語言 Connection: 瀏覽器與服務器之間連接的類型 Cookie: 當前頁面設置的任何Cookie Host: 發出請求的頁面所在的域 User-Agent: 瀏覽器的用戶代理字符串 Referer: 發出請求的頁面的URI
[注意] HTTP規范將這個頭部字段拼錯了,而為保證與規范一致,也只能將錯就錯(正確拼寫應該是referrer)。
設置頭部
使用setRequestHeader()方法可以設置自定義的請求頭部信息。這個方法接受兩個參數:頭部字段的名稱頭部字段的值。要成功發送請求頭部信息,必須在調用open()方法之后且調用send()方法之前調用setRequestHeader()方法
setRequestHeader()方法的一個常用用途是使用POST請求時,將Content-Type的頭部信息設置為表單提交的內容類型
xhr.open('post','service.php',true); xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); xhr.send('data=test123');
[注意]盡量使用自定義頭部字段名稱,不要使用瀏覽器正常發送的字段名稱,否則可能會影響服務器的響應。
xhr.open('get','test.php',true); xhr.setRequestHeader('myHeader','myValue'); xhr.send();
經測試,瀏覽器無法將自定義的頭部字段添加到報文中。
獲取頭部
調用XHR對象的getResponseHeader()方法並傳入頭部字段名稱,可以取得相應的響應頭部信息。而調用getAllResponseHeaders()方法則可以取得一個包含所有頭部信息的長字符串。
var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } //異步接受響應 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ /* Date: Wed, 01 Mar 2017 14:00:21 GMT Server: Apache/2.4.9 (Win32) PHP/5.5.12 Connection: Keep-Alive X-Powered-By: PHP/5.5.12 Content-Length: 1134 Keep-Alive: timeout=5, max=99 Content-Type: text/html */ console.log(xhr.getAllResponseHeaders()); console.log(xhr.getResponseHeader('keep-alive'));//timeout=5, max=99 }else{ alert('發生錯誤:' + xhr.status); } } } //發送請求 xhr.open('get','test.php',true); xhr.send();