淺析XMLHttpRequest

来源:http://www.cnblogs.com/andycbluo/archive/2016/01/27/5162931.html
-Advertisement-
Play Games

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

 

運行結果如下所示:

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 複習之餘,做點筆記<C語言之數據類型> 一、整數數據類型 (1)整數數據類型 整數類型 char 字元型變數 1位元組(8Bit) short 短整型 2位元組(16Bit) int 整形變數 取決於編譯器 long 長整形變數 取決於編譯器 對int的理解:在電腦中有CPU和記憶體,在CPU中有寄存器
  • 實現情況: 可自主註冊, 登陸系統可購物,充值(暫未實現),查詢餘額。 擼了兩天一夜的代碼,不多說,直接上碼,註釋神馬的後面再說 1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 shopping_list = [ 4 ['Iphone 6s pl...
  • http://www.tuicool.com/articles/MzayMri 電腦剛出世時,編程卡片為八十列;無獨有偶,Unix 早期的終端,每行最大顯示字元數也為八十個;Unix 大多文本工具也沿襲了這傳統,至今在 Maillist 上可以看到不少資深 Linux 工程師習慣把郵件正文 wra...
  • 在之前的Java技術彙總文章里,向大家介紹了Java入門學習的基礎資料,今天小編彙總了5篇Java技術進階實操的乾貨,趕緊來看看吧!另外,喜歡寫博客的工程師博主可以加工程師博主交流群:391519124,分享你的博文,和大牛們一起交流技術~一、Stack Overflow 上人氣爆表的10個 Jav...
  • 寫爬蟲之前,首先要明確爬取的數據。然後,思考從哪些地方可以獲取這些數據。下麵以一個實際案例來說明,怎麼尋找一個好的爬蟲策略。(代碼僅供學習交流,切勿用作商業或其他有害行為) 1).方式一:直接爬取網站 目標網址:http://chanyouji.com/ 註意:這個網站會攔截IP,訪問次數過多...
  • 背景 今天的分享主要來自我之前的工作經驗以及平時的學習總結和思考。我之前的背景主要是做框架、系統和平臺架構,之前的工作過的公司eBay、攜程、唯品會都是平臺型互聯網公司,所以今天主要帶著平臺架構視角和大家分享心得體會。架構的視角每個人都不一樣,可以說一萬種眼光,有業務架構、安全架構、平臺架構、數據架
  • NodeJS新手攻略 1、在官網(https://nodejs.org/)下載node安裝包進行安裝windows和Linux 版本都有 這裡說下windows系統安裝 安裝nodeJS直接下一步 安裝過程中可選擇安裝路徑 node -v npm -v 直接cmd使用nodejs命令查看是否安裝成功
  • JS 變數提升 函數提升
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...