函数节流和防抖

函数节流和防抖

Posted by limantang on July 23, 2019

函数节流(throttle)&函数防抖(debounce)

  1. 在浏览器中有些事件是随着用户的操作不断触发的, 比如窗口resize, 页面滚动scroll, 鼠标移动mousemove, 键盘input输入,用户不停出发这些操作的时候, 如果脚本里面绑定了对应的事件处理方法, 这个方法就会不停地触发, 我们并不想这样, 因为这样既有可能造成性能问题

    解决办法就是给相应的事件添加延迟执行逻辑

  2. 节流函数&防抖函数

    触发一个事件时通过setTimeout让这个事件延迟一会执行, 如果在这个时间段内又触发了事件, 那么就clear原来的定时器, 再弄一个新的定时器延迟执行

    这就是这两个函数的原理, 但是这两个函数的区别是: 函数防抖是如果某事件一直触发并且时间间隔小于计时器的延迟时间, 那么就会一直clear掉计时器, 被触发的事件一次也不会执行, 直到最后一次事件触发 函数节流是如果事件一直触发, 那么也会保证在这当中至少间隔n秒也会触发一次事件

  3. 节流应用场景: 页面滚动, 窗口resize 防抖应用场景: 表单验证

  4. 实现

    • 版本一

            // 符合直观的错误版本
            let count = 0;
            function test() {
            console.log(count++)
            }
            window.addEventListener('reisze', () => {
            const timer = undefined;
            clearTimeout(timer);
            timer = setTimeout(() => {
            test();
            }, 300)
            }, false)
      

      上面错在每次执行resize对应的事件时都会创建一个新的timer,达不到节流效果

    • 版本二

            const timer = undefined;
            window.addEventListener('reszie', () => {
            clearTimeout(timer);
            timer = setTimeout(() => {
            test();
            }, 300)
            }, false)
      

      缺点是创建了全局变量, 有可能会变量冲突

    • 版本三

            const debounce = (fn, delay) => {
            const timer = undefined;
            return function () {
                clearTimeoiut(timer);
                timer = setTimeout(() => {
                    fn();
                    }, delay)
                }
            }
            const callback = throttle(test, 300);
            window.addEventListener('reszie',callback, false);
      

      如果用户不断的resize, 那么延迟函数一次都不会执行, 这就是函数防抖 此版本可以满足大部分需求, 代码理解也较为容易 版本一和版本二都是函数节流和防抖的基础写法 版本三是标准的函数防抖(debounce)

    • 版本四

      这个版本中添加一个额外功能, 当用户触发事件时, 即使用户不停地触发事件也要保证在某段时间内要保证事件至少执行一次 这个功能就是函数节流和防抖的重要区别 下面实现是函数节流的实现

             let count = 0;
             function test() {
               console.log(count++)
             }
             const throttle = (fn, delay, atLeast) => {
               let timer = undefined;
               let previous = undefined;
               return function () {
                 const now = +new Date();
                 if (!previous) {
                   previous = now;
                 }
                 if (now - previous > atLeast) {
                   fn();
                   //	重置上一次开始时间为本次结束时间
                   previous = now;
                   clearTimeout(timer);
                 } else {
                   clearTimeout(timer);
                   timer = setTimeout(() => {
                     fn();
                   }, delay)
                   //	重置时间
                   previous = undefined;
                 }
               }
             }
            const callback = throttle(test, 300, 1000);
            window.onresize = callback;