防抖和节流
最近经常遇到“防抖”和“节流”这两个概念。作为前端执行优化中比较常用的手法,今天,我就整理一下这两个概念,算作一个备忘。
先说是什么
防抖(Debounce)和节流(throttle)都是用来控制同一函数执行频率的技巧,避免极其频繁的调用引发的性能问题(惯用场景,后面再说),在技巧上两者有一点微妙的区别。
简单理解
防抖,在同一函数被频繁调用的情况下,根据同一函数两次被调用之间的时间间隔判断是否要执行该函数,当时间间隔大于我们预先的设定值,则认为是没有频繁调用,可以执行该函数,否则不予以执行。或者可以认为是,将第一次和最后一次之间的所有函数调用(过渡频繁)都集中在最后一次统一执行,其他调用执行屏蔽掉。
节流,在同一函数被频繁调用的情况下,限制函数周期性的被调用执行,即,每隔一定时间(预先设定)保证调用执行一次,周期内的其他调用执行被屏蔽掉。
举个例子
如果把电梯从 1 楼 到 20 楼认为是一次函数的执行(需要花费一定时间)每一次进来一个人被认为会触发一次函数调用,那在没有防抖和节流的情况下,应该是每次进来一个人都会触发一次函数调用,然后电梯带着这个人从 1 楼到 20楼,这显然是不高效的。
防抖,如果前后两个人进电梯时间间隔小于或等于 10 秒钟,那等一下后面那个人,依次类推,直到有一个人进电梯的时间比他前面的人晚了 10 秒以上,那么就不等后面那个人,电梯就带着此时里面所有的人上去(不考虑电梯容量),否则会一直等下去。
节流,在防抖的情况下考虑,如果不断有人进来,且时间间隔不大于 10 秒,那电梯就一直等着不上去,这样在某些情况下也是不行的。节流则保证,不管怎样,电梯每 8 秒上去一次,周期性的,不再等后面的人了。
再说实现
防抖和节流本质是控制函数执行的频率,所以,防抖和节流本身作为一个高阶函数,其参数之一一定有一个是待控制的函数fn,返回值也应该是一个函数,就是将待控制函数经过防抖/节流处理之后,再返回。这里用setTimeout来简单实现。
1 | /** |
1 | /** |
immediate 函数
immediate 是 debounce 的一个升级(精确)版本。和 debounce 的区别是,debounce 在 setTimeout 内执行函数,也就是两次函数触发时间间隔 delta 时间之后才执行,而 immediate 则认为是设置当前函数是可执行,然后,经过 delta 时间之后,该可执行有效,则立马执行。
1 | /** |
惯用场景
我们知道,JavaScript 是遵循事件驱动的,一些行为(事件)会触发一些响应(回调),这个被称为事件流,如果行为过快,就会频繁触发响应,如果,回调处理的时间超过了事件触发的间隔事件,因为 JavaScript 事件处理是单线程的,所以就会紊乱,如果涉及到 UI 交互的话,视觉感受就是持续卡顿。
因此,防抖和节流技术通常用在事件处理上,比如,resize、mousemove、click、keypress等可能频繁触发的事件上。
PS: 现代浏览器的帧速率通常为 60fps,就是说 16.7ms 为 1 帧,意味着给我们处理回调响应的时间为 16.7ms,如果超过这个时间过大,则表现为性能不佳,如果此时该回调事件又被触发了多次,都在等待回调,则浏览器就凌乱了,业务逻辑上也有可能出问题。所以,话说回来了,防抖和节流就是控制整个事件被触发的次数的。
PS2: 节流的requestAnimationFrame实现,参看这里