Ajax 請求與回應


使用 Ajax,可以從非同步物件的 responseText 取得伺服端的回應文字,不過要注意,伺服端回應時若沒有指明編碼(例如 Content-Type: text/html; charset=Big5 之類),responseText 預設會使用 UTF-8 編碼來解讀傳回的文字。如果回應是 XML,則可以使用 responseXML 取得剖析後的 DOM 物件。

在〈URL 編碼〉中介紹過如何為請求參數進行編碼,如果是透過表單發送請求,瀏覽器會自動進行 URL 編碼,如果是透過 Ajax,以非同步物件發送請求參數,有的瀏覽器會進行 URL 編碼,有的瀏覽器不會,為了跨瀏覽器,最保險的方式,就是自行處理 URL 編碼,再透過非同步物件發送請求。

在 JavaScript 中,可以使用 encodeURIComponent() 編碼,編碼後的結果是遵守 RFC 3986 的規範,但正如〈URL 編碼〉中介紹過的,在 RFC 3986 之前,HTTP 亦規範了 GET 與 POST 在發送請求參數時的編碼,大致上也是編碼為 %hexhex,不過空白字元是編碼為 + 而不是 RFC 3986 的 %20

如果直接透過瀏覽器按下發送按鈕來送出表單,瀏覽器會自動處理編碼(依網頁上指定的編碼來處理),並將空白字元編碼為 +,但透過非同步物件發送請求參數時,必須自行處理。

發送請求參數時,若使用 encodeURIComponent() 編碼後,要再將 %20 取代為 +,以符合 HTTP 的規範。

要注意的是,在字串處理方面,JavaScript 支援 Unicode,內部實作上採用 16 位元編碼每個字串元素,大致上可視為 UCS-2/UTF-16(這當中還有些歷史因素造成的細節,詳見《Effective JavaScript》條款七),不過,傳入 encodeURIComponent() 的字串最後會以 UTF-8 進行編碼,若將 encodeURIComponent() 的結果透過非同步物件發送出去,伺服端必須以 UTF-8 來處理接收到的字串。

下面這個範例是 GET 的一個示範,在新增書籤時,若 URL 已重複(已有的書籤是 https://caterpillar.onlyfun.net 與 https://openhome.cc)則以訊息提示:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script type="text/javascript">
            window.onload = function() {
                var xhr = window.XMLHttpRequest && 
                      (window.location.protocol !== 'file:' 
                          || !window.ActiveXObject) ?
                       function() {
                           return new XMLHttpRequest();
                       } :
                       function() {
                          try {
                             return new ActiveXObject('Microsoft.XMLHTTP');
                          } catch(e) {
                             throw new Error('XMLHttpRequest not supported');
                          }
                       };

                function param(obj) {
                    var pairs = [];
                    for(var name in obj) {
                        var pair = encodeURIComponent(name) + '=' + 
                                   encodeURIComponent(obj[name]);
                        pairs.push(pair.replace('/%20/g', '+'));
                    }
                    return pairs.join('&');
                }

                document.getElementById('url').onblur = function() {
                    var request = xhr();
                    request.onreadystatechange = function() {
                        if(request.readyState === 4) {
                            if(request.status === 200) {
                                var message = '';
                                if(request.responseText === 'urlExisted') {
                                    message = 'URL 已存在';
                                }
                                document.getElementById('message')
                                        .innerHTML = message;
                            }
                        }
                    };
                    var params = param(
                       { url : document.getElementById('url').value}
                    );
                    request.open('GET', 'Ajax-1.php?' + params + 
                         '&time=' + new Date().getTime()); // 避免GET被快取
                    request.send(null);
                };
            };
        </script>        
    </head>
    <body>
        新增書籤:<br>
        網址:<input id="url" type="text">
        <span id="message" style="color:red"></span><br>
        名稱:<input type="text">
    </body>
</html>

按我執行

如果要發送 POST,記得要使用 setRequestHeader() 設定內容類型,因為 POST 要發送的資料會放在請求的本體中,必須告知發送的資料類型為何,例如發送表單類型資料時,必須設置請求標頭 'Content-Type''application/x-www-form-urlencoded',如果是 XML,設定請求標頭的 'Content-Type''text/xml',如果是 JSON,請求標頭 'Content-Typ' 建議設為 'application/json'。以下是個示範:

...
var url = 'somewhere';
var queryString = 'a=10&b=20';
xmlHttp.open('POST', url);
xmlHttp.setRequestHeader('Content-Type',  'application/x-www-form-urlencoded');
xmlHttp.send(queryString); 

既然提到了 encodeURIComponent(),這邊順便比較一下與 encodeURI()escape() 的差別。

正如先前所言,encodeURIComponent() 可使用在編碼請求參數,它不編碼的字元有英文字母、數字、-_.!~*'

encodeURI()encodeURIComponent() 類似,傳入的字串會以 UTF-8 處理,不編碼的字元除了 encodeURIComponent() 不編碼的字元之外,對;,/:@&=+$ 也不加以編碼,所以像 URL 重新導向之類的需求,必須使用 encodeURI(),例如:

location.href=encodeURI("https://openhome.cc");

escape() 事實上是一個不建議再被使用的函式,它對 0 到 255 以外的字元,編碼為 %u 型式,而不是 URL 編碼,不編碼的字元除了 encodeURIComponent() 不編碼的字元之外,對 @*/+ 也不加以編碼。