1. 程式人生 > >函式防抖和節流

函式防抖和節流

在前端開發的過程中,我們經常會需要繫結一些持續觸發的事件,如 resize、scroll、mousemove 等等,但有些時候我們並不希望在事件持續觸發的過程中那麼頻繁地去執行函式。

通常這種情況下我們怎麼去解決的呢?一般來講,防抖和節流是比較好的解決方案。

讓我們先來看看在事件持續觸發的過程中頻繁執行函式是怎樣的一種情況。

html 檔案中程式碼如下

<div id="content" style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>

<script>

    var num = 1;

    var content = document.getElementById('content');

    function count() {

        content.innerHTML = num++;

    };

    content.onmousemove = count;

</script>

複製程式碼

在上述程式碼中,div 元素綁定了 mousemove 事件,當滑鼠在 div(灰色)區域中移動的時候會持續地去觸發該事件導致頻繁執行函式。效果如下

可以看到,在沒有通過其它操作的情況下,函式被頻繁地執行導致頁面上資料變化特別快。所以,接下來讓我們來看看防抖和節流是如何去解決這個問題的。

防抖(debounce

所謂防抖,就是指觸發事件後在 n 秒內函式只能執行一次,如果在 n 秒內又觸發了事件,則會重新計算函式執行時間。

防抖函式分為非立即執行版和立即執行版。

非立即執行版:

function debounce(func, wait) {

    var timeout;

    return function () {

        var context = this;

        var args = arguments;

        if (timeout) clearTimeout(timeout);

        timeout = setTimeout(function(){

            func.apply(context, args)

        }, wait);

    }

}

複製程式碼

非立即執行版的意思是觸發事件後函式不會立即執行,而是在 n 秒後執行,如果在 n 秒內又觸發了事件,則會重新計算函式執行時間。

我們依舊使用上述繫結 mousemove 事件的例子,通過上面的防抖函式,我們可以這麼使用

content.onmousemove = debounce(count,1000);

複製程式碼

效果如下

可以看到,在觸發事件後函式 1 秒後才執行,而如果我在觸發事件後的 1 秒內又觸發了事件,則會重新計算函式執行時間。

上述防抖函式的程式碼還需要注意的是 this 和 引數的傳遞

var context = this;

var args = arguments;

複製程式碼

防抖函式的程式碼使用這兩行程式碼來獲取 this 和 引數,是為了讓 debounce 函式最終返回的函式 this 指向不變以及依舊能接受到 e 引數。

立即執行版:

function debounce(func,wait,immediate) {

    var timeout;

    return function () {

        var context = this;

        var args = arguments;

        if (timeout) clearTimeout(timeout);

        var callNow = !timeout;

        timeout = setTimeout(function(){

            timeout = null;

        }, wait)

        if (callNow) func.apply(context, args)

    }

}

複製程式碼

立即執行版的意思是觸發事件後函式會立即執行,然後 n 秒內不觸發事件才能繼續執行函式的效果。

使用方法同上,效果如下

在開發過程中,我們需要根據不同的場景來決定我們需要使用哪一個版本的防抖函式,一般來講上述的防抖函式都能滿足大部分的場景需求。但我們也可以將非立即執行版和立即執行版的防抖函式結合起來,實現最終的雙劍合璧版的防抖函式。

雙劍合璧版:

/**

 * @desc 函式防抖

 * @param func 函式

 * @param wait 延遲執行毫秒數

 * @param immediate true 表立即執行,false 表非立即執行

 */

function debounce(func,wait,immediate) {

    var timeout;

    return function () {

        var context = this;

        var args = arguments;

        if (timeout) clearTimeout(timeout);

        if (immediate) {

            var callNow = !timeout;

            timeout = setTimeout(function(){

                timeout = null;

            }, wait)

            if (callNow) func.apply(context, args)

        }

        else {

            timeout = setTimeout(function(){

                func.apply(context, args)

            }, wait);

        }

    }

}

複製程式碼

節流(throttle

**所謂節流,就是指連續觸發事件但是在 n 秒中只執行一次函式。**節流會稀釋函式的執行頻率。

對於節流,一般有兩種方式可以實現,分別是時間戳版和定時器版。

時間戳版:

function throttle(func, wait) {

    var previous = 0;

    return function() {

        var now = Date.now();

        var context = this;

        var args = arguments;

        if (now - previous > wait) {

            func.apply(context, args);

            previous = now;

        }

    }

}

複製程式碼

使用方式如下

content.onmousemove = throttle(count,1000);

複製程式碼

效果如下

可以看到,在持續觸發事件的過程中,函式會立即執行,並且每 1s 執行一次。

定時器版:

function throttle(func, wait) {

    var timeout;

    return function() {

        var context = this;

        var args = arguments;

        if (!timeout) {

            timeout = setTimeout(function(){

                timeout = null;

                func.apply(context, args)

            }, wait)

        }

    }

}

複製程式碼

使用方式同上,效果如下

可以看到,在持續觸發事件的過程中,函式不會立即執行,並且每 1s 執行一次,在停止觸發事件後,函式還會再執行一次。

我們應該可以很容易的發現,其實時間戳版和定時器版的節流函式的區別就是,時間戳版的函式觸發是在時間段內開始的時候,而定時器版的函式觸發是在時間段內結束的時候。

同樣地,我們也可以將時間戳版和定時器版的節流函式結合起來,實現雙劍合璧版的節流函式。

雙劍合璧版:

/**

 * @desc 函式節流

 * @param func 函式

 * @param wait 延遲執行毫秒數

 * @param type 1 表時間戳版,2 表定時器版

 */

function throttle(func, wait ,type) {

    if(type===1){

        var previous = 0;

    }else if(type===2){

        var timeout;

    }

    return function() {

        var context = this;

        var args = arguments;

        if(type===1){

            var now = Date.now();

            if (now - previous > wait) {

                func.apply(context, args);

                previous = now;

            }

        }else if(type===2){

            if (!timeout) {

                timeout = setTimeout(function(){

                    timeout = null;

                    func.apply(context, args)

                }, wait)

            }

        }

    }

}

作者:淘淘笙悅

連結:https://juejin.im/post/5b651dc15188251aa30c8669

來源:掘金

著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。