防抖和节流你弄懂了吗

913 阅读3分钟
原文链接: github.com

debounce防抖

防止抖动(强制一个函数在某个连续时间段内只执行一次,哪怕它本来会被调用多次)

比如你在某个2s时间段内连续触发了mousemove,浏览器可能会触发几百个相关事件,不适用防抖的话,会对用户体验造成很大的影响

实现思路

debounce 返回了一个闭包,这个闭包依然会被连续频繁地调用,但是在闭包内部,却限制了原始函数 fn 的执行,强制 fn 只在连续操作停止后只执行一次。

/**
 * @param  fn (function )  实际要执行的函数
 * @param  delay(number)  延迟时间,也成阈值 单位为毫秒(ms)
 * 
 * return (function) 返回了一个防抖之后的函数
 * 
 */

function _debounce(fn, delay) {

    //设置定时器,用来执行settimeout
    var timer;
    // 返回一个函数,这个函数会在延迟时间delay(ms)之后执行
    if (typeof fn != "function" || typeof delay != 'number') {
        // alert(fn);
        return;
    }
    return function() {

        // 保存函数调用时的上下文和参数,传递给 fn
        var _this = this
        var args = arguments
            // 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn
        clearTimeout(timer)
            // 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作),
            // 再过 delay 毫秒就执行 fn
        timer = setTimeout(function() {
            fn.apply(_this, args)
        }, delay)
    }
}

以mousemove 为例,我们看下这个函数的使用方式

    <script>
        var temp = document.getElementById('mycarousel'),
            lis = temp.getElementsByTagName('li');
        for (var i = 0; i < lis.length; i++) {
            lis[i].onmousemove = _debounce(function(e) {
                //触发
                console.log(1)
            }, 2000)
        }
    </script>

使用后
gif

使用前

gif2

在输入框实现实时检索的场景中,通过input的change事件实时监测用户输入数据之后向服务器发送ajax请求。但在浏览器中的input框中,即使是用户正常的输入速度,事件被处罚的频率也是很高的,以这种高频率发送请求实在浪费资源,增加服务器的压力。

而在这是debounce 就派上用场了,在用户暂停输入一会儿之后,在发送请求。

      input.addEventListener('onchange', _debounce(function(e) {
            // 发送 ajax 请求
            console.log(1)
        }, 100), false);

throttle(节流)

持续触发事件时,throttle 会合并一定时间内的事件,并在该时间结束时真正去触发一次事件(固定函数执行的速率)

实现思路

相比 debounce,无非是多了一个时间间隔的判断,其他的逻辑基本一致。

/**
 *
 * @param fn {Function}   实际要执行的函数
 * @param delay {Number}  执行间隔,单位是毫秒(ms)
 *
 * @return {Function}     返回一个“节流”函数
 */
function throttle(fn, delay) {
    // 记录上次执行的时间
    var last
        // 定时器
    var timer
        // 默认间隔为 250ms
    delay || (delay = 250)
        // 返回的函数,每过 delay 毫秒就执行一次 fn 函数
    return function() {
        // 保存函数调用时的上下文和参数,传递给 fn
        var context = this
        var args = arguments
        var now = +new Date()
            // 如果距离上次执行 fn 函数的时间小于 delay,那么就放弃
            // 执行 fn,并重新计时
        if (last && now < last + delay) {
            clearTimeout(timer)
                // 保证在当前时间区间结束后,再执行一次 fn
            timer = setTimeout(function() {
                    last = now
                    fn.apply(context, args)
                }, delay)
                // 在时间区间的最开始和到达指定间隔的时候执行一次 fn
        } else {
            last = now
            fn.apply(context, args)
        }
    }
}

使用场景

throttle 常用的场景是限制 resize 和 scroll 的触发频率。以 scroll 为例

 window.onscroll = _throttle(function(e) {
            // 代码
            console.log(1);
        }, 250)

使用后

gif3

使用前

gif4

总结

debounce 强制函数在某段时间内只执行一次,throttle 强制函数以固定的速率执行。在处理一些高频率触发的 DOM 事件的时候,它们都能极大提高用户体验。