使用 responseType


在過去,原生的 XMLHttpRequest 在取得資料上,僅提供 responseTextresponseXML 兩個特性,如果想接收其他的格式,例如 JSON,要以 responseText 取得純文件資料,然後使用 JSON 相關 API 來剖析為物件,如果要取得二進位資料,要 overrideMimeType("text/plain; charset=x-user-defined"),並在取得 responseText 之後,透過字串的 charCodeAt 逐一取得字元,並處理為二進位數值。

在 XMLHttpRequest Level 1 規範中,增加了 responseType,可用來指定回應的類型,可設定的數值有 'arraybuffer''blob''document''json''text',預設值為空字串,可透過 response 取得對應的 ArrayBufferBlob、HTML DOM、JSON 物件與字串。

以接收 JSON 為例,以下會提示可搜尋的選項(沒有真的有搜尋功能就是了),如果有符合的選項,則會以 JSON 的格式傳回字串陣列,例如 '["caterpillar", "ceo"]' 這樣的格式(範例的伺服端上可搜尋的字串有 "caterpillar""car""ceo""c++""justin""java""javascript")。

你必須知道搜尋輸入方塊的位置,以讓選項對齊在輸入方塊下方,這邊使用〈封裝樣式處理〉中的第三個範例改寫:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <style type="text/css">
        div {
            color: #ffffff;
            background-color: #ff0000;
            border-width: 1px;
            border-color: black;
            border-style: solid;
            position: absolute;
        }    
    </style>
</head>
<body>
    <hr>
    搜尋:<input id="search" type="text">

<script type="module">
    import x from './js/XD-1.2.0.js';

    let doc = x(document);
    let search = doc.elemsById('search');

    search.addEvt('keyup', evt => {
        doc.elemsByTag('div').remove();

        let value = search.val();

         // 沒有輸入值,直接結束
        if(value === '') {
            return;
        }

        let xhr = new XMLHttpRequest();
        xhr.open('GET', `ResponseType-1.php?keyword=${value}`);
        xhr.responseType = 'json'; // 回應是 JSON
        xhr.onload = function(evt) {
            let request = evt.target;

            if(request.status === 200) {
                // response 會是 JSON 物件
                let keywords = request.response;

                // 字串陣列長度不為0時加以處理
                if(keywords.length !== 0) {
                    let innerHTML = keywords.map(keyword => `${keyword}<br>`)
                                            .join('');

                    let offset = search.offset();
                    let offsetWidth = search.attr('offsetWidth');
                    let offsetHeight = search.attr('offsetHeight');

                    // 建立容納選項的<div>
                    let div = x('div').toElemCollection()
                                      .html(innerHTML)
                                      .css({
                                          left  : `${offset.x}px`,
                                          top   : `${offset.y + offsetHeight}px`,
                                          width : `${offsetWidth}px`
                                      });

                    document.body.appendChild(div.get());
                }
            }
        };
        xhr.send(null);
    });

</script>

</body>
</html>

按此觀看結果

為了簡化範例,使用了〈封裝樣式處理〉的程式庫封裝成果,而 XMLHttpRequest 的部份為原生操作,最主要的地方在於 xhr.responseType = 'json',並透過 let keywords = request.response 取得了傳回的 JSON 物件,伺服端會傳回 JSON 的陣列格式,因此 keywords 會是 Array

這個例子沒有考慮使用者打字速度,因此每鍵一個字就會發出一次請求,你可以試著設定時間,例如一秒才發出一次,以避免使用者打字速度過快,頻繁發出請求的問題。

另外,對於每個選項,你還可以加上滑鼠點選事件,在使用者點選項目時,將項目值設定至輸入方塊,並移除 <div>,這些額外工作,可以自行練習看看。

若想以 POST 傳送 JSON 給伺服端,請求標頭 'Content-Type' 建議設為 'application/json'。當然,伺服端要能夠剖析 JSON 字串以取出資料,但無需親自撰寫,http://www.json.org/ 網站中提供許多語言實作的 JSON 剖析器,可協助剖析 JSON。