JS函數節流和函數防抖

1.爲何須要函數防抖和函數節流?

  • 在瀏覽器中某些計算和處理要比其餘的昂貴不少。例如DOM操做比起非DOM交互須要更多的內存和CPU佔用時間。連續嘗試進行過多的DOM操做可能會致使瀏覽器掛起,甚至崩潰;
  • 例如當調整瀏覽器大小的時候,resize事件會連續觸發;若是在resize事件處理程序內部嘗試進行DOM操做,其高頻率的更改可能會讓瀏覽器崩潰;
  • 爲了繞開上面的問題,須要對該類函數進行節流;

2.什麼是函數防抖和函數節流

防抖(debounce)和節流(throttle)都是用來控制某個函數在必定時間內執行多少次的技巧,二者類似而又不一樣。 背後的基本思想是某些代碼不能夠在沒有間斷的狀況下連續重複執行。瀏覽器

2.1 函數防抖 (debounce)

若是一個事件被頻繁觸發屢次,而且觸發的時間間隔太短,則防抖函數可使得對應的事件處理函數只執行最後觸發的一次。 函數防抖能夠把多個順序的調用合併成一次。bash

2.2 函數節流 (throttle)

若是一個事件被頻繁觸發屢次,節流函數能夠按照固定頻率去執行對應的事件處理方法。 函數節流保證一個事件必定時間內只執行一次。閉包

3.應用場景

類型 場景
函數防抖 1. 手機號、郵箱輸入檢測
2. 搜索框搜索輸入(只需最後一次輸入完後,再放鬆Ajax請求)
3. 窗口大小resize(只需窗口調整完成後,計算窗口大小,防止重複渲染)
4.滾動事件scroll(只需執行觸發的最後一次滾動事件的處理程序)
5. 文本輸入的驗證(連續輸入文字後發送 AJAX 請求進行驗證,(中止輸入後)驗證一次就好
函數節流 1. DOM元素的拖拽功能實現(mousemove
2. 射擊遊戲的 mousedown/keydown 事件(單位時間只能發射一顆子彈)
3. 計算鼠標移動的距離(mousemove
4. 搜索聯想(keyup
5. 滾動事件scroll,(只要頁面滾動就會間隔一段時間判斷一次)

4.如何實現

4.1 函數防抖實現

function debounce(fn, delay, scope) {
    let timer = null;
    // 返回函數對debounce做用域造成閉包
    return function () {
        // setTimeout()中用到函數環境老是window,故須要當前環境的副本;
        let context = scope || this, args = arguments;
        // 若是事件被觸發,清除timer並從新開始計時
        clearTimeout(timer);
        timer = setTimeout(function () {
            fn.apply(context, args);
        }, delay);
    }
}
複製代碼
  • 代碼解讀
  1. 第一次調用函數,建立一個定時器,在指定的時間間隔以後運行代碼;
  2. 當第二次調用該函數時,它會清除前一次的定時器並設置另外一個;
  3. 若是前一個定時器已經執行過了,這個操做就沒有任何意義;
  4. 然而,若是前一個定時器還沒有執行,其實就是將其替換爲一個新的定時器;
  5. 目的是只有在執行函數的請求中止了delay時間以後才執行

4.2 函數節流實現

4.2.1 利用時間戳簡單實現

function throttle(fn, threshold, scope) {
    let timer;
    let prev = Date.now();
    return function () {
        let context = scope || this, args = arguments;
        let now = Date.now();
        if (now - prev > threshold) {
            prev = now;
            fn.apply(context, args);
        }
    }
}
複製代碼

4.2.2 利用定時器簡單實現

function throttle2(fn, threshold, scope) {
    let timer;
    return function () {
        let context = scope || this, args = arguments;
        if (!timer) {
            timer = setTimeout(function () {
                fn.apply(context, args);
                timer = null;
            }, threshold)
        }
    }
}
複製代碼

5 舉例(scroll事件)

CSS代碼

.wrap {
       width: 200px;
        height: 330px;
        margin: 50px;
        margin-top: 200px;
        position: relative;
        float: left;
        background-color: yellow;
    }
    .header{
        width: 100%;
        height: 30px;
        background-color: #a8d4f4;
        text-align: center;
        line-height: 30px;
    }
    .container {
        background-color: pink;
        box-sizing: content-box;
        width: 200px;
        height: 300px;
        overflow: scroll;
        position: relative;
    }
    .content {
        width: 140px;
        height: 800px;
        margin: auto;
        background-color: #14ffb2;
    }
複製代碼

HTML代碼

<div class="wrap">
        <div class="header">滾動事件:普通</div>
        <div class="container">
            <div class="content"></div>
        </div>
    </div>
    <div class="wrap">
        <div class="header">滾動事件:<strong>加了函數防抖</strong></div>
        <div class="container">
            <div class="content"></div>
        </div>
    </div>
    <div class="wrap">
        <div class="header">滾動事件:<strong>加了函數節流</strong></div>
        <div class="container">
            <div class="content"></div>
        </div>
    </div>
複製代碼

JS代碼

let els = document.getElementsByClassName('container');
    let count1 = 0,count2 = 0,count3 = 0;
    const THRESHOLD = 200;

    els[0].addEventListener('scroll', function handle() {
        console.log('普通滾動事件!count1=', ++count1);
    });
    els[1].addEventListener('scroll', debounce(function handle() {
        console.log('執行滾動事件!(函數防抖) count2=', ++count2);
    }, THRESHOLD));
    els[2].addEventListener('scroll', throttle(function handle() {
        console.log(Date.now(),', 執行滾動事件!(函數節流) count3=', ++count3);
    }, THRESHOLD));
複製代碼
// 函數防抖
function debounce(fn, delay, scope) {
    let timer = null;
    let count = 1;
    return function () {
        let context = scope || this,
            args = arguments;
        clearTimeout(timer);
        console.log(Date.now(), ", 觸發第", count++, "次滾動事件!");
        timer = setTimeout(function () {
            fn.apply(context, args);
            console.log(Date.now(), ", 可見只有當高頻事件中止,最後一次事件觸發的超時調用才能在delay時間後執行!");
        }, delay);
    }
}
複製代碼
// 函數節流
function throttle(fn, threshold, scope) {
    let timer;
    let prev = Date.now();
    return function () {
        let context = scope || this, args = arguments;
        let now = Date.now();
        if (now - prev > threshold) {
            prev = now;
            fn.apply(context, args);
        }
    }
}
複製代碼

執行結果展現及比較

測試函數防抖和函數節流

普通滾動

防抖滾動

節

6.總結

  • debouncethrottle均是經過減小高頻觸發事件的實際事件處理程序的執行來提升事件處理函數運行性能的手段,並無實質上減小事件的觸發次數。
  • debounce能夠把多個順序的調用合併成一次
  • throttle保證一個事件必定時間內只執行一次。
相關文章
相關標籤/搜索