不是吧,我的节流函数似乎不太对?
节流函数作为前端最基础的工具函数,大家肯定都倒写入流了,但是它是不是有什么坑在里面呢?这篇文章带大家缕一缕!
写一个节流函数
替各位大佬花五分钟写个节流函数,如下:
ts
/**
* 节流函数
* @param fn 原函数
* @param delay 延迟间隔
* @param immediate 是否立即执行
* @returns 节流函数
*/
export const _throttle = (
fn: Function,
delay: number = 500,
immediate: boolean = true,
): Function => {
let flag = true
return function (this: any, ...rest: any[]) {
if (flag) {
if (immediate) fn.apply(this, rest)
setTimeout(() => {
if (!immediate) fn.apply(this, rest)
flag = true
}, delay)
flag = false
}
}
}
- 节流函数返回一个方法,执行时使用计时器节流。
- 添加了一个
immediate
标志位,如果为true
则立即执行再计时,否则即使结束再执行。
这能有啥问题?
节流函数的使用场景
提问题之前,我们想回想下节流函数的使用场景。我们通常会在某些触发频率无法控制的浏览器事件下使用节流,避免短时间多次触发回调导致性能问题,也避免不同浏览器内核造成的频率不同产生的影响。常见事件比如:mousemove、resize、scroll等等。
和这事件又有啥关系呢?
这些场景下我们事件的回调都需要获取事件对象,并且使用事件对象上的某个属性来操作dom。
举个🌰,我们通过监听window的resize事件,来做某些图表大小的调整,这时假设我们设置了节流函数延时200ms,在这200ms的开始,触发了第一次事件我们的节流函数启动了定时器,并且将现在的回调放到了计时器中,等待200ms后执行,其他这200ms内的所有回调触发都会被我们略过。如果这200ms内,窗口大小变化非常大,并且在最后没有超出这200ms,那最后我们触发的回调也只会是第一次的。这就造成了图表大小不对,样式错位等等等问题。用户操作结束事件可以在这第一个200ms内,也可以在其他区间内,这就是个不可控因素。
当然你可以说,正常谁会这么拉窗口大小,这是个伪命题。这只是个🌰,其他情况可以自己想象。可能延时区间够小,他的问题就越看不出来,但是总有些时候有些特别的场景,让人头疼。
怎么解决?
其实知道问题解决也非常简单,首先把节流立即执行这个操作就很有问题,因为这样总是忽略了区间最后的参数。然后既然我们需要的是最后一个事件的参数,那我们搞个闭包把参数存起来就得了,最后计时器计时结束,我们执行使用存储的参数就搞定了。
ts
/**
* 节流函数
* @param fn 原函数
* @param delay 延迟间隔
* @returns 节流函数
*/
export const _throttle = (
fn: Function,
delay: number = 500,
): Function => {
let flag = true
let temp: any[]
return function (this: any, ...rest: any[]) {
temp = rest
if (flag) {
setTimeout(() => {
fn.apply(this, temp)
flag = true
}, delay)
flag = false
}
}
}