# 防抖与截流

  • 可参考underscore,lodash通用工具库

# 函数防抖-限制操作频率的函数

  • 防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
  • 适用场景:
    • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
    • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
    • 生产环境请用lodash.debounce

# 函数截流

  • 防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效
  • 适用场景:
    • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
    • 缩放场景:监控浏览器resize
    • 动画场景:避免短时间内多次触发动画引起性能问题

# 异同点

  • 相同:在不影响客户体验的前提下,将频繁的回调函数,进行次数缩减.避免大量计算导致的页面卡顿.
  • 不同:防抖是将多次执行合并为最后一次执行,节流是将多次执行变为在规定时间内只执行一次.

# 防抖代码实现

  • 参考链接:
    • https://juejin.im/post/5c6bab91f265da2dd94c9f9e
    • https://github.com/mqyqingfeng/Blog/issues/22
    • https://www.codercto.com/a/36013.html
  • 防抖分为两种:
    • 1)非立即执行版:事件触发->延时->执行回调函数;如果在延时中,继续触发事件,则会重新进行延时.在延时结束后执行回调函数.常见例子:就是input搜索框,客户输完过一会就会自动搜索
    • 2)立即执行版:事件触发->执行回调函数->延时;如果在延时中,继续触发事件,则会重新进行延时.在延时结束后,并不会执行回调函数.常见例子:就是对于按钮防点击.例如点赞,心标,收藏等有立即反馈的按钮,还有下拉加载.
$('textarea').on('keydown', debounce(ajaxAction, 2500));
// 保证了回调函数之间的调用间隔,至少是delay
//简版
function debounce(func, delay){
  let timer = null // 声明计时器
  return function(...args){
    clearTimeout(timer)
    timer = setTimeout(()=>{
      func.apply(this, args)
    }, delay)
  }
}
function debounce(fn, delay, immediate){
  let timer = null
  return function(...args){
    clearTimeout(timer)
    if(immediate){
      fn.apply(this,args)
      timer = setTimeout(()=>{
        timer = null
      }, delay)
    }else{
      timer = setTimeout(()=>{
        fn.apply(this, args)
      }, delay)
    }
  }
}
// 带有立即执行选项的防抖函数
const debounce = (func, delay, immediate) => {
  let timer // 保存定时器
  // 返回的闭包不要使用箭头函数,这样可以根据实际情况更加灵活的使用this
  return function(...args){
    if(timer){
      clearTimeout(timer) //不管是否立即执行,都要先清空定时器
    }
    if(immediate){
      // 如果是立即执行,则定时器中不再包含回调函数,
      // 而是在回调函数执行后,仅起到延时和销毁定时器的作用
      if(!timer) func.apply(this, args)
      timer = setTimeout(() => {
        timer = null
      }, delay)
    }else{
      // 如果是非立即执行,则重新设定定时器,并将回调函数放入其中
      // setTimeout的回调函数用箭头函数来写,可以直接绑定外层的this
      timer = setTimeout(() => {
        func.apply(this, args)
      }, delay)
    }
  }
}

# 截流代码实现

// 时间戳版
function throttle(fn, delay){
  let previous = 0
  return function(...args){
    let now = Date.now() // 记录此刻触发时的时间戳
    if(now - previous > delay){// 时间差大于规定时间,则触发
      fn.apply(this, args)
      previous = now
    }
  }
}
// 定时器版
function throttle(fn, delay){
  let timer
  return function(...args){
    if(!timer){
      timer = setTimeout(() => {
        timer = null
        fn.apply(this, args)
      }, delay)
    }
  }
}
// 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
// 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件
// 时间戳加定时器版,这版有问题
function throttle(fn, delay){
  let timer
  let previous = 0
  return function(...args){
    let now = Date.now()
    if(now - previous >= delay){
      fn.apply(this, args)
      previous = now
    }else{
      timer = setTimeout(() => {
        fn.apply(this, args)
        timer = null
      }, delay)
    }

  }
}