防抖(debounce)和节流(throttle)是网页开发中两种比较常见的控制函数调用频率的方法。防抖比较适合搜索框搜索的场景;节流比较适合控制按钮点击频率的场景。
下面我们将来分别实现这两个函数。
实现防抖函数
防抖函数的实现分 4 步。
首先,防抖函数本质上是对原函数的封装,因此我们二话不说先返回一个函数,我们叫它 debouncedFn
:
function debounce(fn, delay = 220) {
return function debouncedFn() {
// TODO
}
}
-
fn
是要做防抖处理的函数 -
delay
表示调用延迟
我们先实现防抖函数的首次触发——触发后,等待 delay
ms 后调用 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
进行调用
解决了首次的触发调用实现后,再解决后续的触发调用问题。
先要明白原理:在首次触发后的 delay
ms 内,如果再次触发,就要重新等待 delay
ms 才会调用函数 fn
——也就是说会清除前一次的定时器,重新启动一个定时器。
根据这个思路,我们需要一个变量保存上一次的定时器 ID,我们暂且叫它 timer
:
function debounce(fn, delay = 220) {
let timer = null
return function debouncedFn(...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
这样,在每次触发 debounceFn
的 delay
ms 内,如果再次触发,就会清空上一次的定时器,重新启动一个定时器,过了 delay
ms 后调用原始函数 fn
。
当然,这种写法同样适应于 delay
ms 后 fn
被调用后,再次触发 debounceFn
的场景——fn
被调用后,再次触发时,clearTimeout(timer)
实际上不起任何作用(因为定时器回调已被执行),随后就是新创建了一个定时器,效果等同于调用 fn
的 delay
ms 内的再次触发。
到此为止,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 会在 delay
ms 后,被清除(设置为 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
(!timer
为 true
),就表示当前区间还没有调用函数 fn
;如果 timer
有值,就表示当前区间内已经调用过一次 fn
了,要忽略后续触发。
到此为止,debounce
函数就实现完毕了。
原文始发于微信公众号(写代码的宝哥):如何使用 JS 实现防抖和节流函数?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243986.html