js去抖和节流函数详解

Debounce 和 Throttle 的原理及实现仅作为理解去抖和节流函数的概念,里面提供的代码并不一定正确

示例:

  • 网上做示例,一般用scroll或者mousemove事件,我们很难控制触发事件的次数,这里我们使用click事件,我们触发了几次事件,心里都有数,更便于个人对去抖和节流函数的理解
  • 以下去抖和节流函数代码均为自己理解所写,功能并不全面,但是一般的场景均能满足
    • 去抖和节流函数均没有考虑返回值的情况
    • 节流函数没有考虑立即执行和延时执行同时存在的情况
    • 截图
      js去抖和节流函数详解_第1张图片

<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>demotitle>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    body,
    html {
      width: 100%;
      height: 100%;
    }

    div {
      float: left;
      width: 250px;
      height: 150px;
      border: 1px solid red;
      cursor: pointer;
    }
  style>
head>

<body>
  <p>分别点击以下区域,并观察控制栏打印结果p>
  <div class="a">
    <p>debounce方法去抖,延时执行p>
    <h3>应用:h3>
    <p>1 缩放浏览器p>
    <p>2 键盘输入文字自动提示p>
    <p>3 滚动到底部加载下一页p>
  div>
  <div class="b">
    <p>debounce方法去抖,立即执行p>
    <h3>应用:h3>
    <p>1 防止用户短时间内多次提交p>
  div>
  <div class="c">
    <p>throttle方法节流,延时执行p>
    <h3>应用:h3>
    <p>1 缩放浏览器p>
  div>
  <div class="d">
    <p>throttle方法节流,立即执行p>
    <h3>应用:h3>
    <p>1 滚动时全屏切换,如:<a href="http://www.jq22.com/yanshi1124" target="_blank">jQuery全屏滚动插件fullPage.js演示a>p>
  div>
body>
<script>
  let a = debounce(function (e) {
    console.log('debounce方法去抖,延时执行', e.target, `事件类型:${e.type}`)
  }, 500)
  let b = debounce(function (e) {
    console.log('debounce方法去抖,立即执行', e.target, `事件类型:${e.type}`)
  }, 500, true)
  let c = throttle(function (e) {
    console.log('throttle方法节流,延时执行', e.target, `事件类型:${e.type}`)
  }, 500)
  let d = throttle(function (e) {
    console.log('throttle方法节流,立即执行', e.target, `事件类型:${e.type}`)
  }, 500, true)

  // 绑定滚动事件
  document.querySelector('.a').addEventListener('click', a)
  document.querySelector('.b').addEventListener('click', b)
  document.querySelector('.c').addEventListener('click', c)
  document.querySelector('.d').addEventListener('click', d)

  // 去抖(多次事件只执行1次,wait为延时执行时间,immediate表示首次事件是立即执行还是延时执行)
  function debounce(fn, wait, immediate) {
    // 闭包形成局部作用域
    // 定时器
    let timer = null
    return function () {// #1
      // 保存#1的this上下文
      const self = this
      // 保存#1的参数列表
      const args = arguments
      // 清除定时器,阻止fn的执行
      clearTimeout(timer)
      if (immediate) {// 事件立即执行
        // 首次事件时,timer为null
        let callNow = !timer
        // 接下来的事件,timer都是定时器的引用,即存在
        timer = setTimeout(() => {
          // 延时时间过后,恢复timer为null
          timer = null
        }, wait)
        // 首次事件时,callNow为true,所以会立即执行
        if (callNow) fn.apply(self, args)
      } else {// 事件延时执行
        // 开启新的定时器,延时wait时间后,执行fn
        timer = setTimeout(() => {
          // 执行fn,改变this指向#1的上下文,并传入#1的参数
          fn.apply(self, args)
        }, wait)
      }
    }
  }

  // 节流(多次事件间隔执行,wait为间隔执行时间,immediate表示首次事件是立即执行还是延时执行)
  function throttle(fn, wait, immediate) {
    // 闭包形成局部作用域
    // 定时器
    let timer = null
    // 开始时间
    let start = 0
    return function () {// #1
      // 保存#1的this上下文
      const self = this
      // 保存#1的参数列表
      const args = arguments
      if (immediate) {// 立即执行
        // 每次要执行fn时,记录当前时间(此时fn并未执行)
        let end = new Date()
        if (end - start >= wait) {// 累积时间大于间隔时间时执行(首次事件的时间戳远远大于wait)
          // 执行fn,改变this指向#1的上下文,并传入#1的参数
          fn.apply(self, args)
          // 更新开始时间
          start = end
        }
      } else {// 延时执行
        if (!timer) {
          timer = setTimeout(() => {
            // 执行fn,改变this指向#1的上下文,并传入#1的参数
            fn.apply(self, args)
            // 赋值为null,以便下次继续执行
            timer = null
          }, wait)
        }
      }
    }
  }
script>

html>

网上更完美的封装函数

// 去抖(默认延迟执行,通过immediate来开启相应的效果)
/* // 延迟执行
debounce(function () { }, 500)
// 立即执行
debounce(function () { }, 500, true) */
function debounce(func, wait, immediate) {
  var timeout, result
  return function () {
    var context = this
    var args = arguments
    if (timeout) clearTimeout(timeout)
    if (immediate) {
      var callNow = !timeout
      timeout = setTimeout(function () {
        timeout = null
      }, wait)
      if (callNow) result = func.apply(context, args)
    }
    else {
      timeout = setTimeout(function () {
        func.apply(context, args)
      }, wait)
    }
    return result
  }
}

// 节流(默认立即执行和延迟执行同时开启,通过options来关闭相应的效果,但是两者不能同时关闭)
/* // 立即执行和延迟执行同时开启
throttle(function () { }, 500)
throttle(function () { }, 500, {
  // 取消立即执行
  leading: false
})
throttle(function () { }, 500, {
  // 取消延迟执行
  trailing: false
}) */
function throttle(func, wait, options) {
  var timeout, context, args, result
  var previous = 0
  if (!options) options = {}

  var later = function () {
    previous = options.leading === false ? 0 : new Date().getTime()
    timeout = null
    func.apply(context, args)
    if (!timeout) context = args = null
  }

  var throttled = function () {
    var now = new Date().getTime()
    if (!previous && options.leading === false) previous = now
    var remaining = wait - (now - previous)
    context = this
    args = arguments
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout)
        timeout = null
      }
      previous = now
      func.apply(context, args)
      if (!timeout) context = args = null
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining)
    }
  }
  return throttled
}

你可能感兴趣的:(javascript概念)