平時用慣了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源碼



