函数节流和函数防抖

函数节流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)
原文地址:https://www.cnblogs.com/houfee/p/9848985.html