Ajax與Comet的介紹與區別

Ajax(Asynchronous JavaScript + XML的簡寫)可以向服務器請求數據而無需卸載(刷新)頁面,帶來更好的用戶體驗。 
Ajax技術的核心是XMLHttpRequest對象(簡稱XHR)。

一、XMLHttpRequest對象

  1. /* 兼容IE早期版本 */
  2. function createXHR(){
  3. if (typeof XMLHttpRequest != "undefined"){
  4. return new XMLHttpRequest();
  5. } else if (typeof ActiveXObject != "undefined"){ // 適用於IE7之前的版本
  6. if (typeof arguments.callee.activeXString != "string"){
  7. var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
  8. "MSXML2.XMLHttp"],
  9. i, len;
  10. for (i=0,len=versions.length; i < len; i++){
  11. try {
  12. new ActiveXObject(versions[i]);
  13. arguments.callee.activeXString = versions[i];
  14. break;
  15. } catch (ex){
  16. //skip
  17. }
  18. }
  19. }
  20. return new ActiveXObject(arguments.callee.activeXString);
  21. } else { // XHR對象和ActiveX對象都不存在,則拋出錯誤
  22. throw new Error("No XHR object available.");
  23. }
  24. }

1. XHR的用法

xhr.open("請求的類型get|post等", "請求的URL", "是否異步發送請求");

說明: 
(1)URL相對於執行代碼的當前頁面(當然也可以使用絕對路徑) 
(2)open()方法並不會真正發送請求,而只是啓動一個請求以備發送

xhr.send("請求主體發送的數據");

說明: 
(1)如果不需要通過請求主體發送數據(比如get請求),則必須傳入null,因爲這個參數對有些瀏覽器來說是必需的 
(2)調用send()之後,請求就會被分派到服務器

補充:xhr.open()方法爲“false”,即同步請求,JavaScript代碼會等到服務器響應後再繼續執行;否則,繼續執行後續代碼。

在收到服務器響應後,相應的數據會自動填充XHR對象的屬性。

  • responseText:作爲響應主體被返回的文本
  • responseXML:如果響應的內容類型是”text/xml”或”application/xml”,這個屬性中將保存包含着響應數據的XML DOM文檔
  • status:響應的HTTP狀態
  • statusText:HTTP狀態的說明
  1. // 爲確保接收到適當的響應 200:成功;304:資源未被修改
  2. if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
  3. console.log(xhr.responseText);
  4. }

說明: 
(1)有的瀏覽器會錯誤的報告成功狀態碼爲204 
(2)無論內容類型是什麼,響應主體的內容都會保存到responseText屬性中;而對於XML數據而言,responseXML同時也將被賦值,否則其值爲null

對於異步請求,可以檢測XHR對象的readyState屬性,該屬性表示請求/響應過程的當前活動階段

  • 0:未初始化。尚未調用open()方法
  • 1:啓動。已經調用open()方法,但尚未調用send()方法
  • 2:發送。已經調用send()方法,但尚未接收到響應
  • 3:接收。已經接收到部分響應數據
  • 4:完成。已經接收全部響應數據,而且已經可以在客戶端使用了。

readyState屬性的值發生變化,都會觸發readystatechange事件。可以利用這個事件來檢測每次狀態變化後readyState的值。不過,必須在調用open()之前指定onreadystatechange事件處理程序才能確保跨瀏覽器兼容性。

  1. var xhr = createXHR();
  2. xhr.onreadystatechange = function(event){
  3. // 不要使用this,作用域會產生問題,在部分瀏覽器中會執行失敗
  4. if (xhr.readyState == 4){
  5. if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
  6. console.log(xhr.responseText);
  7. } else {
  8. console.log("Request was unsuccessful: " + xhr.status);
  9. }
  10. }
  11. };
  12. xhr.open("get", "example.txt", true);
  13. xhr.send(null);

在接收到響應數據之前可以調用abort()方法來取消異步請求:

  1. xhr.abort();
  2. xhr = null; // 解除引用,釋放內存

2. HTTP頭部信息

setRequestHeader():設置自定義的請求頭信息。必須在調用open()方法之後且調用send()方法之前調用。 
getResponseHeader() getAllResponseHeaders():可以獲取指定(全部)響應頭信息。

  1. var xhr = createXHR();
  2. xhr.onreadystatechange = function(){};
  3. xhr.open("get", "example.php", true);
  4. xhr.setRequestHeader("MyHeader", "MyValue");
  5. xhr.send(null);

3. GET請求

open()方法的URL尾部的查詢字符串必須經過正確的編碼

  1. function addURLParam(url, name, value) {
  2. url += (url.indexOf("?") == -1 ? "?" : "&");
  3. url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
  4. return url;
  5. }
  6. var url = "http://test.com";
  7. url = addURLParam(url, "uid" , 5);
  8. url = addURLParam(url, "siteid", 123); // "http://test.com?uid=5&siteid=123"
  9. xhr.open("get", url, true);
  10. xhr.send(null);

4. POST請求

POST請求將數據作爲請求的主體

  1. /* 序列化表單 */
  2. function serialize(form){
  3. var parts = new Array();
  4. var field = null;
  5. for (var i=0, len=form.elements.length; i < len; i++){
  6. field = form.elements[i];
  7. switch(field.type){
  8. case "select-one":
  9. case "select-multiple":
  10. for (var j=0, optLen = field.options.length; j < optLen; j++){
  11. var option = field.options[j];
  12. if (option.selected){
  13. var optValue = "";
  14. if (option.hasAttribute){
  15. optValue = (option.hasAttribute("value") ?
  16. option.value : option.text);
  17. } else {
  18. optValue = (option.attributes["value"].specified ?
  19. option.value : option.text);
  20. }
  21. parts.push(encodeURIComponent(field.name) + "=" +
  22. encodeURIComponent(optValue));
  23. }
  24. }
  25. break;
  26. case undefined: //fieldset
  27. case "file": //file input
  28. case "submit": //submit button
  29. case "reset": //reset button
  30. case "button": //custom button
  31. break;
  32. case "radio": //radio button
  33. case "checkbox": //checkbox
  34. if (!field.checked){
  35. break;
  36. }
  37. /* falls through */
  38. default:
  39. parts.push(encodeURIComponent(field.name) + "=" +
  40. encodeURIComponent(field.value));
  41. }
  42. }
  43. return parts.join("&");
  44. }
  1. /* 發送請求 */
  2. function submitData(){
  3. var xhr = createXHR();
  4. xhr.onreadystatechange = function(event){
  5. if (xhr.readyState == 4){
  6. if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
  7. alert(xhr.responseText);
  8. } else {
  9. alert("Request was unsuccessful: " + xhr.status);
  10. }
  11. }
  12. };
  13. xhr.open("post", "postexample.php", true);
  14. // 表單提交的內容類型
  15. xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  16. var form = document.getElementById("user-info");
  17. // 請求主體爲數據
  18. xhr.send(serialize(form));
  19. }

二、XMLHttpRequest 2級

XMLHttpRequest 1級只是把已有的XHR對象的實現細節描述了出來。而XMLHttpRequest 2級則進一步發展了XHR。並非所有瀏覽器都完整地實現了XMLHttpRequest 2級規範,但所有瀏覽器都實現了它規定的部分內容。

1. FormData

  1. // 創建FormData對象
  2. var data = new FormData();
  3. data.append("name", "ligang");
  1. // 用表單元素填充
  2. xhr.open("post", "postexample.php", true);
  3. var form = document.getElementById("user-info");
  4. // 使用FormData的方便之處在於不必明確地在XHR對象上設置請求頭。
  5. // xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  6. xhr.send(new FormData(form));

2. 超時設定

IE8爲XHR對象添加了一個timeout屬性,表示請求在等待響應多少毫秒後就終止。

  1. xhr.open("get", "timeout.php", true);
  2. xhr.timeout = 60 * 1000;
  3. xhr.ontimeout = function(){
  4. alert("Request did not return in a second.");
  5. };
  6. xhr.send(null);

對於其他瀏覽器的兼容做法

  1. xhr.open("get", "timeout.php", true);
  2. xhr.onreadystatechange = function(event){
  3. if (xhr.readyState == 4){
  4. // 清除定時器
  5. clearTimeout(timeout);
  6. if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
  7. console.log(xhr.responseText);
  8. } else {
  9. console.log("Request was unsuccessful: " + xhr.status);
  10. }
  11. }
  12. };
  13. // 設置超時時間 1分鐘
  14. var timeout = setTimeout(function() {
  15. xmlHttpRequest.abort();
  16. xmlHttpRequest = null;
  17. }, 60 * 1000);
  18. xmlHttpRequest.send(null);

3. overrideMimeType()方法

重寫XHR響應的MIME類型,必須在send()方法之前

如果,服務器返回的MIME類型是text/plain,但數據中實際包含的是XML。根據MIME類型,responseXML屬性中仍然是null。此時,通過overrideMimeType()方法,可以保證把響應當作XML而非純文本來處理(即,responseXML中被賦值)。

  1. var xhr = createXHR();
  2. xhr.open("get", "text.php", true);
  3. xhr.overrideMimeType("text/xml");
  4. xhr.send(null);

三、進度事件

6個進度事件:

  • loadstart:在接收到響應數據的第一個字節時觸發。
  • progress:在接收響應期間持續不斷地觸發。
  • error:在請求發生錯誤時觸發。
  • abort:在因爲調用abort()方法而終止時觸發。
  • load:在接收到完整的響應數據時觸發。
  • loadend:在通信完成或者觸發error、abort或load事件後觸發。 
    圖 進度事件

1. load事件

可以代替readystatechagne事件。其處理程序會接收到一個event對象,其target屬性指向XHR對象實例,因而可以訪問到XHR對象的所有方法和屬性。然而,並非所有瀏覽器都實現了事件對象。

2. progress事件

其處理程序會接收一個event對象,其target屬性指向XHR對象實例,但包含着三個額外的屬性

  • lengthComputable:是一個表示進度信息是否可用的布爾值
  • position:表示已經接收的字節數
  • totalSize:根據content-length響應頭確定的預期字節數 
    注意其必須在調用open()方法之前添加
  1. var xhr = createXHR();
  2. xhr.onload = function(event){
  3. // event.target存在兼容性問題,所以只能使用xhr
  4. if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
  5. console.log(xhr.responseText);
  6. } else {
  7. console.log("Request was unsuccessful: " + xhr.status);
  8. }
  9. };
  10. xhr.onprogress = function(event){
  11. var divStatus = document.getElementById("status");
  12. if (event.lengthComputable){
  13. divStatus.innerHTML = "Received " + event.position + " of " + event.totalSize + " bytes";
  14. }
  15. };
  16. xhr.open("get", "altevents.php", true);
  17. xhr.send(null);

四、跨源資源共享

CORS(Cross-Origin Resource Sharing)背後的基本思想,就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功還是失敗。

在發送請求時,給其附加一個額外的Origin頭部,其中包含請求頁面的源信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應。

Origin: http://www.test.com

如果服務認爲這個請求可以接受,在Access-Control-Allow-Origin頭部中回發相同的源信息(如果是公共資源,可以回發”*”)。

Access-Control-Allow-Origin: http://www.test.com

注意:請求和響應都不包含cookie信息。

1. IE中實現CORS:XDR(XDomainRequest),所有的XDR請求都是異步的,不能創建同步請求。其使用方法類似於XHR。

2. 其他瀏覽器對CORS的實現:通過XMLHttpRequest對象實現對CORS的原生支持。只需給open()方法傳入絕對地址。支持同步請求。

跨域XHR對象的安全限制: 
(1)不能使用setRequestHeader()設置自定義頭部。 
(2)不能發送和接收cookie。 
(3)調用getAllResponseHeaders()方法總會返回空字符串。

建議:訪問本地資源,最好使用相對URL;訪問遠程資源,使用絕對URL。

3. 跨瀏覽器的CORS

  1. function createCORSRequest(method, url){
  2. var xhr = new XMLHttpRequest();
  3. if ("withCredentials" in xhr){ // 檢測XHR是否支持CORS的簡單方式,就是檢測是否存在withCredentials屬性
  4. xhr.open(method, url, true);
  5. } else if (typeof XDomainRequest != "undefined"){ // IE XDR
  6. xhr = new XDomainRequest();
  7. xhr.open(method, url);
  8. } else {
  9. xhr = null;
  10. }
  11. return xhr;
  12. }
  13. var request = createCORSRequest("get", "http://www.somewhere-else.com/xdr.php");
  14. if (request){
  15. request.onload = function(){
  16. //do something with request.responseText
  17. };
  18. request.send();
  19. }

五、其他跨域技術

利用DOM中能夠執行跨域請求的功能,在不依賴XHR對象的情況下也能發送某種請求,其不需要修改服務器端代碼。

1. 圖像Ping

<img>標籤,可以從任何網頁中加載圖像,無需關注是否跨域。這也是廣告跟蹤瀏覽量的主要方式。 
圖像Ping是與服務器進行簡單、單向的跨域通信的一種方式。瀏覽器得不到任何具體的數據。但通過監聽load和error事件,可以知道響應是什麼時間接收到的。

  1. var img = new Image();
  2. img.onload = img.error = function() {
  3. console.log("Done!");
  4. };
  5. img.src = "http://www.test.com/getImage?id=1";

缺點
(1)只能發送Get請求 
(2)無法訪問服務器的響應文本

2. JSONP(JSON with padding)

兩部分組成:回調函數和數據。 
回調函數是當響應到來時應該在頁面調用的函數。回到函數的名字一般是在請求中指定的。而數據是傳入回調函數中的JSON數據。 
JSONP是通過動態<script>元素來使用的

  1. function handleResponse(response){
  2. alert("You're at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name);
  3. }
  4. var script = document.createElement("script");
  5. script.src = "http://freegeoip.net/json/?callback=handleResponse";
  6. document.body.insertBefore(script, document.body.firstChild);

優點:能夠直接訪問響應文本,支持在瀏覽器與服務器之間雙向通信。 
缺點: 
(1)JSONP是從其他域中加載代碼執行,其安全性無法確保。 
(2)不能很容易的確定JSONP請求是否失敗。

3. Comet

更高級的Ajax技術,服務器向頁面推送數據。 
兩種實現Comet的方式:長輪詢和流。 
Ajax與Comet-Comet長輪詢 
(1)長輪詢:頁面發起一個到服務器的請求,然後服務器一直保持連接打開,直到有數據可發送。發送完數據之後,瀏覽器關閉連接,隨即又發起一個到服務器的新請求。【區別:短輪詢,服務器立即發送響應,無論是否有效,而長輪詢是等待發送響應。】

(2)HTTP流:生命週期內只使用一個HTTP連接。瀏覽器向服務器發送一個請求,而服務器保持連接打開,然後週期性地向瀏覽器發送數據。

  1. /**
  2. * progress:接收數據時調用的函數
  3. * finished:關閉連接時調用的函數
  4. */
  5. function createStreamingClient(url, progress, finished){
  6. var xhr = new XMLHttpRequest(),
  7. received = 0;
  8. xhr.open("get", url, true);
  9. xhr.onreadystatechange = function(){
  10. var result;
  11. if (xhr.readyState == 3){
  12. //get only the new data and adjust counter
  13. result = xhr.responseText.substring(received);
  14. received += result.length;
  15. //call the progress callback
  16. progress(result);
  17. } else if (xhr.readyState == 4){
  18. finished(xhr.responseText);
  19. }
  20. };
  21. xhr.send(null);
  22. return xhr;
  23. }
  24. var client = createStreamingClient("streaming.php",function(data){alert("Received: " + data);}, function(data){alert("Done!");});

服務器發送事件:SSE和事件流

4. Web Sockets

目標是在一個單獨的持久連接上提供全雙工、雙向通信。 
優點:能夠在客戶端和服務器之間發送非常少量的數據,而不必擔心HTTP那樣字節級的開銷。 
缺點:制定協議的時間比制定JavaScript API的時間還要長。

  1. // 必須給WebSocket構造函數傳入絕對URL
  2. var socket = new WebSocket("ws://www.example.com/server.php");
  3. // 向服務器發送數據(只能發送純文本,其他數據需要序列化)
  4. socket.send("Hello");
  5. // 接收服務器的響應數據
  6. socket.onmessage = function(event) {
  7. var data = event.data;
  8. };

其他事件:

  • open:在成功建立連接時觸發。
  • error:在發生錯誤時觸發,連接不能持續。
  • close:在連接關閉時觸發。

注意:WebSocket對象不支持DOM 2級事件偵聽器,必須使用DOM 0級語法分別定義各個事件。

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