函数去抖与函数节流

函数去抖与函数节流

在这里插入图片描述
笔者:林大
有任何疑问欢迎关注微信公众号:网易游戏运维平台。(长按识别上图二维码)
微信公众号原文链接:函数去抖与函数节流

摘要

本文介绍两种用于控制前端 UI 事件触发频率的手段:函数去抖(Debounce)和函数节流(Throttle)。

背景

函数去抖(Debounce)和函数节流(Throttle)是限制一个函数的触发频率的两种手段,它的使用场景通常是在 Web 前端用于控制 DOM 事件和向后台发起请求的频率,以到达前端性能优化的目的。

前端时常会有高频率 UI 事件触发的场景,如鼠标的 scroll 事件:

请在这里输入图片描述

图中的数字是 scroll 事件的触发次数,可以看到短短几秒内 scroll 事件就可以触发将近一百次,如果把类似「下拉加载更多」这种操作直接不加限制绑定到 scroll 事件上的话,性能将会损耗巨大,导致用户不可忍受的响应速度。

再看另一个具体的例子,以下是在网易 SRE 部门专用的移动运维 app —— SABox 中查询某个 SRE 的联系信息,每当输入框的内容发生改变时,就会触发 onchage 事件,改变人员信息列表的渲染内容,同时在旁边打印出那个时刻的输入框内容。可以看到在 ios 模拟器中列表的渲染已经有点延迟,如果在配置较低的安卓机,响应速度将会更慢。

请在这里输入图片描述

因此,限制 UI 事件的触发频率是非常有必要的,而函数去抖和函数节流这两个手段的原理也非常简单。

函数去抖(Debounce)

简单的一句话描述 Debounce 就是「将一段时间内若干次函数调用聚合成一次」。举一个比较不恰当的例子:坐电梯的时候,把「电梯启动」看作函数真正执行,把「有人进入电梯」看作函数调用,电梯会在等待一段时间之后就会关门启动,当电梯门准备关上时,有人进来了,然后电梯又需要等待固定的一段时间,直到这段时间内没有其他人再进入电梯,最终才启动。

Debounce的定义 :当执行函数一段时间之后,才会允许下一次执行,若在这段时间内又调用此函数则将重新计时。

请在这里输入图片描述

上图演示了 Debounce 的过程:不断点击长方形,并不会让函数真正执行,直到短暂的中止点击之后,函数被真正执行,这就是「将一段时间内若干次函数调用聚合成一次」。

下面是 Debounce 的简单实现:

var debounce = function(fn, delay) {
    var timer = null;
    return function() {
        var context = this, args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function() {
            fn.apply(context, args);
        }, delay);
    };
};

window.onresize = debounce(myFunc, 100);

核心是定时器。当事件触发时,利用 setTimout 让其延迟执行,如果在这个延迟的时间间隔内又触发了事件,则重新计时(clearTimeout)。

Debounce 的应用场景非常多:输入框的各种事件(表单校验、ajax请求)、拖动浏览器窗口大小时的 resize 事件等,下面是一个窗口 resize 的例子:

请在这里输入图片描述

调用的函数是把当前窗口的尺寸打印出来,左边的每次 resize 都触发了,右边的只有在松开鼠标(停止 resize)之后才触发,节省了大量的函数调用。

再用回 SABox 人员列表的例子,只有在输入的间隔大于一定时间,才触发列表的渲染事件:

请在这里输入图片描述

Debounce 还能扩展出更多的特性,例如 leading 标志,设定 leading = true 可以让事件的触发马上发生,而不是等到聚合结束:

请在这里输入图片描述

函数节流(Throttle)

Throttle 更接近「限制频率」的思路,它预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。换句话说,如果我规定执行周期是1秒,则这1秒内最多只能调用一次这个函数,要想再调用必须等到下1秒。

在下拉加载、拖拽等场景中,Debounce 并不适用,因为如果这些动作用户一直持续在做,就永远等不到事件真正触发的那个时刻,在这种情况下,我们需要用 Throttle ,保证在一个周期内,事件被执行一次。

请在这里输入图片描述

上图演示了 Throttle 的过程,在往下滚动页面时,限制某个周期内计算一次与页面底端的距离,一旦小于某个值就触发「加载更多」。如果这里使用了 Debounce,则必须等到滚动结束才会触发「加载更多」,换言之一直滚动就触发不了。

下面是 Throttle 的简单实现:

var throttle = function(fn, delay) {
    var last = 0;
    return function() {
        var curr = new Date();
        if (curr - last > delay) {
            fn.apply(this, arguments);
            last = curr;
        }
    }
}

window.onresize = throttle(myFunc, 100);

核心原理是每次事件触发时都比对与上次事件触发的时间间隔,如果大于所需的时间间隔,则允许触发,然后记录下新的触发时间,用于下一次比对。

Lodash 中的 Debounce 和 Throttle

Lodash 是一个非常实用的 JavaScript 工具库,它里面已经集成了 Debounce 和 Throttle 两种功能,功能完善。

/**
 * @param {Function} func 待调用的函数
 * @param {number} [wait=0] 时间间隔(毫秒)
 * @param {Object} [options={}]
 * @param {boolean} [options.leading=false]
 *  leading 标志:是否在一开始就触发
 * @param {number} [options.maxWait]
 *  最大等待时间(毫秒)
 * @param {boolean} [options.trailing=true]
 *  trailing 标志:是否在结束后触发
 * @returns {Function} 返回经过 Debounce 处理的函数
 */
_.debounce(func, [wait=0], [options={}])

/**
 * @param {Function} func 待调用的函数
 * @param {number} [wait=0] 时间间隔(毫秒)
 * @param {Object} [options={}]
 * @param {boolean} [options.leading=true]
 *  leading 标志:是否在一开始触发
 * @param {boolean} [options.trailing=true]
 *  trailing 标志:是否在结束后触发
 * @returns {Function} 返回经过 Throttle 处理的函数
 */
_.throttle(func, [wait=0], [options={}])

lodash throttle 的源码里很有趣的是:实际上它返回了一个传入了 options.maxWait = wait 的 debounce 函数。短短的源码不超过20行:

function throttle(func, wait, options) {
  let leading = true
  let trailing = true

  if (typeof func != 'function') {
    throw new TypeError('Expected a function')
  }
  if (isObject(options)) {
    leading = 'leading' in options ? !!options.leading : leading
    trailing = 'trailing' in options ? !!options.trailing : trailing
  }
  return debounce(func, wait, {
    'leading': leading,
    'maxWait': wait,
    'trailing': trailing
  })
}

export default throttle

总结

Debounce 和 Throttle 都是限制函数调用次数的手段,本文仅介绍了它们在 Web 前端的 UI 性能优化中的应用;Debounce 适用于输入事件、窗口 resize 等频繁进行但穿插较长间隔的场景, Throttle 适用于拖拽、下拉加载更多等必须在一个周期里要执行一次的场景。但 Debounce 和 Throttle 的应用不仅仅局限于前端 和 JavaScript,其他语言、服务器端在需要限制执行频率的时候,也可借鉴其思想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值