debounce 与 throttle 是开发中常用的高阶函数,作用都是为了防止函数被高频调用,换句话说就是,用来控制某个函数在一定时间内执行多少次。
使用场景
比如绑定响应鼠标移动、窗口大小调整、滚屏等事件时,绑定的函数触发的频率会很频繁。若稍处理函数微复杂,需要较多的运算执行时间和资源,往往会出现延迟,甚至导致假死或者卡顿感。为了优化性能,这时就很有必要使用 debounce 或 throttle 了。
debounce 与 throttle 区别
防抖 (debounce) :多次触发,只在最后一次触发时,执行目标函数。
节流(throttle):限制目标函数调用的频率,比如:1s内不能调用2次。
手写一个 throttle
实现方案有以下两种:
- 第一种是用时间戳来判断是否已到执行时间,记录上次执行的时间戳,然后每次触发事件执行回调,回调中判断当前时间戳距离上次执行时间戳的间隔是否已经达到时间差(Xms) ,如果是则执行,并更新上次执行的时间戳,如此循环。
- 第二种方法是使用定时器,比如当 scroll 事件刚触发时,打印一个 hello world,然后设置个 1000ms 的定时器,此后每次触发 scroll 事件触发回调,如果已经存在定时器,则回调不执行方法,直到定时器触发,handler 被清除,然后重新设置定时器。
这里我们采用第一种方案来实现,通过闭包保存一个 previous 变量,每次触发 throttle 函数时判断当前时间和 previous 的时间差,如果这段时间差小于等待时间,那就忽略本次事件触发。如果大于等待时间就把 previous 设置为当前时间并执行函数 fn。
我们来一步步实现,首先实现用闭包保存 previous 变量。
- const throttle = (fn, wait) => {
- // 上一次执行该函数的时间
- let previous = 0
- return function(...args) {
- console.log(previous)
- ...
- }
- }
执行 throttle 函数后会返回一个新的 function ,我们命名为 betterFn 。
- const betterFn = function(...args) {
- console.log(previous)
- ...
- }
betterFn 函数中可以获取到 previous 变量值也可以修改,在回调监听或事件触发时就会执行 betterFn ,即 betterFn(),所以在这个新函数内判断当前时间和 previous 的时间差即可。
- const betterFn = function(...args) {
- let now = +new Date();
- if (now - previous > wait) {
- previous = now
- // 执行 fn 函数
- fn.apply(this, args)
- }
- }
结合上面两段代码就实现了节流函数,所以完整的实现如下。
- // fn 是需要执行的函数
- // wait 是时间间隔
- const throttle = (fn, wait = 50) => {
- // 上一次执行 fn 的时间
- let previous = 0
- // 将 throttle 处理结果当作函数返回
- return function(...args) {
- // 获取当前时间,转换成时间戳,单位毫秒
- let now = +new Date()
- // 将当前时间和上一次执行函数的时间进行对比
- // 大于等待时间就把 previous 设置为当前时间并执行函数 fn
- if (now - previous > wait) {
- previous = now
- fn.apply(this, args)
- }
- }
- }
- // DEMO
- // 执行 throttle 函数返回新函数
- const betterFn = throttle(() => console.log('fn 函数执行了'), 1000)
- // 每 10 毫秒执行一次 betterFn 函数,但是只有时间差大于 1000 时才会执行 fn
- setInterval(betterFn, 10)