Ajax的技術核心是XMLHttpRequest對象(簡稱XHR)
一、創建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();
二、XHR的用法
1. open()方法:
-
- 三個參數:發送的請求類型;請求的url;是否異步發送
- 注意:
- url相對於執行代碼的當前頁面(也可以使用絕對路徑)
- 調用open()方法並不會真正發送請求,而只是啟動一個請求以備發送
- 只能向同一個域中使用相同端口和協議的url發送請求,如果url和啟動請求的頁面有任何差別,都會引發安全錯誤!
2. 發送請求:send()方法
-
- 接收一個參數,即要作為請求主體發送的數據。
- 如果不需要通過請求主體發送數據,則必須傳入null。這個參數對某些瀏覽器來說是必須的。
3. 收到響應后,響應的數據會自動填充xhr對象的屬性。相關的屬性有:
-
- responseText
- responseXML
- status
- statusText
收到響應,首先要檢查status屬性,一般來說,可將http狀態代碼為200作為成功標志。此外,狀態碼304表示請求資源沒有被修改,可以直接使用瀏覽器中緩存的版本,則響應也有效。
對於異步請求,可以檢測xhr對象的readyStatus屬性,該屬性表示請求/響應過程的當前活動階段,可取值如下:
-
- 0:未初始化。尚未調用open()方法
- 1:啟動。已經調用open()方法,尚未調用send()方法
- 2:發送。已經調用send()方法,尚未接收到響應
- 3:接收。已經接收部分響應數據。
- 4:完成。已經接收到全部響應數據,而且已經可以在客戶端使用了。【一般只需檢查這個階段】
只要readyStatus屬性改變,都會觸發一次readyStatechange事件。
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);
三、XMLHttpRequest 2 級
四、進度事件
五、跨域資源共享
1. 跨域 :
跨域安全策略:默認情況下, XHR對象只能訪問與包含它 的頁面位於同一個域中的資源。
所謂跨域,就是因為JavaScript同源策略的限制,a.com 域名下的js無法操作b.com或是c.a.com域名下的對象。
簡單來說,同源策略是指一段腳本只能讀取來自同一來源的窗口和文檔的屬性,這里的同一來源指的是主機名、協議和端口號的組合.
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頭部讓瀏覽器與服務器進行溝通,從而決定請求或相應是應該成功,還是應該失敗。(這一塊的東西看着煩,先不管了)
六、其他跨域技術
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>
七、安全
參考: JSONP解決跨域問題