防抖与节流

117 阅读2分钟

1.出现问题

一个函数被频繁触发,页面出现抖动甚至卡顿的效果,使用户体验变差.

一般常见的问题,比如:页面滚定,打印出当期的top值.

window.onload = function(){
    window.onscroll = function() {
        let scrollTop = document.body.scrollTop || document.documentElement.scrollTop
        console.log('滚定位置:' + scrollTop)
    }
}

效果图:

滚动过程中实时显示滚动位置,对onscroll函数的调用频率太高了;那怎么处理这种高频调用?

2.解决办法

2.1.防抖

单位时间内,对函数进行多次调用,只最后一次有效.

/**
* 防抖
* @param fn 被调用的函数
* @param delay 限制的时间段
*/
function debounce(fn, delay) {
    let timer = null
    return function () {
        // 如果已经有个定时器,则删除掉,然后重新添加个定时器,保证在限制时间段内只有一个定时器
        if (timer) {
          clearTimeout(timer)
        }
        timer = setTimeout(function () {
            fn()
            timer = null
        }, delay)
    }
}

效果图:

滚动后停止1s后,打印出当前的滚动位置.

2.2.节流

单位时间内,对函数进行多次调用,只第一次有效.

/**
* 节流
* @param fn 被调用的函数
* @param delay 限制的时间段
*/
function throttle(fn, delay) {
    let valid = false // 记录状态
    return function () {
        // 第一次状态为false,先将状态变为true,保证以后调用直接return出去,然后添加定时器,定时器回调里执行
        // 需要执行的函数,并将状态改为初始状态
        // 定时器执行完成完成后,进入下一阶段的节流
        if (valid) {
            return false
        }
        valid = true
        setTimeout(function () {
            fn()
            valid = false
        }, delay)
    }
}

使用另一种实现方式:思路和上面一样,也是使用一个标志性变量.

/**
* 节流
* @param fn 被调用的函数
* @param delay 限制的时间段
*/
function throttle(fn, delay) {
    let timer = null 
    return function () {
        // timer为null时,直接执行被调用函数,然后添加定时器,在限制时间内timer一直有值,
        // 知道定时器回调执行,timer的值被重置,若再调用函数,则进入下一阶段的节流
        if (!timer) {
            fn()
        }
        timer = setTimeout(function () {
            timer = null
        }, delay)
    }
}

效果图:

3.升级

以上是防抖和节流的简单实现.

单若是被调用的函数是需要传递参数的,那就需要考虑上下文环境以及arguments的问题了.

/**
 * 防抖
 * @param fn 被调用的函数
 * @param delay 限制的时间段
 */
function debounce(fn, delay) {
    let timer = null;
    return function () {
        let context = this
        let args = arguments
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(function () {
            fn.apply(context, args);
            timer = null;
        }, delay);
    };
}

/**
 * 节流
 * @param fn 被调用的函数
 * @param delay 限制的时间段
 */
function throttle(fn, delay) {
    let timer = null 
    return function () {
        let context = this
        let args = arguments
        if (!timer) {
            fn.apply(context, args);
        }
        timer = setTimeout(function () {
            timer = null
        }, delay)
    }
}

使用防抖方式对被调用函数进行传参的具体代码:

window.onload = function () {
    function debounce(fn, delay) {
        let timer = null;
        return function () {
            let context = this
            let args = arguments
            if (timer) {
                clearTimeout(timer);
            }
            timer = setTimeout(function () {
                fn.apply(context, args);
                timer = null;
            }, delay);
        };
    }

    function scroll(a) {
        let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
        console.log("滚定位置:" + a + ' - ' + scrollTop);
    }

    let myScroll = debounce(scroll, 1000);

    window.onscroll = function(){
        myScroll('aa') // 被调用函数传递的参数
    };
};

效果图:

4.参考

segmentfault.com/a/119000001…