使用 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。


展开阅读全文