XMLHttpRequest

1. 概述

XMLHttpRequest是一个JavaScript对象,它最初由微软设计(一开始的实现并非一个JavaScript对象,因此又带来了"喜闻乐见"的兼容性问题),随后被Mozilla、Apple和Google采纳。如今,该对象已经被W3C组织标准化。

var xhr = new XMLHttpRequest();

通过它,你可以很容易的取回一个URL上的资源数据。尽管名字里有XML, 但XMLHttpRequest可以取回所有类型的数据资源(其实不然,现在的XHR有两个版本了,旧版本的支持的数据资源并不多),并不局限于XML。而且除了HTTP ,它还支持file和ftp协议。

1.2 XMLHttpRequest Level 2

在HTML5的概念形成后,W3C开始标准化这个接口,2008年2月,提出了XMLHttpRequest Level 2草案。

这个XMLHttpRequest的新版本,提出了很多有用的新功能。

兼容性查阅

2. State

readyState表示了请求的五种状态

状态 描述
0 UNSET(未打开) open()方法还未被调用
1 OPENED(未发送) send()方法还未被调用
2 HEADERS_RECEIVED(已获得响应头) send()方法已经被调用,且响应头和响应状态已经返回
3 LOADING(正在下载响应体) 响应体下载中,responseText已经获取了部分数据
4 DONE(请求完成) 整个请求过程已经完毕

当readyState属性改变时,会调用onreadyStateChange

xhr.onreadyStateChange = function () {
    if(this.readyState == 4){
        // 请求结束
    }
}

3. Request

3.1 open()初始化一个请求

这个方法的参数可以为

  • method: HTTP方法,有"GET","POST","HEAD","DELETE","PUT"
  • url: 该请求所要访问的URL
  • async: 默认为true,即异步执行,如果为false,则会阻塞直到数据返回
  • user: 为授权使用,默认为空
  • password: 为授权使用,默认为空

注意此时还没有发送请求,只是做初始化的工作而已。

xhr.open("POST","www.example.com");

3.2 setRequestHeader()设置请求头

在调用open()方法初始化一个请求之后,我们可以给HTTP请求头赋值。不过这个方法是受限的,有一部分请求头是不能被设置的。

-- -- --
Accept-Charset Content-Transfer-Encoding TE
Accpet-Encoding Date Trailer
Connection Expect Transfer-Encoding
Content-Length Host Upgrade
Cookie Keep-Alive User-Agent
Cookie2 Referer Via
// 表单数据的编码类型
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

3.3 timeout[XHR 2]超时处理

网络任务通常是不可靠的,所以要考虑超时的问题。可以通过设置timeout属性来设置HTTP请求的时限,单位是毫秒。

如果请求超过了时限,就自动停止HTTP请求,可以通过timeout事件来指定回调函数。

xhr.timeout = 3000;
xhr.ontimeout = function (){
    // 超时处理
}

3.4 withCredentials[XHR 2]跨域

withCredentials为true时,可以进行跨域请求,反之,则不行。

xhr.withCredentials = true;

这个属性也可以用来检测浏览器是否支持XHR 2

function isSupport() {
    return new XMLHttpRequest().withCredentials ? true : false;
}

关于跨域的实现其实是依赖了新增的HTTP请求头,需要服务器的支持。在浏览器端发送请求的时候,加上一个Origin的头部信息,服务器可以根据这个头部信息来决定是否响应,如果服务器认为这个请求可以接受,就可以在Access-Control-Allow-Origin头部中回发相同的源信息。如果没有这个头部,或者源信息不匹配,浏览器就会驳回请求。

请求和响应都不包含cookie信息

3.5 upload[XHR 2]跟踪上传事件

可以在upload上添加一个事件监听来跟踪上传的过程,upload有一个progress事件,每当进度有变化的时候,就会触发。

xhr.upload.onprogress = function (event) {
}

3.6 发送请求

send(data)发送请求,如果是异步模式(默认),该方法会立刻返回,相反,如果是同步模式,则直到请求的响应完全接受以后,该方法才会返回。

该方法可以发送多种类型的数据

send(null); // get 请求的情况
send(Blob data); // post
send(FormData data);// post
send(ArrayBuffer data);// post

3.7 中止请求[XHR 2]

调用abort(),如果请求已经被发送,则立刻中止请求。

xhr.abort();

4. Response

4.1 获取http请求的状态

可以通过status来获取http请求的状态码,statusText来获取状态的描述。一般来说,会结合readyState和HTTP状态码来判断一个xhr请求是否成功返回

xhr.onreadyStateChange = function () {
    if(this.readyState == 4){
        if(this.status == 200){
            // 成功返回
        }
    }
}

4.3 获得响应头

通过getResponseHeader()/getAllResponeseHeader()能查询响应头,但是并非所有的响应头都可以获取的到,例如cookie。

4.5 overrideMimeType()[XHR 2]重写服务器返回的MIME类型

这个方法会强迫xhr对象将响应当作指定的类型来进行处理,不过必须在send()方法之前调用这个方法。

xhr.open("get","");
xhr.overrideMimeType("text/xml");
xhr.send(null);

这样,即使服务器返回的MIME类型是text/plain,xhr还是会将响应当作XML进行处理。

4.7 指定服务器返回的MIME类型

设置responseType能够改变响应类型,通知服务器客户端期待的响应格式

对应的数据类型
"" 字符串(默认值)
"arraybuffer" ArrayBuffer
"document" Document
"json" JSON
"text" 字符串

在指定了responseType之后,就必须使用response来接收响应实体的类型。

var arrayBuffer = xhr.response;

4.8 [常用]响应体内容

responseText最常用的接收服务器响应的内容

4.9 responseXML 响应体内容

接收服务器响应的xml内容,如果服务器返回的MIME类型是text\plain的话,responseXML为null

5. 进度事件[XHR 2]

Progress Events定义了与客户端通信有关的事件,共有6个进度事件

  • loadstart: 在接收到响应数据的第一个字节时触发
  • progress: 在接收响应期间持续不断地触发
  • error: 在请求发生错误时触发
  • abort: 在调用abort()方法而终止连接时触发
  • load: 在接收到完整的响应数据时触发
  • loadend: 在通信完成或触发error、abort或load事件后触发

5.1 progress事件

onprogress事件处理程序会接收到一个event对象,其target属性是XHR对象,此外还包含3个额外的属性

  • lengthComputable: 表示进度信息是否可用的布尔值
  • position: 表示已经接收的字节数
  • totalSize: 根据Content-length响应头确定的预期字节数
xhr.onprogress = function(event) {
    if(event.lengthComputabel) {
        var percent = event.position / event.totalSize;
    }
}

6. 实现

首先是获取xhr对象,这一点主要是在IE中比较麻烦

function createXhr() {
    var xhr;
    if(window.XMLHttpRequest){ // 非IE
        return new XMLHttpRequest();
    } else if (window.ActiveXObject) { // IE
        var versions = [ "MSXML2.XMLHTTP", "MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0"];
        for (var i = 0; i < versions.length; i++)
        try {
            xhr = new ActiveXObject(version[i])
            if(xhr)break;
        }catch (e) {};
    }
}

结合上述内容,我们来进行封装,这里利用了一个技巧,叫惰性加载机制,在其他的章节中有详细的介绍。

(function (){
    var ajax = {
        _createXhr : function () {
            var xhr,curVersion;
            if(window.XMLHttpRequest){
                _createXhr = function () {
                    return new XMLHttpRequest();
                }
                xhr = new XMLHttpRequest();
            } else if (window.ActiveXObject) {
                var versions = [ "MSXML2.XMLHTTP", "MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0"];
                for (var i = 0; i < versions.length; i++)
                try {
                    xhr = new ActiveXObject(version[i])
                    if(xhr){
                        curVersion = version[i];
                        _createXhr = function () {
                            return new ActiveXObject(curVersion);
                        }
                        break;
                    }
                }catch (e) {};
            }

            if(!xhr) {
                throw new Error("fail to create XHR");
            }

            return xhr;
        },
        _serialize : function(data) {
            if(typeof data == "object") {
                var p = [];
                for(var key in data){
                    p.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
                }
                return p.join("&");
            } else if (typeof data == "string"){
                return data;
            } else {
                 throw new Error("fail to serialize parameters");
            }
        },
        _get : function(params,xhr) { // get请求
            var url = params.url;
            if(params.data) {
                url = url.indexOf("?") > 0 ? url : (url + "?");
                url += this._serialize(params.data);
            }
            xhr.open("get",url);
            xhr.send(null);
        },
        _post : function(params,xhr) {// post请求
            var url = params.url;
            if(params.data) {
                var data = this._serialize(params.data);
            }

            xhr.open("post",url);
            // 增加请求头
            xhr.setRequestHeader("Content-Type", params.contentType || "application/x-www-form-urlencoded");
            xhr.send(data);
        },
        send : function (params){
            if (!params.url) {
                throw new Error("invalid parameters");
            }
            var requestType = params.requestType || "GET";
            var timeout = params.timeout || 60000;
            var callback = params.callback || function(){};

            var xhr = ajax._createXhr();

            // 超时错误,可以使用timeout和ontimeout,
            // 这里使用定时器来实现
            if("timeout" in xhr){
                xhr.timeout = timeout;
                xhr.ontimeout = function () {
                    callback({msg:"timeout"});
                }
            } else {
                var timer = setTimeout(function () {
                    xhr.abort();
                    callback({msg:"timeout"});
                },timeout)
            }

            // 正常返回
            xhr.onreadystateChange = function () {
                if(xhr.readyState == 4 && xhr.status == 200){   
                    timer && clearTimeout(timer);
                    var ret = xhr.responseText;
                    try {
                        ret = typeof JSON.parse == "function"? JSON.parse(ret) : ret;
                    }catch(_){};
                    callback(ret);
                }
            }

            // 请求错误
            xhr.onerror = function () {
                timer && clearTimeout(timer);
                callback({msg:"error"});
            }

            // 开启跨域            
            if("withCredential" in xhr){
                xhr.withCredentials = true;
            }

            // 处理参数
            requestType = requestType.toUpperCase();

            switch(requestType){ // 之后可以补充多个请求方法
                case "GET" : 
                    ajax._get(params, xhr);
                    break;
                case "POST" : 
                    ajax._post(params, xhr);
                    break;
            }            
        }
    };

    window.ajax = ajax;
})();

参考

  1. MDN
  2. W3C标准
  3. 《JavaScript权威指南》 脚本化HTTP
  4. XMLHttpRequest Level 2 使用指南
  5. 《JavaScript高级程序设计》

results matching ""

    No results matching ""