成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

通過函數(shù)節(jié)流與函數(shù)分時(shí)提升應(yīng)用性能

lijy91 / 733人閱讀

摘要:結(jié)束語在這里,我們雖然僅僅涉及了一些高階函數(shù)應(yīng)用的皮毛,但這兩個(gè)技巧,實(shí)是項(xiàng)目開發(fā)當(dāng)中克敵制勝,提高性能的實(shí)戰(zhàn)利器。

通過函數(shù)節(jié)流與函數(shù)分時(shí)提升應(yīng)用性能

在例如表單自動(dòng)補(bǔ)全,數(shù)據(jù)埋點(diǎn),文章內(nèi)容自動(dòng)保存,視口監(jiān)聽,拖拽,列表渲染等高頻操作時(shí),如果同時(shí)有其它UI行為占據(jù)線程,瀏覽器端時(shí)常會(huì)出現(xiàn)卡頓現(xiàn)象,服務(wù)器端也面臨著較大壓力。這時(shí),函數(shù)節(jié)流,與函數(shù)分時(shí)將成為我們的一大輔助。

一、函數(shù)節(jié)流

看一則自動(dòng)補(bǔ)全的例子

//自動(dòng)補(bǔ)全
const input       = document.querySelector("#autocompleteSearch"),
      completeArr = []; //需要被渲染的補(bǔ)全提示數(shù)據(jù)

input.addEventListener("keydown", e => {
    
    const value = e.currentTarget.value,
          xhr   = new XMLHttpRequest();
    
    
    xhr.addEventListener("load", ()=> {
        //請(qǐng)求完成后,將數(shù)據(jù)裝載到數(shù)組中
        xhr.status === 200 && completeArr.push(xhr.responseText);
    });
    
    xhr.open("GET", "http://api.com");
    xhr.send(value);
    
});

在這里,我沒有提供具體的UI層的操作,只提供了觸發(fā)事件時(shí)的handler,實(shí)際的開發(fā)中可能還需要涉及要補(bǔ)全數(shù)據(jù)數(shù)組的渲染邏輯以及排序和清除邏輯,但這并不妨礙我們理解本問題的過程。

可以看到的是,為了實(shí)時(shí)更新補(bǔ)全數(shù)據(jù),每次當(dāng)用戶按下按鍵時(shí),我們都要向服務(wù)器去發(fā)起一次請(qǐng)求。如果產(chǎn)品的用戶基數(shù)很大,并發(fā)一高,那就實(shí)在是有些坑后端隊(duì)友了。

回想需求,我們要根據(jù)用戶輸入的關(guān)鍵字像服務(wù)器索取補(bǔ)全的字段,反饋給用戶快速選擇。

實(shí)際上,在用戶輸入表單的過程中,可能按下很多次按鍵才會(huì)打出一個(gè)字,或者是打出了很多個(gè)字后,才能檢索出真整的數(shù)據(jù)。

基于這個(gè)角度來換一下思路,如何限制請(qǐng)求的發(fā)送呢?

判斷value的長(zhǎng)度,輸入兩個(gè)三個(gè)字以上,再向服務(wù)器發(fā)起請(qǐng)求

將事件的handler觸發(fā)頻率降低

第一種思路,不失為是一種可行的方案,但是很難復(fù)用,而且用戶真實(shí)想要搜入的字?jǐn)?shù)并不確定。

第二種思路,既能限制頻率,減少請(qǐng)求,還能近實(shí)時(shí)向用戶反饋,無視用戶輸入的字符串長(zhǎng)度,還可以實(shí)現(xiàn)高復(fù)用。

下面提供實(shí)現(xiàn)的方式,首先,實(shí)現(xiàn)函數(shù)節(jié)流:

const throttle =  (fn, time = 1000)=> {
    let triggered = true,        // 首次觸發(fā)狀態(tài)的標(biāo)識(shí)
        timer;                  // 定時(shí)器觸發(fā)標(biāo)識(shí)
    return function () {
        if (triggered) {
            // 首次觸發(fā) 回調(diào)直接執(zhí)行
            fn.apply(this, arguments);
            //執(zhí)行后 使首次觸發(fā)標(biāo)識(shí)為假
            return triggered = false;
        }
        if (timer) {
            // 定時(shí)器標(biāo)識(shí) 如果為真 代表著之前的分流限制范圍 尚未結(jié)束
            return false;
        }
        timer = setInterval(()=> {
            //如果定時(shí)器標(biāo)識(shí)不為真 則為定時(shí)器賦上引用
            clearInterval(timer);
            // 取反定時(shí)器標(biāo)識(shí)
            timer = !timer;
            // 執(zhí)行回調(diào)
            fn.apply(self, arguments);
        }, time)
    }
    
};

上述代碼,利用了閉包與高階函數(shù),限制了函數(shù)的觸發(fā),關(guān)鍵點(diǎn)在于首次觸發(fā)與之前的節(jié)流是否結(jié)束的判斷。

改造一下上面的自動(dòng)補(bǔ)全代碼。

const input          = document.querySelector("#autocompleteSearch"),
      completeArr    = [],
      keydownHandler = throttle(e => {
    
          const value = e.currentTarget.value,
                xhr   = new XMLHttpRequest();
    
    
          xhr.addEventListener("load",()=> {
              //請(qǐng)求完成后,將數(shù)據(jù)裝載到數(shù)組中
              xhr.status === 200 && completeArr.push(xhr.responseText);
          });
    
          xhr.open("GET", "http://api.com");
          xhr.send(value);
    
      }); //需要被渲染的補(bǔ)全提示數(shù)據(jù)

input.addEventListener("keydown",keydownHandler);


function throttle(fn, time = 1000) {
    let triggered = true,        // 首次觸發(fā)狀態(tài)的標(biāo)識(shí)
        timer;                  // 定時(shí)器觸發(fā)標(biāo)識(shí)
    return function () {
        if (triggered) {
            // 首次觸發(fā) 回調(diào)直接執(zhí)行
            fn.apply(this, arguments);
            //執(zhí)行后 使首次觸發(fā)標(biāo)識(shí)為假
            return triggered = false;
        }
        if (timer) {
            // 定時(shí)器標(biāo)識(shí) 如果為真 代表著之前的分流限制范圍 尚未結(jié)束
            return false;
        }
        timer = setInterval(()=> {
            //如果定時(shí)器標(biāo)識(shí)不為真 則為定時(shí)器賦上引用
            clearInterval(timer);
            // 取反定時(shí)器標(biāo)識(shí)
            timer = !timer;
            // 執(zhí)行回調(diào)
            fn.apply(self, arguments);
        }, time)
    }
    
}

如此,實(shí)現(xiàn)了keydown事件觸發(fā)的頻率,當(dāng)然,一些其他高頻的事件回調(diào)依舊適合,我們可以根據(jù)具體的業(yè)務(wù)場(chǎng)景,來傳入合理的time值,達(dá)到節(jié)流,既減輕了服務(wù)器端的壓力,又提升了性能,例如上面的自動(dòng)補(bǔ)全,1秒的延遲,用戶幾乎感受不到,何樂而不為呢?

二、分時(shí)函數(shù)

上面那種隱藏在用戶操作背后,節(jié)流函數(shù)是一個(gè)很好的解決方案。同時(shí),我們可能會(huì)面臨另外一種場(chǎng)景,即是一次性渲染。

比如說,我們有這樣的需求,后臺(tái)給了我們2000行記錄的數(shù)據(jù),要一次性用列表全部渲染出來。

2000行數(shù)據(jù)可不是一個(gè)小數(shù)目,如果里面內(nèi)嵌了很多子節(jié)點(diǎn)邏輯,那么很有可能我們也許要渲染上萬個(gè)節(jié)點(diǎn),眾所周知,DOM可是瀏覽器環(huán)境性能的最大損耗者。為了提升用戶體驗(yàn)與性能,通常情況下,我會(huì)使用兩種操作。

使用DOM的fragment,避免每次節(jié)點(diǎn)生成時(shí)的反復(fù)插入,可以在合理的時(shí)機(jī)向相應(yīng)的節(jié)點(diǎn)插入,這方面的資料很多,可以自行查閱。

使用函數(shù)分時(shí) 來分批處理渲染邏輯

先看如何在不分時(shí)的情況下操作節(jié)點(diǎn):

const
    list        = document.querySelector("#ul"),
    virtualList = document.createDocumentFragment(), // 虛擬dom容器
    listArr     = [
        {text: "hello react!"}
        // 假設(shè)這里有2000條記錄
    ];

for (let i of listArr) {
    // 使用for of 遍歷數(shù)據(jù)
    const li       = document.createElement("li");
    li.textContent = i;
    // 插入虛擬容器中
    virtualList.appendChild(li);
    
}

// 把載滿節(jié)點(diǎn)的虛擬容器 插入到真實(shí)的列表元素中
list.appendChild(virtualList);

再來看分函數(shù)分時(shí)的實(shí)現(xiàn):

function chunkFunc({fn, arr, count = arr.length, time = 200, sCb, aCb}) {
    
    /*
     * @params
     * fn   : 需要被分時(shí)的處理邏輯
     * arr  : 全部的業(yè)務(wù)數(shù)據(jù)
     * count: 每次分時(shí)的具體數(shù)量
     *        假設(shè)總共2000條數(shù)據(jù)
     *        我們可以設(shè)定
     *        每次分成200條執(zhí)行
     *        默認(rèn)為業(yè)務(wù)數(shù)據(jù)的長(zhǎng)度
     * time : 分時(shí)的時(shí)間間隔 默認(rèn)200 毫秒
     * sCb  : singleCallback 每次分時(shí)遍歷結(jié)束時(shí)執(zhí)行的回調(diào)
     * aAb  : allCallback 全部遍歷結(jié)束時(shí)需要做的回調(diào)
     * */
    
    let
        timer,      // 用以分時(shí)的定時(shí)器標(biāo)識(shí)
        start;      // 遍歷處理邏輯
    
    start = () => {
        for (let i = 0; i < count; i++) {
            //如果count給了值 我們循環(huán)count次 每次循環(huán)都從業(yè)務(wù)數(shù)據(jù)里取值 然后執(zhí)行處理邏輯
            fn(arr.shift());
        }
        //分時(shí)遍歷結(jié)束 如果有回調(diào) 執(zhí)行回調(diào)
        sCb && sCb();
    };
    
    return () => {
        // 默認(rèn)每200毫秒執(zhí)行一次
        timer = setInterval(function () {
            // 如果原始數(shù)據(jù)被取空了 則停止執(zhí)行
            if (arr.length === 0) {
                aCb && aCb();
                return clearInterval(timer)
            }
            // 不然 執(zhí)行遍歷邏輯
            start();
        }, time);
    }
}

實(shí)現(xiàn)方式很簡(jiǎn)單,即根據(jù)用戶給定的分時(shí)單位與時(shí)間,利用定時(shí)器重新包裝用戶處理邏輯,這里我們需要將渲染邏輯稍微改動(dòng),抽離出遍歷邏輯,添加遍歷結(jié)束回調(diào)方法(可選)。

重構(gòu)代碼如下:

const
    list    = document.querySelector("#ul"),
    listArr = [
        {text: "hello react!"}
        // 假設(shè)這里有2000條記錄
    ];

let virtualDOM = document.createDocumentFragment();


chunkFunc({
    fn(data) {
        // 生成節(jié)點(diǎn)邏輯
        const li       = document.createElement("li");
        li.textContent = data.text;
        virtualDOM.appendChild(li);
    },
    sCb() {
        // 分時(shí)遍歷結(jié)束 將虛擬節(jié)點(diǎn) 插入LIST
        list.appendChild(virtualDOM);
        // 重置虛擬節(jié)點(diǎn) 避免重復(fù)生成節(jié)點(diǎn)
        virtualDOM = document.createDocumentFragment();
    },
    aCb() {
        // 最終結(jié)束后 解除引用
        virtualDOM = null;
    },
    arr  : listArr,
    count: 8,
    time : 300,
})();

通過抽離了插入、生成節(jié)點(diǎn)的邏輯、給出不同階段的回調(diào),我們成功的將本來需要一次性生成的節(jié)點(diǎn),分批生成,提高了性能和用戶體驗(yàn)。

結(jié)束語

在這里,我們雖然僅僅涉及了一些高階函數(shù)應(yīng)用的皮毛,但這兩個(gè)技巧,實(shí)是項(xiàng)目開發(fā)當(dāng)中克敵制勝,提高性能的實(shí)戰(zhàn)利器。根據(jù)不同的業(yè)務(wù)場(chǎng)景伸縮,我們可以衍生出不同的方法。如果能結(jié)合單例模式,代理模式等常用的設(shè)計(jì)模式,將會(huì)有更為廣擴(kuò)的發(fā)揮。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/91993.html

相關(guān)文章

  • JavaScript 設(shè)計(jì)模式開發(fā)實(shí)踐讀書筆記

    摘要:設(shè)計(jì)模式與開發(fā)實(shí)踐讀書筆記最近利用碎片時(shí)間在上面閱讀設(shè)計(jì)模式與開發(fā)實(shí)踐讀書這本書,剛開始閱讀前兩章內(nèi)容,和大家分享下我覺得可以在項(xiàng)目中用的上的一些筆記。事件綁定暫時(shí)這么多,以后會(huì)不定期更新一些關(guān)于我讀這本書的筆記內(nèi)容 JavaScript 設(shè)計(jì)模式與開發(fā)實(shí)踐讀書筆記 最近利用碎片時(shí)間在 Kindle 上面閱讀《JavaScript 設(shè)計(jì)模式與開發(fā)實(shí)踐讀書》這本書,剛開始閱讀前兩章內(nèi)容,...

    FingerLiu 評(píng)論0 收藏0
  • 如何使用函數(shù)來優(yōu)化性能?

    摘要:原理連續(xù)觸發(fā)事件,但是事件函數(shù)只在在規(guī)定的周期之內(nèi)只執(zhí)行一次。代碼實(shí)現(xiàn)使用在使用節(jié)流函數(shù)后,我們?cè)跁和]斎氲暮缶蜁?huì)輸入輸入框內(nèi)的值,暫停時(shí)間小于,則不會(huì)輸出,將重新計(jì)算函數(shù)執(zhí)行時(shí)間。使用分時(shí)函數(shù)這樣在調(diào)用分時(shí)函數(shù)后每隔創(chuàng)建個(gè)節(jié)點(diǎn)。 一、節(jié)流函數(shù) 1. 使用場(chǎng)景 DOM.onclick()事件,我們給一個(gè)DOM節(jié)點(diǎn)綁定了點(diǎn)擊事件,當(dāng)點(diǎn)擊該元素時(shí)觸發(fā)事件函數(shù)的執(zhí)行,但是當(dāng)我們頻繁點(diǎn)擊該元素...

    lncwwn 評(píng)論0 收藏0
  • JavaScript中高階函數(shù)的魅力

    摘要:上傳進(jìn)度下面通過高階函數(shù)的方式我們來實(shí)現(xiàn)函數(shù)節(jié)流節(jié)流函數(shù)計(jì)時(shí)器是否是第一次調(diào)用首次調(diào)用直接放行存在計(jì)時(shí)器就攔截設(shè)置使用節(jié)流分時(shí)函數(shù)節(jié)流函數(shù)為我們提供了一種限制函數(shù)被頻繁調(diào)用的解決方案。 高階函數(shù)是指至少滿足下列條件之一的函數(shù) 1:函數(shù)可以作為參數(shù)被傳遞 2:函數(shù)可以作為返回值輸出 JavaScript語言中的函數(shù)顯然的是滿足了高階函數(shù)的條件,下面我們一起來探尋JavaScript種高階...

    Tony_Zby 評(píng)論0 收藏0
  • js編程中經(jīng)常遇到的一些問題(持續(xù)更新)

    摘要:一前言本文適合有一定開發(fā)基礎(chǔ)的讀者,文章涉及開發(fā)中經(jīng)常遇到的一些令人疑惑的問題,理解這些問題有助于我們快速提升對(duì)這門語言的理解和應(yīng)用能力。 一:前言 本文適合有一定JS開發(fā)基礎(chǔ)的讀者,文章涉及開發(fā)中經(jīng)常遇到的一些令人疑惑的問題,理解這些問題有助于我們快速提升對(duì)JS這門語言的理解和應(yīng)用能力。文章只講述具體問題中的關(guān)鍵問題,不涵蓋全面的知識(shí)點(diǎn)。如想了解具體的知識(shí),可以參考筆者博客的相關(guān)文章...

    willin 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<