防抖节流的原理和使用场景,并实现防抖函数和节流函数

一、问题背景

      开发中,经常会遇到以下场景:监听鼠标移动 onmousemove,监听页面滚动 onscroll,监听大小变化 onresize,监听 input 输入等。这些场景下,事件会被频繁触发,但我们并不想事件被频繁触发,这时就需要通过防抖和节流来限制频繁操作。

二、两者区别及实现

      防抖和节流都是为了解决事件频繁触发的问题,但在实现原理上有些不同。

      防抖函数(debounce)是在短时间内多次触发同一事件时,只执行第一次或最后一次。根据执行时机,可有两种写法。

      一段时间内的持续触发,只执行第一次触发操作的写法:

debounceFirst(func, delay) {
    //闭包中,timer 是同一个
    let timer;
    return function() {
        // onscroll触发的,实际是这个函数
        // 函数执行时才能确认this指向
        // 在此例中,方法由window对象调用,所以this指向window
        const context = this;
        const args = arguments;
        // 第一次触发时,timer为undefined,carryOut为true,执行func
        // 1s内再次触发时,timer为定时器id,carryOut为false,不执行func
        if (timer) {
            // clearTimeout后timer的值依然为定时器id,carryOut为false,不执行func
            clearTimeout(timer)
        };
        const carryOut = !timer;
        timer = setTimeout(() => {
            // 超过设置的延迟时间1s后,将timer赋值为null,carryOut为true,执行func
            timer = null;
        }, delay)
        if (carryOut) {
            // 通过 apply 接收 func 的参数
            func.apply(context, args);
        }
    }
}
// 当第一次触发窗口滚动时执行一次function,后面的都不执行;
// 当持续1s没有窗口滚动,1s后再次触发时,再次执行一次function
window.onscroll = debounceFirst(function, 1000);

      一段时间内的持续触发,只执行最后一次触发操作的写法:

debounceLast(func, delay) {
    let timer;
    return function() {
        // onscroll触发的,实际是这个函数
        const context = this;
        const args = arguments;
        // 1s内再次触发时,清除定时器
        if (timer) {
            clearTimeout(timer)
        };
        // 重新开启定时器,1s后执行func
        timer = setTimeout(() => {
            func.apply(context, args);
        }, delay)
    }
}
// 当持续1s没有窗口滚动,才执行一次function
window.onscroll = debounceLast(function, 1000);

      由以上两种写法可以看出,若事件持续执行,没有间隔设置的 delay 时长时,可能函数被触发一次后永远不会被触发,甚至一次都不会触发。节流函数可以解决这一问题。

      节流函数(throttle)是指连续触发事件在n秒内执行一次,2n秒内执行两次...根据执行时机,同样有两种写法。

      n秒内的持续触发,在n秒开始时执行一次。

function throttleStart(func, wait) {
    let previous = 0;
    return function() {
        const now = Date.now();
        const context = this;
        const args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}
// 窗口滚动时function立即执行一次,然后在连续触发中每1s执行一次function
window.onscroll = throttleStart(function, 1000);

        n秒内的持续触发,在n秒结束时执行一次。

function throttleEnd(func, wait) {
    let timeout;
    return function() {
        const context = this;
        const args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
              timeout = null;
              func.apply(context, args)
            }, wait)
        }
    }
}
// 1s内的连续触发在1s结束后,执行一次function
window.onscroll = throttleEnd(function, 1000);

你可能感兴趣的:(JavaScript)