JavaScript 节流、防抖

为何须要节流、防抖

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

什么是节流、防抖

  • 节流
当事件频发触发时,事件处理程序每隔一段时间执行一次
  • 防抖
若是某个频繁触发的事件在规定的时间内没有再次触发,则执行事件处理程序,若是在这段时间内事件再次触发了,则从新计时

何时须要节流、防抖

  • 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函数为何会有防抖的功能呢?javascript

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代码了,直接上节流方法的实现,有两种实现方式,分别是时间戳和定时任务

定时任务css

// 节流函数
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))

时间戳html

// 节流函数
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. 下拉刷新
下拉加载更多,一直下拉,但一段事件内只执行一次事件处理程序(加载内容)