zoukankan      html  css  js  c++  java
  • 函数节流和函数防抖

    函数节流Throttle和函数防抖Debounce

    函数节流与函数防抖

    浅谈JavaScript的函数节流

    【进阶 6-3 期】深入浅出面试必考题 - 节流函数 throttle - 其中有underscore.js源码解析

    【进阶 6-4 期】深入浅出防抖函数 debounce- 其中有underscore.js源码解析

    节流(throttle)

    介绍

    函数节流:Throttling enforces a maximum number of times a function can be called over time. As in "execute this function at most once every 100 milliseconds".

    理解:函数节流指的是某个函数在一定时间间隔内(例如 3 秒)只执行一次,在这 3 秒内 无视后来产生的函数调用请求,也不会延长时间间隔。

    原理和实现

    函数节流触发事件:window对象的resize、scroll事件、拖拽时的mousemove事件、文字输入、自动完成的keyup事件。

    实现方案:

    • 第一种是通过时间戳来判断是否已到达执行时间,记录上次的执行时间戳,然后每次调用这个方法时,判断该次触发时间和上次执行的时间差,是否在规定的执行时间之外,如果是则执行,并更新上次执行的时间戳,如果不是,不执行函数,等待下一次触发,如此循环

    • 第二种方法设置定时器,比如当scroll事件触发时,打印一个holle world,然后设置一个1000ms定时器,此后每次触发scroll事件触发回调,如果已经存在定时器,则回调不执行方法,直到定时器触发,handler被清除,然后重新设置定时器。

    第一种实现:

    /**
     * 节流函数
     * @param {function} fn - 回调函数
     * @param {number} wait - 定时节流
     */
    const throttle = function(fn, wait = 500) {
      // 第一次执行设置时间为 0
      let provious = 0
      return function(...args) {
        let now = +new Date()
        // 将当前时间 和 上一次执行的时间对比
        // 判断是否在节流周期中
        if(now - provious > wait) {
          // 使用闭包,储存该次触犯时的时间戳
          provious = now
          // 执行回调
          fn.apply(this, args)
        }
      }
    }
    
    // 模拟回调函数
    const func = function(...args) {
      // your code ...
      console.log('func方法', ...args)
    }
    
    // 调用节流
    const betterFn = throttle(func, 1000)
    
    // 模拟事件触发,并传入参数
    setInterval(betterFn, 2000, [1, 2, 3, 4])

    第二种实现:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <style>
        * {margin: 0;padding: 0;}
        .father {width: 300px;height: 300px;background-color: #3de;margin: 100px auto;padding: 10px;overflow: auto;border: 10px solid red;}
        .son {width: 400px;height: 600px;background-color: yellowgreen;}
      </style>
    </head>
    
    <body>
      <div class="father"><div class="son"></div>
      </div>
      <script src="./节流.js"></script>
    </body>
    
    </html>
    节流.js:
    /**
     * 节流函数
     * @param {function} fn - 回调函数
     * @param {number} wait - 定时节流
     */
    const throttle = function(fn, wait = 500) {
      let timer = null
      return function(...args) {
        if(!timer) {
          // 执行回调
          fn.apply(this, args)
          timer = setTimeout(() => {
            timer = null
          }, wait)
        }
      }
    }
    
    // 模拟回调函数
    const func = function(...args) {
      // your code ...
      console.log('holle world', ...args)
    }
    
    // 调用节流
    const betterFn = throttle(func, 1000)
    
    // 模拟scroll事件触发,并传入参数
    let fNode = document.querySelector('.father');
    fNode.onscroll = function(){
       // 获取元素中被卷去的内容的距离 获取元素内部总被卷去的内容的横向距离  和  纵向距离
      console.log('x:' + fNode.scrollLeft)
      console.log('y:' + fNode.scrollTop)
      // 在 1000ms内 触发不会执行回调
      betterFn([1, 2, 3, 4])
    }

    防抖(debounce)

    函数防抖:Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in "execute this function only if 100 milliseconds have passed without it being called".

    防抖函数:指的是某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次

    实现1

    // html样式同节流
    /**
     * 防抖函数
     * @param {function} fn - 回调函数
     * @param {number} wait - 时间间隔
     */
    const debounce = function(fn, wait = 500) {
      let timer = null
      return function(...args) {
        if(timer) clearTimeout(timer)
        timer = setTimeout(() => {
          fn.apply(this, args)
        }, wait)
      }
    }
    
    // 模拟回调函数
    const func = function(...args) {
      // your code ...
      console.log('fn 防抖执行了', ...args)
    }
    
    // 调用防抖
    const betterFn = debounce(func, 1000)
    
    // 模拟scroll事件触发,并传入参数
    let fNode = document.querySelector('.father');
    fNode.onscroll = function(){
       // 获取元素中被卷去的内容的距离 获取元素内部总被卷去的内容的横向距离  和  纵向距离
      console.log('x:' + fNode.scrollLeft);
      console.log('y:' + fNode.scrollTop);
      betterFn([1, 2, 3, 4])
    }

    demo1

    <input type="text" id="one">
    <script type="text/javascript">
        // 键盘抬起发送ajax jQuery实现 函数防抖
        // $("#one").on("keyup",checkEmail());
        // function checkEmail(){
        //     let timer=null;
        //     return function (){
        //         clearTimeout(timer);
        //         timer=setTimeout(function(){
        //             console.log('执行检查');
        //         },800);
        //     }
        // }
        // 键盘抬起发送ajax原生js实现 函数防抖
        let timeout = null
        onkeyup = function() {
            clearTimeout(timeout);
            timeout = setTimeout(function() {
                console.log("发送ajax")
            }, 1000);
        }
        document.getElementById("one").onkeyup = function() {
            console.log("键盘抬起")
            onkeyup()
        }
    </script>

    demo2

    当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间

    <input type="text" id="one">
    <script type="text/javascript">
        // 窗口变化 ----- 函数防抖
        var myFunc = function() {
            console.log("我要发送ajax")
        }
        var throttle = function(fn, delay){
            let timer = null
            return function(){
                let context = this, args = arguments
                clearTimeout(timer)
                timer = setTimeout(function(){
                    fn.apply(this, args)
                }, delay)
            };
        };
        window.onresize = throttle(myFunc, 1000);
    </script>

    实现2

    以上的防抖没法做到第一次触发就马上执行回调,下面加上第一次触发就执行回调

    /**
     * 防抖函数
     * @param {function} fn - 回调函数
     * @param {number} wait - 定时防抖
     * @param {boolean} immediate - 第一次触发回调事件就执行
     */
    const debounce = function(fn, wait = 500, immediate) {
      let timer = null
      return function(...args) {
        if (timer) clearTimeout(timer)
    
        // ------ 新增部分 start ------ 
        // immediate 为 true 表示第一次触发后执行
        // timer 为空表示首次触发
        if (immediate && !timer) {
          fn.apply(this, args)
        }
        // ------ 新增部分 end ------
    
        timer = setTimeout(() => {
          fn.apply(this, args)
        }, wait)
      }
    }
    
    // 模拟回调函数
    const func = function(...args) {
      // your code ...
      console.log('fn 防抖执行了', ...args)
    }
    
    // 调用防抖
    const betterFn = debounce(func, 1000, true)

    加强版 throttle【感叹:大神的代码】

    需求:函数防抖中:在用户操作非常频繁时,不等设置的延时时间结束就进行下次操作,会频繁的清除计时器并重新生成,所以函数fn一直都没办法执行,导致用户操作迟迟得不到响应。

    解决思路:

    函数节流函数防抖合二为一,变成加强版的节流函数,关键在于wait时间内,可以重新生成定时器,但是只要wait的时间一到,必须给用户一个响应。此时不管用户是否还在频繁操作。

    // fn 是需要节流处理的函数
    // wait 是时间间隔
    function throttle(fn, wait) {
      
      // previous 是上一次执行 fn 的时间
      // timer 是定时器
      let previous = 0, timer = null
      
      // 将 throttle 处理结果当作函数返回
      return function (...args) {
        
        // 获取当前时间,转换成时间戳,单位毫秒
        let now = +new Date()
        
        // ------ 新增部分 start ------ 
        // 判断上次触发的时间和本次触发的时间差是否小于时间间隔
        if (now - previous < wait) {
             // 如果小于,则为本次触发操作设立一个新的定时器
           // 定时器时间结束后执行函数 fn 
           if (timer) clearTimeout(timer)
           timer = setTimeout(() => {
              previous = now
                fn.apply(this, args)
            }, wait)
        // ------ 新增部分 end ------ 
          
        } else {
           // 第一次执行
           // 或者时间间隔超出了设定的时间间隔,执行函数 fn
           previous = now
           fn.apply(this, args)
        }
      }
    }
    
    // DEMO
    // 执行 throttle 函数返回新函数
    const betterFn = throttle(() => console.log('fn 节流执行了'), 1000)
    // 第一次触发 scroll 执行一次 fn,每隔 1 秒后执行一次函数 fn,停止滑动 1 秒后再执行函数 fn
    document.addEventListener('scroll', betterFn)
  • 相关阅读:
    17款最佳的代码审查工具
    最全的开发人员在线速查手册
    Expression Blend 4 激活码
    TimeSpan XML序列化
    如何使用策略模式
    .Net 笔记(二) 泛型和集合
    VS2010 旗舰版序列号
    WPF ArrangeOverride与MeasureOverride
    实现zbar扫描二维码的时候就把照片存储出来的办法
    用Jpush极光推送实现抓取特定某个用户Log到七牛云服务器
  • 原文地址:https://www.cnblogs.com/houfee/p/9848985.html
Copyright © 2011-2022 走看看