淺析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>

 

運行結果以下所示:

相關文章
相關標籤/搜索