昨天刷抖音时,刷到渡一袁进老师讲解关于防抖一步一步的实现过程,不懂防抖的来学学,懂防抖的也来看看自己的理解是否正确,下面就复盘一下老师的思路。
作为前端开发,如果不会函数防抖,都不敢出门跟人打招呼,老师说的哈哈哈
场景
举个例子,在一个瀑布流布局中,我们需要根据窗口尺寸的改变而动态适配布局,这时候我们可能会这样写:
// 适配窗口布局
function layout() {
//...
}
window.onresize = layout
监听窗口的 onresize 事件,在窗口尺寸发生变化时,重新进行布局,这样是能够实现效果,但是发现随着窗口尺寸频繁的变化,页面重新布局时会有些卡顿,造成效率问题,这是因为 onresize 事件会监听窗口每时每刻的变化,实际上我们只想看到拖动完窗口最后一次的布局效果,这时就满足了防抖的使用条件。
使用条件
要用上函数防抖,需要满足3个条件:
- 频繁的调用某个函数
- 会造成效率问题
- 需要的结果以最后一次为准
❗注意:如果不满足这3个条件,就无需使用函数防抖,如果防抖的等待时间过长,会降低用户操作的实时性和流畅性,影响用户的操作感受,从而降低用户体验,所以使用它是有代价的,需要进行适当的权衡和调整,保证用户体验和性能的平衡。
防抖就像是电梯关门,当一个人进来后,门不会立马关上,比如说电梯会等2秒,如果期间又有人进来了,就重新开始计时等待,直到2秒内没人来后才真正关上门。如果把关门想象成函数,进来了3个人,结果只调用一次,以最后一次为准。
实现
下面我们来实现一下防抖的效果:
let timer
window.onresize = () => {
clearTimeout(timer)
timer = setTimeout(() => {
layout()
}, 500)
}
现在就实现了所需要的防抖效果,在调用函数时都会清空上一个定时器,并设置500毫秒的等待时长。但是这样写并不通用,万一我其他地方想用到呢?我们将防抖逻辑提取出来:
function debounce(func, duration = 500) {
let timer
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
func()
}, duration)
}
}
window.onresize = debounce(layout, 1000)
这样就初步提取了防抖的整体逻辑,传入需要执行的函数与等待时长(默认值500毫秒),但是又会发现万一传入的函数有参数呢?目前的实现就无法接受参数,我们再来改造一下:
function debounce(func, duration = 500) {
let timer
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, duration)
}
}
window.onresize = debounce(layout, 1000)
在返回函数中,利用剩余参数来获取传入的实参,同时将参数传入方法以及绑定 this,这时就能够取到 onresize 事件的默认参数 event。
需要格外注意的是函数中的 this 指向,不能使用箭头函数作为返回函数,因为箭头函数本身没有 this ,会绑定上一个不是箭头函数作用域内的 this 从而指向 window 而不是调用者本身。
总结
通过适用的场景来了解防抖,一步一步实现防抖函数,当然这里只是初步实现了最简单的版本,用意在于让大家了解整个防抖实现的思路,完全不需要背下来,只要一步一步的推导,如果还需要背就证明没有完全理解。
最后,如果想看防抖函数的具体实现,可以参考 lodash 库 debounce 的实现。