Javascript高級程序設計第21章(Ajax與Comet)

背景:在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就呵呵了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章