平時用慣了jQuery.ajax之類的方法,卻時常忽略了它背后的實現,本文是學習了AJAX基礎及幾種跨域解決方案之后的一些收獲。
一、AJAX——XMLHttpRequest
談起Ajax我們都很熟悉,它的核心對象是XMLHttpRequest(簡稱XHR)。
1.創建對象:
在ie7及以上版本支持原生的寫法創建該對象。
2.發送請求:
open(type,url,isasync):第一個參數是請求類型(get,post),第二個參數是要請求的url,第三個參數是bool值表示是否為異步請求。該方法沒有真正的發送請求,只是啟動了一個請求以備發送。
send(body):該方法用來真正的發送請求,參數是請求真正要發送的數據內容。參數是必填項,即便不需要向服務器發送內容,也要傳遞參數null。
【備注】在get請求中,如果url結尾有查詢字符串,那么必須先對其鍵和值都要進行encodeURIComponent()編碼。
3.響應結果:請求的響應結果會自動復制到xhr對象的屬性中。
- status:響應結果的狀態碼。
- statusText:響應結果狀態說明。
- responseText:響應返回的文本結果主體;
- responseXML:如果響應的內容類型為"text/xml",那么結果會以XML DOM的格式賦值在該屬性中。如果請求結果是非XML格式,該屬性為null。
4.異步請求:
大多數情況下我們都需要使用異步請求,此時可以通過檢測請求的readyState來判斷請求是否已經完成。實際上,每次readyState屬性改變時都會觸發一次onstatechange事件。readyState屬性有五種取值情況:
- 0:未初始化,也就是還未調用open()方法;
- 1:啟動,已經調用open()方法,尚未調用send()方法;
- 2:發送,已經調用send()方法,還未收到響應;
- 3:接收。已經接收到一部分響應結果數據;
- 4:完成。已經接收到全部響應數據,可以在客戶端調用了(最常用)。
【備注】為了保證瀏覽器兼容性,需要在調用open()方法之前指定onreadystatechange事件處理程序。
5.自定義HTTP請求頭
setRequestHeader(key,value):發送自定義消息頭。最常用的場景是在post請求中模擬表單提交:xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
【備注】該方法必須在open()之后send()之前調用。
6.簡例 demo1.html:
var obj= document.getElementById("result"); //創建XHR對象 var xhr = new XMLHttpRequest(); //狀態變化事件 xhr.onreadystatechange=function(){ obj.innerHTML+="readyState:"+xhr.readyState+"<br/>"; //請求完成 if(xhr.readyState==4){ //響應結果 if ((xhr.status==200) || xhr.status == 304){ var msg="status:"+xhr.status+"<br/>"; msg+="statusText:"+xhr.statusText+"<br/>"; msg+="responseText:"+xhr.responseText; obj.innerHTML+=msg; } else { alert("請求失敗: " + xhr.status); } } } //啟動請求 xhr.open("get", "weather.json", true); //自定義HTTP頭 xhr.setRequestHeader("testheader","hello"); //發送請求 xhr.send(null);
二、XHR進度事件
1.load事件
load事件是在響應結果接收完畢時調用,可以用來簡化readystatechange事件。不過,只要瀏覽器接收到結果就會觸發該事件,因此還需要自行判斷響應狀態status。
xhr.onload=function(){ if ((xhr.status==200) || xhr.status == 304){ obj.innerHTML+="responseText:"+xhr.responseText; } else { alert("請求失敗: " + xhr.status); } }
2.progress事件
progress事件會在瀏覽器接收數據的過程中周期性觸發。onprogress事件處理程序可以接收到event對象參數,其中event.targe是xhr對象,它還有三個重要的屬性:
- lengthComputable:布爾值,表示進度是否可用;
- loaded:已經接收的字節數;
- total:根據響應頭中的Content-Length預期需要接收的總字節數。
xhr.onprogress=function(event){ if(event.lengthComputable){ objstatus.innerHTML=event.loaded+"/"+event.total; } }
三、跨域資源共享(CORS)
XHR的一個主要約束是同源策略,即:相同域、相同端口、相同協議,可以通過跨域資源共享CORS(Cross-Origin Resourse Sharing)實現跨域資源共享。其基本思想是通過自定義HTTP頭讓瀏覽器與服務器溝通,從而確定是否正常響應。如果服務器允許請求,則在響應頭添加"Access-Control-Allow-Origin" 來回發源信息.
1.IE對CORS支持——XDR(XDomainRequest)
IE中使用XDR對象實現CORS,它的使用與XHR對象類似,也是實例化后調用open()和send()方法。不同的是,XDR的open()方法只有兩個參數:請求類型和URL。
xhr=new XDomainRequest(); xhr.open(method,url); xhr.send();
2.其他瀏覽器支持CORS——原生XHR
大多數瀏覽器的XHR對象原生支持CORS,只需要在open()方法中傳入響應的url即可。
3.跨瀏覽器支持CORS
綜合以上兩種情況,可以實現跨瀏覽器的CORS。檢查XHR是否支持CORS的最簡單方式是檢查 withCredentials屬性,然后結合檢查XDomainRequest對象是否存在即可。
function createCORSRequest(method,url){ //創建XHR對象 var xhr = new XMLHttpRequest(); //啟動請求 if("withCredentials" in xhr){ xhr.open(method,url,true); }else if(typeof XDomainRequest!='undefined'){ xhr=new XDomainRequest(); xhr.open(method,url); }else{ xhr=null; } return xhr; }
4.實例
下面看個簡單的例子,http://www.jsdemo.com/demoajax/demo3.htm 跨域請求 http://www.othersite.com/weather.ashx 中的數據。這是本地搭建的兩個測試站點,demo源碼見文章底部。
weather.ashx首先檢測來源頁面,然后決定是否返回Access-Control-Allow-Origin頭。
public void ProcessRequest (HttpContext context) { context.Response.ContentType = "text/plain"; string referrer = context.Request.ServerVariables["HTTP_REFERER"]; if (!string .IsNullOrEmpty(referrer) && referrer.IndexOf( "http://www.jsdemo.com") > -1) { context.Response.AddHeader( "Access-Control-Allow-Origin" , "http://www.jsdemo.com"); } context.Response.Write( "{\"weather\": \"晴\",\"wind\": \"微風\"}" ); }
demo3.htm通過CORS方式進行跨域請求,並將結果解析后顯示在頁面中。
var xhr=createCORSRequest("get","http://www.othersite.com/weather.ashx"); xhr.onload=function(){ if(xhr.status==200||xhr.status==304){ var result=xhr.responseText; var json=JSON.parse(result); var obj= document.getElementById("result"); obj.innerHTML="天氣:"+json.weather+";風力:"+json.wind; } } xhr.send();
四、圖像跨域請求
<img>標簽是沒有跨域限制的,我們可以利用圖像標簽實現一種簡單的、單向的跨域通信。圖像ping通常用於跟蹤用戶點擊數和廣告曝光次數等。
特點:圖像跨域請求只能用於瀏覽器和服務器之間的單向通信,它只能發送Get請求,而且服務訪問服務器的響應內容。
來看個小例子demo4.htm,客戶端點擊鏈接時觸發跨域請求。
<a href="javascript:void(0);" onclick="Click()">點擊我</a> <script> function Click(){ var img=new Image(); img.onload=function(){ alert('DONE'); } img.src="http://www.othersite.com/demo4.ashx?r="+Math.random(); } </script>
服務端進行簡單的計數,並且發送回一像素大小的圖像。客戶端接收到該結果后會彈窗提示“DONE”。
public static int Count=0; public void ProcessRequest (HttpContext context) { context.Response.ContentType = "text/plain" ; Count++; context.Response.ContentType = "image/gif" ; System.Drawing. Bitmap image = new System.Drawing. Bitmap(1, 1); image.Save(context.Response.OutputStream, System.Drawing.Imaging. ImageFormat .Gif); context.Response.Write(Count); }
五、JSONP跨域請求
1.JSONP結構
JSONP是很常用的一種跨域請求方案,常見的JSONP請求格式如下:
響應結果看上去就像是包在函數調用中的JSON結構:
JSONP結果由兩部分組成:回調函數和數據。回調函數一般是在發起請求時指定的,當響應完成時會在頁面中調用的函數;數據當然就是請求返回的JSON數據結果。
2.發起請求:
JSONP原理上是利用了動態<script>標簽實現的,通過創建script對象,並且將其src屬性設置為跨域請求的url地址。當請求完成后,JSONP響應加載到頁面中便立即執行。
<script> function showResult(json){ var obj= document.getElementById("result"); obj.innerHTML="天氣:"+json.weather+";風力:"+json.wind; } var script=document.createElement("script"); script.src="http://www.othersite.com/demo5.ashx?callback=showResult"; document.body.insertBefore(script,document.body.firstChild); </script>
3.特點:
- JSONP可以實現瀏覽器和服務器雙向通信,並且能夠訪問響應中的文本;
- JSONP是從其他域中加載代碼並執行的,需要注意其安全性;
- JSONP請求結果成敗不易確定。
附件:DEMO源碼