背景:在XHR出現之前,Ajax式的通信必須藉助一些hack手段來實現,大多數是使用隱藏的框架或內嵌框架,如window.name,document.domain + iframe,動態創建外域或同域JS,JSONP等等,具體可看這篇文章 JavaScript跨域總結與解決方法
1.兼容性:IE7+、Firefox等主流瀏覽器都支持原生的XHR對象,在這些瀏覽器中創建XHR對象,比較簡潔的全兼容寫法:
var xhr = null;
function createXHR(){
if (window.XMLHttpRequest){
// 新瀏覽器
xhr = new XMLHttpRequest();
}else if (window.ActiveXObject){
// IE5,IE6
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
}
2.XHR的用法
①在使用XHR對象時,要調用的第一個方法是open(),它接受3個參數:要發送的請求的類型("get", "post"等)、請求的URL和表示是否異步發送請求的布爾值。
xhr.open("get", "example.php", false);
調用open()方法並不會發送請求,而只是啓動一個請求以備發送,只能向同一個域中使用相同端口和協議的URL發送請求。如果URL與啓動請求的頁面有任何茶幣,都會引發安全錯誤。
要發送特定的請求,必須像這樣:
xhr.send(null);
如果不需要通過請求主體發送數據,則必須傳入null,因爲這個參數對有些瀏覽器來說是必需的。
在收到響應後,響應的數據會自動填充XHR對象的屬性
responseText:作爲響應主體被返回的問題(常用)
responseXML:如果響應的內容類型是"text/xml"或"application/xml",這個屬性中將保存着響應數據的XML DOM文檔
status:響應的HTTP狀態(常用)HTTP 200~299,響應成功,304使用瀏覽器緩存
statusText:HTTP狀態的說明 200-》ok,跨瀏覽器不太可靠
②發送異步請求,可以監測XHR對象的readyState屬性,該屬性表示請求/響應過程中的當前活動階段
0:未初始化
1:啓動。已經調用open(),但尚未調用send()
2:發送。已經調用send(),但尚未接收到響應
3:接收。已經接收到部分響應數據
4:完成。已經接收到全部響應數據,而且已經可以在客戶端使用了。(最有用)
xhr.onreadystatechnage = function(){
//....
}
不過我們必須在調用open()之前指定onreadystatechange事件處理程序才能確保瀏覽器兼容性
1.這裏用了DOM 0級方法是因爲不需要用到event對象
2.這裏沒有使用xhr對象是因爲DOM 0事件處理程序的作用域問題。如果使用this對象,在有的瀏覽器中會導致函數執行失敗,或者導致錯誤發生
②HTTP頭部信息
使用setRequestHeader()方法可以設置自定義的請求頭部信息。這個方法接受兩個參數:頭部字段的名稱和頭部字段的值。要成功發送請求頭部信息,必須在調用open()方法之後且調用send()方法之前調用setRequestHeader()。最好使用自定義的頭部字段名稱,不要使用瀏覽器正常發送的字段名稱,否則有可能會影響服務器的相應
xhr.setRequestHeader("myheader", "MyValue")
調用XHR對象的getResponseHeader()方法並傳入頭部字段名稱,可以取得相應的響應頭部信息。而調用getAllResponseHeaders()方法則取得一個包含所有頭部信息的長字符串
POST請求:與get請求相比,POST請求消耗的資源會更多一些。從性能角度上看,GET請求的速度最多可達到POST請求的兩倍
默認情況下,服務器對POST請求和提交Web表單的請求並不會一視同仁。因此,服務器端必須有程序來讀取發送過來的原始數據,並從中解析出有用的部分。不過,我們可以使用XHR來模仿表單提交:首先將Cotent-Type頭部信息設置爲application/x-www-form-urlencoded,也就是表單提交時的內容類型,其次是以適當的格式創建一個字符串
xhr.open("post", "postexample.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var form = document.getElementById("user-info");
xhr.send(serialize(form));
3.XMLHttpRequest 2級
①FormData:FormData爲序列化表單以及創建與表單格式相同的數據(用於通過XHR傳輸)提供了便利
var data = new FormData();
data.append("name", "Nicholas");
或者傳入表單元素var data = new FormData(document.forms[0]);
使用FormData的方便之處體現明確地在XHR對象上設置頭部
兼容性:主流瀏覽器,IE 10+、Android 3+版WebKit,這樣在移動端就不用擔心了
②超時設定:僅支持IE 8+
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000;
xhr.ontimeout = function(){
alert("Request did not return in a second.");
};
xhr.send(null);
③overrideMimeType()方法
用於重寫XHR相應的MIME類型
var xhr = new XMLHttpRequest();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml" );
//xhr.overrideMimeType(text/plain);
xhr.send(null);
需必須send()方法之前,才能保證重寫相應的MIME類型
③進度事件
6個進度事件
loadstart:在接收到響應數據的第一個字節時觸發
progress:在接收響應期間持續不斷地觸發
error:在請求發生錯誤時觸發
abort:在因爲調用abort()方法而終止連接時觸發
load:在接受到完整的響應數據時觸發
loadend:在通信完成或觸發error、abort或load事件後觸發(很少瀏覽器支持)
loadstart -> progress+ -> (error || abort
|| load) -> loadend
window.onload = function(){
var xhr = createXHR();
xhr.onload = function(event){
if ((xhr.status >= 200 && xhr.status < 300) ||
xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
if (event.lengthComputable){ //進度信息是否可用的布爾值
divStatus.innerHTML = "Received " + event.position + " of " + event.totalSize + " bytes";
} //position表示已經接收到的字節數,totalSize表示根據Content-Length響應頭部確定的預期字節數
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
};
爲確保正確執行,必須在調用open()方法之前添加onprogress事件處理程序
IE8+只支持load事件,目前還沒有瀏覽器支持loadend事件
4.跨源資源共享
CORS(Cross-Origin Resource Sharing,跨源資源共享),定義了再必須訪問跨源資源時,瀏覽器與服務器應該如何溝通。CORS背後的基本思想,就是使用自定義的HTTP頭部讓瀏覽器與服務器進行如何溝通,從而決定請求或響應式應該成功,還是應該失敗。比如,瀏覽器附加一個origin頭部 Origin: http://www.nczonline.net ,服務器 Access-Control-Allow-Origin: hhtp://www.nczonline.net,如果沒有這個頭部,或者有這個頭部但源信息不匹配,瀏覽器就會駁回請求
IE對CORS的實現: XHR(XDomainRequest)
限制:cookie不會隨請求發送,也不會隨響應返回
只能設置請求頭部信息中的Content-Type字段
不能訪問響應頭部信息
只能設置GET和POST請求
var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.onerror = function(){
alert("Error!");
};
//you'll need to replace this URL with something that works
xdr.open("get", "http://www.somewhere-else.com/xdr.php");
xdr.send(null);
//xdr.open("post", ""http://www.baidu.com");
//xdr.contentType = "application/x-www-form-urlencoded";
其他瀏覽器對CORS的實現:標準的XHR對象 new XMLHttpRequest()
在嘗試打開不同來源的資源時,無需額外編寫代碼就可以出發這個代碼。
但跨域XHR對象也有一些限制:
①不能使用setRequestHeader()設置自定義頭部
②不能發送和接受cookie
③調用getAllResponseHeaders()方法總會返回空字符串
由於無論同源請求還是跨源請求都使用相同的接口,因此對於本地資源,最好使用相對URL
5.跨瀏覽器的CORS
即使瀏覽器對CORS的支持程度並不多一樣,但所有瀏覽器都支持簡單的(非Preflight和不帶憑據的)請求,因此有必要實現一個跨瀏覽器的方案。檢測XHR是否支持CORS的最簡單的方式,就是檢查是否存在withCredentials屬性。再結合XDomainRequest對象是否存在,就可以兼顧所有瀏覽器了。
function createCORSRequest(method, url){
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;
}
var request = createCORSRequest("get", "http://www.somewhere-else.com/xdr.php");
if (request){
request.onload = function(){
//do something with request.responseText
};
request.send();
}
Firefox、Safari和Chrome中的XMLHttpRequest對象與IE中的XDomainRequest對象類似,都提供了足夠用的接口,因此以上模式是相當有用的。這兩個對象共同的屬性/方法如下:
abort():用於停止正在進行的請求
onerror:用於代替onreadystatechange檢測錯誤
onload:代替onreadystatechange檢測成功
responseText:用於取得相應內容
send():用於發送請求
以上成員都包含在createCORSRequest()函數返回的對象中,在所有瀏覽器中都能正常使用
6.其他跨域技術
①圖像ping:使用<img>標籤,是在線廣告跟蹤瀏覽量的主要方式。
方式:單向,動態創建圖像,使用onload和onerror事件處理程序來確定是否接收到了響應。請求的數據是通過查詢字符串(如url?test=hello)發送的,而響應可以是任意內容,但通常是像素圖或204響應
解釋:204響應是指服務器成功處理了客戶端的請求,但服務器無返回響應,即Content-Length爲0,因爲客戶端只管把數據發送給服務器,無需關心響應,詳情戳點擊打開鏈接
var img = new Image();
img.onload = img.onerror = function(){
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
用途:圖像Ping最常用於跟蹤用戶點擊頁面或動態廣告曝光次數。
缺點:只能發送GET請求,二是無法返回服務器的響應文本。
②JSONP:JSON with padding
JSONP和JSON看起來差不多,只不過是被包含在函數調用中的JSON,如callback({"name": "Nicholas"});
JSONP由兩部分組成:回調函數和數據。回調函數是當響應到來時在頁面中調用的函數。回調函數的名字一般是在請求中指定。而數據就是傳入函數中的JSON數據。
function handleResponse(response){
alert("You're at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
JSONP與圖像Ping對比的優點是:能夠直接訪問響應文本,支持在瀏覽器與服務器之間雙向通信。
缺點是:從其他域中加載代碼執行,如果其他域不安全,很可能會在響應中夾帶一些惡意代碼。
另外,要確定JSONP請求是否失敗並不容易,雖然HTML5給<script>元素新增了一個onerror事件處理程序,但目前還沒有得到任何瀏覽器支持。爲此,開發人員不得部使用計時器檢測指定時間內是否接收到了響應
7.Comet
Comet:服務器推送。剛好與Ajax相反,Ajax是一種從頁面向服務器請求數據的技術,而Comet則是一種服務器向頁面推送數據的技術。Comet能夠讓信息近乎實時地被推送到頁面上,非常適合處理體育比賽的分數和股票漲價。
有兩種實現Comet的方式:長輪詢和流。
①長輪詢是傳統輪詢(也成爲短輪詢,瀏覽器定時向服務器發送請求,看有沒有更新的數據)的一個翻版,長輪詢把段輪訓顛倒了一下。頁面發起一個到服務器的請求,然後服務器一直保持連接打開,直到有數據可發送。發送完了數據,瀏覽器關閉連接,隨即又發送一個到服務器的新請求。
它們的區別在於服務器如何發送數據,短輪詢是服務器立即發送響應,無論數據是否有效,而長輪詢是等待發送響應。輪詢的優勢有所有瀏覽器都支持,因爲使用XHR對象和setTimeout()就能實現。而你要做的就是決定什麼時候發送請求。
②HTTP流,與輪詢不同,因爲它在頁面的整個生命週期只使用一個HTTP連接。具體來說,就是瀏覽器向服務器發送一個請求,而服務器保持連接打開,然後週期性地向瀏覽器發送數據。
<?php
$i = 0;
while(true){
//輸出一些數據,然後立即刷新輸出緩存
echo "Number is $i";
flush();
//等幾秒鐘
sleep(10);
$i++
}
所有服務器語言都支持打印到輸出緩存然後刷新(將輸出緩存中的內容一次性全部發送給客戶端)的功能。
function createStreamingClient(url, progress, finished){
var xhr = new XMLHttpRequest(),
received = 0;
xhr.open("get", url, true);
xhr.onreadystatechange = function(){
var result;
if (xhr.readyState == 3){
//get only the new data and adjust counter
result = xhr.responseText.substring(received);
received += result.length;
//call the progress callback
progress(result);
} else if (xhr.readyState == 4){
finished(xhr.responseText);
}
};
xhr.send(null);
return xhr;
}
var client = createStreamingClient("streaming.php", function(data){
alert("Received: " + data);
}, function(data){
alert("Done!");
});
在Firefox、safari、Opera和Chrome中,通過偵聽readystatechange事件及檢測readyState的值是否爲3,就可以利用XHR對象實現HTTP流。有時候,當連接關閉時,很肯呢過還需要重新連接,所以關注連接什麼時候關閉時很有必要的。這個例子比較簡單,而且也能在大多數瀏覽器正常運行(除IE),但管理Comet的連接時很容易出錯的,需要時間不斷改進才能達到完美。
8.服務器發送事件
SSE(Server-Sent-Event,服務器發送事件),因爲兼容非常有限,所以不詳細說了。
Web Sockets:使用自定義協議,適合移動應用,IOS上應用沒問題,但Android就呵呵了