js防抖节流函数的实现与优化

预备知识

剩余参数、默认参数、定时器、箭头函数this指向、apply函数、Date对象…

防抖

持续触发事件,n秒内再次触发某事件,将重新计算时间才执行。因此,只有最后一次操作能被触发

function debounce(fn, delay = 500) {
    let timer = null

    return (...args) => {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, delay)
    }
}

节流

持续触发事件,n秒内再次触发某事件,将等待上一次事件处理函数完成后,才执行。可以把这个理解成游戏中的“技能冷却时间”。

思想碰撞

定时器实现

function throttle(fn, delay = 1500) {
    let timer = null

    return (...args) => {
        if (timer) {
            return
        }
        timer = setTimeout(() => {
            fn.apply(this, args)
            timer = null
        }, delay)
    }
}

存在的问题

第一次触发时,也将等待delay时间才能执行。与冷却时间的初衷不符合!

定时器优化(个人觉得最佳方法,文尾有解释)

使用first记录是否是第一次触发函数。如果是第一次触发,立即执行。如果不是,则将采用定时器。

function throttle(fn, delay = 1500) {
    let timer = null
    let first = true

    return (...args) => {
        if (timer) {
            return
        }
        if (first) {
            fn.apply(this.args)
            first = false
            return
        }

        timer = setTimeout(() => {
            fn.apply(this, args)
            timer = null
        }, delay)
    }
}
优化后解决的问题

若第一次触发,将立即执行,不会有任何延迟。

时间戳实现

function throttle(fn, delay = 1500) {
    let pre = +new Date()

    return (...args) => {
        const now = +new Date()
        if ((now - pre) >= delay) {
            fn.apply(this, args)
            pre = +new Date()
        }
    }
}

存在的问题

  1. 第一次触发时,也将等待delay时间才能执行。同样与冷却时间的初衷不符合!
  2. 最后一次触发时,若now - pre < delay将不会执行。
  3. 使用Date对象获取时间戳,计算时间差的办法,会有一个安全漏洞—更改本地时间会出错,因此不推荐!

时间戳实现优化(时间戳+定时器)

function throttle(fn, delay = 1500) {
    let timer = null // 定时器
    let pre = +new Date() // 计算开始时间

    return (...args) => {
        const now = +new Date() // 计算当前时间
        const remaining = now - pre // 计算剩余时间
        clearTimeout(timer) // 取消先前调用的setTimeout
        if(remaining > delay) {
            // 超过冷却时间,立即执行
            fn.apply(this, args)
            pre = +new Date() // 更新开始时间
        }else {
            // 未超过冷却时间,禁止执行。同时保证最后一次调用,仍然可以执行。
            timer = setTimeout(() => {
                fn.apply(this, args)
            }, delay)
        }
    }
}
优化后解决的问题

最后一次触发,若now - pre < delay也将会执行。

优化后仍然存在的问题
  1. 第一次触发,会有延迟,依旧需要等待delay时间差才能执行。
  2. 修改本地时间,任会出现错误。

总结

个人觉得使用定时器优化方案是最佳方案。优点如下:

  1. 第一次执行将立即执行,不会有任何延迟。
  2. 最后一次执行,也将会触发执行。
  3. 不存在更改本地时间的BUG。

网友们觉得呢?欢迎评论区讨论~

你可能感兴趣的:(前端,前端)