JavaScript 节流、防抖

2,253 阅读4分钟

为什么需要节流、防抖

当某个事件频繁触发时,事件处理函数会频繁执行,如果处理函数有一些费时、耗性能的操作,就会导致页面出现卡顿甚至浏览器崩溃,这时就需要节流防抖

什么是节流、防抖

  • 节流

当事件频发触发时,事件处理程序每隔一段时间执行一次

  • 防抖

如果某个频繁触发的事件在规定的时间内没有再次触发,则执行事件处理程序,如果在这段时间内事件再次触发了,则重新计时

什么时候需要节流、防抖

  • scroll
  • resize
  • 表单输入(input事件)校验
  • ...

实现

  • 防抖

<!DOCTYPE html>
<html lang = "en">
  <style>
    #wrap .scrollBox, #wrap .list{
      float: left;
    }
    .scrollBox {
      width: 40%;
      height: 500px;
      margin-right: 5%;
      border: 1px solid red;
      overflow: scroll;
    }
    .list {
      width: 40%;
      border: 1px solid red;
    }
  </style>
  <body>
    <div id = "wrap">
      <div class = "scrollBox"><!-- 内容动态生成 --></div>
      <ul class = "list">
        <li>我是列表</li>
      </ul>
    </div>
  </body>
  <script>
    // 防抖函数,不如不理解为什么能防抖,请看下面的解释内容
    function debounce (handle, delay) {
      let timer = null
      return function () {
        if (timer) {
          // console.log('clearTimeout, clear timer')
          clearTimeout(timer)
        }
        // console.log('setTimeout, set timer')    
        timer = setTimeout(handle, delay)
      }
    }
    // 事件处理函数,生成一个li节点,并添加到ul节点的末尾
    function handle () {
      const node = document.createElement('li')
      const text = document.createTextNode(Date.now())
      node.appendChild(text)
      document.getElementsByClassName('list')[0].appendChild(node)
    }
    // 添加事件监听器,形成了一个闭包
    document.getElementsByClassName('scrollBox')[0].addEventListener('scroll', debounce(handle, 500))
    
    // 生成滚动栏的内容,以下部分和上面的html、css只为构造一个实验环境
    let content = ''
    for (let i = 0; i < 1000; i++) {
      content += '在我上面滚动鼠标'
    }
    const textNode = document.createTextNode(content)
    document.getElementsByClassName('scrollBox')[0].appendChild(textNode)
  </script>
</html>

debounce函数为什么会有防抖的功能呢?

document.getElementsByClassName('scrollBox')[0].addEventListener('scroll', debounce(handle, 500)),这行代码形成了一个闭包,debounce函数运行以后返回一个函数,而返回的这个函数在scroll事件触发期间(即一直滚动页面)其实一直在不停的执行(可以放开注释掉的两行console.log()进行测试),比如:第一次开始滚动,第一次执行到debounce返回的函数,发现!!timer === falseif条件不成立,直接执行后面的setTimeout,设置timer,但是鼠标在一直滚动,马上debounce返回的函数又在执行第二次了,发现timer不为空,if条件成立,执行clearTimeout清除timer,但是这时你会发现刚才设置的定时任务还没有执行,就被清除了,这就达到了防抖的要求,鼠标一直滚动一直重复上面的过程(函数一直在执行),直到停止滚动,停止前一次设置的定时任务不会被清除,规定的延时时间一到,执行handle(处理程序),下一次开始滚动,清除上一次的timer,接着重复上面的过程

  • 节流

有了防抖的基础,节流这里,就省略掉构建实验环境的html代码了,直接上节流方法的实现,有两种实现方式,分别是时间戳和定时任务

定时任务

// 节流函数
function throttle (handle, delay) {
  // 节流的关键点在于timer,timer被赋值以后,只有在定时任务执行以后,timer才会重新被置为null
  let timer = null
  return function () {
    if (!timer) {
      // 设置timer
      timer = setTimeout(() => {
        handle()
        // 定时任务执行完毕,置空timer
        timer = null
      }, delay)
    }    
  }
}
// 事件处理函数
function handle () {
  console.log('I am handle function')
}
// 添加事件监听器
document.getElementById('app').addEventListener('scroll', throttle(handle, 1000))

时间戳

// 节流函数
function throttle (handle, delay) {
  // 节流的关键所在
  let prevTime = Date.now()
  return function () {
    if (Date.now() - prevTime >= delay) {
      // 时间到了以后执行事件处理程序,并重置prevTime
      handle()
      prevTime = Date.now()
    }    
  }
}
// 事件处理函数
function handle () {
  console.log('I am handle function')
}
// 添加事件监听器
document.getElementById('app').addEventListener('scroll', throttle(handle, 1000))

节流和防抖有什么区别

  • 共同点

都是限制事件处理程序的执行频率

  • 区别

节流: 定时重复执行事件处理程序,不论事件触发有多频繁 防抖:只有事件最后一次触发(指定时间内没有再次触发)才会执行处理程序

应用场景

  • 防抖

  1. search搜索框、表单输入验证等触发input事件的操作

只有内容输入完成以后,才执行最后的事件处理程序(比如:搜索内容、验证表单内容)

  1. 窗口的resize事件

窗口resize停止后,最后执行一次事件处理程序

  • 节流

  1. 鼠标频繁点击的场景

游戏,比如打地鼠、CS、英雄联盟、王者荣耀等需要频繁点击操作的场景

  1. 下拉刷新

下拉加载更多,一直下拉,但一段事件内只执行一次事件处理程序(加载内容)