3
头图

防抖和节流到底是什么?

防抖和节流属于性能优化的知识,它可以有效的降低高频事件触发时,你定义的方法的执行次数。

还是没有感觉???那么,来看下面的场景:

  1. 用户在搜索框输入关键词(只有当他输入完成时我们才去向服务器发送请求,然后给出搜索结果)
  2. 自动保存用户填写的表单数据

上面的场景都对应着一个高频事件,即input或者textarea的onKeyUp事件,我们一般是在用户触发这个事件后去向服务器发送请求(这样做的好处是不需要用户去点击搜索按钮,有一种实时查询的感觉)。

那么问题来了,当用户输入一个要查询的关键词,可能需要多次按下和抬起键盘的按键,难道每次onKeyUp的时候我们都要去请求服务器吗?显然不够优雅(因为如果有大量用户同时搜索,服务器压力会很大)。而 防抖(debounce) 正是要解决类似这样的问题。

在浏览器中我们经常会遇到类似的事件(如浏览器scroll,resize,mousemove...)接下来,我们使用 自动保存 的场景来说明一下在 JavaScript 中如何实现防抖。

场景描述:用户在textarea中输入文字后,要为他自动保存到服务器(可以理解为保存为草稿)这时我们需要做的是 优化 请求服务器的次数,需要用到防抖函数。

实例演示:文章中提供了在线演示的地址,便于直观的理解防抖和节流起到的作用:

防抖函数

先来看一个常见的错误写法,注意!!!百度中搜出的很多结果都是这个样子,用了之后就会发现,你的函数还是会立刻执行,并不会延时执行。

function debounce(fn, delay) {
    let timer = null
    return function (args) {
        if (timer) {
            clearTimeout(timer) 
        }
        timer = setTimeout(fn.call(this, args), delay)
    }
}

问题出在 timer = setTimeout(fn.call(this, args), delay) 这一行。

修改成下面的样子,就可以按设定的delay延时执行了:

function debounce(fn, delay) {
    let timer = null
    return function (args) {
        if (timer) {
            clearTimeout(timer) 
        }
        timer = setTimeout(function() {
            fn.call(this, args)
        }, delay)
    }
}

// 或者
function debounce(fn, delay) {
  let timer = null
    return function (args) {
      if (timer) {
        clearTimeout(timer)
      }
      timer = setTimeout(function() {
        fn(args)
      }, delay)
    }
}

不要小看这小小的区别,它可能会浪费你大量的时间,而且让你对防抖产生怀疑...

下面贴一个完整的例子,还有 防抖在线演示地址,方便你更好的理解这个场景。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>防抖和节流</title>
    <style>
        .de_wrapper {
          padding: 20px;
          display: flex;
        }
        .col {
            width: 40%;
        }
        .log {
          height: 300px;
          overflow-y: scroll;
          background-color: #fff;
        }
    </style>
</head>
<body>
<div class="de_wrapper">
    <div class="col">
        <h3>未使用防抖(每次按键抬起都会触发保存)</h3>
        <textarea name="" id="1" cols="30" rows="10" onKeyUp="printLog(event)"></textarea>
        <div id="log" class="log"></div>
    </div>
    <div class="col">
        <h3>使用防抖(停止输入2秒后保存)</h3>
        <!-- <textarea name="" id="2" cols="30" rows="10"></textarea> -->
        <textarea name="" id="2" cols="30" rows="10" onKeyUp="debounceLog(event)"></textarea>
        <div id="log1" class="log"></div>
    </div>
</div>
<script>
    let log = null
    let log1 = null

    window.onload = function() {
        log = document.getElementById('log')
        log1 = document.getElementById('log1')

        // 写法1
        // document.getElementById('2').addEventListener('keyup', function(e) {
        //     debounceLog(e)
        // })

        // 写法2
        // document.getElementById('2').addEventListener('keyup', debounceLog)

        // 写法3
        // document.getElementById('2').addEventListener('keyup', debounce(printDebounceLog, 2000))

    } 

    function printLog(e) {
        log.innerText += `keyup 事件触发【请求服务器保存数据...】: ${e.target.value}\n`
    }

    function printDebounceLog(e) {
        log1.innerText += `keyup 事件触发【请求服务器保存数据...】: ${e.target.value}\n`
    }

    let debounceLog = debounce(printDebounceLog, 2000)

    function debounce(fn, delay) {
      let timer = null
        return function (args) {
          if (timer) {
            clearTimeout(timer)
          }
          timer = setTimeout(function() {
            fn(args)
          }, delay)
        }
    }
</script>
</body>
</html>

节流函数

节流函数(throttle)与防抖函数的区别:函数节流无论事件触发多么频繁,在一定时间内只会执行一次回调;而函数防抖是在高频事件的最后一次触发回调。

节流函数使用场景:一个很形象的例子就是mousedown发射子弹,每秒只能发出一颗子弹,在线演示地址

function throttle(fn, limit) {
  let lastTime
  return function(args) {
    if (!lastTime) {
      fn.apply(this,args)
      lastTime = Date.now()
    } else {
      if ((Date.now() - lastTime) >= limit) {
        fn.apply(this,args)
        lastTime = Date.now()
      }
    }
  }
}
// 页面结构
<div class="de_wrapper">
    <div class="col">
        <h3>未使用节流(点击按钮可以疯狂发射子弹)</h3>
        <div class="sky"></div>
        <button class="fire_btn">发射</button>
      </div>
      <div class="col">
        <h3>使用节流(发射子弹速度会被限制)</h3>
        <div class="sky"></div>
        <button class="fire_btn">发射</button>
    </div>
</div>

<script>

let sky = null
let sky1 = null
let btn = null
let btn1 = null

window.onload = function() { 
    sky = document.querySelectorAll('.sky')[0]
    sky1 = document.querySelectorAll('.sky')[1]
    btn = document.querySelectorAll('.fire_btn')[0]
    btn1 = document.querySelectorAll('.fire_btn')[1]

    btn.addEventListener('click', fire)
    btn1.addEventListener('click', throttle(t_fire, 1000))
} 

function fire() {
  const b = document.createElement('span')
  b.classList.add('bullet')
  sky.appendChild(b)
  setTimeout(() => {
    sky.removeChild(b)
  }, 1000)
}

function t_fire() {
  const b = document.createElement('span')
  b.classList.add('bullet')
  sky1.appendChild(b)
  setTimeout(() => {
    sky1.removeChild(b)
  }, 1000)
}

function throttle(fn, limit) {
  let lastTime
  return function(args) {
    if (!lastTime) {
      fn.apply(this,args)
      lastTime = Date.now()
    } else {
      if ((Date.now() - lastTime) >= limit) {
        fn.apply(this,args)
        lastTime = Date.now()
      }
    }
  }
}

总结

  1. 函数防抖:将多次操作合并为一次操作进行,原理是维护一个计时器,后设置的定时器会取代之前的定时器,如果高频事件一直在触发那么回调函数一直不会执行。
  2. 函数节流:使得一定时间内只触发一次函数。原理是通过判断是否满足限制时间,满足则执行。

IICOOM-个人博客|技术博客 《防抖和节流》


来了老弟
508 声望31 粉丝

纸上得来终觉浅,绝知此事要躬行


引用和评论

1 篇内容引用
0 条评论