如何使用 JS 实现防抖和节流函数?

防抖(debounce)和节流(throttle)是网页开发中两种比较常见的控制函数调用频率的方法。防抖比较适合搜索框搜索的场景;节流比较适合控制按钮点击频率的场景。

下面我们将来分别实现这两个函数。

实现防抖函数

防抖函数的实现分 4 步。

首先,防抖函数本质上是对原函数的封装,因此我们二话不说先返回一个函数,我们叫它 debouncedFn

function debounce(fn, delay = 220{
    return function debouncedFn({
        // TODO
    }
}
  • fn 是要做防抖处理的函数
  • delay 表示调用延迟

我们先实现防抖函数的首次触发——触发后,等待 delayms 后调用 fn

function debounce(fn, delay = 220{
    return function debouncedFn(...args{
        setTimeout(() => {
            fn(...args)
        }, delay)
    }
}
  • 我们将用户调用的参数 args 传递给原始函数 fn

不过现在的实现有个缺点,就是调用 fn 时,会丢失上下文环境 this。这个问题可以通过 fn.apply() 方法解决:

function debounce(fn, delay = 220{
    return function debouncedFn(...args{
        setTimeout(() => {
            fn.apply(this, args)
        }, delay)
    }
}
  • 调用 fn 时,我们将当前上下文环境 this 作为函数 fn 的上下文,并传入参数 args 进行调用

解决了首次的触发调用实现后,再解决后续的触发调用问题。

先要明白原理:在首次触发后的 delayms 内,如果再次触发,就要重新等待 delayms 才会调用函数 fn——也就是说会清除前一次的定时器,重新启动一个定时器。

根据这个思路,我们需要一个变量保存上一次的定时器 ID,我们暂且叫它 timer

function debounce(fn, delay = 220{
    let timer = null

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

这样,在每次触发 debounceFndelayms 内,如果再次触发,就会清空上一次的定时器,重新启动一个定时器,过了 delayms 后调用原始函数 fn

当然,这种写法同样适应于 delayms 后 fn 被调用后,再次触发 debounceFn 的场景——fn 被调用后,再次触发时,clearTimeout(timer) 实际上不起任何作用(因为定时器回调已被执行),随后就是新创建了一个定时器,效果等同于调用 fndelayms 内的再次触发。

到此为止,debounce 函数就实现完毕了。

实现节流函数

节流函数的实现分 5 步。

首先,节流函数本质上是对原函数的封装,因此我们二话不说先返回一个函数,我们叫它 throttledFn

function throttle(fn, delay = 220{
    return function throttleFn({
        // TODO
    }
}
  • fn 是要做节流处理的函数
  • delay 表示节流区间(一个区间内无论触发多少次,只唯一调用一次 fn

我们先实现节流函数的首次触发——触发后,无需等待,直接调用 fn

function throttle(fn, delay = 220{
    return function throttleFn(...args{
        fn.apply(this, args)
    }
}

调用时,为了保留函数上下文,使用了 fn.apply() 方法调用 fn

当然,调用 fn 的同时,我们会同步启动一个定时器,是做什么用的呢?不卖关子了,其实就是「标记当前是否处在一个节流区间」

function throttle(fn, delay = 220{
    return function throttleFn(...args{
        fn.apply(this, args)
        setTimeout(() => {
            // TODO
        }, delay)
    }
}

我们引入一个变量 timer 用于存储定时器 ID,这个定时器 ID 会在 delayms 后,被清除(设置为 null)。由此,我们就可以用这个 timer,标记 fn 被调用后的处在的那个间隔区间。

function throttle(fn, delay = 220{
    let timer = null

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

在这个区间范围内,fn 只能被调用一次,这可以通过判断 timer 的有无来实现。

function throttle(fn, delay = 220{
    let timer = null

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

如果 timer 值是 null!timertrue),就表示当前区间还没有调用函数 fn;如果 timer 有值,就表示当前区间内已经调用过一次 fn 了,要忽略后续触发。

到此为止,debounce 函数就实现完毕了。


原文始发于微信公众号(写代码的宝哥):如何使用 JS 实现防抖和节流函数?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243986.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!