函数节流与函数防抖
函数节流和函数防抖是一个老生常谈的话题了,两者都是对大量频繁重复调用代码的一种优化方案
今天在某群和大家讨论时,顺便搜了一些相关博客
发现有一篇关于两者的定义竟然写反了。。。所以决定自己来写一下-.-权当加深记忆了
函数节流(throttle)
正如其命名的含义,节流。
限制函数在一定时间内调用的次数。
类似的实际生活中的场景
早晚高峰的地铁排队。
太多的人拥挤到站台上,大家都想搭上这班车,人挤人之间,难免会出现一些问题。
所以在很多地铁站,高峰期会设置很多层的屏障,来增加你进站的时间,从而减少站台的压力。
在程序中的实践
同理,代入程序中,我们可以通过限制函数调用的频率,来抑制资源的消耗。
比如我们要实现一个元素拖拽的效果,我们是可以在每次move
事件中都进行重绘DOM,但是这样做,程序的开销是非常大的。
所以在这里我们可以用到函数节流
的方法,来减少重绘的次数:
// 普通方案
$dragable.addEventListener('mousemove', () => {
console.log('trigger')
})
// 函数节流的实现方案
let throttleIdentify = 0
$dragable.addEventListener('mousemove', () => {
if (throttleIdentify) return
throttleIdentify = setTimeout(() => throttleIdentify = 0, 500)
console.log('trigger')
})
这样来做的话,在拖动的过程中,能保证500ms内,只会重绘一次DOM。
在同时监听了mouseover
后,两者最终的效果是一致的,但是在拖动的过程中,函数节流
版触发事件的次数会减少很多,遂消耗更少的资源。
通用的函数节流实现
/**
* 函数节流的实现
* @param {Function} func 要实现函数节流的原函数
* @param {Number} interval 节流的间隔
* @return {Function} 添加节流功能的函数
*/
function throttle (func, interval) {
let identify = 0
return (...args) => {
if (identify) return
identify = setTimeout(() => identify = 0, interval)
func.apply(this, args)
}
}
类似函数节流的操作
平时开发中经常会做的ajax
请求获取数据,这里可以用到类似函数节流的操作。
在我们发送一个请求到后台时,当返回的数据还没有接收到时,我们会添加一个标识,来表明当前有一个请求正在被处理,如果这时用户再触发ajax
请求,则会直接跳过本次函数的执行。
同样的还有滑动加载更多数据,如果不添加类似的限制,可能会导致发送多条请求,渲染重复数据。
我曾经在某软件里遇到过-.-连续点击登录按钮数十次,结果连着给我弹了三次密码错误,随后告诉我输入密码错误超过三次,您的账号已被锁定。
函数节流的定义:限制函数在一定时间内调用的次数
函数防抖(debounce)
是函数在特定的时间内不被再调用后执行。
实际的例子
还是拿城市交通工具来说事儿。。
坐公交时,到站了,是由司机来操作车门的开合的。
当司机准备离站时,关闭车门,这是突然跑过来一人,司机只好再将车门打开,让人上来。
又或者,乘坐升降电梯时,电梯门关闭后,外边跑来一人又将电梯门按开。
这两件事儿都是因为关门
这一个事件处理太快导致的,徒增一次开关门的消耗。
在程序中的实践
监听窗口大小重绘的操作。
在用户拖拽窗口时,一直在改变窗口的大小,如果我们在resize
事件中进行一些操作,消耗将是巨大的。
而且大多数可能是无意义的执行,因为用户还处于拖拽的过程中。
所以我们可以用函数防抖
来优化相关的处理
// 普通方案
window.addEventListener('resize', () => {
console.log('trigger')
})
// 函数防抖方案
let debounceIdentify = 0
window.addEventListener('resize', () => {
debounceIdentify && clearTimeout(debounceIdentify)
debounceIdentify = setTimeout(() => {
console.log('trigger')
}, 300)
})
我们在resize
事件中添加了一个300ms的延迟执行逻辑。
并且在每次事件触发时,都会重新计时,这样做也就可以保证,函数的执行肯定是在距离上次resize
事件被触发的300ms后。
两次resize
事件间隔小于300ms的都会被忽略,这样就节省了很多无意义的事件触发。
输入框的输入联想
几乎所有的搜索引擎都会对你输入的文字进行预判,并在下方推荐相关的结果。
但是这个联想意味着我们需要将当前用户所输入的文本传递到后端,并获取返回数据,展示在页面中。
如果遇到打字速度快的人,比如260字母/分钟的我,在一小段时间内,会连续发送大量的ajax
请求到后端。
并且当前边的数据返回过来后,其实已经失去了展示的意义,因为用户可能从you
输入到了young
,这两个单词相关的结果肯定是不一样的。
所以我们就可以在监听用户输入的事件那里做函数防抖的处理,在XXX秒后发送联想搜索的ajax
请求。
通用的函数防抖实现
/**
* 函数防抖的实现
* @param {Function} func 要实现函数节流的原函数
* @param {Number} delay 结束的延迟时间
* @return {Function} 添加节流功能的函数
*/
function debounce (func, delay) {
let debounceIdentify = 0
return (...args) => {
debounceIdentify && clearTimeout(debounceIdentify)
debounceIdentify = setTimeout(() => {
debounceIdentify = 0
func.apply(this, args)
}, delay)
}
}
类似函数防抖的操作
在一些与用户的交互上,比如提交表单后,一般都会显示一个loading框来提示用户,他提交的表单正在处理中。
但是发送表单请求后就显示loading是一件很不友好的事情,因为请求可能在几十毫秒内就会得到响应。
这样在用户看来就是页面中闪过一团黑色,所以可以在提交表单后添加一个延迟函数,在XXX秒后再显示loading框。
这样在快速响应的场景下,用户是不会看到一闪而过的loading框,当然,一定要记得在接收到数据后去clearTimeout
let identify = setTimeout(showLoadingModal, 500)
fetch('XXX').then(res => {
// doing something
// clear timer
clearTimeout(identify)
})
函数防抖的定义:函数在特定的时间内不被再调用后执行
总结
函数节流、函数防抖
两者都是用来解决代码短时间内大量重复调用的方案。
当然,也是各有利弊。
在实际开发中,两者的界定也很模糊。
比如搜索关键字联想,用节流或者防抖都可以来做,拖拽DOM、监听resize等等,这两个都是可以来实现的。
两者的区别在于:
函数节流在一定时间内肯定会触发多次,但是最后不一定会触发
函数防抖可能仅在最后触发一次
记住上边这两句,我觉得遇到类似需要进行优化的场景,应该就能够知道该用哪个了。