淺析XMLHttpRequest


在Ajax技術出現之前,客戶端瀏覽器與服務器之間的交互是非常傳統的方式,每一次,瀏覽器向服務器發送一個請求,服務器接受並處理,返回相對應的處理結果給瀏覽器,瀏覽器接收服務器的返回結果,重新加載新的結果,這樣的交互方式方式,用戶需要花費一定的時間來每一次等待頁面的重新加載,以求獲取服務器的響應,如果網絡不給力或者加載的對象比較大,需要花費一定的時間,那么,用戶就並需花費大量的時間在等待上面。

為了避免這種無謂的等待跟提高用戶的操作體驗,微軟第一個站出來,開發了XMLHttpRequest Object,用以實現瀏覽器與服務器之間的異步通信,進行數據交互,很快,這種方法被大量的采用和廣泛的應用,現在所有主流的瀏覽器都支持了這樣的交互方式,通過XMLHttpRequest Object.

Microsoft最初開發的XMLHttpRequest是基於ActiveXObject控件的,與其它的主流瀏覽器不同(其它的瀏覽器都是內置本地Javascript支持XMLHttpRequest Object),所以在具體的跨瀏覽器開發的時候,需要特別留意這一點。盡管在具體的實現細節上,舊的IE瀏覽器(IE7之前)與其它的主流瀏覽器不同,但是慶幸的是大家基於這個XMLHttpRequest Object與服務器進行交互的方式確實基本相同,都是采用相同的方法跟屬性,這也給我們跨瀏覽器操作帶了極大的便利性。

這里我們簡單的介紹一下XMLHttpRequest Object的一些屬性,方法,以及如何利用這個Object實現與瀏覽器的異步操作。

舊版本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 Object

var xhr = new XMLHttpRequest();

跨瀏覽器實現

綜上所述,我們可以用以下的方法來實現跨瀏覽器創建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>

 

運行結果如下所示:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM