AJAX里的狀態(tài)鎖與封裝

本博客著作權(quán)歸饑人谷_Lyndon和饑人谷所有,轉(zhuǎn)載請注明出處

學(xué)習(xí)AJAX的時候,對狀態(tài)鎖、代碼封裝兩個部分很感興趣。狀態(tài)鎖保證了在一些特殊情況下發(fā)出正確請求,獲得正確的返回數(shù)據(jù);代碼封裝使得代碼可讀性提升,代碼結(jié)構(gòu)化且適合維護(hù)。兩者都非常有用,因此我寫一個博客來梳理一下。


>>> 為什么需要狀態(tài)鎖?

當(dāng)數(shù)據(jù)請求速度/網(wǎng)速很慢的時候,如果用戶多次點擊請求按鈕,那么很有可能發(fā)出多次重復(fù)的請求,在get方式下,如果不對用戶的多次重復(fù)點擊做出處理,那么每次構(gòu)造的URL很有可能是一致的,最終就會返回很多重復(fù)的數(shù)據(jù),違背了開發(fā)者的初衷。

狀態(tài)鎖是一種優(yōu)雅的方法,概括而言:狀態(tài)鎖事先聲明一個變量,其中true表示開啟(鎖住用戶操作,用戶操作無效),false表示關(guān)閉(用戶可以進(jìn)行操作,操作將被處理),其核心的步驟如下:

1. 初始狀態(tài)下,狀態(tài)鎖是關(guān)閉的,用戶可以進(jìn)行操作

var lock = false;

2. 創(chuàng)建AJAX對象時進(jìn)行邏輯判斷,如果狀態(tài)鎖為開啟(true)狀態(tài),那么將忽視用戶的頻繁點擊,否則將發(fā)送請求

if(lock){
    return;
}

3. 請求一經(jīng)發(fā)出,需要經(jīng)歷處理過程,在這時,狀態(tài)鎖啟動,直到響應(yīng)就緒才關(guān)閉,否則,狀態(tài)鎖開啟,無法進(jìn)行請求

lock = true;
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
        ...
        lock = false;
    }
}else{
    lock = true;
}

>>> 不設(shè)置狀態(tài)鎖會怎樣

以下是不設(shè)置狀態(tài)鎖時前端頁面和服務(wù)器的代碼,只需要觀察Network的請求就可以獲知問題:

<div id="ct">
    <ul id="news"></ul>
    <button id="btn">點我加載</button>
</div>
<script>
    function $(id){
        return document.querySelector(id);
    }
    var btn = $("#btn");
    var ul = $("#news");
    var pageIndex = 0;
    btn.addEventListener("click", function(){
        var xhr = new XMLHttpRequest();
        xhr.open("get", "/loadMore?index=" + pageIndex + "&length=5", true);
        xhr.send();
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4){
                if(xhr.status === 200 || xhr.status === 304){
                    var results = JSON.parse(xhr.responseText);
                    console.log(results);
                    var fragment = document.createDocumentFragment();
                    for(var i = 0; i < results.length; i++){
                        var node = document.createElement("li");
                        node.innerText = results[i];
                        fragment.appendChild(node);
                    }
                    ul.appendChild(fragment);
                    pageIndex = pageIndex + 5;
                }else{
                    console.log("error");
                }
            }
        };
    })
</script>
app.get('/loadMore', function(req, res) {
    var pageIndex = parseInt(req.query.index);
    var length = parseInt(req.query.length);
    data = [];
    for(var i = 0; i < length; i++){
        var news = "新聞" + (i + pageIndex).toString();
        data.push(news);
    }
    setTimeout(function(){
        res.send(data)}, 5000
    )
});

服務(wù)端故意讓每次的響應(yīng)時間延遲5s,也就是點擊后不會立即有數(shù)據(jù)渲染在頁面上,數(shù)據(jù)拖延了5s才向前端進(jìn)行發(fā)送。如果用戶很急迫地一連點擊5次按鈕,返回結(jié)果是:

1.png

其原因是:每次的readyState都沒有到4(請求已完成,響應(yīng)已經(jīng)就緒)時,用戶就已經(jīng)迫不及待地發(fā)出了下一個請求,這時候的pageIndex并沒有執(zhí)行加5的操作,導(dǎo)致每次的請求都是http://localhost:8080/loadMore?index=0&length=5,而當(dāng)數(shù)據(jù)全部展現(xiàn)到頁面上后,再進(jìn)行一次點擊,此時的pageIndex已經(jīng)變成25了,新的請求就會變成http://localhost:8080/loadMore?index=25&length=5,輸出結(jié)果會非常的混亂。


>>> 添加狀態(tài)鎖

按照之前的說法,加入一個狀態(tài)鎖可以保證的效果是:當(dāng)響應(yīng)還沒有完成的時候,無論用戶怎么點擊按鈕,我都讓這一行為return為空,也即不返回任何結(jié)果/不產(chǎn)生任何效力。

以下是添加注釋的JS代碼。

// 狀態(tài)鎖初始狀態(tài)為關(guān)閉(false)狀態(tài),用戶可以發(fā)出請求
var lock = false;
function $(id){
    return document.querySelector(id);
}
var btn = $("#btn");
var ul = $("#news");
var pageIndex = 0;
btn.addEventListener("click", function(){
    var xhr = new XMLHttpRequest();
    // 如果狀態(tài)鎖狀態(tài)為開啟(true),則忽略用戶點擊操作,不發(fā)送AJAX請求
    if(lock){
        return;
    }
    // 如果狀態(tài)鎖狀態(tài)為關(guān)閉,則發(fā)送AJAX請求
    if(!lock) {
        xhr.open("get", "/loadMore?index=" + pageIndex + "&length=5", true);
        xhr.send();
        // 執(zhí)行過程中,狀態(tài)鎖為開啟狀態(tài),用戶無論怎樣點擊都是無效的
        lock = true;
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200 || xhr.status === 304) {
                    var results = JSON.parse(xhr.responseText);
                    console.log(results);
                    var fragment = document.createDocumentFragment();
                    for (var i = 0; i < results.length; i++) {
                        var node = document.createElement("li");
                        node.innerText = results[i];
                        fragment.appendChild(node);
                    }
                    // 如果響應(yīng)就緒,狀態(tài)鎖為關(guān)閉狀態(tài),用戶可以進(jìn)行下一次的請求
                    lock = false;
                    ul.appendChild(fragment);
                    pageIndex = pageIndex + 5;
                } else {
                    console.log("error");
                    // 否則,響應(yīng)出錯,狀態(tài)鎖保持開啟狀態(tài)
                    lock = true;
                }
            }
        };
    }
})

添加狀態(tài)鎖后,返回的結(jié)果會變?yōu)檎!?/p>

2.png

>>> AJAX封裝

AJAX封裝的第一個出發(fā)點:一個頁面上通常有多處需要使用AJAX,如果不進(jìn)行封裝,每次需要使用AJAX時,都需要寫相似度極高的代碼,造成信息冗余,而AJAX封裝抽取出普遍的通則,這樣在多次使用AJAX時僅需要直接調(diào)用封裝完成的代碼即可,便利了前端開發(fā)。

AJAX封裝的第二個出發(fā)點:將復(fù)雜的問題進(jìn)行拆解,由大化小,且力求使得每一個降解的子代碼段變得邏輯更加簡潔。如果不進(jìn)行合理的封裝,代碼中的一個函數(shù)內(nèi)既有if...else...,又有循環(huán),還有其他的變量計算,看起來非常缺乏條理。

針對這一弊端,將原有的代碼按照功能劃分成多個子部分,比如專門負(fù)責(zé)創(chuàng)建AJAX的、專門處理數(shù)據(jù)請求的、數(shù)據(jù)到來之后渲染頁面的,這樣在AJAX最核心的部分中只需要調(diào)用定義的函數(shù),整個代碼段的結(jié)構(gòu)會非常明晰,也方便后期維護(hù)。

1. 基礎(chǔ)的變量聲明放在script的最前面

var btn = document.querySelector("#load-more");
var ct = document.querySelector("#ct");
var pageIndex = 0;
var isDataArrive = true;

2. 創(chuàng)建事件偵聽器的時候,盡量在核心部分使用函數(shù),函數(shù)的布局跟著邏輯行進(jìn),比如:1)數(shù)據(jù)尚未來臨如何應(yīng)對 2)數(shù)據(jù)來臨如何應(yīng)對 3)加載數(shù)據(jù) 4)渲染頁面

btn.addEventListener("click", function(e) {
    e.preventDefault();
    // 數(shù)據(jù)尚未來臨,操作無效
    if (!isDataArrive) {
        return;
    }
    // 否則執(zhí)行數(shù)據(jù)加載,加載的數(shù)據(jù)為news,因為news需要經(jīng)過處理才能展現(xiàn)在頁面上,因此構(gòu)建一個匿名函數(shù)用以渲染頁面
    loadData(function (news) {
        renderPage(news);
        pageIndex = pageIndex + 5;
        isDataArrive = true;
    })
    isDataArrive = false;
});

3. 在實際應(yīng)用場景中,一個頁面中會有很多需要利用AJAX的地方,所以經(jīng)常是傳遞一個AJAX對象,然后直接將其中的value放到相應(yīng)的函數(shù)中。其中每一個AJAX對象應(yīng)該包含這些要素:1)請求方式 2)請求接口地址 3)傳遞的參數(shù) 4)請求成功后執(zhí)行什么 5)請求失敗后執(zhí)行什么

function loadData(callback){
    // 請求方式、URL、參數(shù)、請求成功后怎樣、請求失敗后怎樣
    // ajax("get", url, data, onSuccess, onError)
    ajax({
        type: "get",
        url: "/loadMore",
        data: {
            index: pageIndex,
            length: 5
        },
        // 請求成功后執(zhí)行,這里的callback相當(dāng)于上一段代碼后中的匿名函數(shù)
        onSuccess: callback,
        onError: function(){
            console.log("error")
        }
    })
}

4. 既然已經(jīng)定義好了AJAX對象,就要開始將其中的value放入對應(yīng)的函數(shù)中

function ajax(options){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if(xhr.readyState === 4){
            if(xhr.status === 200 || xhr.status === 304){
                var results = JSON.parse(xhr.responseText)
                // 往`callback`中傳遞參數(shù)
                options.onSuccess(results);
            }else{
                options.onError();
            }
        }
    }
    var query = "?";
    for(key in options.data){
        query += key + "=" + options.data[key] + "&"
    }
    query = query.substr(0, query.length - 1);
    xhr.open(options.type, options.url + query, true);
    xhr.send();
}

5. 接下來是渲染頁面的部分

function renderPage(news){
    var fragment = document.createDocumentFragment();
    for(var i = 0; i < news.length; i++){
        var node = document.createElement("li");
        node.innerText = news[i];
        fragment.appendChild(node);
    }
    ct.appendChild(fragment);
}

>>> 總結(jié)

AJAX封裝最明顯的特征就是:大問題拆解為小問題,但是小問題之間又環(huán)環(huán)相扣。需要熟悉的是AJAX對象,以及如何將對象中的值與回調(diào)函數(shù)結(jié)合起來。當(dāng)然在面臨更加靈活的AJAX對象時(比如需要綜合考慮到getpost兩種請求方式,數(shù)據(jù)返回格式可能不是JSON字符串),需要對代碼做出更優(yōu)化封裝,以應(yīng)對更多樣的情況并考慮到容錯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,872評論 25 709
  • AJAX 原生js操作ajax 1.創(chuàng)建XMLHttpRequest對象 var xhr = new XMLHtt...
    碧玉含香閱讀 3,557評論 0 7
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,148評論 4 61
  • 誰為誰一瞬執(zhí)念成魔 誤了終生 誰為誰一瞬眷戀成癡 誤了輪回 疑愛刻恨的異朽 似正非邪的東方 蕩盡心血的恨意決然 傾...
    流落閱讀 295評論 0 2

友情鏈接更多精彩內(nèi)容