让面试官眼前一亮的防抖

4,106 阅读6分钟

防抖

前段时间发布了一篇关于手写防抖的文章,今天想来做一个提升,让各位全面理解防抖节流。

什么是防抖

我们写了一个简单页面,当鼠标经过容器,页面的count计数就会加一,并把这个count插入到页面中(innerHtml) 但是事实上,防抖是想把执行推到最后一次,把之前的触发全部取消,他的目的是为前端做性能的优化。在这个案例中,防抖就大可不必了,因为在这个案例中,事实上就是做了一个加1操作和dom更新,而这样的操作耗时是远远小于屏幕更新时间的。倘若我们的项目变得复杂了起来,是一些耗时的任务,当我们在这种场景下还不做防抖,不断的去触发,那么此时耗时任务所花销的时间就大于我们触发的时间,这样就无法的得到准确的执行机会,上一次的任务还没执行完,下一次的任务又被触发,这样就会造成页面的卡顿。因此我们必须减少任务执行的次数。

防抖的作用

  1. 提升性能:避免频繁触发一些高成本的操作,防止因过度频繁执行而导致系统资源浪费和性能下降。(将执行推到最后一次,只执行最后一次触发)
  2. 防止误操作:例如用户可能在短时间内多次重复操作,防抖可以确保只对最后一次稳定的操作进行响应,避免因瞬间多次操作带来的混乱。
  3. 优化用户体验:使交互更加流畅和自然,不会因为过于灵敏的触发而给用户带来困扰。
  4. 增强程序稳定性:减少不必要的频繁处理,降低可能因频繁操作引发错误的几率。
  5. 保证逻辑的准确性:确保只有在合适的时机执行相关操作,以符合预期的业务逻辑。

手写令面试官眼前一亮的防抖

防抖其实就是设置一个定时器,如果在定时器时间内,任务被频繁触发,则清除这个定时器并设置一个新的定时器,只有在规定时间内任务没有被再次触发,这个任务才会得到执行。基于此,我们来看看基础版本的防抖应该怎么写

基础版

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #container {
            width: 100%;
            height: 200px;
            line-height: 200px;
            text-align: center;
            color: #fff;
            background-color: #444;
            font-style: 30px;
        }
    </style>
</head>

<body>
    <div id="container"></div>
    <script>
        var count = 1;
        var container = document.getElementById('container');
        function getUserAction() {
            container.innerHTML = count++;
        }
        function debounce(fn, delay) {
            // 闭包

            var timer = null;
            return function (event) {
                // 事件的执行函数 this-> container
                // 控制执行的次数
                var context = this;
                var args = arguments;
                clearTimeout(timer);
                timer = setTimeout(function () {
                    fn.apply(context, args)
                }, delay);
                // fn this -> 普通函数来运行
            }
        }
        container.onmousemove = debounce(getUserAction, 1000)
    </script>
</body>

</html>

debounce防抖函数中返回了一个函数,在这里就形成了一个timer闭包,供返回函数使用,通过timer对定时器进行保留,通过对定时器时间的控制timer = setTimeout(fn,delay)和定时器的删除clearTimeout(timer),实现了防抖的效果。具体对基础版本的讲解,可以看手写防抖,你也应该学会。this?闭包? - 掘金 (juejin.cn)

在基础版中,我们对原来要操作的getUserAction函数包裹了一个debounce防抖函数,在这种情况下,this的指向就会丢失,本来是getUserAction函数的this绑定到container身上,现在由于debounce的包裹,变成了debounce的this绑定到container身上,因此在基础版中我们做了this的传递,通过context上下文变量保存this并通过apply方法做一个显示绑定,除此之外,基础版中还对arguments参数进行了传递,确保我们的原本需要执行的函数能够拿到这些参数。那么之所以称之为基础版,是因为他还有更加惊艳人的地方。

进阶版

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>手写debounce</title>
</head>

<body>
    <script>
        // 业务层 防抖功能 为了性能优化 减少请求次数
        // fn 是真正需要执行的函数   this args权力
        // delay 防抖时间 定时器id  clear  最后一次
        // immediate 立即执行一次
        function debounce(fn, delay, immediate) {
            // 自由变量空间
            var timer, result;
            // 真正执行的函数
            return function () {
                var context = this;
                var args = arguments;
                if (timer) clearTimeout(timer);
                if (immediate) {
                    var callNow = !timer;
                    timer = setTimeout(function () {
                        timer = null;
                    }, delay)
                    if (callNow) result = fn.apply(context, args);
                } else {
                    timer = setTimeout(function () {
                        result = fn.apply(context, args);
                    }, delay)
                }
                return result;
            }
        }

        function getUserAction() {
            container.innerHTML = count++;
        }
        debounce(getUserAction, 1000, true)
    </script>
</body>

</html>

在进阶版中,我们添加了一个imeediate参数,如果设置了 immediate 为 true 且当前没有定时器时,会立即执行一次函数,然后再设置定时器,在定时器到期后再次执行函数;如果 immediate 为 false ,则只在定时器到期后执行函数。除此之外,我们还添加了一个返回值result,在一些情况下,我们也许需要用到这个result,因此从开发的角度来看,我们有必要对结果进行保留,以便日后使用。

代码详细分析:

  • debounce 函数接受要执行的函数 fn、防抖时间间隔 delay 以及是否立即执行一次的标志 immediate

    • 它通过定义一些变量来管理定时器和执行结果。
    • 在返回的函数内部,获取当前上下文和参数。如果存在定时器则清除它。如果设置了立即执行且当前没有定时器,就立即执行函数并设置定时器,否则直接设置定时器延迟执行函数。最后返回执行结果。
  • getUserAction 函数用于更新页面上某个元素的内容。

  • 最后对 getUserAction 进行了防抖处理,设置了防抖时间为 1000 毫秒且立即执行一次。

当 immediate 参数为 true 时,debounce 函数会在触发事件后立即执行一次被包装的函数,然后在延迟指定的时间后,如果没有再次触发事件,才会再次执行函数。如果在延迟期间再次触发了事件,则会重新开始计时,直到延迟时间结束后再次执行函数。

当 immediate 参数为 false 时,debounce 函数会在触发事件后延迟指定的时间,如果在延迟期间没有再次触发事件,才会执行被包装的函数。如果在延迟期间再次触发了事件,则会重新开始计时,直到延迟时间结束后才执行函数。

结尾

总的说来,这一次的防抖相比于基础版,其实就是添加了一个立即执行效果,弄懂这个效果并能够独立实现便足以让面试官眼前一亮了。