前言
说到防抖,相信做前端的没有谁不知道,甚至说到手写一个防抖函数,也基本是信手拈来。
js
function debounce(fn, delay) {
let timer;
return function (...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
使用场景
说到防抖,那还得提一嘴它的使用场景:
- 输入框监听用户输入,发送接口请求数据,是没必要没输入一个字符就请求一次的;
- 在处理窗口的 resize 事件时,也没必要没监听到变化就触发事件执行;
以上是经典的使用场景,当然,在我们需要限制触发的次数时,都可以考虑使用防抖函数。
遇到的问题
在我自己以前使用防抖的场景中,也基本是上述的两个场景。直接 addEventListener('resize', debounce(fn, 300)),一切都用的理所当然。
然而在最近修改一个问题时,确遇到了点麻烦。
我们系统接入了 qiankun 微前端框架,在频繁切换菜单的时候,会出现几个菜单疯狂的来回跳转,此时只有刷新页面或者等页面崩溃再刷新操作。
在解决这问题的时候,最开始也是准备从 qiankun 这个框架入手,想着是不是因为切换太频繁,导致子应用没有卸载成功,出现了与主应用抢占 URL 的现象。但是经历了各种尝试,发现子应用是卸载成功了的,也没定位到到底是哪儿一直在触发切换URL,或许这得去仔细研究下 qiankun 的源码才能知道了,因为不切换子应用菜单时,是没问题的。但是这个问题又比较紧急,给不了时间去研究,那只能看能不能另辟蹊径了。于是,防抖函数进入了我的脑海。
因为正常情况下,是没有用户会快速频繁切换多个菜单的(排除"找茬"的测试),或者也会有用户这样操作,但我相信这只是会极个别的。因此,在切换菜单时,增加一个防抖处理,是不是也能解决这个问题呢?
于是,我开始在菜单切换的地方开始加入防抖。一开始,我的代码是这样的:
html
... (click)="changeTab($event)"
js
function changeTab($event) {
debounce(jump, 100);
}
function jumpTab(tabId) {
xxx;
}
刚写完,发现不对啊,那个 jumpTab 是需要参数的,但是 changeTab 中没有传参数,jumpTab 是不知道跳到哪个菜单的。于是,我想当然的进行了修改:
js
function changeTab($event) {
debounce(jump($event), 100);
}
改完一执行,发现菜单是可以跳转的,但是那个防抖没用上,在快速切换后,菜单会沿着点击的顺序挨个跳转,还是不行。为啥会不行呢,我去 debounce 函数中打断点一看,每次执行 changeTab 事件时,都在重新执行一遍 debounce,而且这个地方传入的是 jump($event),本身就会执行一次,相当于是这个 debounce 根本就没用。
然后也试过这样写:
js
function changeTab($event) {
debounce(jump, 100)($event);
}
最后结果同上,并没有实现防抖功效。
原本以为自己对防抖函数也算是理解了,但没想到这换个地方用,就不对了。然后又看了一遍 debounce 函数的实现,它是利用了闭包的特质,debounce 返回一个函数,后续的调用实际上都是调用的返回的这个函数,而不是我这种每次都在重新调用 debounce 函数。
那么思路就有了,再定义一个函数,将 debounce 函数执行并返回,在 changeTab 中执行这个新定义的函数。
js
function debounceFn = () => debounce(jump, 100)
function changeTab($event) {
debounceFn($event);
}
这样就能保证每次 changeTab 都是执行的同一个函数。
后续
当然这个问题的解决方法也算是一种取巧,还是得看下为啥会出现这个问题的根因。另一方面,通过这个问题,也加深了自己对防抖函数的认知。