引言
上一节我们学习了 Lodash 中防抖和节流函数是如何实现的,并对源码浅析一二,今天这篇文章会通过七个小例子为切入点,换种方式继续解读源码。其中源码解析上篇文章已经非常详细介绍了,这里就不再重复,建议本文配合上文一起服用,猛戳这里学习 https://github.com/yygmind/blog/issues/41(点击「
有什么想法或者意见都可以在评论区留言,欢迎大家拍砖。
节流函数 Throttle
我们先来看一张图,这张图充分说明了 Throttle(节流)和 Debounce(防抖)的区别,以及在不同配置下产生的不同效果,其中 mousemove
事件每 50 ms 触发一次,即下图中的每一小隔是 50 ms。今天这篇文章就从下面这张图开始介绍。
![f244fa2088cd8669a749b4965320518f.png](https://img-blog.csdnimg.cn/img_convert/f244fa2088cd8669a749b4965320518f.png)
角度 1
lodash.throttle(fn, 200, {leading: true, trailing: true})
mousemove 第一次触发
先来看下 throttle 源码
function throttle(func, wait, options) {
// 首尾调用默认为 true
let leading = true
let trailing = true
if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
// options 是否是对象
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading
trailing = 'trailing' in options ? !!options.trailing : trailing
}
// maxWait 为 wait 的防抖函数
return debounce(func, wait, {
leading,
trailing,
'maxWait': wait,
})
}
所以 throttle(fn, 200, {leading: true, trailing: true})
返回内容是 debounce(fn, 200, {leading: true, trailing: true, maxWait: 200})
,多了 maxWait: 200
这部分。
先打个预防针,后面即将开始比较难的部分,看下 debounce 入口函数。
// 入口函数,返回此函数
function debounced(...args) {
// 获取当前时间
const time = Date.now()
// 判断此时是否应该执行 func 函数
const isInvoking = shouldInvoke(time)
// 赋值给闭包,用于其他函数调用
lastArgs = args
lastThis = this
lastCallTime = time
// 执行
if (isInvoking) {
// 无 timerId 的情况有两种:
// 1、首次调用
// 2、trailingEdge 执行过函数
if (timerId === undefined) {
return leadingEdge(lastCallTime)
}
// 如果设置了最大等待时间,则立即执行 func
// 1、开启定时器,到时间后触发 trailingEdge 这个函数。
// 2、执行 func,并返回结果
if (maxing) {
// 循环定时器中处理调用
timerId = startTimer(timerExpired, wait)
return invokeFunc(lastCallTime)
}
}
// 一种特殊情况,trailing 设置为 true 时,前一个 wait 的 trailingEdge 已经执行了函数
// 此时函数被调用时 shouldInvoke 返回 false,所以要开启定时器
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait)
}
// 不需要执行时,返回结果
return result
}
对于 debounce(fn, 200, {leading: true, trailing: true, maxWait: 200})
来说,会经历如下过程。
1、
shouldInvoke(time)
中,因为满足条件lastCallTime === undefined
,所以返回 true2、
lastCallTime = time
,所以lastCallTime
等于当前时间,假设为 03、
timerId === undefined
满足,执行leadingEdge(lastCallTime)
方法
// 执行连续事件刚开始的那次回调
function leadingEdge(time) {
// 1、设置上一次执行 func 的时间
lastInvokeTime = time
// 2、开启定时器,为了事件结束后的那次回调
timerId = startTimer(timerExpired, wait)
// 3、如果配置了 leading 执行传入函数 func
// leading 来源自 !!options.leading
return leading ? invokeFunc(time) : result
}
4、在
leadingEdge(time)
中,设置lastInvokeTime
为当前时间即 0,开启 200 毫秒定时器,执行invokeFunc(time)
并返回
// 执行 Func 函数
function invokeFunc(time) {
// 获取上一次执行 debounced 的参数