Open yygmind opened 5 years ago
1.如果 leading 和 trailing 选项都是 true,在 wait 期间只调用了一次 debounced 函数时,总共会调用几次 func,1 次还是 2 次,为什么?
如果leading和trailing同时为true,总共会调用方法一次,第一次leadingEdge中调用之后,会把lastArgs设置为undefined,所以尽管有定时器,运行了trailingEdge方法,但由于不满足条件,方法不会再次被调用;
2.如何给 debounce(func, time, options) 中的 func 传参数? let debounceFun = debounce(func, time,options); debounceFun(参数); 第二道题不知道理解有没有错,欢迎交流
引言
前面几节我们学习了节流函数 throttle,防抖函数 debounce,以及各自如何在 React 项目中进行应用,今天这篇文章主要聊聊 Lodash 中防抖和节流函数是如何实现的,并对源码浅析一二。下篇文章会举几个小例子为切入点,换种方式继续解读源码,敬请期待。
有什么想法或者意见都可以在评论区留言,欢迎大家拍砖。
防抖函数 debounce
Lodash 中节流函数比较简单,直接调用防抖函数,传入一些配置就摇身一变成了节流函数,所以我们先来看看其中防抖函数是如何实现的,弄懂了防抖,那节流自然就容易理解了。
防抖函数的定义和自定义实现我就不再介绍了,之前专门写过一篇文章,戳这里学习
进入正文,我们看下 debounce 源码,源码不多,总共 100 多行,为了方便理解就先列出代码结构,然后再从入口函数着手一个一个的介绍。
代码结构
debounce(func, wait, options)
方法提供了 3 个参数,第一个是我们想要执行的函数,为方便理解文中统一称为传入函数func
,第二个是超时时间 wait,第三个是可选参数,分别是leading
、trailing
和maxWait
。入口函数
debounce
函数最终返回了debounced
,返回的这个函数就是入口函数了,事件每次触发后都会执行debounced
函数,而且会频繁的执行,所以在这个方法里需要「判断是否应该执行传入函数 func」,然后根据条件开启定时器,debounced
函数做的就是这件事。开闭定时器
入口函数中多次使用了
startTimer
、timerExpired
这些方法,都是和定时器以及时间计算相关的,除了这两个方法外还有cancelTimer
和remainingWait
。startTimer
这个就是开启定时器了,防抖和节流的核心还是使用定时器,当事件触发时,设置一个指定超时时间的定时器,并传入回调函数,此时的回调函数
pendingFunc
其实就是timerExpired
。这里区分两种情况,一种是使用requestAnimationFrame
,另一种是使用setTimeout
。cancelTimer
定时器有开启自然就需要关闭,关闭很简单,只要区分好 RAF 和非 RAF 时的情况即可,取消时传入时间 id。
timerExpired
startTimer
函数中传入的回调函数pendingFunc
其实就是定时器回调函数timerExpired
,表示定时结束后的操作。定时结束后无非两种情况,一种是执行传入函数 func,另一种就是不执行。对于第一种需要判断下是否需要执行传入函数 func,需要的时候执行最后一次回调。对于第二种计算剩余等待时间并重启定时器,保证下一次时延的末尾触发。
remainingWait
这里计算仍然需要等待的时间,使用的变量有点多,足足有 9 个,我们先看看各个变量的含义。
maxWait in options
maxWait - timeSinceLastInvoke
距上次执行 func 的剩余等待时间变量是真的多,没看明白建议再看一遍,当然核心是下面这部分,根据
maxing
判断具体应该返回的剩余等待时间。这部分比较核心,完整的代码注释如下。
执行传入函数
聊完定时器和时间相关的函数后,这部分源码解析已经进行了大半,接下来我们看一下执行传入函数 func 的逻辑,分为执行刚开始的那次回调
leadingEdge
,执行结束后的那次回调trailingEdge
,正常执行 func 函数invokeFunc
,以及判断是否应该执行 func 函数shouldInvoke
。leadingEdge
执行事件刚开始的那次回调,即事件刚触发就执行,不再等待 wait 时间之后,在这个方法里主要有三步。
lastInvokeTime
trailingEdge
这里就是执行事件结束后的回调了,这里做的事情很简单,就是执行 func 函数,以及清空参数。
invokeFunc
说了那么多次执行 func 函数,那么具体是如何执行的呢?真的很简单,就是
func.apply(thisArg, args)
,除此之外需要重置部分参数。shouldInvoke
在入口函数中执行
invokeFunc
时会先判断下是否应该执行,我们来详细看下具体逻辑,和remainingWait
中类似,变量有点多,先来回顾下这些变量。maxWait in options
我们来一步一步看下判断的核心代码,总共有 4 种逻辑。
会发现一共有 4 种情况返回 true,区分开看也比较理解。
lastCallTime === undefined
第一次调用时timeSinceLastCall >= wait
超过超时时间 wait,处理事件结束后的那次回调timeSinceLastCall < 0
当前时间 - 上次调用时间小于 0,即更改了系统时间maxing && timeSinceLastInvoke >= maxWait
超过最大等待时间对外 3 个方法
debounced
函数提供了 3 个方法,分别是cancel
、flush
和pending
,通过如下方式提供属性进行绑定。cancel
这个就是取消执行,取消主要做的就是清除定时器,然后清除必要的闭包变量,回归初始状态。
flush
这个是对外提供的立即执行方法,方便需要的时候调用。
result
结果trailingEdge
,执行完成后会清空定时器id,lastArgs
和lastThis
pending
获取当前状态,检查当前是否在计时中,存在定时器 id
timerId
意味着正在计时中。节流函数 throttle
节流函数的定义和自定义实现我就不再介绍了,之前专门写过一篇文章,戳这里学习
throttle
这部分源码比较简单,相比防抖来说只是触发条件不同,说白了就是
maxWait
为wait
的防抖函数。isObject()
上面使用了
isObject
判断是否是一个对象,原理就是typeof value
,如果是object
或者function
时返回 true。举几个小例子说明下
思考题
源码解析已经完成,那你真的理解了吗,留下几道思考题给大家,欢迎作答,答案会在下篇文章中给出。
leading
和trailing
选项都是 true,在wait
期间只调用了一次debounced
函数时,总共会调用几次func
,1 次还是 2 次,为什么?debounce(func, time, options)
中的func
传参数?参考
推荐阅读
❤️ 看完三件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙: