Ajax 是 JavaScript 和 Web 開發史上最重要的發展之一,簡單地說,Ajax 是在瀏覽器使用 JavaScript 進行伺服器的後台請求,以便讀取附加資訊或者導致伺服器回應的過程,例如,請求可能是讀取一組待呈現的資料、查看使用者名稱是否可用,或更新資料庫的紀錄等。
Ajax 的處理從一個物件開始,再透過該物件進行互動。這個稱為「Ajax」的物件有些地方會叫作「XHR」物件,XHR 是 XML HTTP 請求(XML HTTP Request)的縮寫,或是使用這些關鍵字的變形。這些術語說明,Ajax 以 JavaScript 和超文本傳輸協定(HTTP)請求其他的資源,最初,回傳的資料是採用可擴充標記語言(XML)格式,現在已經較少使用了。
每個瀏覽器都定義了一個具備必要功能的 XMLHttpRequest 物件,但需注意的是,建立該物件的細節在不同瀏覽器可能稍有不同,透過以下程式,便能在所有的瀏覽器可靠地建立 Ajax 物件:
有了 Ajax 物件之後,下一步就是指定該物件的結果處理器,結果處理器是在 Ajax 交易期間呼叫的函數。
建立 Ajax 物件並指定回應處理函數之後,就可以執行實際的請求了:
最常見的請求類型是 GET 和 POST。GET 是請求任何 HTML 頁面的標準方法,也是點擊連結時瀏覽器發出的請求類型。理論上來說,GET 請求最適合讀取資料;POST 請求是提交表單的標準方法(除了搜尋引擎表單之外,搜尋引擎通常使用 GET)。同樣的,原則上 POST 請求適合發起伺服器的異動或回應,換句話說,GET 用於常見而重複的請求,並可存入書籤;POST則用無需考慮重複性的獨特請求,例如更新特定資料庫紀錄或者提交聯絡人表單(在這兩種情況下,整體概念都一樣,但細節略有不同)。
URL 參數可能是絕對路徑或相對路徑,如果連結的是 http://www.example.com/page.html 頁面,假設 page.php 和 page.html 在同一個目錄中,則 http://www.example.com/page.php 或者 page.php 都有效。針對此 URL,有兩個常見的問題和混淆的地方:
預設值為 true──非同步,不過還是應該明確提供此參數,非同步請求期間可以執行其他的 JavaScript 程式碼(例如處理其他事件),同時等待伺服器回應。同步的請求情況非常少,它阻止 JavaScript 在發起請求和處理的同時進行其他工作,包括處理基於使用者的事件。如果建立的是同步請求,就不需要透過函數來處理 readyState 的變化,因為腳本在進行任何其他工作之前必須等待伺服器回應。
這兩個參數可有可無,分別代表使用者名稱和密碼,如果資源受到 HTTP 認證保護,則得提供這兩個參數,但使用這些參數時,必須在 JavaScript 程式碼存取參數值,唯一安全的方法是讓使用者輸入值,而不是寫死在頁面的來源程式碼中。
最後一步是實際發送請求:
發送請求但尚未完成前,可以呼叫物件的
發出 Ajax 非同步請求之後,在物件的
在處理 readyState 變化的函數中,可以檢查 readyState 屬性並作出相對的反應,例如屬性值 4 意味著處理已完成,頁面能夠使用處理後的結果。其他值則表示 Ajax 請求仍在處理中,腳本不需要作任何事情,或是顯示「載入中......」訊息。
也可以在呼叫
物件的
根據 Ajax 的具體使用方法,如果返回有問題的代碼,便可重新啟用預設的瀏覽器行為:重新導向瀏覽器到 Ajax 的頁面,或真正地提交表單等等。
從伺服器資源抓取資料後,下一步就是送出資料給伺服器,有些情況下,傳輸資料可能會影響返回的資料,例如為了取得某部門的員工,Ajax 請求可以將部門識別字送到伺服器。其他情況下,發送的資料需通過伺服器的驗證,例如使用者名稱是否存在;或觸發伺服器回應,例如張貼評論置留言板上。
將資料作為請求的一部份送給伺服器有兩種方法:
然而發出 POST 請求時,必須為 send() 方法提供資料,而不是附加到 URL 之後,而且 POST 方法是為了增加可靠性。
不管發起的是 GET 或 POST 請求,資料結構都一樣,兩者各視合適的場合使用。
向伺服器發送資料的方案之一是建立 FormData 物件,接著呼叫 FormData 物件的
Ajax 最困難的方面就是偵錯,依照以下步驟偵錯便可找出原因:
第一件是測試伺服器資源,確認資料正傳回給 JavaScript,不僅做為偵錯的第一步,建議在建立伺服端資源之後立刻進行測試,以便在編寫任何 JavaScript 之前確認沒有問題。許多伺服端資源可以藉由直接在瀏覽器載入之方式進行測試,載入資源的效果往往和透過 JavaScript 接收回應相同。針對 PHP 腳本或其他動態的伺服端工具,可先載入他們以便確認運作正常。最初的 Ajax 嘗試遭到多次失敗,就是因為 PHP 腳本有錯誤,如果先測試腳本檔案,就能發現這些錯誤。在任何情況下,一定要確保透過 URL(http://something)載入 Ajax 頁面以及相關的資源。
如果 PHP 腳本(或其他的伺服端資源)需要接收一些資料才能正常運作,那麼測試會更加困難。倘若腳本採用 GET 方法,則可自行將資料附加到 URL 後面;如果是 POST 送給伺服端資源,就得向伺服器張貼資料,這並不容易,方法之一是送出表單資料給 PHP 腳本。實際上如果採用漸進式改善,就處於此一過程中:在完全不涉及 JavaScript 的情況下,測試以表單提交資料給 PHP 腳本。
第二件是使用網路監視器,例如 Firebug 和其他瀏覽器工具,像是 Opera 的 Dragonfly 以及 Safari 的 Web Inspector 都內建網路監視器,它能夠驗證請求的發生、顯示請求中傳輸的資料,並且顯示回應返回的資料,這些資料幾乎總是能揭示問題的起因,即使無法直接顯露問題,網路監視器也能提示問題是出在請求或回應中,由此便能相對地應用 JavaScript 或者 PHP 的偵錯技術。
開始使用不同的資料格式(如 JSON 和 XML)時,希望驗證資料本身的完整性,有時可能出現這種情況:伺服器返回明確的回應,但是資料未正確格式化,使得無法應用於 JavaScript 程式中。這時可以利用 JSONint 驗證 JSON 資料,或使用 XML Validator 驗證 XML 資料。
我們必須知道同一個頁面何時會發出多個 Ajax 請求。當頁面只有單比請求時,才能使用單個 Ajax 物件,如果要求多個不同的請求(可能在同一個時間)時,就得使用多個 Ajax 物件避免衝突。
最後,瀏覽器快取機制使偵錯變得困難,因為瀏覽器將試圖緩存 Ajax 請求的結果,所以針對伺服端資源的修改就無法立即反應,此一障礙有多種克服手段:
參考 W3school AJAX:http://www.w3schools.com/ajax/default.asp
1Ajax 基礎知識
以實際的 JavaScript 程式碼來說,Ajax 請求的執行從下面三個步驟開始:- 建立 Ajax 物件
- 發出請求
- 處理伺服器的回應
- 在請求中包含資料
- 偵錯 Ajax 交易
- 處理不同類型的伺服器回應
上述內容需要有與 JavaScript 溝通的實際伺服器資源,例如 PHP。
建立 Ajax 物件
Ajax 的處理從一個物件開始,再透過該物件進行互動。這個稱為「Ajax」的物件有些地方會叫作「XHR」物件,XHR 是 XML HTTP 請求(XML HTTP Request)的縮寫,或是使用這些關鍵字的變形。這些術語說明,Ajax 以 JavaScript 和超文本傳輸協定(HTTP)請求其他的資源,最初,回傳的資料是採用可擴充標記語言(XML)格式,現在已經較少使用了。
每個瀏覽器都定義了一個具備必要功能的 XMLHttpRequest 物件,但需注意的是,建立該物件的細節在不同瀏覽器可能稍有不同,透過以下程式,便能在所有的瀏覽器可靠地建立 Ajax 物件:
function getXMLHttpRequestObject(){ //定義成函數
var ajax = null; //如果無法指派 XMLHttpRequest 物件給 ajax,該變數的值為 null
if(window.XMLHttpRequest){
ajax = new XMLHttpRequest();
}else if(window.ActiveXObject){ //IE 較舊版本
ajax = new ActiveXObject('MSXML2.XMLHTTP.3.0');
}
return ajax;
}
var ajax = getXMLHttpRequestObject(); //使用函數
更早期的 IE 版本必須藉由建立新的 ActiveXObject,並以 MSXML2.XMLHTTP.3.0 作為參數,上述程式碼還能應用到 IE5 等舊版本(在各種不同地程式碼和其他資源上,該值的內容會有所差別)。
Ajax 與漸進式改善
和透過 JavaScript 實作的任何功能一樣,必須留意使用者可能未啟用 JavaScript 的情況,Ajax 需要以 JavaScript 執行請求,再用結果更新頁面。
Ajax 廣泛應用從伺服器讀取資料,並以得到的資訊更新頁面。這種情況是產生另一份 HTML 頁面,並在不透過 JavaScript 的前提下呈現資料。第一個頁面必須連結到其他頁面,再由 JavaScript 讓該連結觸發 Ajax 的呼叫,並將瀏覽器提交給伺服器腳本,而在啟用 JavaScript 的情況下則透過 Ajax 中斷提交。
另一個主要的 Ajax 用途是發送資料給伺服器,例如,從聯絡人表單或註冊腳本送出資料。在這種情況下,必須考慮無法使用 Ajax 時,如何將表單交給伺服器腳本,而在啟用 JavaScript 的情況下則透過 Ajax 中斷提交。
此外請留意,搜尋引擎無法看到 JavaScript 建立的動態內容,如果需讓 Ajax 取得的內容出現在搜尋結果中,必須以非動態的內容方式呈現,例如前文說明地連結輔助頁面。
Ajax 要求使用者上網,但是透過 HTML5 的本地端儲存能力,便可克服此侷限性(假設 HTML5 作為一種選項的話)。再者,除非採取額外措施,否則 JavaScript 產生的動態異動無法存入書籤。
指定結果處理器
有了 Ajax 物件之後,下一步就是指定該物件的結果處理器,結果處理器是在 Ajax 交易期間呼叫的函數。
- .onreadystatechange:為了關聯該函數與 Ajax 的呼叫,可將函數填入物件的 onreadystatechange 屬性
ajax.onreadystatechange = handleStateChange; //此處也允許使用匿名變數
發出請求
建立 Ajax 物件並指定回應處理函數之後,就可以執行實際的請求了:
- .open( 請求類型[字串], 伺服器資源的 URL[字串], 請求採用同步或非同步[true/false] ):呼叫物件的 open() 方法,執行請求
請求類型
最常見的請求類型是 GET 和 POST。GET 是請求任何 HTML 頁面的標準方法,也是點擊連結時瀏覽器發出的請求類型。理論上來說,GET 請求最適合讀取資料;POST 請求是提交表單的標準方法(除了搜尋引擎表單之外,搜尋引擎通常使用 GET)。同樣的,原則上 POST 請求適合發起伺服器的異動或回應,換句話說,GET 用於常見而重複的請求,並可存入書籤;POST則用無需考慮重複性的獨特請求,例如更新特定資料庫紀錄或者提交聯絡人表單(在這兩種情況下,整體概念都一樣,但細節略有不同)。
注意,方法類型必須採用全大寫字母
伺服器資源的 URL
URL 參數可能是絕對路徑或相對路徑,如果連結的是 http://www.example.com/page.html 頁面,假設 page.php 和 page.html 在同一個目錄中,則 http://www.example.com/page.php 或者 page.php 都有效。針對此 URL,有兩個常見的問題和混淆的地方:
- 涉及同源策略,作為安全手段,瀏覽器的 JavaScript 不能請求其他網域上的資源。意思是 http://www.example.com/page.html 無法發起對 http://shop.example.com/page.php 或 http://www.domo.com/page.php 的請求。
- Ajax 請求必須透過伺服器才能正常運作,這表示 JavaScript 所在的 HTML 頁面也得藉由 URL(http://something)載入,如果請求返回狀態碼 0,或者回傳 PHP 程式碼而非資料,就說明可能不是透過 URL 發出請求。
請求採用同步或非同步
預設值為 true──非同步,不過還是應該明確提供此參數,非同步請求期間可以執行其他的 JavaScript 程式碼(例如處理其他事件),同時等待伺服器回應。同步的請求情況非常少,它阻止 JavaScript 在發起請求和處理的同時進行其他工作,包括處理基於使用者的事件。如果建立的是同步請求,就不需要透過函數來處理 readyState 的變化,因為腳本在進行任何其他工作之前必須等待伺服器回應。
第 4 和第 5 個參數
這兩個參數可有可無,分別代表使用者名稱和密碼,如果資源受到 HTTP 認證保護,則得提供這兩個參數,但使用這些參數時,必須在 JavaScript 程式碼存取參數值,唯一安全的方法是讓使用者輸入值,而不是寫死在頁面的來源程式碼中。
最後一步是實際發送請求:
- .send():實際發送請求
ajax.send(null); //暫時以 null 值作為方法的唯一參數,代表請求中的資料
發送請求但尚未完成前,可以呼叫物件的
abort() 方法撤銷,常見的手法是設定一個計時器,當請求耗費的時間過長予以撤銷:
- .abort():撤銷請求
ajax.open('GET', 'http://www.example.com/page.php', true);
var ajaxAbortTimer = setTimeout (function(){
if(ajax){
ajax.abort();
ajax = null;
}
}, 5000);
上述程式碼建立一個在 5 秒之後呼叫的匿名函數。在函數中,如果 ajax 物件值仍然不為假,就能確定請求依舊在進行,可以中止。中止或完成請求後,ajax 變數將變為假值(如 null),代表請求不在處於活動中。此外也能以他種方式向使用者指出這個問題。
處理伺服器回應
發出 Ajax 非同步請求之後,在物件的
readyState 屬性變化時,將呼叫指派 Ajax 物件 onreadystatechange 屬性的函數:
- .readyState:呈現 XMLHttpRequest 的狀態,變化由 0 到 4
- 0,request not initialized(未發送)
- 1,server connection established(開啟)
- 2,request received(已接收標頭)
- 3,processing request(載入中)
- 4,request finished and response is ready(完成)
onreadystatechange 屬性所指的函數用來處理伺服器回應,在請求過程中的每個階段都將呼叫該函數。在處理 readyState 變化的函數中,可以檢查 readyState 屬性並作出相對的反應,例如屬性值 4 意味著處理已完成,頁面能夠使用處理後的結果。其他值則表示 Ajax 請求仍在處理中,腳本不需要作任何事情,或是顯示「載入中......」訊息。
也可以在呼叫
open() 方法之後顯示「載入中......」訊息,並在 readyState 等於 4 時隱藏起來。當 readyState 等於 4 時,代表 Ajax 請求已經完成了整個週期,但於試圖處理回應之前還要進行一次檢查:確認回應正常。物件的
status 屬性能夠完成確認工作,該屬性代表伺服器對資源請求的對應代碼,這些伺服器的 HTTP 代碼包括:
- 200,OK(正常)
- 301,Moved Permanently(永久性移動)
- 304,Not Modified(未作修改)
- 307,Temporary Redirect(暫時重新導向)
- 401,Unauthorized(未授權)
- 403,Forbidden(禁止存取)
- 404,Not Found(找不到)
- 500,Internal Server Error(內部伺服器錯誤)
if(ajax.readyState == 4){
if((ajax.status >= 200) && (ajax.status < 300) || (ajax.status == 304)){
//回應完成
}else{
//顯示錯誤
}
}else{
//顯示載入中
}
根據 Ajax 的具體使用方法,如果返回有問題的代碼,便可重新啟用預設的瀏覽器行為:重新導向瀏覽器到 Ajax 的頁面,或真正地提交表單等等。
- .statusText:代表伺服器返回對應狀態碼的字串訊息,可用於任何的錯誤回報
- .responseXML:代表伺服器返回對應的 XML 形式的資料
- .responseText:代表伺服器返回對應的字串形式的資料
ajax = null; //釋放該物件所需的瀏覽器資源
發送資料
從伺服器資源抓取資料後,下一步就是送出資料給伺服器,有些情況下,傳輸資料可能會影響返回的資料,例如為了取得某部門的員工,Ajax 請求可以將部門識別字送到伺服器。其他情況下,發送的資料需通過伺服器的驗證,例如使用者名稱是否存在;或觸發伺服器回應,例如張貼評論置留言板上。
將資料作為請求的一部份送給伺服器有兩種方法:
-
將資料附加到 URL 後:以「名稱 = 值」為資料結構配對,然後配對間用「&」隔開,位保證請求資料的安全性,可將其封裝至
encodeURIComponent()呼叫中ajax.open('GET', 'http://www.example.com/somepage.php?id=' + encodeURIComponent(id), true); -
把資料作為 send() 方法(代替 null)的唯一參數
var data = 'email=' + encodeURIComponent(email) + '&password=' + encodeURIComponent(password); ajax.open('GET', 'http://www.example.com/somepage.php', true); ajax.send(data);
兩種方法的最終結果相同,但後者的程式碼更清楚。
然而發出 POST 請求時,必須為 send() 方法提供資料,而不是附加到 URL 之後,而且 POST 方法是為了增加可靠性。
- setRequestHeader( 標頭名稱, 標頭內容 ):向伺服器指明發送的內容類別
var data = 'email=' + encodeURIComponent(email) + '&password=' + encodeURIComponent(password);
ajax.open('POST', 'http://www.example.com/somepage.php', true);
ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); //指定合適的資料編碼
ajax.send(data);
採用 GET 方法,接收資料的 PHP 腳本分別以 $_GET['email'] 和 $_GET['password'] 存取發送資料;採用 POST 方法,則接收資料的 PHP 腳本分別以 $_POST['email'] 和 &_POST['password'] 存取送出的資料。不管發起的是 GET 或 POST 請求,資料結構都一樣,兩者各視合適的場合使用。
透過 GET 傳輸的資料量有限制,根據不同瀏覽器,大約為 2~4 KB
向伺服器發送資料的方案之一是建立 FormData 物件,接著呼叫 FormData 物件的
append() 方法新增各個項目(但 FormData 尚未得到所有瀏覽器支援):
if(typeof FormData == 'function'){ //確定瀏覽器支援 FormData 物件
var data = new FormData(); //建立 FormData 物件
//新增各個項目資料
data.append('email', email);
data.append('password', password);
}else{
//使用前面兩種方法
}
data.send(data);
注意,由於資料會自動編碼,因此不需執行 encodeURIComponent(),也沒有必要設定 Content-Type
將資料或表單送給伺服器之前,必須先知道安全性方面的影響,在後台進行請求,並不代表資料的傳送是秘密和安全的,在要求較高安全性的場合下,伺服器資源該透過 HTTPS 存取(假設伺服器支援)。
window.onload = function(){
'use strict';
var ajax = getXMLHttpRequestObject(); //建立 Ajax 物件
//建立 onreadystatechange 函數,於物件的 readyState 屬性改變時呼叫
ajax.onreadystatechange = function(){
if(ajax.readyState == 4){ //當狀態為傳送完成(4)時
//如果返回正確的狀態碼即回應資料;否則返回錯誤
if( (ajax.status >= 200 && ajax.status < 300) || (ajax.status == 304)){
document.getElementById('output').innerHTML = ajax.responseText;
}else{
document.getElementById('output').innerHTML = 'Error: ' + ajax.statusText;
}
}
};
//點擊按鈕時發起 Ajax 請求
document.getElementById('btn').onclick = function(){
ajax.open('GET', '../resources/test.txt', true);
ajax.send(null);
};
}
基本偵錯
Ajax 最困難的方面就是偵錯,依照以下步驟偵錯便可找出原因:
第一件是測試伺服器資源,確認資料正傳回給 JavaScript,不僅做為偵錯的第一步,建議在建立伺服端資源之後立刻進行測試,以便在編寫任何 JavaScript 之前確認沒有問題。許多伺服端資源可以藉由直接在瀏覽器載入之方式進行測試,載入資源的效果往往和透過 JavaScript 接收回應相同。針對 PHP 腳本或其他動態的伺服端工具,可先載入他們以便確認運作正常。最初的 Ajax 嘗試遭到多次失敗,就是因為 PHP 腳本有錯誤,如果先測試腳本檔案,就能發現這些錯誤。在任何情況下,一定要確保透過 URL(http://something)載入 Ajax 頁面以及相關的資源。
如果 PHP 腳本(或其他的伺服端資源)需要接收一些資料才能正常運作,那麼測試會更加困難。倘若腳本採用 GET 方法,則可自行將資料附加到 URL 後面;如果是 POST 送給伺服端資源,就得向伺服器張貼資料,這並不容易,方法之一是送出表單資料給 PHP 腳本。實際上如果採用漸進式改善,就處於此一過程中:在完全不涉及 JavaScript 的情況下,測試以表單提交資料給 PHP 腳本。
第二件是使用網路監視器,例如 Firebug 和其他瀏覽器工具,像是 Opera 的 Dragonfly 以及 Safari 的 Web Inspector 都內建網路監視器,它能夠驗證請求的發生、顯示請求中傳輸的資料,並且顯示回應返回的資料,這些資料幾乎總是能揭示問題的起因,即使無法直接顯露問題,網路監視器也能提示問題是出在請求或回應中,由此便能相對地應用 JavaScript 或者 PHP 的偵錯技術。
開始使用不同的資料格式(如 JSON 和 XML)時,希望驗證資料本身的完整性,有時可能出現這種情況:伺服器返回明確的回應,但是資料未正確格式化,使得無法應用於 JavaScript 程式中。這時可以利用 JSONint 驗證 JSON 資料,或使用 XML Validator 驗證 XML 資料。
我們必須知道同一個頁面何時會發出多個 Ajax 請求。當頁面只有單比請求時,才能使用單個 Ajax 物件,如果要求多個不同的請求(可能在同一個時間)時,就得使用多個 Ajax 物件避免衝突。
最後,瀏覽器快取機制使偵錯變得困難,因為瀏覽器將試圖緩存 Ajax 請求的結果,所以針對伺服端資源的修改就無法立即反應,此一障礙有多種克服手段:
- 在瀏覽器中停用快取,只有使用者受到影響
-
由伺服器資源指定不應該快取請求,影響相同伺服端資源的所有人
於 PHP 送出一個 Cache-Control 標頭,就可實作此功能,阻止所有瀏覽器的快取請求: -
製造看上去每次都是唯一的請求,常見做法是在請求 URL 上新增一隨機值,例如時間戳記:
var url = 'http://www.example.com/somepage.php?stamp=' + new Date.getTime(); ajax.open('GET', url, true);因為每個請求都有不同且唯一的 URL,所以瀏覽器不會使用前面快取的版本,而是再次發起請求。