天天看點

JS防抖和節流

  前言

 在網頁實際運作的某些場景下,有些事件會不間斷的被觸發,如scroll事件,而不像我們想象中的,滾動一次觸發一次,稍微滾動一下就會觸發n多次scroll事件。如下:

window.onscroll = function (){
        console.log(123);
    }  //監聽滾動條滑動      

 我隻是輕微的滾動了一下滾動條就觸發了這麼多次的scroll事件,這種情況下,由于過于頻繁地DOM操作和資源加載,嚴重影響了網頁性能,甚至會造成浏覽器崩潰。

 此時,我們可以采用 debounce(防抖)和 throttle(節流)的方式來減少調用頻率,同時又不影響實際效果。

  防抖

 防抖通過設定setTimeout定時器的方式延遲執行,當快速多次點選的時候,每一次都會重置定時器,隻有你一段時間都不點選時定時器才能到達條件并執行事件函數。即如果觸發事件後在 n 秒内又觸發了事件,則會重新計算函數延執行時間。

 如下,我們模拟一個表單送出的例子,多次快速點選送出後隻會執行一次:

<input type="submit" id="btn" value="送出">
<script>
    var btn = document.getElementById('btn');
    // 點選後觸發debounce()函數,第一個參數為真實要執行的函數,第二個參數為定時器延遲時間
    btn.addEventListener('click',debounce(submit,1000));
    //真實要執行的函數
    function submit(e){
        console.log("送出成功!");
        console.log(this)
        console.log(e);
    }
    //防抖函數
    function debounce(fn,delay){
        //設定time為定時器
        var time = null;
        //閉包原理,傳回一個函數
        return function (e){
            //如果定時器存在則清空定時器
            if(time){
                clearTimeout(time);
            }
            //設定定時器,規定時間後執行真實要執行的函數
            time = setTimeout(() => {//此箭頭函數裡的this指向btn這個按鈕
                fn.call(this,arguments);//改變真實要執行函數的this指向,原submit函數裡面的this指向window
            },delay);
        }
    }
</script>      

 運作後,狂點送出按鈕停下來後隻會執行一次,而不會出現多次點選而多次送出。對于代碼中的閉包還有箭頭函數的this指向問題不清楚的,可以翻看我的這兩篇:​​閉包​​、​​箭頭函數​​。

  節流

 節流其實就很好了解了,減少一段時間的觸發頻率。簡單來說,就是你一直狂點不停的話,它會每隔一定時間就執行一次。它與防抖最大的差別就是,無論事件觸發多麼頻繁,都可以保證在規定時間内可以執行一次執行函數。下面利用計算時間戳實作:

<input type="submit" id="btn" value="送出">
<script>
    var btn = document.getElementById('btn');
    // 點選後觸發debounce()函數,第一個參數為真實要執行的函數,第二個參數為定時器延遲時間
    btn.addEventListener('click',throttle(submit,500));
    //真實要執行的函數
    function submit(e){
        console.log("送出成功!");
        console.log(this)
        // console.log(e);
    }
    //節流函數
    function throttle(fn,delay){
        //bef為上一次執行時間,初始值為0
        var bef = 0;
        return function (e){
            //擷取目前時間戳
            var now = new Date().getTime();
            //如果目前時間減去上次時間大于限制時間時才執行
            if(now - bef > delay){
                console.log(this);
                fn.call(this,arguments);
                bef = now;
            }
        }
    }
</script>      

 運作後,狂點不停的話,每隔500毫秒才執行一次。

 也可以用定時器實作節流,如下:

<input type="submit" id="btn" value="送出">
<script>
    var btn = document.getElementById('btn');
    // 點選後觸發debounce()函數,第一個參數為真實要執行的函數,第二個參數為定時器延遲時間
    btn.addEventListener('click',throttle(submit,500));
    //真實要執行的函數
    function submit(e){
        console.log("送出成功!");
        // console.log(this)
        console.log(e);
    }
    //節流函數
    function throttle(fn,delay){
        var flag = true;
        return function (e){
            if(flag){
                setTimeout(() => {
                    //到規定時間後執行函數,同時flag=true
                    fn(this,arguments);
                    flag = true;
                },delay);
            }
            //防止一直執行
            flag = false;
        };
    }

</script>      

  總結

 最後我們也明白了防抖和節流的主要差別,那麼他們各自适應的場景又有哪些呢?一般當我們送出表單時使用防抖,但在頁面的無限加載場景下,我們需要使用者在滾動頁面時,每隔一段時間發一次 ajax 請求,而不是在使用者停下滾動頁面操作時才去請求資料。這樣的場景,就适合用節流技術來實作。