《JavaScript》高級程序設計第21章:Ajax和Comet,jsonp

Ajax的技術核心是XMLHttpRequest對象(簡稱XHR)javascript

1、建立XMLHttpRequest對象


 

 1     function createXHR(){
 2         if(typeof XMLHttpRequest != "undefined"){
 3             //IE7, FireFox, Opera, Chrome, Safari都支持原生的XHR對象,這些瀏覽器中可使用XMLHttpRequest構造函數
 4             return new XMLHttpRequest();        
 5         } else if (typeof ActiveXObject != "undefined"){
 6             //使用於IE7以前的版本
 7             if(typeof arguments.callee.activeXString != 'string'){
 8                 var versions = [ "MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
 9                                  "MSXML2.XMLHttp"],
10                     i, len;
11                 for(i=0, len=versions.length; i<len; i++){
12                     try{
13                         new ActiveXObject(versions[i]);
14                         arguments.callee.activeXString = versions[i];
15                         break;
16                     } catch(ex){
17                         //跳過
18                     }
19                 }
20             }
21             return new ActiveXObject(arguments.callee.activeXString);
22         } else {
23             throw new Error("No XHR object available");
24         }
25     }
26 
27     //建立XHR對象
28     var xhr = createXHR();

2、XHR的用法


 

  1. open()方法:

    • 三個參數:發送的請求類型;請求的url;是否異步發送
    • 注意:
      • url相對於執行代碼的當前頁面(也可使用絕對路徑)
      • 調用open()方法並不會真正發送請求,而只是啓動一個請求以備發送
      • 只能向同一個域中使用相同端口協議的url發送請求,若是url和啓動請求的頁面有任何差異,都會引起安全錯誤!

  2. 發送請求:send()方法

    • 接收一個參數,即要做爲請求主體發送的數據。
    • 若是不須要經過請求主體發送數據,則必須傳入null。這個參數對某些瀏覽器來講是必須的。

  3. 收到響應後,響應的數據會自動填充xhr對象的屬性。相關的屬性有:

    • responseText
    • responseXML
    • status
    • statusText

   收到響應,首先要檢查status屬性,通常來講,可將http狀態代碼爲200做爲成功標誌。此外,狀態碼304表示請求資源沒有被修改,能夠直接使用瀏覽器中緩存的版本,則響應也有效。php

   對於異步請求,能夠檢測xhr對象的readyStatus屬性,該屬性表示請求/響應過程的當前活動階段,可取值以下:html

    • 0:未初始化。還沒有調用open()方法
    • 1:啓動。已經調用open()方法,還沒有調用send()方法
    • 2:發送。已經調用send()方法,還沒有接收到響應
    • 3:接收。已經接收部分響應數據。
    • 4:完成。已經接收到所有響應數據,並且已經能夠在客戶端使用了。【通常只需檢查這個階段】

   只要readyStatus屬性改變,都會觸發一次readyStatechange事件。html5

 1     var xhr = createXHR();
 2     //必需要在open()方法前指定onreadyStatechange事件處理程序才能確保跨瀏覽器兼容性
 3     xhr.onreadyStatechange = function(){
 4         //這裏使用xhr對象而非this對象,緣由是此事件的做用域問題
 5         //若是使用this對象,在有的瀏覽器中會致使函數執行失敗,或者致使錯誤發生。
 6         if(xhr.readyState == 4){
 7             if((xhr.status >= 200 && xhr.status <300) || xhr.status == 304){
 8                 alert(xhr.responseText);
 9             } else {
10                 alert("Request was unsuccessful: " + xhr.status);
11             }
12         }
13     };
14     xhr.open("get", "example.txt", true);
15     xhr.send(null);

 

3、XMLHttpRequest 2 級


 

4、進度事件


 

5、跨域資源共享


 

1. 跨域 :

  跨域安全策略:默認狀況下, XHR對象只能訪問與包含它 的頁面位於同一個域中的資源。java

  所謂跨域,就是由於JavaScript同源策略的限制,a.com 域名下的js沒法操做b.com或是c.a.com域名下的對象。ajax

  簡單來講,同源策略是指一段腳本只能讀取來自同一來源的窗口和文檔的屬性,這裏的同一來源指的是主機名協議端口號的組合.json

 

URL跨域

說明瀏覽器

是否容許通訊緩存

http://www.a.com/a.js
http://www.a.com/b.js
同一域名下 容許
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不一樣文件夾 容許
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不一樣端口 不容許
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不一樣協議 不容許
http://www.a.com/a.js
http://70.32.92.74/b.js
域名和域名對應ip 不容許
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不一樣 不容許
http://www.a.com/a.js
http://a.com/b.js
同一域名,不一樣二級域名(同上) 不容許(cookie這種狀況下也不容許訪問)
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不一樣域名 不容許

 

2. 跨域資源共享(CORS,Cross-Origin Resource Sharing)

  是W3C的標準。

  基本思想:使用自定義的http頭部讓瀏覽器與服務器進行溝通,從而決定請求或相應是應該成功,仍是應該失敗。(這一塊的東西看着煩,先無論了)

 

6、其餘跨域技術


 

1. 圖像Ping

  即便用<img>標籤。由於網頁能夠從任何網頁中加載圖像,而不用擔憂是否跨域。

  請求的數據是經過查詢字符串形式發送的,而響應能夠是任意內容。經過圖像Ping,瀏覽器得不到任何具體的數據,可是經過偵聽load和error事件,能知道響應是何時接收到的。

  

1     var img = new Image();
2     //將onload和onerror事件處理程序指定爲同一個函數。這樣不管是什麼響應,只要請求完成,就能獲得通知。
3     img.onload = img.onerror = function(){
4         alert('Done');
5     };
6     //請求從設置src屬性那一刻開始,這裏在請求中發送了一個name參數
7     img.src='http://xxxxxxxx/test?name=Nicholas';

  缺點:

    • 只能發送GET請求
    • 沒法訪問服務器的響應文本
    • 所以只能用於瀏覽器與服務器間的單向通訊

2. JSONP(JSON with padding,填充式JSON/參數式JSON)

  是被包含在函數調用中的JSON。由兩部分組成:

    1)回調函數:當響應到來時應該在頁面中調用的函數。通常在請求中指定

    2)數據:傳入回調函數中的JSON數據

  JSONP是經過動態<script>元素來使用,使用時能夠爲src屬性指定一個跨域URL。這裏script和img元素相似,都有能力不受限地從其餘域加載資源。由於JSONP是有效的JavaScript代碼,因此在請求完成後,即在JSONP響應加載到頁面中之後,會當即執行。

1     function handleResponse(response){
2         alert("You are at IP address " + response.ip + ", which is in " +
3             response.city + ", " + response.region_name);               //試了下真的能夠彈出個人地址!!!
4     }
5 
6     var script = document.createElement("script");    
7     script.src = "http://freegeoip.net/json/?callback=handleResponse";    //指定回調函數是handleResponse()
8     document.body.insertBefore(script,document.body.firstChild);  

  與圖片Ping相比,JSONP的優勢在於:

    1)可以直接訪問響應文本

    2)支持在瀏覽器與服務器之間雙向通訊。

  不足:

    1)JSONP是從其餘域中加載代碼執行。其餘域的安全性難以保證

    2)要確保JSONP請求是否失敗並不容易。<script>元素的onerror事件處理程序不被瀏覽器支持,所以必須使用計時器檢測指定時間內是否收到響應。可是用戶上網速度和帶寬並不必定。

 

  JSONP的原理:

  網頁能夠獲得從其餘來源動態產生的Json資料,而這種使用模式就是所謂的Jsonp。用Jsonp抓到的資料並非Json,而是任意的Javascript,用Javascript直譯器執行而不是用Json解析器解析。好比:

  看如下的代碼就理解jsonp如何進行回調函數了。

JavaScript:

//注意:Jsonp只能使用GET請求
$.ajax({
  url: 'http://www.shihj.com',
  dataType: 'jsonp',
    success: function (response) {
        console.log(response);
    }
});

PHP:

$response = array('Javascript', 'PHP', 'Html', 'CSS');
$response = json_encode($response);

$callback = isset($_GET['callback']) ? $_GET['callback'] : false;
if ($callback) {
    $response = 'try{' . $callback . '(' . $response . ')}catch(e){}';
}
exit($response);

 

3. Comet(「服務器推送」)

  Ajax是一種從頁面向服務器請求數據的技術,而Comet是一種服務器向頁面推送數據的技術。Comet可以讓信息近乎實時地被推送到頁面上

  有兩種實現Comet的方式:

  1)長輪詢:

    定義:頁面發起一個到服務器的請求,而後服務器一直保持鏈接打開,知道有數據可發送。發送完數據後,瀏覽器關閉鏈接,隨即又發起一個到服務器的新請求。這一過程在頁面打開期間一直持續不斷。

    優勢:全部瀏覽器都支持,使用XHR和setTimeout()就能實現

  2)http流:

    定義:瀏覽器向服務器發送一個請求,而服務器保存鏈接打開,而後週期性地向瀏覽器發送數據。

 1     //在IE中不可用
 2     //三個參數:url, 接收到數據時調用的函數, 關閉鏈接時調用的函數  
 3     function createStreamingClient(url, progress, finished){
 4         var xhr = new XMLHttpRequest(),
 5             received = 0;
 6 
 7         xhr.open("get", url, true);
 8         xhr.onreadystatechange = function(){
 9             var result;
10 
11             if(xhr.readyState == 3){
12 
13                 //只取得最新數據並調整計數器
14                 result = xhr.responseText.substring(received);
15                 received  += result.length;
16 
17                 //調用progress回調函數
18                 progress(result);
19             } else if (xhr.readyState == 4){
20                 finished(xhr.responseText);
21             }
22         };
23         xhr.send(null);
24         return xhr;
25     } 
26 
27     var client = createStreamingClient("streaming.php", function(data){
28                     alert("Received: " + data);
29                 }, function(data){
30                     alert("Done!");
31                 });
32     });

 

4. 服務器發送事件(SSE:Server-Sent Events)

  是圍繞只讀Comet交互推出的API或者模式。SSE API用於建立到服務器的單向鏈接,服務器經過這個鏈接能夠發送任意數量的數據。

  支持短輪詢,長輪詢和HTTP流,且能在斷開鏈接時自動肯定什麼時候從新鏈接

  不支持ie

5. Web Sockets

 目標:在一個單獨的持久鏈接上提供全雙工、雙向通訊。

 在js中建立了WebSocket後,會有一個http請求發送到瀏覽器以發起鏈接。在取得服務器響應後,創建的鏈接會使用http升級從http協議交換爲Web Socket協議。只有支持這種協議的服務器才能正常工做。

  http://    --->    ws://

  https://    --->    wss://

 建立Web Socket:

  var socket = new WebSocket("ws://www.example.com/server.php");

  必須傳入絕對url。同源策略對Web Socket不適用。所以能夠鏈接任何站點。

 實例化WebSocket對象後,瀏覽器就會立刻嘗試建立鏈接。與xhr相似,WebSocket也有一個表示當前狀態的readyState屬性:

WebSocket.OPENING (0) 正在創建鏈接
WebSocket.OPEN (1) 已經創建鏈接
WebSocket.CLOSING (2) 正在關閉鏈接
WebSocket.CLOSE (3) 已經關閉鏈接

 要關閉Web Socket鏈接,能夠在任什麼時候候調用close()方法:socket.close();

6. SSE & Web Sockets

  1) 是否有自由度創建和維護Web Sockets服務器?

  2)是否須要雙向通訊?

7. 跨域方法小結:

  以上,我看Web Socket還能夠實現跨域,但SSE好像沒有涉及到跨域?SSE和Web Socket兩節應該都是偏向於講述兩種Comet(服務器推送)的方法。而至於跨域的方法,綜合上面介紹的幾種:

  1)圖片Ping:使用<img>標籤

  2)JSONP:經過動態<script>元素調用

  3)後臺代理方法:這種方式能夠解決全部跨域問題,也就是將後臺做爲代理,每次對其它域的請求轉交給本域的後臺,本域的後臺經過模擬http請求去訪問其它域,再將返回的結果返回給前臺,這樣作的好處是,不管訪問的是文檔,仍是js文件均可以實現跨域。

  4)設置document.domain:只適用於不一樣子域的框架間的交互。

  5) 使用window.name

    • 在應用頁面(a.com/app.html)中建立一個iframe,把其src指向數據頁面(b.com/data.html)。數據頁面會把數據附加到這個iframe的window.name上
    • 在應用頁面(a.com/app.html)中監聽iframe的onload事件,在此事件中設置這個iframe的src指向本地域的代理文件(代理文件和應用頁面在同一域下,因此能夠相互通訊)
    • 獲取數據之後銷燬這個iframe,釋放內存;這也保證了安全(不被其餘域frame js訪問)。

  6) 使用HTML5的新方法:window.postMessage()

    window.postMessage(message, targetOrigin) 方法是html5新引進的特性,可使用它來向其它的window對象發送消息,不管這個window對象是屬於同源或不一樣源。

 調用postMessage方法的window對象是指要接收消息的那一個window對象,該方法的第一個參數message爲要發送的消息,類型只能爲字符串;第二個參數targetOrigin用來限定接收消息的那個window對象所在的域,若是不想限定域,可使用通配符 * 。
 須要接收消息的window對象,但是經過監聽自身的message事件來獲取傳過來的消息,消息內容儲存在該事件對象的data屬性中。
 上面所說的向其餘window對象發送消息,其實就是指一個頁面有幾個框架的那種狀況,由於每個框架都有一個window對象。在討論第種方法的時候,咱們說過,不一樣域的框架間是能夠獲取到對方的window對象的,雖然沒什麼用,可是有一個方法是可用的-window.postMessage。下面看一個簡單的示例,有兩個頁面:

a.com/index.html中的代碼:

1 <iframe id="ifr" src="b.com/index.html"></iframe>
2 <script type="text/javascript">
3     window.onload = function(){
4         var ifr = document.getElementById('ifr');
5         var targetOrigin = "http://b.com";  //若寫成http://b.com/c/proxy.html效果同樣
6                                             //若寫成'http://c.com'就不會執行postMessage了
7         ifr.contentWindow.postMessage('I was there! ', targetOrigin);
8     };
9 </script>

b.com/index.html中的代碼:

 1 <script type="text/javascript">
 2     window.addEventListener('message', function(event){
 3         //經過origin屬性判斷來源地址
 4         if(event.origin == 'http://a.com'){
 5             alert('event.data');    //彈出I was there
 6             alert(event.source);    //對a.com, index.html中window對象的引用
 7                                     //因爲同源策略,這裏event.source不能夠訪問window對象
 8         }
 9     },false)
10 </script>

 

7、安全


 

參考: JSONP解決跨域問題

相關文章
相關標籤/搜索