js去抖和节流函数详解

Posted vcxiaohan2

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js去抖和节流函数详解相关的知识,希望对你有一定的参考价值。

Debounce 和 Throttle 的原理及实现仅作为理解去抖和节流函数的概念,里面提供的代码并不一定正确

示例:

  • 网上做示例,一般用scroll或者mousemove事件,我们很难控制触发事件的次数,这里我们使用click事件,我们触发了几次事件,心里都有数,更便于个人对去抖和节流函数的理解
  • 以下去抖和节流函数代码均为自己理解所写,功能并不全面,但是一般的场景均能满足
    • 去抖和节流函数均没有考虑返回值的情况
    • 节流函数没有考虑立即执行和延时执行同时存在的情况
    • 截图
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>demo</title>
  <style>
    * 
      margin: 0;
      padding: 0;
    

    body,
    html 
      width: 100%;
      height: 100%;
    

    div 
      float: left;
      width: 250px;
      height: 150px;
      border: 1px solid red;
      cursor: pointer;
    
  </style>
</head>

<body>
  <p>分别点击以下区域,并观察控制栏打印结果</p>
  <div class="a">
    <p>debounce方法去抖,延时执行</p>
    <h3>应用:</h3>
    <p>1 缩放浏览器</p>
    <p>2 键盘输入文字自动提示</p>
    <p>3 滚动到底部加载下一页</p>
  </div>
  <div class="b">
    <p>debounce方法去抖,立即执行</p>
    <h3>应用:</h3>
    <p>1 防止用户短时间内多次提交</p>
  </div>
  <div class="c">
    <p>throttle方法节流,延时执行</p>
    <h3>应用:</h3>
    <p>1 缩放浏览器</p>
  </div>
  <div class="d">
    <p>throttle方法节流,立即执行</p>
    <h3>应用:</h3>
    <p>1 滚动时全屏切换,如:<a href="http://www.jq22.com/yanshi1124" target="_blank">jQuery全屏滚动插件fullPage.js演示</a></p>
  </div>
</body>
<script>
  let a = debounce(function (e) 
    console.log('debounce方法去抖,延时执行', e.target, `事件类型:$e.type`)
  , 500)
  let b = debounce(function (e) 
    console.log('debounce方法去抖,立即执行', e.target, `事件类型:$e.type`)
  , 500, true)
  let c = throttle(function (e) 
    console.log('throttle方法节流,延时执行', e.target, `事件类型:$e.type`)
  , 500)
  let d = throttle(function (e) 
    console.log('throttle方法节流,立即执行', e.target, `事件类型:$e.type`)
  , 500, true)

  // 绑定滚动事件
  document.querySelector('.a').addEventListener('click', a)
  document.querySelector('.b').addEventListener('click', b)
  document.querySelector('.c').addEventListener('click', c)
  document.querySelector('.d').addEventListener('click', d)

  // 去抖(多次事件只执行1次,wait为延时执行时间,immediate表示首次事件是立即执行还是延时执行)
  function debounce(fn, wait, immediate) 
    // 闭包形成局部作用域
    // 定时器
    let timer = null
    return function () // #1
      // 保存#1的this上下文
      const self = this
      // 保存#1的参数列表
      const args = arguments
      // 清除定时器,阻止fn的执行
      clearTimeout(timer)
      if (immediate) // 事件立即执行
        // 首次事件时,timer为null
        let callNow = !timer
        // 接下来的事件,timer都是定时器的引用,即存在
        timer = setTimeout(() => 
          // 延时时间过后,恢复timer为null
          timer = null
        , wait)
        // 首次事件时,callNow为true,所以会立即执行
        if (callNow) fn.apply(self, args)
       else // 事件延时执行
        // 开启新的定时器,延时wait时间后,执行fn
        timer = setTimeout(() => 
          // 执行fn,改变this指向#1的上下文,并传入#1的参数
          fn.apply(self, args)
        , wait)
      
    
  

  // 节流(多次事件间隔执行,wait为间隔执行时间,immediate表示首次事件是立即执行还是延时执行)
  function throttle(fn, wait, immediate) 
    // 闭包形成局部作用域
    // 定时器
    let timer = null
    // 开始时间
    let start = 0
    return function () // #1
      // 保存#1的this上下文
      const self = this
      // 保存#1的参数列表
      const args = arguments
      if (immediate) // 立即执行
        // 每次要执行fn时,记录当前时间(此时fn并未执行)
        let end = new Date()
        if (end - start >= wait) // 累积时间大于间隔时间时执行(首次事件的时间戳远远大于wait)
          // 执行fn,改变this指向#1的上下文,并传入#1的参数
          fn.apply(self, args)
          // 更新开始时间
          start = end
        
       else // 延时执行
        if (!timer) 
          timer = setTimeout(() => 
            // 执行fn,改变this指向#1的上下文,并传入#1的参数
            fn.apply(self, args)
            // 赋值为null,以便下次继续执行
            timer = null
          , wait)
        
      
    
  
</script>

</html>

网上更完美的封装函数

// 去抖(默认延迟执行,通过immediate来开启相应的效果)
/* // 延迟执行
debounce(function ()  , 500)
// 立即执行
debounce(function ()  , 500, true) */
function debounce(func, wait, immediate) 
  var timeout, result
  return function () 
    var context = this
    var args = arguments
    if (timeout) clearTimeout(timeout)
    if (immediate) 
      var callNow = !timeout
      timeout = setTimeout(function () 
        timeout = null
      , wait)
      if (callNow) result = func.apply(context, args)
    
    else 
      timeout = setTimeout(function () 
        func.apply(context, args)
      , wait)
    
    return result
  


// 节流(默认立即执行和延迟执行同时开启,通过options来关闭相应的效果,但是两者不能同时关闭)
/* // 立即执行和延迟执行同时开启
throttle(function ()  , 500)
throttle(function ()  , 500, 
  // 取消立即执行
  leading: false
)
throttle(function ()  , 500, 
  // 取消延迟执行
  trailing: false
) */
function throttle(func, wait, options) 
  var timeout, context, args, result
  var previous = 0
  if (!options) options = 

  var later = function () 
    previous = options.leading === false ? 0 : new Date().getTime()
    timeout = null
    func.apply(context, args)
    if (!timeout) context = args = null
  

  var throttled = function () 
    var now = new Date().getTime()
    if (!previous && options.leading === false) previous = now
    var remaining = wait - (now - previous)
    context = this
    args = arguments
    if (remaining <= 0 || remaining > wait) 
      if (timeout) 
        clearTimeout(timeout)
        timeout = null
      
      previous = now
      func.apply(context, args)
      if (!timeout) context = args = null
     else if (!timeout && options.trailing !== false) 
      timeout = setTimeout(later, remaining)
    
  
  return throttled

以上是关于js去抖和节流函数详解的主要内容,如果未能解决你的问题,请参考以下文章

JS debounce和throttle 去抖和节流

JS基础-防抖和节流

函数防抖和节流快速记忆

JS 函数防抖和函数节流

lodash的防抖和节流方法

防抖(debounce)和节流(throttle)