likes
comments
collection
share

前端性能优化:防抖节流

作者站长头像
站长
· 阅读数 22

前言


防抖(Debounce)和节流(Throttle)是两种常用于控制频繁事件触发的技术,它们在处理高频率事件(如键盘输入、窗口调整大小、滚动等)时尤为有用。虽然它们有着相似的目标,但在实现原理和适用场景上有所不同。

防抖

防抖(Debounce)是一种控制频繁事件的技术,它确保在一段时间内多次触发同一事件时,只有最后一次事件处理程序被执行。防抖的核心原理是在事件被触发后,设置一个延迟定时器。如果在定时器执行前再次触发事件,则重置定时器,重新计时。这样,只有在事件停止触发一段时间后,处理函数才会被执行。

防抖就好像我们按下一个按钮后启动一个计时器,如果在计时器结束前再次按下按钮,计时器会重新开始计时,只有等到没有再次按下按钮的情况下,计时器结束后才会执行一次操作。

示例场景:

  • 输入搜索框:在用户停止输入一段时间后再发送搜索请求,以避免频繁的请求。
  • 窗口调整大小:在用户停止调整窗口大小一段时间后再执行某些操作。

节流

节流(Throttle)是一种控制频繁事件的技术,它确保在一段时间内只允许某个函数执行一次。节流的核心原理是在第一次触发事件时立即执行处理函数,然后在规定的时间间隔内忽略随后的事件,直到时间间隔结束后再允许执行下一次事件处理。

节流就好像我们玩的FPS游戏,像那种单发的枪,不管我们鼠标按得有多快,它规定时间内也只能开一枪。

示例场景:

  • 页面滚动:限制滚动事件的触发频率,每隔100毫秒执行一次事件处理函数,从而减轻浏览器的渲染压力。
  • 按钮点击:防止按钮在短时间内被多次点击,限制按钮点击的频率

再形象点可以如下图表示: 前端性能优化:防抖节流 防抖和节流虽然看似相似,但它们的应用场景和实现原理有所不同。接下来,我们将深入探讨这两种技术的具体实现方法、适用场景以及如何在实际开发中灵活运用它们。

代码实现

防抖

function debounce(func, delay) {
    return function (args) {
        clearTimeout(func.id);
        func.id = setTimeout(() => {
            func(args);
        }, delay);
    };
}
  • 闭包和定时器: debounce 函数返回的是一个闭包,这个闭包保存了 funcdelay 的引用,并且能够访问 func.id,这是一个用于跟踪当前 setTimeout 调用的标识符。每次闭包被调用时,它会首先清除当前的定时器(如果有),然后重新设置一个新的定时器。
  • 清除定时器: clearTimeout(func.id); 这行代码是防抖的关键。每当闭包函数被调用时,它都会取消之前设置的定时器。这意味着只要在 delay 时间内有任何新的事件触发,之前的定时器就会被取消,从而防止了在 delay 时间内多次执行 func
  • 执行时机: 只有在最后一次事件触发后的 delay 时间内没有新的事件发生,func 才会被执行。这是因为每次事件触发都会重置定时器,而 func 只会在定时器最终未被清除的情况下执行。

拿出整个代码来看看效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>防抖</title>
</head>
<body>
    <div>
        没有防抖的input <input type="text" id="inputA">
    </div>
    <div>
        有防抖的input <input type="text" id="inputB">
    </div>
    <script>
        const inputa = document.getElementById("inputA");
        const inputb = document.getElementById("inputB");
        const ajax = (content) => {
            const currentTime = new Date().toLocaleTimeString();
            console.log(`[${currentTime}] ajax request: ` + content);
        };
        function debounce(func, delay) {
            return function (args) {
                clearTimeout(func.id);
                func.id = setTimeout(() => {
                    func(args);
                }, delay);
            };
        }
        inputa.addEventListener('keyup',(e)=>{
            ajax(e.target.value);
        })
        let debounceFunc=debounce(ajax,1000)
        inputb.addEventListener('keyup',(e)=>{
            let value=e.target.value;
            debounceFunc(value)
        })
    </script>
</body>

</html>

我们模仿一个ajax请求返回时间和内容:

前端性能优化:防抖节流

节流

const throttle=(func,delay)=>{
     // last 记录上一次是啥时候执行的 
     // deferTimer 定时器id
     let last,deferTimer  // 自由变量
     // keyup return func 调用时都能找到闭包中的自由变量
     return (args)=>{
         // 当前时间, 隐式类型转换
         let now=+new Date();
         if(last&&now-last<delay){
             clearTimeout(deferTimer)
             deferTimer=setTimeout(()=>{
                 last=now
                 func(args)
             },delay)
         }
         else{
             last=now  // 第一次时间
             func(args)  // 先执行一次
         }

      }
   }

这段代码实现节流的主要就是: 时间检查:

-   获取当前时间戳 `now`
-   如果自上一次执行以来的时间小于 `delay`,则不允许立即执行 `func`
-   如果允许执行,更新 `last` 为当前时间戳并执行 `func`

延迟执行:

-   如果当前时间与上一次执行时间的差小于 `delay`,则清除现有的 `deferTimer`(如果存在),并设置一个新的 `setTimeout`
-   新的 `setTimeout` 将在 `delay` 毫秒后执行 `func` 并更新 `last`

最后的执行: 如果用户停止触发事件(如停止按键),并且最后一个事件触发后的时间超过了 delay,那么在该事件之后不会立即有另一个事件来触发执行。但是,由于我们在延迟执行时设置了 setTimeout,所以即使没有新的触发事件,func 也会在 delay 时间后执行一次,确保了在一定时间内至少执行一次。

我们也给出全部代码来看看实现效果:

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

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

<body>
    <div class="row">
        <div>
            没有节流的input <input type="text" id="inputa" />
        </div>
        <div>
            节流后的input <input type="text" id="inputc" />
        </div>
    </div>
    <script>
        const inputA = document.getElementById('inputa');
        const inputC = document.getElementById('inputc');


        const ajax = (content) => {
            const currentTime = new Date().toLocaleTimeString();
            console.log(`[${currentTime}] ajax request: ` + content);
        };

        const throttle = (func, delay) => {

            let last, deferTimer

            return (args) => {

                let now = +new Date();
                if (last && now - last < delay) {
                    clearTimeout(deferTimer)
                    deferTimer = setTimeout(() => {
                        last = now
                        func(args)
                    }, delay)

                }
                else {
                    last = now
                    func(args)
                }

            }
        }
        inputA.addEventListener('keyup', (e) => {
            ajax(e.target.value);
        })

        let throttledFunc = throttle(ajax, 500)
        inputC.addEventListener('keyup', (e) => {
            let value = e.target.value;

            throttledFunc(value)
        })
    </script>
</body>

</html>

前端性能优化:防抖节流

总结

防抖和节流在控制高频事件方面各有优势:

  • 防抖:适用于那些需要在一段时间内不触发事件后执行操作的场景,例如搜索输入框、窗口调整等。它通过重置定时器,确保事件处理程序只在最后一次触发后的延迟时间内执行。
  • 节流:适用于那些需要控制函数执行频率的场景,例如滚动事件、按钮点击等。它通过设置时间间隔,确保在规定的时间间隔内函数只执行一次。

在实际开发中,选择使用防抖还是节流,取决于具体的应用场景和需求。合理运用这两种技术,可以显著提升用户体验和系统性能。

转载自:https://juejin.cn/post/7387320448168804390
评论
请登录