防抖和节流

108 阅读1分钟

这两个东西都以闭包的形式存在。

防抖(debounce)

  • 事件被触发n秒后再执行回调,如果在这n秒内又被调用,则重新计时。

节流(throttle)

  • 在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。(相当于间隔n秒执行一次,间隔n秒执行一次......)

防抖(debounce)

使用场景:

  • scroll 事件滚动触发
  • 搜索框输入查询
  • 表单验证
  • 按钮提交事件
  • 浏览器窗口缩放

1. 非立即执行回调的防抖

<div>debounce:<input type="text" id="debounce-input" /></div>
<script>
const inputDom = document.getElementById('debounce-input')
​
function debounce(func, wait) {
  let timeout
  // let result; // 如果需要返回值,在这里定义一个变量
  return function () {
    const that = this // 改变执行函数内部 this 的指向
    const args = arguments // 解决 doSomeThing event指向问题
    clearTimeout(timeout)
    timeout = setTimeout(function () {
      func.apply(that, args)
        //需要函数的返回值,在这里给result赋值
        // result = func.apply(that, args)
    }, wait)
    // return result;
  }
}
​
function doSomeThing(e) {
  console.log('我是防抖~~~')
  // console.log(e)
  // console.log(this);
  // 可能会做 回调 或者 ajax 请求
}
​
inputDom.onkeyup = debounce(doSomeThing, 300)
</script>

2. 立即执行回调的防抖

<div>debounce:<input type="text" id="debounce-input" /></div>
<script>
const inputDom = document.getElementById('debounce-input')
​
function debounce(func, wait, immediate) {
  // immediate 是否立即执行
  let timeout
  return function () {
    const that = this // 改变执行函数内部 this 的指向
    const args = arguments // 解决 doSomeThing event指向问题
    clearTimeout(timeout) //  每次进来先清除上一次的 setTimeout
    if (immediate) {
      const callNow = !timeout //需要一个条件判断是否要去立即执行
      timeout = setTimeout(function () {
        timeout = null
      }, wait)
      // 立即执行
      if (callNow) func.apply(that, args)
    } else {
      // 不会立即执行
      timeout = setTimeout(function () {
        func.apply(that, args)
      }, wait)
    }
  }
}
​
function doSomeThing(e) {
  console.log('我是防抖~~~')
  // console.log(e)
  // console.log(this);
  // 可能会做 回调 或者 ajax 请求
}
​
inputDom.onkeyup = debounce(doSomeThing, 300, true) 
</script>

3. 取消防抖

 <div>
   debounce:<input type="text" id="debounce-input" />
   <button id="cancel-btn">取消防抖</button>
</div>
<script>
const inputDom = document.getElementById('debounce-input')
const cancelBtnDom = document.getElementById('cancel-btn')
​
function debounce(func, wait) {
  let timeout
  let debounced = function () {
    const that = this // 改变执行函数内部 this 的指向
    const args = arguments // 解决 doSomeThing event指向问题
    clearTimeout(timeout)
    timeout = setTimeout(function () {
      func.apply(that, args)
    }, wait)
  }
  debounced.cancel = function () {
    // 新增取消方法
    clearTimeout(timeout)
    timeout = null
  }
​
  return debounced
}
​
function doSomeThing(e) {
  console.log('我是防抖~~~')
  // console.log(e)
  // console.log(this);
  // 可能会做 回调 或者 ajax 请求
}
​
const doDebounce = debounce(doSomeThing, 1000, true)
​
inputDom.onkeyup = doDebounce
​
cancelBtnDom.onclick = function () {
  doDebounce.cancel()
}
</script>

节流(throttle)

应用场景:

  • 监听 scroll 滚动事件;
  • DOM 元素的拖拽功能的实现;
  • 射击游戏;
  • 计算鼠标移动的距离;

1. 时间戳

<div style="height: 10000px"></div>
<script>
// 第一次立即执行,最后一次不会被调用触发执行
function throttle(func, wait) {
  let old = 0 // 之前的时间戳
  let throttled =  function () {
    const that = this
    const args = arguments
    let now = new Date().valueOf() // 获取当前时间戳
    if (now - old > wait) {
      func.apply(that, args) // 立即执行
      old = now
    }
  }
  // 取消节流
  throttled.cancel = function() {
      old = new Date()
  }
  
  return throttled
}
​
function doSomeThing(e) {
  console.log('我是节流~~~')
  // console.log(e)
  // console.log(this);
  // 可能会做 回调 或者 ajax 请求
}
​
document.onscroll = throttle(doSomeThing, 500)
</script>

2. 定时器

<div style="height: 10000px"></div>
<script>
// 第一次不立即执行,最后一次会被调用触发执行
function throttle(func, wait) {
  let timeout
  let throttled =  function () {
    const that = this
    const args = arguments
    if (!timeout) {
      timeout = setTimeout(function () {
        func.apply(that, args)
        timeout = null
      }, wait)
    }
  }
  
  // 取消节流
  throttled.cancel = function() {
    clearTimeout(timeout)
    timeout = null
  }
  
  return throttled
}
​
function doSomeThing(e) {
  console.log('我是节流~~~')
  // console.log(e)
  // console.log(this);
  // 可能会做 回调 或者 ajax 请求
}
​
document.onscroll = throttle(doSomeThing, 500)
</script>

3. 时间戳 + 定时器

<div style="height: 10000px"></div>
<script>
// 第一次立即执行,最后一次会被调用触发执行
function throttle(func, wait) {
  let timeout
  let old = 0 // 之前的时间戳
​
  return function () {
    const that = this
    const args = arguments
    let now = new Date() // 获取当前时间戳
    if (now - old > wait) {
      // 第一次会立即执行
      if (timeout) {
        clearTimeout(timeout)
        timeout = null
      }
      func.apply(that, args) // 立即执行
      old = now
    } else if (!timeout) {
      // 最后一次会执行
      timeout = setTimeout(function () {
        func.apply(that, args)
        old = new Date()
        timeout = null
      }, wait)
    }
  }
}
​
function doSomeThing(e) {
  console.log('我是节流~~~')
  // console.log(e)
  // console.log(this);
  // 可能会做 回调 或者 ajax 请求
}
​
document.onscroll = throttle(doSomeThing, 500)
</script>

4. 优化

能够通过参数让自己能取决立即执行或最后一次执行

<div style="height: 10000px"></div>
<script>
function throttle(func, wait, options) {
  let timeout
  let old = 0 // 之前的时间戳
  if (!options) options = {}
  return function () {
    const that = this
    const args = arguments
    let now = new Date().valueOf() // 获取当前时间戳
    if (options.leading === false && !old) { // 让第一次不执行
      old = now
    }
    if (now - old > wait) {
      // 第一次会立即执行
      if (timeout) {
        clearTimeout(timeout)
        timeout = null
      }
      func.apply(that, args) // 立即执行
      old = now
    } else if (!timeout && options.trailing !== false) {
      // 最后一次会执行
      timeout = setTimeout(function () {
        func.apply(that, args)
        old = new Date().valueOf()
        timeout = null
      }, wait)
    }
  }
}
​
function doSomeThing(e) {
  console.log('我是节流~~~')
  // console.log(e)
  // console.log(this);
  // 可能会做 回调 或者 ajax 请求
}
​
/*
 * 第一次会立即执行,最后一次不会被调用 {leading:true,trailing:false}
 * 第一次不会立即执行,最后一次会被调用 {leading:false,trailing:true}
 * 第一次会立即执行,最后一次会被调用 {leading:true,trailing:true}
 * options = { leading:xxx,trailing:xxx }; 默认 options 为 {leading:true,trailing:true}
 * throttle(doSomeThing,wait,options)
 */
document.onscroll = throttle(doSomeThing, 500)
</script>