javascript之实现节流函数

100 阅读2分钟

当持续触发事件时,保证一定时间段内只调用一次事件处理函数。

根据定义我们实现第一版节流函数。

function throttle(func, wait) {
    let timer = null
    let context = this
    let args = arguments

    const throttled = function () {
        if (!timer) {
            timer = setTimeout(function() {
                clearTimeout(timer)
                timer = null
                func.apply(context, args)
            }, wait)
        }
    }
    return throttled
}

在实际场景中, 我们更希望初次触发事件时能够立即执行,停止触发时还能再执行一次。基于此我们得到下面的代码:

function throttle(func, wait) {
    let previous = 0
    let timer = null
    let context = this
    let args = arguments

    let layer = function () {
        clearTimeout(timer)
        timer = null
        previous = +new Date()
        func.apply(context, args)
    }

    var throttled = function () {
        let now = +new Date()
        
        // 下次调用func剩下的时间
        let remaining = wait - (now - previous)
        
        // 第一次调用时previous为0, remaining肯定小于0,于是直接执行函数
        // remaining > wait 修改了系统时间的情况
        if (remaining < 0 || remaining > wait) {
            if (timer) {
                clearTimeout(timer)
                timer = null
            }
            previous = now
            func.apply(context, args)
        } else if (!timer) {
            timer = setTimeout(layer, remaining)
        }
    }

    return throttled
    
}

更进一步, 我们希望可以选择是否立即执行,在结束事件时是否要执行事件,并且我们希望可以取消节流事件。 这里我们设置options为第三个参数:

  • leading:false 表示禁用第一次执行
  • trailing: false 表示禁用停止触发的回调
function throttle(func, wait, options = {}) {
    let previous = 0
    let timer = null
    let context = this
    let args = arguments

    let layer = function () {
        clearTimeout(timer)
        timer = null
        // a. false 时设置为0 是为了下一次调用时, 备注b那里可以重新赋值,不然会导致计算remaining时,因为(now - previous) 值太过大使remaining变为负数,导致错误调用立即执行
        previous = options.leading === false ? 0 : +new Date()
        func.apply(context, args)
    }

    var throttled = function () {
        let { leading, trailing } = options
        // b. 备注a那里使previous为0 ,方便可以在这里重新赋值
        if (!previous && leading === false) previous = +new Date()
        let now = +new Date()
        
        // 下次调用func剩下的时间
        let remaining = wait - (now - previous)
        
        // leading为true时,第一次调用时previous为0, remaining肯定小于0,于是直接执行函数
        // remaining > wait 修改了系统时间的情况
        if (remaining < 0 || remaining > wait) {
            if (timer) {
                clearTimeout(timer)
                timer = null
            }
            previous = now
            func.apply(context, args)
        } else if(!timer && trailing !== false) {
            timer = setTimeout(layer, remaining)
        }
    }
    
    throttled.cancel = function() {
        clearTimeout(timer)
        timer = null
        previous = 0
    }

    return throttled
    
}