JS學(xué)習(xí)筆記之函數(shù)防抖與節(jié)流

在日常開發(fā)中,我們經(jīng)常能夠碰到以下工作場景:

    • 對提交按鈕進(jìn)行變態(tài)的點(diǎn)擊壓力測試
    • 輸入框內(nèi)容的實(shí)時校驗(yàn)(譬如驗(yàn)證用戶名是否已存在)
    • 圖片滾動加載scroll操作
    • 窗口放大縮小resize操作
    • 對某一區(qū)域進(jìn)行mousemove操作

上述頻繁觸發(fā)事件的操作,如果我們不采取任何操作,勢必會造成極差的用戶體驗(yàn)。譬如,對提交按鈕連續(xù)點(diǎn)擊發(fā)起請求,會給服務(wù)器帶來壓力;窗口放大縮小,會連續(xù)觸發(fā)瀏覽器的resize函數(shù),如果涉及到大量的dom操作,勢必又會引起頁面的回流與重繪,有可能會讓頁面變得卡頓等等。
如果我們不想頻繁的觸發(fā)某一事件,這時候就可以考慮用函數(shù)防抖、函數(shù)節(jié)流了。

函數(shù)防抖(debounce)

原理:在規(guī)定的時間t內(nèi),如果連續(xù)觸發(fā)某一事件,則不會調(diào)用事件回調(diào)函數(shù);連續(xù)觸發(fā)某一事件,t時間內(nèi),不再觸發(fā)該事件,則執(zhí)行事件回調(diào)函數(shù)。
我們以連續(xù)點(diǎn)擊提交按鈕為例:

  • 正常操作:
<button id="submit">提交</button>
<script>
    function doAjax() {
        console.log("Todo ajax...");
    }
    var btn = document.querySelector("#submit");
    btn.addEventListener("click", doAjax);
</script>
demo01.gif

在短時間內(nèi),我們對提交按鈕連續(xù)點(diǎn)擊,可以看到連續(xù)的請求被發(fā)起,我想這個時候后端的同事應(yīng)該會淚流滿面吧。

  • 使用防抖
<button id="submit">提交</button>
<script>
    function doAjax() {
        console.log("Todo ajax...");
    }

    function debounce(fn, delay) {
        var timer = null;
        return function() {
            timer && clearTimeout(timer);
            var context = this,         // 將執(zhí)行環(huán)境指向當(dāng)前dom
                arg = arguments;        // 事件e
            timer = setTimeout(function() {
                fn.call(context, arg);
            },delay);
        }
    }

    var btn = document.querySelector("#submit");
    btn.addEventListener("click", debounce(doAjax, 1000));
</script>
demo02.gif

我們可以觀察到,連續(xù)點(diǎn)擊提交按鈕,并沒有執(zhí)行請求;隔了1s后,執(zhí)行請求(也就是在這1s內(nèi),沒有點(diǎn)擊提交按鈕),這樣就很好的解決了我們的煩惱。

  • 防抖之立即執(zhí)行
    上述的防抖函數(shù),已經(jīng)可以解決我們大部分場景下的問題,但有一個需要注意的點(diǎn):點(diǎn)擊該提交按鈕,需要等一段時間后,才會調(diào)用函數(shù)。而我們工作當(dāng)中的另外一種需求為:點(diǎn)擊后立即執(zhí)行,在接下來的連續(xù)觸發(fā)中,不執(zhí)行事件回調(diào)函數(shù)。
<button id="submit">提交</button>
<script>
    function doAjax() {
        console.log("Todo ajax...");
    }

    function debounce(fn, delay, isImmediate) {
        var timer = null;
        return function() {
            timer && clearTimeout(timer);
            var context = this,         // 將執(zhí)行環(huán)境指向當(dāng)前dom
                arg = arguments;        // 事件e

            if(isImmediate) {
                !timer && fn.call(context, arg);    // timer為null(即沒有被執(zhí)行過,或被重置)
                // 立即執(zhí)行,后續(xù)連續(xù)點(diǎn)擊不起作用
                timer = setTimeout(function() {
                    timer = null;
                }, delay);
                
            } else {
                timer = setTimeout(function() {
                    fn.call(context, arg);
                },delay);
            }
        }
    }

    var btn = document.querySelector("#submit");
    btn.addEventListener("click", debounce(doAjax, 1000, true));
</script>
demo03.gif

從上圖我們可以觀察到:第一次點(diǎn)擊,請求被執(zhí)行,后續(xù)連續(xù)的點(diǎn)擊操作都不被執(zhí)行;等過了1s后,再次點(diǎn)擊,請求被執(zhí)行。

函數(shù)節(jié)流(throttle)

原理:連續(xù)觸發(fā)某一事件,會固定每隔一段時間執(zhí)行一次事件回調(diào)函數(shù)(區(qū)別于防抖的連續(xù)觸發(fā),不執(zhí)行事件回調(diào)函數(shù))
這里,我們以mousemove事件舉例:

  • 正常操作
<div id="box"></div>
<script>
    var box = document.getElementById("box");
    var count = 1;
    function doAction() {
        box.innerText = count++;
    }
    box.addEventListener("mousemove", doAction);
</script>

demo04.gif

可以看到,我們的小鼠標(biāo),輕輕一劃,連續(xù)觸發(fā)了不知道多少次mousemove事件,如果這里涉及到復(fù)雜的ajax操作,那又要悲劇了,ε(┬┬﹏┬┬)3!

  • 節(jié)流之時間戳實(shí)現(xiàn)
<div id="box"></div>
<script>
    var box = document.getElementById("box");
    var count = 1;
    function doAction() {
        box.innerText = count++;
    }
    function throttle(fn, delay) {
        var start = new Date();
        return function() {
            var context = this,         // 將執(zhí)行環(huán)境指向當(dāng)前dom
                arg = arguments;        // 事件e
            var current = new Date();

            if(current - start >= delay) {
                fn.call(context, arg);
                start = current;
            }
        }
    }


    box.addEventListener("mousemove", throttle(doAction, 1000));
demo05.gif

由上圖我們可以觀察到:在藍(lán)色塊內(nèi),連續(xù)觸發(fā)mousemove事件,數(shù)字以恒定速率(這里是1s)出現(xiàn)。

  • 節(jié)流之定時器實(shí)現(xiàn)
<div id="box"></div>
<script>
    var box = document.getElementById("box");
    var count = 1;
    function doAction() {
        box.innerText = count++;
    }
    function throttle(fn, delay) {
        var timer = null;
        return function() {
            var context = this,         // 將執(zhí)行環(huán)境指向當(dāng)前dom
                arg = arguments;        // 事件e
            if(!timer) {
                timer = setTimeout(function() {
                    timer = null;
                    fn.call(context, arg);
                },delay);
            }
        }
    }

    box.addEventListener("mousemove", throttle(doAction, 1000));

demo06.gif

觀察上圖,在藍(lán)色塊內(nèi),連續(xù)觸發(fā)mousemove事件,數(shù)字以恒定速率出現(xiàn)。

二者區(qū)別如下:

  • 時間戳版會在開始時立即執(zhí)行一次,最后時間間隔內(nèi)不再執(zhí)行;(注冊事件函數(shù)的時候,會執(zhí)行一次,拿到初始時間,等你mousemove的時候,時間間隔肯定遠(yuǎn)大于你的delay時間,或者你不等待,直接觸發(fā)mousemove事件)
  • 定時器版開始時不執(zhí)行,最后時間間隔內(nèi)再執(zhí)行一次。

總結(jié)

防抖與節(jié)流的區(qū)別

函數(shù)防抖好比是公交車??吭谡九_后,乘客源源不斷地上車,但司機(jī)只會等所有乘客上車之后,才發(fā)車。
函數(shù)節(jié)流好比是你每天都會喝水,但是你不會一喝水就上廁所,而是每隔一段時間就去上廁所。

應(yīng)用場景區(qū)別

函數(shù)節(jié)流不管事件觸發(fā)有多頻繁,都會保證在規(guī)定時間內(nèi)一定會執(zhí)行一次真正的事件處理函數(shù),而函數(shù)防抖只是在最后一次事件后才觸發(fā)一次函數(shù)。 比如在頁面的無限加載場景下,我們需要用戶在滾動頁面時,每隔一段時間發(fā)一次 Ajax 請求,而不是在用戶停下滾動頁面操作時才去請求數(shù)據(jù)。這樣的場景,就適合用節(jié)流技術(shù)來實(shí)現(xiàn)。

參考文獻(xiàn)
司徒正美-函數(shù)防抖與函數(shù)節(jié)流
蝦扯蛋之函數(shù)防抖和節(jié)流
前端麻辣燙-JS函數(shù)節(jié)流與防抖

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

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

  • 概念解釋 在一定時間內(nèi),代碼執(zhí)行的次數(shù)不一定要非常多,執(zhí)行的代碼越多,帶來的效果也是一樣,反而會因?yàn)閳?zhí)行次數(shù)過多而...
    辣瓜瓜閱讀 1,641評論 0 2
  • 函數(shù)節(jié)流 還記得上篇文章中說到的圖片懶加載嗎?我們在文章的最后實(shí)現(xiàn)了一個頁面滾動時按需加載圖片的方式,即在觸發(fā)滾動...
    柏丘君閱讀 2,973評論 1 19
  • ??JavaScript 與 HTML 之間的交互是通過事件實(shí)現(xiàn)的。 ??事件,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,684評論 1 11
  • 本節(jié)介紹各種常見的瀏覽器事件。 鼠標(biāo)事件 鼠標(biāo)事件指與鼠標(biāo)相關(guān)的事件,主要有以下一些。 click 事件,dblc...
    許先生__閱讀 2,838評論 0 4
  • 我們是我們的竊竊私語 是夜晚的花園 白瓷器上有細(xì)膩的裂紋 就像 一個吻上有細(xì)膩的裂紋 我們是我們的床 我們是我們?nèi)?..
    我是不是蝎大人閱讀 396評論 0 0

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