刨根問底ajax原理與封裝

提及ajax,你們都不陌生。可是因爲目前不少框架或者庫等都對網絡請求作了封裝,致使了不少初學者只知其然而不知其因此然。因此今天咱們就詳細瞭解一下ajax的實現原理和封裝ajax的關鍵步驟。javascript

ajax的核心是XMLHttpRequest對象。首先咱們先建立一個XMLHTTPRequest對象 var xhr = new XMLHttpRequest();java

注意:本文所說起的內容不兼容古老的IE,有想了解的同窗自行查閱ActiveXObject相關內容。

XMLHttpRequest

在使用XMLHttpRequest對象的第一步,咱們首先要調用open方法來初始化請求參數,xhr.open('get','/test',true),雖然名字叫open,可是此時請求還並無發送。ajax

open(method, url[, async, username, password])segmentfault

  • method:請求類型,例如GET,POST等
  • url:請求地址(這裏有同源限制,就是咱們常常會看到的跨域問題啦)
  • async:是否發送異步請求。可選參數,默認爲true。
  • username&password:可選參數,受權驗證使用的,可是咱們通常不這麼用,使用後請求變成這個樣子了,http(s)://username:password@url。
若是調用了open方法後再次對它進行調用,則至關於調用了abort方法,abort方法咱們在後面介紹。

若是咱們想爲爲請求綁定一些操做,這個時候就能夠開始啦。經常使用的操做有以下幾個:跨域

setRequestHeader(key, value)瀏覽器

顧名思義,這個方法用於設置請求頭內容。服務器

  • key:要設置的請求頭名稱
  • value:對應請求頭的值

overrideMimeType(type)cookie

重寫服務器返回的MIME類型。經過這個方法能夠告訴服務器你想要的數據類型。網絡

注意:以上這些操做必須定義在send方法以前。不然,就拿setRequestHeader來講,你都把請求發出去了再設置還有什麼用?

這個時候,咱們就能夠經過調用send 方法來發送請求了,xhr.send(null)app

send(data)

發送請求,若是是同步請求的話,會阻塞代碼的執行,直至收到服務器響應纔會繼續。

  • data:發送給服務器的數據。爲了兼容不一樣的瀏覽器,即便是不須要傳數據,也須要傳入參數null。

readyStateChanhe()

每次readyState的值改變的時候都會觸發這個函數。

getResponseHeader(name)

獲取指定響應頭部的值,參數是響應頭部的名稱,而且不區分大小寫。

getAllResponseHeaders()

獲取服務器發送的全部HTTP響應的頭部。

在這裏咱們穿插幾個概念,readyState,這個屬性代表了請求的狀態,伴隨HTTP請求的整個生命週期,它的值代表此時請求所處的階段,具體以下:

readyState

數值 描述
0 初始化,open()還沒有調用
1 open()已經調用,可是send未調用
2 已獲取到返回頭信息
3 正在下載返回體信息
4 請求完成

還有幾個較爲經常使用的屬性

名稱 含義
responseText 響應的文本
status 響應的狀態碼
statusText 響應的狀態信息
responseXML 響應內容是「text/xml」或者是「application/xml」格式的時候,這個屬性的值就是響應數據的XMLDOM文檔。

咱們用下面這段代碼作個測試

var xhr = new XMLHttpRequest();
console.log(xhr.readyState)
xhr.onreadystatechange = function(){
    console.log('------')
    console.log('readyState:' + xhr.readyState)
    console.log('ResponseHeaders:' + xhr.getAllResponseHeaders())
    console.log('ResponseText:' + xhr.responseText.length)
    console.log('------')
}
xhr.open('get','/')
xhr.send(null)

下圖咱們能夠直觀的看到在建立了XMLHttpRequest對象的時候,readyState的值爲0。

image
而後咱們定義了onreadystatechange函數,讓其打印一些屬性,並調用open方法,此時readyState變爲1。

image
最後咱們調用send方法,能夠看到經歷了以下過程:

  1. send方法調用以後,readyState變爲2,此時responseHeader已經獲取到了,responseText爲空;
  2. 響應數據開始下載,readyState變爲3
  3. 響應數據下載結束,readyState變爲4.咱們能夠發現此時responseText的長度比以前長。

image

abort()

取消響應,調用這個方法會終止已發送的請求。咱們嘗試在以前的代碼最後加一句。

xhr.abort();
console.log(xhr.readyState);

image
image

也就是說,send執行之後,並無去嘗試請求數據,而是直接取消掉了,而且咱們發現abort會將readyState的值置爲0。

除此以外,XMLHttpRequest還有一個很重要的屬性withCredentials,cookie在同域請求的時候,會被自動攜帶在請求頭中,可是跨域請求則不會,除非把withCredentials的值設爲true(默認爲false)。同時須要在服務端的響應頭部中設置Access-Control-Allow-Credentials:true。不只如此Access-Control-Allow-Origin的值也必須爲當前頁面的域名。


封裝

到此爲止,咱們終於講完了XMLHttpRequest的一些經常使用概念。接下來,咱們嘗試本身封裝一個支持get和post的簡易jax請求。

function ajax(url, option){
    option = option || {};
    var method = (option.method || 'GET').toUpperCase(),
        async = option.async === undefined ? true : option.async,
        params = handleParams(option.data);
    var xhr = new XMLHttpRequest();
    if(async){
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4){
               callback(option,xhr);
            }
        };
    }
    if (method === 'GET'){
        xhr.open("GET",url + '?' + params, async);
        xhr.send(null)
    }else if (method === 'POST'){
        xhr.open('POST', url, async);
        xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
        xhr.send(params);
    }
    if(!async){
        callback(option,xhr);
    }
    function callback(opt,obj){
        var status = obj.status;
        if (status >= 200 && status < 300 ){
            opt.success && opt.success(obj.responseText,obj.responseXML);
        }else{
            opt.fail && opt.fail(status);
        }
    }
    function handleParams(data) {  
        var arr = [];
        for (var i in data) {
            arr.push(encodeURIComponent(i) + '=' + encodeURIComponent(data[i]));
        }
        return arr.join('&');
    }
}
//  測試調用
ajax('/xxx',{
    method:'POST',
    data:{
        key: 'test'
    },
    success:function(){
        console.log('success')
    },
    fail:function(){
        console.log('fail')
    }
});

小結

其實ajax實現原理並不複雜,複雜的是容錯、兼容性等的處理,使用的時候儘可能使用庫或者框架提供的封裝,避免沒必要要的漏洞。


補充

感謝@蝦嗶嗶的提問,這裏作個簡單的補充說明。

async是一個可選的布爾值參數,默認爲true,意味着是否執行異步操做,若是值爲false,則send()方法不會返回任何東西,直到接受到了服務器的返回數據。若是爲值爲true,一個對開發者透明的通知會發送到相關的事件監聽者。這個值必須是true,若是multipart 屬性是true,不然將會出現一個意外。

根據個人分析,當async爲false的時候,readyState會在send方法以後直接由1變成4。也就是說異步模式,send方法會馬上返回。同步模式下,只有響應徹底接受後,send纔會返回。

另外,因爲同步模式會阻塞,較新版本的Chrome在主線程上的同步請求已被棄用。

相關文章
相關標籤/搜索