防抖与节流

基本概念

  • 防抖(debounce): n 秒后在执行该回调,若在 n 秒内被重复触发,则重新计时。场景:不可控的高频触发,如点击按钮、输入框搜索
  • 节流(throttle): n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效。场景:快速连续的触发,如滚动条事件、mousemove

假设电梯有两种运行策略 防抖节流,超时设定为15秒,不考虑容量限制 电梯第一个人进来后,15秒后准时运送一次,这是节流。 电梯第一个人进来后,等待15秒。如果过程中又有人进来,15秒等待重新计时,直到15秒后开始运送,这是防抖。

两者的区别:

  • 防抖可能用于无法预知的用户主动行为,如用户输入内容去服务端动态搜索结果。用户打字的速度等是无法预知的,具有非规律性。
  • 节流可能用于一些非用户主动行为或者可预知的用户主动行为,如用户滑动商品橱窗时发送埋点请求、滑动固定的高度是已知的逻辑,具有规律性。
  • 防抖是关注于最后一次的事件触发,而节流则是在规定的时间里只执行一次。

防抖代码实现

// 防抖-非立即执行
function laterDebounce(fn, wait) {
    var timer = null
    return function () {
        var context = this
        var args = arguments
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(context, args)
        }, wait)
    }
}

// 防抖-立即执行,立即执行的意思是先执行再等待,会出现wait时间内连续点击两次,第二次无效的情况
function immediateDebounce(fn, wait) {
    var timer = null
    var flag = true
    return function () {
        var context = this
        var args = arguments
        clearTimeout(timer)
        if (flag) {
            fn.apply(context, args)
            flag = false
        }
        timer = setTimeout(() => {
            flag = true
        }, wait)
    }
}

// 第一次点击立即执行,接下来防抖(比较符合场景)
function firstImmediateDebounce(fn, wait, immediate) {
    var timer = null
    var count = 0
    return function () {
        var context = this
        var args = arguments
        clearTimeout(timer)
        if (immediate && count === 0) {
            fn.apply(context, args)
            count++
        } else {
            timer = setTimeout(() => {
                fn.apply(context, args)
                count++
            }, wait)
        }
    }
}

节流代码实现

// 节流:时间戳写法
function throttle1(fn, wait) {
    var oldTime = 0
    return function () {
        var now = Date.now()
        if (now - oldTime >= wait) {
            var context = this
            var args = arguments
            fn.apply(context, args)
            oldTime = Date.now()
        }
    }
}

// 节流:定时器写法
function throttle2(fn, wait) {
    var canExec = true
    var timer = null
    return function () {
        if (canExec) {
            canExec = false
            var context = this
            var args = arguments
            fn.apply(context, args)
            timer = setTimeout(() => {
                canExec = true
                clearTimeout(timer)
            }, wait)
        }
    }
}