当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
根据定义我们实现第一版节流函数。
function throttle(func, wait) {
let timer = null
let context = this
let args = arguments
const throttled = function () {
if (!timer) {
timer = setTimeout(function() {
clearTimeout(timer)
timer = null
func.apply(context, args)
}, wait)
}
}
return throttled
}
在实际场景中, 我们更希望初次触发事件时能够立即执行,停止触发时还能再执行一次。基于此我们得到下面的代码:
function throttle(func, wait) {
let previous = 0
let timer = null
let context = this
let args = arguments
let layer = function () {
clearTimeout(timer)
timer = null
previous = +new Date()
func.apply(context, args)
}
var throttled = function () {
let now = +new Date()
// 下次调用func剩下的时间
let remaining = wait - (now - previous)
// 第一次调用时previous为0, remaining肯定小于0,于是直接执行函数
// remaining > wait 修改了系统时间的情况
if (remaining < 0 || remaining > wait) {
if (timer) {
clearTimeout(timer)
timer = null
}
previous = now
func.apply(context, args)
} else if (!timer) {
timer = setTimeout(layer, remaining)
}
}
return throttled
}
更进一步, 我们希望可以选择是否立即执行,在结束事件时是否要执行事件,并且我们希望可以取消节流事件。 这里我们设置options为第三个参数:
- leading:false 表示禁用第一次执行
- trailing: false 表示禁用停止触发的回调
function throttle(func, wait, options = {}) {
let previous = 0
let timer = null
let context = this
let args = arguments
let layer = function () {
clearTimeout(timer)
timer = null
// a. false 时设置为0 是为了下一次调用时, 备注b那里可以重新赋值,不然会导致计算remaining时,因为(now - previous) 值太过大使remaining变为负数,导致错误调用立即执行
previous = options.leading === false ? 0 : +new Date()
func.apply(context, args)
}
var throttled = function () {
let { leading, trailing } = options
// b. 备注a那里使previous为0 ,方便可以在这里重新赋值
if (!previous && leading === false) previous = +new Date()
let now = +new Date()
// 下次调用func剩下的时间
let remaining = wait - (now - previous)
// leading为true时,第一次调用时previous为0, remaining肯定小于0,于是直接执行函数
// remaining > wait 修改了系统时间的情况
if (remaining < 0 || remaining > wait) {
if (timer) {
clearTimeout(timer)
timer = null
}
previous = now
func.apply(context, args)
} else if(!timer && trailing !== false) {
timer = setTimeout(layer, remaining)
}
}
throttled.cancel = function() {
clearTimeout(timer)
timer = null
previous = 0
}
return throttled
}