前端概念 | 防抖(Debounce)

306 阅读4分钟

概念理解

打工的阿飞

为了更形象地解释防抖(Debounce)这一概念, 可以想象我们的一位朋友阿飞

阿飞到餐厅打工当服务生, 帮助顾客点单是他的主要工作

在顾客点单结束时, 阿飞会询问顾客, "请问还需要其它菜品吗?", 只有当顾客确认不再需要其它的菜品, 阿飞才会把顾客的点单传达到后厨

相当正常的流程, 不是吗? 但阿飞这样的点单流程正是应用了防抖后的结果

那没有应用防抖的点单流程是什么样的呢? 那大家就有的忙活了:

顾客每点一道菜, 阿飞就需要把这一信息传达到后厨(后厨大概率会被点单给淹没吧); 而且如果在阿飞向后厨传递信息的路上, 顾客又点了一道菜, 这时候就需要其它服务生来向后厨传递这一信息

那要是点菜的顾客是一位相声演员, 心血来潮来了一段"报菜名", 那就算是十个阿飞也顶不住啊...

此外, 这还是在点的每一道菜都是顾客确实需要的情况下. 要是顾客点着点着, 觉得菜品点得太多, 决定最先点的几道菜不要了, 那可真就是乱了套了

流程比较

通过比较两种点单流程我们可以发现, 二者最大的不同在于: 有没有确认当前点单就是用户的最终点单, 因为只有最终点单是应该传递给后厨的

类比到前端开发中, 例如我们需要根据用户对浏览器窗口大小的改变, 即 resize 事件, 进行相关处理, 这时需要的往往是用户最终的改变结果, 而改变过程中对 resize 事件的触发往往是不需要处理的

一种实现

既然防抖处理可以有效地降低高频触发任务的负荷, 那么我们可以如何实现它呢?

我认为, 关键点就在于添加对事件本次触发为最终触发的确认, 就是阿飞对点单顾客说的那句"请问还需要其它菜品吗?"

而在程序设计中, 对该确认的一种实现思路是: 在事件触发后添加一段等待时间, 若在等待时间内事件没有再触发, 则确认本次触发为最终触发, 执行相应的处理函数; 若在等待时间内该事件再次触发, 则刷新等待时间, 重新判断

这种实现思路就好比顾客不再点单后, 阿飞不会立即去后厨, 而是先等一会, 如果等了一会之后顾客也没有再点单了, 阿飞才把订单送到后厨

/**
 * 为函数设置防抖
 *
 * @param {Function} func 将要添加防抖的函数
 * @param {number} delay 等待时间
 * @return {Function} 添加防抖后的函数
 */
const debounce = (func, delay) => {
    let inDebounce;

    return function(...args) {
        const context = this;

        // 若在等待时间内函数再次被调用, 即对应事件发生变化, 则刷新重新判断
        clearTimeout(inDebounce);
        inDebounce = setTimeout(
            () => Reflect.apply(func, context, args),
            delay
        );
    }
}

测试

我们可以基于 onscroll 来粗略展示一下防抖的效果

  • HTML

    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
        <meta charset="UTF-8">
        <title>debounce</title>
    </head>
    
    <body>
        <div id="demo" style="height: 1000px"></div>
        <script src="./debounce.js"></script>
    </body>
    
    </html>
    
  • JavaScript

    /**
    * 为函数设置防抖
    *
    * @param {Function} func 将要添加防抖的函数
    * @param {number} delay 等待时间
    * @return {Function} 添加防抖后的函数
    */
    const debounce = (func, delay) => {
        let inDebounce;
    
        return function (...args) {
            const context = this;
    
            // 若在等待时间内函数再次被调用, 即对应事件发生变化, 则刷新重新判断
            clearTimeout(inDebounce);
            inDebounce = setTimeout(
                () => Reflect.apply(func, context, args),
                delay
            );
        }
    }
    
    const demo = document.getElementById("demo");
    function toDebounce() {
        demo.innerHTML += ("上下滑动对应回调函数被执行了<br>");
    }
    
    // window.onscroll = toDebounce;
    window.onscroll = debounce(toDebounce, 1000);
    
  • 添加防抖前

    防抖-原始.gif

  • 添加防抖后

    防抖.gif

从上述两张 Gif 图中, 我们可以看出: 在添加防抖之前, onscroll 对应的函数将会随着页面的上下滑动被执行多次; 而在添加防抖之后, 只有当上下滑动停止, 并经过等待时间也未再次触发后, 对应的函数才会被执行

总结

防抖可以应用于优化高频次触发的事件, 除了上文中提到的改变浏览器窗口大小 resize 和上下滑动浏览器窗口 scroll, 还有自动保存等应用

以上是我对防抖(Debounce)的理解, 如果文章中存在问题或是大家有更好的理解, 欢迎大家指出和交流:)