# JS中防抖与节流的讲解与封装

防抖: 当一直触发某个事件时,只有当停止触发一段时间后才会 执行该做的事情

节流: 当一直触发某个事件时,不会一直执行,而是按一定时间内有规律周期性的执行一次,直至停止触发。

# 防抖

例1:

//防抖
let timer = null
window.onscroll = () => {
if (timer) {
  window.clearTimeout(timer)
  }
   timer = window.setTimeout(() => {
       console.log("执行任务")
  }, 1000)
}

为了更方便的使用防抖方法,封装方法

function debounce(func, wait = 300){
 let timer = null
 return function(...arg){
   if(timer !== null){
     clearTimeout(timer)
  }
   timer = setTimeout(() => {
  func.apply(this, arg)
       timer = null
  }, wait)
}
}
//创建函数 就是 上面 return 的函数
let scrollHandler = debounce(function(e){
     console.log(e)
}, 500)
//无需传参时 可直接使用 因为 scrollHandler 就是一个函数
window.onscroll = scrollHandler

//需要传参时
let scrollHandler = debounce(function(e,c){
     console.log(e,c)
}, 500)
window.onscroll = function(){
    scrollHandler("1", "2") //调用该函数
}

有时我们希望函数能立即执行。因此添加是否立即执行的参数

/**
* @param {function} func - 执行函数
* @param {number} wait - 等待时间
* @param {boolean} immediate - 是否立即执行
* @return {function}
*/
function debounce(func, wait = 300, immediate = false){
   let timer = null
return function(...arg){
       if(timer !== null){
        clearTimeout(timer)
      } else if(immediate){
           func.apply(this, arg)
      }
       timer = setTimeout(() => {
           func.apply(this, arg)
           timer = null
      }, wait)
  }
}

//创建函数 就是 上面 return 得函数
let scrollHandler = debounce(function(e){
     console.log(e)
}, 500)
//无需传参时 可直接使用 因为 scrollHandler 就是一个函数
window.onscroll = scrollHandler

//需要传参时
let scrollHandler = debounce(function(e,c){
     console.log(e,c)
}, 500)
window.onscroll = function(){
    scrollHandler("1", "2") //调用该函数
}

# 节流

例:

//节流
let timer = null
window.onscroll = () => {
   if(timer === null) {
       timer = window.setTimeout(() => {
           console.log("执行任务")
           timer = null
      }, 1000)
  }
}

这样就会实现 没间隔 1秒 就执行一次任务,但是不足之处有两点

  1. 没法一开始就执行一下任务
  2. 没法准确抓住最后停止时再执行一次任务

解决问题:

//节流
let timer = null, last
window.onscroll = () => {
   const now = +new Date()
   //如果last存在 且当前时间小于 last + 延迟时间 即非执行任务 期间
   if (last && now < last + 1000) {
       clearTimeout(timer)
       timer = setTimeout(function() { //保证最后一次执行任务 是 最终状态
           console.log("执行任务")
      }, 300) //停止触发 后300毫秒立即执行
  } else {
       //0秒的时候不执行任务 则加上这句判断 0+wait, 0 + 2*wait ... 执行任务
       if (last) {
      console.log("执行任务")
      }
       
       //0秒 0+wait, 0 + 2*wait ... 执行任务
       console.log("执行任务")
       last = now //将当前时间给 last 用来判断下一个周期
  }
}

这样即解决了上述问题,接下来就是封装起来

/**
 * @param {function} func - 执行函数
 * @param {number} wait - 等待时间  默认 1000毫秒
 * @param {boolean} immediate - 是否立即执行 默认false
 * @param {number} stopTime - 停止触发后多少毫秒后 执行任务 默认300毫秒
 * @return {function}
 */
function throttle (func, wait = 1000, immediate = false, stopTime = 300){
  let last, timer
  return function(...arg) {
    const now = +new Date()
    if (last && now < last + wait) {
      clearTimeout(timer)
      timer = setTimeout(function() {
        func.apply(this, arg)
      }, stopTime)
    } else {
      if (!immediate) {
        if (last) {
          func.apply(this, arg)
        }
      } else {
        func.apply(this, arg)
      }
      last = now
    }
  }
}

//创建函数 就是 上面 return 得函数
let scrollHandler = throttle(function(e){
     console.log(e)
}, 2000)
//无需传参时 可直接使用 因为 scrollHandler 就是一个函数
window.onscroll = scrollHandler

//需要传参时
let scrollHandler = throttle(function(e,c){
     console.log(e,c)
}, 2000, true)
window.onscroll = function(){
    scrollHandler("1", "2") //调用该函数
}

这样防抖与节流的方法 就都好了。

阅读原文 (opens new window)