建立 XMLHttpRequest 物件


Ajax 這個名詞是由 Jesse James Garrett 提出,在他發表的〈Ajax: A New Approach to Web Applications〉 中談到 Google Suggest 與 Google Maps 使用到的技術,是他們在 Adaptive Path 中稱之為 Ajax 的新方法:

Google Suggest and Google Maps are two examples of a new approach to web applications that we at Adaptive Path have been calling Ajax. The name is shorthand for Asynchronous JavaScript + XML, and it represents a fundamental shift in what’s possible on the Web.

文中表示,Ajax 是非同步 JavaScript 結合 XML 的概念,XML 是用來交換結構化資料,但事實上,並非唯一可用的格式。

Ajax 的核心概念為非同步,為何要非同步?傳統表單提交、超鏈結點選,瀏覽器會有預設的處理方式,也就是以同步方式傳送請求,接著等待伺服器回應資料,然後進行換頁動作,在資料提交期間,使用者只能等待最新的畫面回應,中間若作了其它的頁面操作,瀏覽器可能會放棄原有的請求,就算在資料回應之後,使用者 面對的是全新的一個頁面,即使使用者真正所作的只是會更新畫面中某個區域。

如果可以把請求與回應改為非同步進行,也就是發出請求後,瀏覽器無需苦等伺服器的回應,而可以讓使用者對瀏覽 器中的 Web 應用程式進行其它的操作,又不會中斷原本的請求,當伺服器終於處理完請求並送出回應,而瀏覽器接收到回應時,再回過頭來呼叫瀏覽器所設定的對應 動作進行處理,方式是可以利用 DOM 操作更新畫面中的某些區域,那麼就開啟了各種可能的互動模式。

目前來說,在標準瀏覽器中使用 XMLHttpRequest 來建立非同步物件,可以如下呼叫建立非同步物件:

let request = new XMLHttpRequest();

基本上 XMLHttpRequest 可用的幾個方法如下:

  • void open(string method, string url[, boolean asynch, string username, string password])
    開啟對伺服端的連結;method 為 HTTP 請求方式('GET''POST''HEAD' 等);url 為伺服端位址,如果是 GET 的話,可加上請求參數與值;asynch 為非同步設定,預設是 true,表示使用非同步方式,usernamepassword 視伺服端有無要求驗證而設置。

  • void setRequestHeader(string header, string value)

    為 HTTP 請求設定一個標頭值,在呼叫 open 之後呼叫,通常在 openmethod 參數為 'POST' 時使用。

  • void send(content)

    對伺服端傳送請求,openmethod'GET' 時,content 設為 null'POST'content 可放字串、XML、JSON 格式的內容,會放在 POST 本體中發送。在早期,瀏覽器不一定支援全部的 HTTP 方法。

  • void abort()

    用來中斷請求。

  • string getAllResponseHeaders()

    傳回一個字串,其中包含 HTTP 請求的所有回應標頭。

  • string getResponseHeader(string header)

    傳回一個字串,其中包含指定的回應標頭值。

open 方法的第三個參數通常保留預設置 true,若想以同步方式,可以設為 false。若想知道目前請求物件狀態,可以在呼叫 open 方法之前,對 onreadystatechange 設置處理器函式。只要有狀態變化,則會呼叫所設置的處理器函式。

通常會在 onreadystatechange 的處理器函式中,偵測 XMLHttpRequest 物件的狀態,狀態可藉由 readyState 特性取得,這特性會有 0 到 4 的變化,代表各個處理階段,常數可透過 XMLHttpRequest 上的常數名稱取得::

  • XMLHttpRequest.UNSENT

    常數值 0,XMLHttpRequest 物件已建構。

  • XMLHttpRequest.OPENED

    常數值 1,已成功呼叫 open 方法,在這個狀態下,可以使用 setRequestHeader,而後呼叫 send 方法。

  • XMLHttpRequest.HEADERS_RECEIVED

    常數值 2,在呼叫過 send 方法且接收到回應標頭的狀態。

  • XMLHttpRequest.LOADING

    常數值 3,正在接收回應本體。

  • XMLHttpRequest.DONE

    常數值 4,伺服端回應結束,可能是資料傳輸完成,或者是傳送過程因發生錯誤而中斷(例如偵測到無限重導)。

通常只會對 readyStateXMLHttpRequest.DONE 時作處理,XMLHttpRequest 物件的 status 表示 HTTP 回應狀態碼,一個例子如下:

let request = new XMLHttpRequest();
request.onreadystatechange = function(evt) {
    let req = evt.target;
    if(req.readyState === XMLHttpRequest.DONE && req.status === 200) {
        // 對成功回應作處理
    }
};
request.open('GET', 'data.txt');
request.send(null);

(偶而地,也會針對 XMLHttpRequest.LOADING 來進行處理,例如接收到一個持續回應的串流(stream),像是模擬伺服端推播的時候。)

onreadystatechange 的第一個參數會是 Event 實例,其 target 特性會是 XMLHttpRequest 實例,可以使用 statusText 取得回應狀態碼代表的文字訊息,而 XMLHttpRequestresponseText 取得伺服端的回應文字,不過要注意,伺服端回應時若沒有指明字元集(例如 Content-Type: text/html; charset=UTF-8 之類),responseText 預設會使用 UTF-8 字元集來解讀傳回的文字。

如果回應是 XML,可以使用 responseXML 取得剖析後的 XML DOM 物件。

以下是一個非同步取得資料的完整流程示範,其中請求的純文件中包括中文,所以先儲存為 UTF-8 格式:

<table style="text-align: left; width: 100%;" border="1" cellpadding="2" cellspacing="2">
<tbody>
    <tr>
        <td style="vertical-align: top;">標題一</td>
        <td style="vertical-align: top;">標題二</td>
        <td style="vertical-align: top;">標題三</td>
        <td style="vertical-align: top;">標題四</td>
    </tr>
    <tr>
        <td style="vertical-align: top;">資料1</td>
        <td style="vertical-align: top;">資料3</td>
        <td style="vertical-align: top;">資料5</td>
        <td style="vertical-align: top;">資料7</td>
    </tr>
    <tr>
        <td style="vertical-align: top;">資料2</td>
        <td style="vertical-align: top;">資料4</td>
        <td style="vertical-align: top;">資料6</td>
        <td style="vertical-align: top;">資料8</td>
    </tr>
</tbody>
</table>

執行的結果是以非同步方式取得文件,並在同一個頁面顯示內容:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>

    <button id='req'>取得表格</button>
    <div id="table"></div>

<script type="text/javascript">

    document.getElementById('req').onclick = function() {
        let request = new XMLHttpRequest();
        request.onreadystatechange = function(evt) {
            let req = evt.target;
            if(req.readyState === XMLHttpRequest.DONE && req.status === 200) {
                document.getElementById('table').innerHTML = req.responseText;
            }
        };
        request.open('GET', 'XMLHttpRequest-1.txt');
        request.send(null);
    };

</script>   

</body>
</html>

按我觀看執行結果