节流与防抖的概念
函数节流(throttle)
预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期
函数防抖(debounce)
当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间
函数节流(throttle)与 函数防抖(debounce)都是为了限制函数的执行频次,以优化函数触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象。
节流与防抖的应用
常见于如下状况:
- window对象的resize、scroll事件
- 拖拽时的mousemove事件
- 文字输入、自动完成的keyup事件
节流
scroll
事件在文档或文档元素滚动时触发,主要出现在用户拖动滚动条。
1
window.addEventListener('scroll', callback);
该事件会连续地大量触发,所以它的监听函数之中不应该有非常耗费计算的操作。推荐的做法是使用requestAnimationFrame
或setTimeout
控制该事件的触发频率,然后可以结合customEvent
抛出一个新事件。
1 | (function () { |
上面代码中,throttle
函数用于控制事件触发频率,requestAnimationFrame
方法保证每次页面重绘(每秒60次),只会触发一次scroll
事件的监听函数。也就是说,上面方法将scroll
事件的触发频率,限制在每秒60次。具体来说,就是scroll
事件只要频率低于每秒60次,就会触发optimizedScroll
事件,从而执行optimizedScroll
事件的监听函数。
改用setTimeout
方法,可以放置更大的时间间隔。
1 | (function() { |
上面代码中,每次scroll
事件都会执行scrollThrottler
函数。该函数里面有一个定时器setTimeout
,每66毫秒触发一次(每秒15次)真正执行的任务actualScrollHandler
。
下面是一个更一般的throttle
函数的写法,scroll
事件的触发频率,限制在一秒一次。
1 | function throttle(fn, wait) { |
防抖
有时,我们不希望回调函数被频繁调用。比如,用户填入网页输入框的内容,希望通过 Ajax 方法传回服务器,jQuery 的写法如下。
1
$('textarea').on('keydown', ajaxAction);
这样写有一个很大的缺点,就是如果用户连续击键,就会连续触发keydown
事件,造成大量的 Ajax 通信。这是不必要的,而且很可能产生性能问题。正确的做法应该是,设置一个门槛值,表示两次 Ajax 通信的最小间隔时间。如果在间隔时间内,发生新的keydown
事件,则不触发 Ajax 通信,并且重新开始计时。如果过了指定时间,没有发生新的keydown
事件,再将数据发送出去。
这种做法叫做debounce
(防抖动)。假定两次 Ajax 通信的间隔不得小于2500毫秒,上面的代码可以改写成下面这样。
1 | $('textarea').on('keydown', debounce(ajaxAction, 2500)); |
上面代码中,只要在2500毫秒之内,用户再次击键,就会取消上一次的定时器,然后再新建一个定时器。这样就保证了回调函数之间的调用间隔,至少是2500毫秒。
通过lodash使用节流函数和防抖函数
_.throttle(func, [wait=0], [options={}])
创建一个节流函数,在 wait 秒内最多执行 func 一次的函数。 该函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。 可以提供一个 options 对象决定如何调用 func 方法, options.leading 与|或 options.trailing 决定 wait 前后如何触发。 func 会传入最后一次传入的参数给这个函数。 随后调用的函数返回是最后一次 func 调用的结果。
参数
func (Function)
: 要节流的函数。[wait=0] (number)
: 需要节流的毫秒。[options={}] (Object)
: 选项对象。[options.leading=true] (boolean)
: 指定调用在节流开始前。[options.trailing=true] (boolean)
: 指定调用在节流结束后。
返回
(Function)
: 返回节流的函数。
例子
1
2
3
4
5
6
7
8
9// 避免在滚动时过分的更新定位
jQuery(window).on('scroll', _.throttle(updatePosition, 100));
// 点击后就调用 `renewToken`,但5分钟内超过1次。
var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
jQuery(element).on('click', throttled);
// 取消一个 trailing 的节流调用。
jQuery(window).on('popstate', throttled.cancel);
_.debounce(func, [wait=0], [options={}])
创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法。 debounced(防抖动)函数提供一个 cancel 方法取消延迟的函数调用以及 flush 方法立即调用。 可以提供一个 options(选项) 对象决定如何调用 func 方法,options.leading 与|或 options.trailing 决定延迟前后如何触发(愚人码头注:是 先调用后等待 还是 先等待后调用)。 func 调用时会传入最后一次提供给 debounced(防抖动)函数 的参数。 后续调用的 debounced(防抖动)函数返回是最后一次 func 调用的结果。
参数
func (Function)
: 要防抖动的函数。[wait=0] (number)
: 需要延迟的毫秒数。[options={}] (Object)
: 选项对象。[options.leading=false] (boolean)
: 指定在延迟开始前调用。[options.maxWait] (number)
: 设置 func 允许被延迟的最大值。[options.trailing=true] (boolean)
: 指定在延迟结束后调用。
返回
(Function)
: 返回新的 debounced(防抖动)函数
例子
1 | // 避免窗口在变动时出现昂贵的计算开销。 |