刨根問底ajax原理與封裝

志如發表於2019-03-02

說起ajax,大家都不陌生。但是由於目前很多框架或者庫等都對網路請求做了封裝,導致了很多初學者只知其然而不知其所以然。所以今天我們就詳細瞭解一下ajax的實現原理和封裝ajax的關鍵步驟。

ajax的核心是XMLHttpRequest物件。首先我們先建立一個XMLHTTPRequest物件var xhr = new XMLHttpRequest();

注意:本文所提及的內容不相容古老的IE,有想了解的同學自行查閱ActiveXObject相關內容。


XMLHttpRequest

在使用XMLHttpRequest物件的第一步,我們首先要呼叫open方法來初始化請求引數,xhr.open(`get`,`/test`,true),雖然名字叫open,但是此時請求還並沒有傳送。

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

  • method:請求型別,例如GET,POST等
  • url:請求地址(這裡有同源限制,就是我們經常會看到的跨域問題啦)
  • async:是否傳送非同步請求。可選引數,預設為true。
  • username&password:可選引數,授權驗證使用的,但是我們一般不這麼用,使用後請求變成這個樣子了,http(s)://username:password@url。

如果呼叫了open方法後再次對它進行呼叫,則相當於呼叫了abort方法,abort方法我們在後面介紹。

如果我們想為為請求繫結一些操作,這個時候就可以開始啦。常用的操作有如下幾個:

setRequestHeader(key, value)

顧名思義,這個方法用於設定請求頭內容。

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

overrideMimeType(type)

重寫伺服器返回的MIME型別。通過這個方法可以告訴伺服器你想要的資料型別。

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

這個時候,我們就可以通過呼叫send 方法來傳送請求了,xhr.send(null)

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實現原理並不複雜,複雜的是容錯、相容性等的處理,使用的時候儘量使用庫或者框架提供的封裝,避免不必要的漏洞。

相關文章