關於http協議已經在另外兩篇文章中進行了說明,當然很多細節需要在使用過程中研讀相關的標準纔可以,這裏不討論http,專注於理解Ajax的工作原理。
關於Ajax的優點:AJAX 是一種用於創建快速動態網頁的技術。通過在後臺與服務器進行少量數據交換,AJAX 可以使網頁實現異步更新,可以在不重新加載整個網頁的情況下,對網頁的某部分進行更新。傳統的網頁(不使用 AJAX)如果需要更新內容,必需重載整個網頁面。比較常見的一個例子是,當我們在百度或google的搜索框裏輸入關鍵詞時,瀏覽器會將這些關鍵詞送給服務器,服務器返回一個搜索建議的列表,並展現在下拉框裏。
關於Ajax的缺點,對應用Ajax最主要的批評就是,它可能破壞瀏覽器後退按鈕的正常行爲。在動態更新頁面的情況下,用戶無法回到前一個頁面狀態,這是因爲瀏覽器僅能記下歷史記錄中的靜態頁面。一個被完整讀入的頁面與一個已經被動態修改過的頁面之間的差別非常微妙;用戶通常都希望單擊後退按鈕,就能夠取消他們的前一次操作,但是在Ajax應用程序中,卻無法這樣做。不過開發者已想出了種種辦法來解決這個問題,當中大多數都是在用戶單擊後退按鈕訪問歷史記錄時,通過建立或使用一個隱藏的IFRAME來重現頁面上的變更。(例如,當用戶在Google Maps中單擊後退時,它在一個隱藏的IFRAME中進行搜索,然後將搜索結果反映到Ajax元素上,以便將應用程序狀態恢復到當時的狀態。)一個相關的觀點認爲,使用動態頁面更新使得用戶難於將某個特定的狀態保存到收藏夾中。該問題的解決方案也已出現,大部分都使用URL片斷標識符 (通常被稱爲錨點,即URL中#後面的部分)來保持跟蹤,允許用戶回到指定的某個應用程序狀態。(許多瀏覽器允許JavaScript動態更新錨點,這使得Ajax應用程序能夠在更新顯示內容的同時更新錨點。)這些解決方案也同時解決了許多關於不支持後退按鈕的爭論。 進行Ajax開發時,網絡延遲——即用戶發出請求到服務器發出響應之間的間隔——需要慎重考慮。不給予用戶明確的迴應 ,沒有恰當的預讀數據 ,或者對XMLHttpRequest的不恰當處理,都會使用戶感到延遲,這是用戶不欲看到的,也是他們無法理解的。通常的解決方案是,使用一個可視化的組件來告訴用戶系統正在進行後臺操作並且正在讀取數據和內容。Ajax的無刷新重載,由於頁面的變化沒有刷新重載那麼明顯,所以容易給用戶帶來困擾――用戶不太清楚現在的數據是新的還是已經更新過的;現有的解決有:在相關位置提示、數據更新的區域設計得比較明顯、數據更新後給用戶提示等; 對串流媒體的支持沒有FLASH、Java Applet好。
下面我們進入Ajax的應用討論,使用Ajax的標識是XMLHttpRequest對象,這個對象是由瀏覽器支持的,所有現代瀏覽器均支持 XMLHttpRequest 對象(IE5 和 IE6 使用 ActiveXObject)。下面是在js腳本中生成對象的方法。
- var xhr;
- if (window.XMLHttpRequest)
- {// code for IE7+, Firefox, Chrome, Opera, Safari
- xhr=new XMLHttpRequest();
- }
- else
- {// code for IE6, IE5
- xhr=new ActiveXObject("Microsoft.XMLHTTP");
- }
我們先來學習一下這個對象。
XMLHttpRequest 對象提供了對 HTTP 協議的完全的訪問,包括做出 POST 和 HEAD 請求以及普通的 GET 請求的能力。XMLHttpRequest 可以同步或異步地返回 Web 服務器的響應,並且能夠以文本或者一個 DOM 文檔的形式返回內容。儘管名爲 XMLHttpRequest,它並不限於和 XML 文檔一起使用:它可以接收任何形式的文本文檔。
XMLHttpRequest 對象還沒有標準化,但是 W3C 已經開始了標準化的工作,本手冊介紹的內容都是基於標準化的工作草案。
當前的 XMLHttpRequest 實現已經相當一致。但是和標準有細微的不同。例如,一個實現可能返回 null,而標準要求是空字符串,或者實現可能把 readyState 設置爲 3 而不保證所有的響應頭部都可用。
屬性
readyState
HTTP 請求的狀態.當一個 XMLHttpRequest 初次創建時,這個屬性的值從 0 開始,直到接收到完整的 HTTP 響應,這個值增加到 4。
5 個狀態中每一個都有一個相關聯的非正式的名稱,下表列出了狀態、名稱和含義:
狀態 | 名稱 | 描述 |
---|---|---|
0 | Uninitialized | 初始化狀態。XMLHttpRequest 對象已創建或已被 abort() 方法重置。 |
1 | Open | open() 方法已調用,但是 send() 方法未調用。請求還沒有被髮送。 |
2 | Sent | Send() 方法已調用,HTTP 請求已發送到 Web 服務器。未接收到響應。 |
3 | Receiving | 所有響應頭部都已經接收到。響應體開始接收但未完成。 |
4 | Loaded | HTTP 響應已經完全接收。 |
readyState 的值不會遞減,除非當一個請求在處理過程中的時候調用了 abort() 或 open() 方法。每次這個屬性的值增加的時候,都會觸發 onreadystatechange 事件句柄。
responseText
目前爲止爲服務器接收到的響應體(不包括頭部),或者如果還沒有接收到數據的話,就是空字符串。
如果 readyState 小於 3,這個屬性就是一個空字符串。當 readyState 爲 3,這個屬性返回目前已經接收的響應部分。如果 readyState 爲 4,這個屬性保存了完整的響應體。
如果響應包含了爲響應體指定字符編碼的頭部,就使用該編碼。否則,假定使用 Unicode UTF-8。
responseXML
對請求的響應,解析爲 XML 並作爲 Document 對象 返回。
status
由服務器返回的 HTTP 狀態代碼 ,如 200 表示成功,而 404 表示 "Not Found" 錯誤。當 readyState 小於 3 的時候讀取這一屬性會導致一個異常。
statusText
這個屬性用名稱而不是數字指定了請求的 HTTP 的狀態代碼。也就是說,當狀態爲 200 的時候它是 "OK",當狀態爲 404 的時候它是 "Not Found"。和 status 屬性一樣,當 readyState 小於 3 的時候讀取這一屬性會導致一個異常。
事件句柄
onreadystatechange
每次 readyState 屬性改變的時候調用的事件句柄函數。當 readyState 爲 3 時,它也可能調用多次。
方法
abort()
取消當前響應,關閉連接並且結束任何未決的網絡活動。
這個方法把 XMLHttpRequest 對象重置爲 readyState 爲 0 的狀態,並且取消所有未決的網絡活動。例如,如果請求用了太長時間,而且響應不再必要的時候,可以調用這個方法。
getAllResponseHeaders()
把 HTTP 響應頭部作爲未解析的字符串返回。
如果 readyState 小於 3,這個方法返回 null。否則,它返回服務器發送的所有 HTTP 響應的頭部。頭部作爲單個的字符串返回,一行一個頭部。每行用換行符 "/r/n" 隔開。
getResponseHeader()
返回指定的 HTTP 響應頭部的值。其參數是要返回的 HTTP 響應頭部的名稱。可以使用任何大小寫來制定這個頭部名字,和響應頭部的比較是不區分大小寫的。
該方法的返回值是指定的 HTTP 響應頭部的值,如果沒有接收到這個頭部或者 readyState 小於 3 則爲空字符串。如果接收到多個有指定名稱的頭部,這個頭部的值被連接起來並返回,使用逗號和空格分隔開各個頭部的值。
XMLHttpRequest.open()
初始化 HTTP 請求參數
語法
open(method, url, async, username, password)
method 參數是用於請求的 HTTP 方法。值包括 GET、POST 和 HEAD。
url 參數是請求的主體。大多數瀏覽器實施了一個同源安全策略,並且要求這個 URL 與包含腳本的文本具有相同的主機名和端口。
async 參數指示請求使用應該異步地執行。如果這個參數是 false,請求是同步的,後續對 send() 的調用將阻塞,直到響應完全接收。如果這個參數是 true 或省略,請求是異步的,且通常需要一個 onreadystatechange 事件句柄。
username 和 password 參數是可選的,爲 url 所需的授權提供認證資格。如果指定了,它們會覆蓋 url 自己指定的任何資格。
說明
這個方法初始化請求參數以供 send() 方法稍後使用。它把 readyState 設置爲 1,刪除之前指定的所有請求頭部,以及之前接收的所有響應頭部,並且把 responseText、responseXML、status 以及 statusText 參數設置爲它們的默認值。當 readyState 爲 0 的時候(當 XMLHttpRequest 對象剛創建或者 abort() 方法調用後)以及當 readyState 爲 4 時(已經接收響應時),調用這個方法是安全的。當針對任何其他狀態調用的時候,open() 方法的行爲是爲指定的。
除了保存供 send() 方法使用的請求參數,以及重置 XMLHttpRequest 對象以便複用,open() 方法沒有其他的行爲。要特別注意,當這個方法調用的時候,實現通常不會打開一個到 Web 服務器的網絡連接。
XMLHttpRequest.send()
發送一個 HTTP 請求
語法
send(body)
如果通過調用 open() 指定的 HTTP 方法是 POST 或 PUT,body 參數指定了請求體,作爲一個字符串或者 Document 對象 。如果請求體不適必須的話,這個參數就爲 null。對於任何其他方法,這個參數是不可用的,應該爲 null(有些實現不允許省略該參數)。
說明
這個方法導致一個 HTTP 請求發送。如果之前沒有調用 open(),或者更具體地說,如果 readyState 不是 1,send() 拋出一個異常。否則,它發送一個 HTTP 請求,該請求由以下幾部分組成:
- 之前調用 open() 時指定的 HTTP 方法、URL 以及認證資格(如果有的話)。
- 之前調用 setRequestHeader() 時指定的請求頭部(如果有的話)。
- 傳遞給這個方法的 body 參數。
一旦請求發佈了,send() 把 readyState 設置爲 2,並觸發 onreadystatechange 事件句柄。
如果之前調用的 open() 參數 async 爲 false,這個方法會阻塞並不會返回,直到 readyState 爲 4 並且服務器的響應被完全接收。否則,如果 async 參數爲 true,或者這個參數省略了,send() 立即返回,並且正如後面所介紹的,服務器響應將在一個後臺線程中處理。
如果服務器響應帶有一個 HTTP 重定向,send() 方法或後臺線程自動遵從重定向。當所有的 HTTP 響應頭部已經接收,send() 或後臺線程把 readyState 設置爲 3 並觸發 onreadystatechange 事件句柄。如果響應較長,send() 或後臺線程可能在狀態 3 中觸發 onreadystatechange 事件句柄:這可以作爲一個下載進度指示器。最後,當響應完成,send() 或後臺線程把 readyState 設置爲 4,並最後一次觸發事件句柄。
XMLHttpRequest.setRequestHeader()
語法
setRequestHeader(name, value)
name 參數是要設置的頭部的名稱。這個參數不應該包括空白、冒號或換行。
value 參數是頭部的值。這個參數不應該包括換行。
說明
setRequestHeader() 方法指定了一個 HTTP 請求的頭部,它應該包含在通過後續 send() 調用而發佈的請求中。這個方法只有當 readyState 爲 1 的時候才能調用,例如,在調用了 open() 之後,但在調用 send() 之前。
如果帶有指定名稱的頭部已經被指定了,這個頭部的新值就是:之前指定的值,加上逗號、空白以及這個調用指定的值。
如果 open() 調用指定了認證資格,XMLHttpRequest 自動發送一個適當的 Authorization 請求頭部。但是,你可以使用 setRequestHeader() 來添加這個頭部。類似地,如果 Web 服務器已經保存了和傳遞給 open() 的 URL 相關聯的 cookie,適當的 Cookie 或 Cookie2 頭部也自動地包含到請求中。可以通過調用 setRequestHeader() 來把這些 cookie 添加到頭部。XMLHttpRequest 也可以爲 User-Agent 頭部提供一個默認值。如果它這麼做,你爲該頭部指定的任何值都會添加到這個默認值後面。
有些請求頭部由 XMLHttpRequest 自動設置而不是由這個方法設置,以符合 HTTP 協議。這包括如下和代理相關的頭部:
- Host
- Connection
- Keep-Alive
- Accept-charset
- Accept-Encoding
- If-Modified-Since
- If-None-Match
- If-Range
- Range
下面我們通過一個前面提到的搜索引擎建議搜索的例子,例子很簡單,當我們輸入A-Z字母時,服務器返回建議的單詞列表。
首先是客戶端html代碼
- <html>
- <head>
- <mce:script type="text/javascript"><!--
- function showHint(str)
- {
- var xmlhttp;
- if (str.length==0)
- {
- document.getElementById("txtHint").innerHTML="";
- return;
- }
- if (window.XMLHttpRequest)
- {// code for IE7+, Firefox, Chrome, Opera, Safari
- xmlhttp=new XMLHttpRequest();
- }
- else
- {// code for IE6, IE5
- xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
- }
- xmlhttp.onreadystatechange=function()
- {
- if (xmlhttp.readyState==4 && xmlhttp.status==200)
- {
- document.getElementById("txtHint").innerHTML=xmlhttp.responseText;
- }
- }
- xmlhttp.open("GET","/ajax/gethint.php?q="+str,true);
- xmlhttp.send();
- }
- // --></mce:script>
- </head>
- <body>
- <h3>請在下面的輸入框中鍵入字母(A - Z):</h3>
- <form action="">
- 姓氏:<input type="text" id="txt1" onkeyup="showHint(this.value)" />
- </form>
- <p>建議:<span id="txtHint"></span></p>
- </body>
- </html>
原理是當用戶輸入字母后,將字母交由xhr對象傳遞給遠程服務器,並註冊了狀態變更的事件函數或回調函數,這樣,在不影響用戶輸入的情況下,如果服務器返回的提示正確到達,則通過提示區顯示給用戶。
下面是服務器的php代碼:
- <?php
- // 用名字來填充數組
- $a[]="Anna";
- $a[]="Brittany";
- $a[]="Cinderella";
- $a[]="Diana";
- $a[]="Eva";
- $a[]="Fiona";
- $a[]="Gunda";
- $a[]="Hege";
- $a[]="Inga";
- $a[]="Johanna";
- $a[]="Kitty";
- $a[]="Linda";
- $a[]="Nina";
- $a[]="Ophelia";
- $a[]="Petunia";
- $a[]="Amanda";
- $a[]="Raquel";
- $a[]="Cindy";
- $a[]="Doris";
- $a[]="Eve";
- $a[]="Evita";
- $a[]="Sunniva";
- $a[]="Tove";
- $a[]="Unni";
- $a[]="Violet";
- $a[]="Liza";
- $a[]="Elizabeth";
- $a[]="Ellen";
- $a[]="Wenche";
- $a[]="Vicky";
- //獲得來自 URL 的 q 參數
- $q=$_GET["q"];
- //如果 q 大於 0,則查找數組中的所有提示
- if (strlen($q) > 0)
- {
- $hint="";
- for($i=0; $i<count($a); $i++)
- {
- if (strtolower($q)==strtolower(substr($a[$i],0,strlen($q))))
- {
- if ($hint=="")
- {
- $hint=$a[$i];
- }
- else
- {
- $hint=$hint." , ".$a[$i];
- }
- }
- }
- }
- // 如果未找到提示,則把輸出設置爲 "no suggestion"
- // 否則設置爲正確的值
- if ($hint == "")
- {
- $response="no suggestion";
- }
- else
- {
- $response=$hint;
- }
- //輸出響應
- echo $response;
- ?>