浅析XMLHttpRequest

在Ajax技术出现以前,客户端浏览器与服务器之间的交互是很是传统的方式,每一次,浏览器向服务器发送一个请求,服务器接受并处理,返回相对应的处理结果给浏览器,浏览器接收服务器的返回结果,从新加载新的结果,这样的交互方式方式,用户须要花费必定的时间来每一次等待页面的从新加载,以求获取服务器的响应,若是网络不给力或者加载的对象比较大,须要花费必定的时间,那么,用户就并需花费大量的时间在等待上面。javascript

为了不这种无谓的等待跟提升用户的操做体验,微软第一个站出来,开发了XMLHttpRequest Object,用以实现浏览器与服务器之间的异步通讯,进行数据交互,很快,这种方法被大量的采用和普遍的应用,如今全部主流的浏览器都支持了这样的交互方式,经过XMLHttpRequest Object.html

Microsoft最初开发的XMLHttpRequest是基于ActiveXObject控件的,与其它的主流浏览器不一样(其它的浏览器都是内置本地Javascript支持XMLHttpRequest Object),因此在具体的跨浏览器开发的时候,须要特别留意这一点。尽管在具体的实现细节上,旧的IE浏览器(IE7以前)与其它的主流浏览器不一样,可是庆幸的是你们基于这个XMLHttpRequest Object与服务器进行交互的方式确实基本相同,都是采用相同的方法跟属性,这也给咱们跨浏览器操做带了极大的便利性。java

这里咱们简单的介绍一下XMLHttpRequest Object的一些属性,方法,以及如何利用这个Object实现与浏览器的异步操做。ajax

旧版本IE下建立XMLHttpRequest Object浏览器

在IE7以前,XMLHttpRequest Object是经过ActiveXObject来实现,方法能够参考以下:服务器

function getXMLHttpRequest() {
    var versions = ['MS2XML.XMLHTTP.6.0', 'MS2XML.XMLHTTP.3.0', 'MS2XML.XMLHTTP', 'Microsoft.XMLHTTP'];
    for (var i = 0; i < versions.length; i++) {
        try {
            return new ActiveXObject(versions[i]);
        } catch (e) {
            continue;
        }
    }
};

IE7以及其它现代浏览器下建立XMLHttpRequest Object网络

在IE7+以及其它的现代浏览器中,能够简单地使用如下的语句来建立XMLHttpRequest Objectapp

var xhr = new XMLHttpRequest();

跨浏览器实现dom

综上所述,咱们能够用如下的方法来实现跨浏览器建立XMLHttpRequest Object异步

function getXMLHttpRequest() {
    if (typeof XMLHttpRequest !== 'undefined') {
        return new XMLHttpRequest();
    } else {
        var versions = ['MS2XML.XMLHTTP.6.0', 'MS2XML.XMLHTTP.3.0', 'MS2XML.XMLHTTP', 'Microsoft.XMLHTTP'];

        for (var i = 0; i < versions.length; i++) {
            try {
                return new ActiveXObject(versions[i]);
            } catch (e) {
                continue;
            }
        }
    }
};

XMLHttpRequest与服务器通讯三部曲

XMLHttpRequest Object实现与服务器的通讯交互,主要是经过如下的三个步骤来实现:

 - 建立XMLHttpRequest Object

 - XMLHttpRequest.open(Method, URL, Asyn),该方法有三个参数,第一个是request method,主要是经过GET/POST两种方式,第二个参数是请求的URL,可是必须是与当前的页面处于相同的Domain,第三个是布尔变量,true表示有异步请求,false表示为同步请求,客户端必须等待服务器返回加载完毕以后,才能继续之下往下的操做

 - XMLHttpRequest.send(data),该方法有一个参数,若是没有参数传递给服务器,设置为null

 XMLHttpRequest Response

当XMLHttpRequest发送请求上服务器,服务器响应并处理完成以后,就会把处理的结果返回给浏览器,咱们能够经过XMLHttpRequest Object的一些方法和属性来获取返回的操做结果。

咱们能够经过XMLHttpRequest的status, statusText, readyState, responseText以及responseXML属性来查看返回的状态跟结果。

当咱们发送请求上服务器以后,咱们能够经过readyState的属性来监听当前的状态,readyState总过有如下5ge状态:

 - 0 : 尚未进行任何的初始化动做,open method尚未被调用

 - 1 : open method被调用,可是请求尚未send出去

 - 2 : 调用send method发送请求

 - 3 : 数据加载当中

 - 4 : 请求完成

当readyState在不一样的状态之间切换的时候,会触发onreadystatechange事件,咱们能够经过绑定这个事件,对请求的响应状态进行实时的监控:

window.onload = function () {
    var xhr = getXMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {

        }
    }
};

一般咱们最为关心就是当readyState为4的状况,此时咱们能够经过查看当前的HTTP status code,来断定请求是否成功,如下是咱们较为经常使用的status code

 - 200 <= xhr.status < 300,当satus code在这个区间的时候,表示请求成功

 - 304,这个代码表示not modified since last request, the response will get from browser personal cache,依然表示一个成功的请求

 - 另外有一种状况咱们须要留意,当咱们请求一个本地文件(protocol为file://)的时候,此时的status code返回的是undefined

 - 另一个比较特殊的状况是,当Safari浏览器,the response is not modified since last request,这种状况下它返回的并非304,而是一个undefined

所以咱们能够经过如下的代码还检验一个HTTP请求是否成功:

function httpSuccess(xhr) {
    return (200 <= xhr.status < 300) || xhr.status === 304 || 
            (window.location.host.protocol === 'file:' && xhr.status === undefined) || 
            (userAgent.indexOf('Safari') !== -1 && xhr.status === undefined);
}

咱们通常不经过statusText属性来判断当前的请求是否成功,由于不一样的浏览器有不一样实现,对于相同的结果,可能返回不一样的描述。

咱们能够经过responseText跟responseXML这两个属性来获取当前返回的内容,不管content-type为什么值,咱们均可以经过responseText来获取当前的结果,可是responseXML为null,若是当前的content-type不是text/xml或者application/xml.

window.onload = function () {
    var xhr = getXMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (httpSuccess(xhr)) {
                console.debug(xhr.responseText);
            }
        }
    }
};

序列化请求数据

当咱们发送一个请求上服务器的时候,咱们一般会向服务器发送额外的请求数据,这个时候咱们就须要先将请求数据进行格式化,把它转变成服务器能够处理的形式,一般咱们把这个过程称之为序列化。

在客户端,咱们一般是以如下的两种形式向服务器提交请求参数:

 - JSON格式 : {'userName' : 'AndyLuo', 'title' : 'Software Engineering'}

 - 表单数据 : [userNameElem, titleElem]

经过序列化咱们最终须要把它们转换成诸如 https://www.someurl.com?name1=value1&name2=value2&name3=value3的形式

function serialize(data) {
    var rtnValue = '';
    if (Object.prototype.toString.call(data) === '[Object Array]') {
        // handle form elements case
        for (var i = 0; i < data.length; i++) {
            var elem = data[i];
            rtnValue = addUrlParameter('', elem.name, elem.value);
        }
    } else {
        for (var k in data) {
            rtnValue = addUrlParameter('', k, data[k]);
        }
    }
    
    return rtnValue;
}

function addUrlParameter(url, name, value) {
    if (url.indexOf('?') == -1) {
        url += '?';
    } else {
        url += '&';
    }
    
    url += encodeURIComponent(name) + '=' + encodeURIComponent(value);
    
    return url;
}

HTTP Header

咱们能够经过xhr.setRequestHeader(hdrName, hdrValue)来订制header value,也能够经过xhr.getResponseHeader(hdrName)以及xhr.getAllResponseHeaders()来获取服务器响应的header头部信息。

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

xhr.setRequestHeader('Content-Type', 'application/xml');

xhr.setRequestHeader('userName', 'AndyLuo');

xhr.getResponseHeader('userName');

xhr.getALLResponseHeaders();

另外,xhr还提供了一个很是有用的方法overwriteMimeType,咱们能够经过修改MIME类型以得到正确的返回,好比,当前的服务器返回的是一个XML数据,可是它的content-type倒是设置成了text/plain,这种状况之下,responseXML将为null,咱们就能够经过overwriteMimeType('text/xml')来对返回的content type进行修改以获得咱们预期的结果。

GET/ POST 方式请求数据

window.onload = function () {
    var xhr = getXMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (httpSuccess(xhr)) {
                console.debug(xhr.responseText);
            }
        }
    }
    
    xhr.open('GET', '/someurl/somepage?param1=value1&param2=value2', true);
    xhr.send(null);
};
window.onload = function () {
    var xhr = getXMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (httpSuccess(xhr)) {
                console.debug(xhr.responseText);
            }
        }
    }
    
    xhr.open('POST', '/someurl/somepage', true);
    xhr.send('param1=value1&param2=value2');
};

 XMLHttpRequest Level 2

 

随着XMLHttpRequest技术的不一样发展,W3C起草了XMLHttpRequest Level 2 Spec,给XMLHttpRequest带了给多特性和可能性,因为尚处于起草阶段,各个浏览器对它的支持也是颇有限。

XMLHttpRequest Level 2的其中一个亮点之一就是引入FormData对象,再POST方法请求数据的时候不,能够方便的对表单数据进行操做,其具体的用法有如下两种方式:

 - FormData.append(name, value)

 - new FormData(formElement)

var formData = new FormData();
formData.append('userName', 'AndyLuo');
xhr.send(formData);

xhr.send(new Formdata(document.forms[0]));

另外一个值得一提的是,Level 2引进了如下的event事件:

 - loadStart : 当客户端接收到第一个字节的时候,触发此事件

 - progress : 当客户端持续接收到一个或者多个数据的时候,触发此事件

 - error : 当处理请求出现错误的时候

 - abort : 当取消当前请求的时候

 - load : 请求完成的时候

 - loadEnd : 请求结束的时候触发此事件

AJAX using XMLHttpRequest

前面咱们提到了传统的浏览器服务器数据交互的模式,用户提交一个请求,等待服务器处理,服务器处理完请求返回数据给浏览器,浏览器从新加载页面显示结果。这样的交互模式并不是十分友好,有的时候咱们仅仅须要服务返回一点点的信息,可是咱们仍是同样要经历一系列的动做和等待,并且这这个这个过程当中,咱们除了等待什么事情也作不了,对于当前的操做页面也彻底失去了控制。

咱们但愿有这样一种方式,当咱们须要服务器信息的时候,咱们点击页面中的某个按钮或者连接,向服务器提出数据请求,而后咱们保留在当前页面继续下面的操做,当服务器返回数据的时候,咱们能够很方便的把数据更新到当前页面合适的位置,这个时候,AJAX就应运而生了。

AJAX是Asynchronize JavaScript and XML的缩写,是一种实现客户端与浏览器实现异步操做的技术,底层实现方式就是利用XMLHttpRequest Object.

因为AJAX的应用很是普遍,为了简化咱们代码的开发,咱们能够把它开发成为一个通用的module,后续工做中,咱们只须要经过这个module就能够很方便的实现AJAX的操做,具体以下所示:

 

function ajax (options) {
    options = {
        url : options.url || '',
        method : options.method || 'POST',
        type : options.type || 'xml',
        asyn : options.asyn || true,
        timeout : options.timeout || '',
        onSuccess : options.onSuccess || function () {},
        onError : options.onError || function () {},
        onComplete : options.onComplete || function () {},
        onTimeout : options.onTimeout || function () {},
        data : options.data || {}
    };
    
    var requestDone = false;
    
    try {
        parseInt(timeout);
        setTimeout(function() {
            requestDone = true;
            options.onTimeout();
        }, timeout * 1000);
    } catch (e) {}
    
    var xhr = createXHR();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && !requestDone) {
            if (httpSuccess(xhr)) {
                options.onSuccess(httpData(xhr, options.type));
            } else {
                options.onError(httpData(xhr, options.type));
            }
            
            options.onComplete();
            
            xhr = null;
        }
    };
    
    if (options.method.toLowerCase() === 'post') {
        xhr.open(options.method, options.url, options.aysn);
        xhr.send(serialize(options.data));
    } else {
        options.url = addURLParameters(options.url, serialize(options.data));
        xhr.open(options.method, options.url, options.aysn);
        xhr.send(null);
    }
    
};

function createXHR() {
    if (typeof XMLHttpRequest !== undefined) {
        return new XMLHttpRequest();
    } else {
        var versions = ['MS2XML.XMLHTTP.6.0', 'MS2XML.XMLHTTP.3.0', 'MS2XML.XMLHTTP', 'Microsoft.XMLHTTP'];
        
        for (var i = 0; i < versions.length; i++) {
            try {
                return new ActiveXObject(versions[i]);
            } catch (e) {
                continue;
            }
        }
    }
};

function httpSuccess (xhr) {
    try {
        return (200 <= xhr.status < 300) 
            || (xhr.status === 304)
            || (!xhr.status && location.protocol === 'file:')
            || (window.userAgent.indexOf('Safari') !== -1 && typeof xhr.status === undefined);
    } catch (e) {
        return false;
    }
    
    return false;
};

function httpData (xhr, type) {
    var contentType = xhr.getResponseHeader('Content-Type');
    var isXMLType = !type && contentType && contentType.indexOf('xml') >= 0;
    var data = (type === 'xml') || isXMLType ? xhr.responseXML : xhr.responseText;
    if (type === 'script') {
        eval.call(window, data);
    }
    
    return data;
};

function serialize(data) {
    var results = [];
    if (Object.prototype.toString.call(data) === '[Object Array]') {
        for (var i = 0; i < data.length; i++) {
            data.push(encodeURIComponent(data[i].name) + '=' + encodeURIComponent(data[i].value));
        }
    } else {
        for (var key in data) {
            data.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
        }
    }
    
    return results.join('&');
}

function addURLParameters(url, paramStr) {
    if (url.indexOf('?') === -1) {
        url += '?';
    } else {
        url += '&';
    }
    
    return url + paramStr;
}

下面是一个简单的使用例子:

 

<!DOCTYPE html>
<html>
    <head>
        <title>AJAX DEMO</title>
        <script type='text/javascript' src='ajax.js'></script>
    </head>
    <body>
        <div id='weather'>
            What's the weather like today?
            <input type='button' id='queryBtn' name='queryBtn' value='Query' />
        </div>
        <div id='console'>
            Today's Weather:<span id='result'></span>
        </div>
        <script type="text/javascript">
            window.onload = function () {
                var queryBtn = document.getElementById('queryBtn');
                queryBtn.addEventListener('click', function() {
                    ajax({
                        url : '<replace your domain url here>',
                        type : 'text',
                        onSuccess : function (data) {
                            var result = document.getElementById('result');
                            result.innerHTML = data;
                        },
                        onError : function (data) {
                            console.debug('fail');
                        }
                    });
                }, false);
            };
        </script>
    </body>
</html>

 

运行结果以下所示:

相关文章
相关标签/搜索