防抖节流整理!

61 阅读1分钟

手写题之防抖节流!

防抖_基本实现

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">

  <!-- CDN引入: 网络的js文件 -->
  <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.4/underscore-umd-min.js"></script>
  

  <script>
    function mjdebounce(fn, delay) {
      // 1 用于记录上一次事件触发的的timer
      let timer = null

      // 2 触发事件时执行的函数
      const _debounce = () => {
        // 2.1 如果有再次触发事件,那么取消上一次的定时器
        if (timer) clearTimeout(timer)

        // 2.2 延迟去执行对应的fn函数
        timer = setTimeout(() => {
          fn()
            // 2.3 执行过函数之后将timer重新置为null
          timer = null
        }, delay)
      }

      // 返回一个新的函数
      return _debounce
    }
  </script>

  <script>
    // 1.获取input元素
    const inputEl = document.querySelector("input")

    // 未进行防抖处理的代码
    // let counter = 1
    // inputEl.oninput = function() {
    //   console.log(`发送网络请求${counter++}:`, this.value)
    // }


    // 2.underscore防抖处理代码
    // let counter = 1
    // inputEl.oninput = _.debounce(function() {
    //   console.log(`发送网络请求${counter++}:`, this.value)
    // }, 1000)

    // 3.自己实现的防抖
    let counter = 1
    inputEl.oninput = mjdebounce(function() {
      console.log(`发送网络请求${counter++}`, this)
    }, 1000)
  </script>

</body>

</html>

防抖_this和参数的绑定

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">

  <script>
    function mjdebounce(fn, delay) {
      let timer = null
      
      // 不能使用箭头函数的方式书写,这样会使内部函数的this绑定到windows
      const _debounce = function(...args) {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
          
          fn.apply(this, args)
          timer = null
        }, delay)
      }

      // 返回一个新的函数
      return _debounce
    }
  </script>

  <script>
    const inputEl = document.querySelector("input")

    let counter = 1
    inputEl.oninput = mjdebounce(function(event) {
      console.log(`发送网络请求${counter++}`, this.value, event)
    }, 1000)
    
  </script>

</body>

</html>

防抖_取消功能

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">
  <button class="cancelBtn">取消</button>

  <script>
    function mjdebounce(fn, delay) {
      // 1 用于记录上一次事件触发的的timer
      let timer = null
      let isInvoke = false

      // 2 触发事件时执行的函数
      const _debounce = function(...args) {
        // 2.1 如果有再次触发事件,那么取消上一次的定时器
        if (timer) clearTimeout(timer)


        // 2.2 延迟去执行对应的fn函数
        timer = setTimeout(() => {
          fn.apply(this, args)
            // 2.3 执行过函数之后将timer重新置为null
          timer = null
          isInvoke = false
        }, delay);
      }

      // 取消函数
      _debounce.cancel = function() {
        if (timer) clearTimeout(timer)
      }

      // 返回新的函数
      return _debounce
    }
  </script>

  <script>
    // 1.获取input元素
    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector(".cancelBtn")

    let counter = 1
    const debounceFn = mjdebounce(function(event) {
      console.log(`发送网络请求${counter++}`, this.value, event)
    }, 3000)
    inputEl.oninput = debounceFn

    cancelBtn.onclick = function() {
      console.log('取消');
      debounceFn.cancel()
    }
  </script>

</body>

</html>

防抖_立即执行

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">
  <button class="cancelBtn">取消</button>

  <script>
    function mjdebounce(fn, delay, immediate = false) {
      // 1 用于记录上一次事件触发的的timer
      let timer = null
      let isInvoke = false

      // 2 触发事件时执行的函数
      const _debounce = function(...args) {
        // 2.1 如果有再次触发事件,那么取消上一次的定时器
        if (timer) clearTimeout(timer)


        // 立即执行
        if (immediate && !isInvoke) {
          fn.apply(this, args)
          isInvoke = true
          return
        }

        // 2.2 延迟去执行对应的fn函数
        timer = setTimeout(() => {
          fn.apply(this, args)
            // 2.3 执行过函数之后将timer重新置为null
          timer = null
          isInvoke = false
        }, delay);
      }

      // 取消函数
      _debounce.cancel = function() {
        if (timer) clearTimeout(timer)
      }

      // 返回新的函数
      return _debounce
    }
  </script>

  <script>
    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector(".cancelBtn")

    let counter = 1
    const debounceFn = mjdebounce(function(event) {
      console.log(`发送网络请求${counter++}`, this.value, event)
    }, 3000, true)
    inputEl.oninput = debounceFn
    cancelBtn.onclick = function() {
      console.log('取消');
      debounceFn.cancel()
    }
  </script>

</body>

</html>

防抖_返回值

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">
  <button class="cancel">取消</button>

  <script>
    // 原则: 一个函数进行做一件事情, 一个变量也用于记录一种状态
    function mjdebounce(fn, delay, immediate = false, resultCallback) {
      // 1.用于记录上一次事件触发的timer
      let timer = null
      let isInvoke = false

      // 2.触发事件时执行的函数
      const _debounce = function(...args) {
        return new Promise((resolve, reject) => {
          try {
            // 2.1.如果有再次触发(更多次触发)事件, 那么取消上一次的事件
            if (timer) clearTimeout(timer)

            // 第一次操作是不需要延迟
            let res = undefined
            if (immediate && !isInvoke) {
              res = fn.apply(this, args)
              if (resultCallback) resultCallback(res)
              resolve(res)
              isInvoke = true
              return
            }

            // 2.2.延迟去执行对应的fn函数(传入的回调函数)
            timer = setTimeout(() => {
              res = fn.apply(this, args)
              if (resultCallback) resultCallback(res)
              resolve(res)
              timer = null // 执行过函数之后, 将timer重新置null
              isInvoke = false
            }, delay);
          } catch (error) {
            reject(error)
          }
        })
      }

      // 3.给_debounce绑定一个取消的函数
      _debounce.cancel = function() {
        if (timer) clearTimeout(timer)
        timer = null
        isInvoke = false
      }

      // 返回一个新的函数
      return _debounce
    }
  </script>

  <script>
    // 1.获取input元素
    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector(".cancel")

    // 2.手动绑定函数和执行
    const myDebounceFn = mjdebounce(function(name, age, height) {
      console.log("----------", name, age, height)
      return "codermjjh 哈哈哈哈"
    }, 1000, false)

    myDebounceFn("mjjh", 18, 1.88).then(res => {
      console.log("拿到执行结果:", res)
    })
  </script>

</body>

</html>

防抖_最终代码

function mjdebounce(fn, delay, immediate = false) {
  // 1 用于记录上一次事件触发的的timer
  let timer = null
  let isInvoke = false

  // 2 触发事件时执行的函数
  const _debounce = function(...args) {
    // 返回值
    return new Promise((resolve, reject) => {
      try {
        // 2.1 如果有再次触发事件,那么取消上一次的定时器
        if (timer) clearTimeout(timer)

        // 立即执行
        if (immediate && !isInvoke) {
          res = fn.apply(this, args)
          resolve(res)
          isInvoke = true
          return
        }

        // 2.2 延迟去执行对应的fn函数
        timer = setTimeout(() => {
          res = fn.apply(this, args)
            // 2.3 执行过函数之后将timer重新置为null
          resolve(res)
          timer = null
          isInvoke = false
        }, delay);
      } catch (error) {
        reject(error)
      }
    })
  }

  // 取消函数
  _debounce.cancel = function() {
    if (timer) clearTimeout(timer)
    timer = null
    isInvoke = false
  }

  // 返回新的函数
  return _debounce
}

export default mjdebounce

节流

节流_基本实现

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">

  <!-- CDN引入: 网络的js文件 -->
  <!-- <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.4/underscore-umd-min.js"></script> -->
  <!-- 本地引入: 下载js文件, 并且本地引入 -->
  <!-- <script src="./js/underscore.js"></script> -->

  <script>
    function mjthrottle(fn, interval) {
      let startTime = 0
			// interval 间隔时间
      // nowTime 当前时间
      // startTime 开始时间
      const _throttle = function() {
        const nowTime = new Date().getTime()
        const waitTime = interval - (nowTime - startTime)
        if (waitTime <= 0) {
          fn()
          startTime = nowTime
        }
      }
      return _throttle
    }
  </script>

  <script>
    // 1.获取input元素
    const inputEl = document.querySelector("input")

    // 2.underscore节流处理代码
    // let counter = 1
    // inputEl.oninput = _.throttle(function() {
    //   console.log(`发送网络请求${counter++}:`, this.value)
    // }, 1000)

    // 3.自己实现的节流函数
    let counter = 1
    inputEl.oninput = mjthrottle(function() {
      console.log(`发送网络请求${counter++}:`, this.value)
    }, 1000)
  </script>

</body>

</html>

节流_this和参数的绑定

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">

  <script>
    function mjthrottle(fn, interval) {
      let startTime = 0

      const _throttle = function(...args) {
        const nowTime = new Date().getTime()
        const waitTime = interval - (nowTime - startTime)
        if (waitTime <= 0) {
          fn.apply(this, args)
          startTime = nowTime
        }
      }

      return _throttle
    }
  </script>

  <script>
    const inputEl = document.querySelector("input")

    let counter = 1
    inputEl.oninput = mjthrottle(function() {
      console.log(`发送网络请求${counter++}:`, this.value)
    }, 1000)
  </script>

</body>

</html>

节流_立即执行 尾部执行

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">

  <script>
    function mjthrottle(fn, interval, {
      leading = true,
      trailing = false
    } = {}) {
      let startTime = 0
      let timer = null

      const _throttle = function(...args) {
        // 1.获取当前时间
        const nowTime = new Date().getTime()

        // 对立即执行进行控制
        if (!leading && startTime === 0) {
          startTime = nowTime
        }

        // 2.计算需要等待的时间执行函数
        const waitTime = interval - (nowTime - startTime)
        if (waitTime <= 0) {
          // console.log("执行操作fn")
          if (timer) clearTimeout(timer)
          fn.apply(this, args)
          startTime = nowTime
          timer = null
          return
        }

        // 3.判断是否需要执行尾部
        if (trailing && !timer) {
          timer = setTimeout(() => {
            // console.log("执行timer")
            fn.apply(this, args)
            startTime = new Date().getTime()
            timer = null
          }, waitTime);
        }
      }

      return _throttle
    }
  </script>

  <script>
    const inputEl = document.querySelector("input")

    let counter = 1
    inputEl.oninput = mjthrottle(function() {
      console.log(`发送网络请求${counter++}:`, this.value)
    }, 3000, {
      trailing: true
    })
  </script>

</body>

</html>

节流_取消功能

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">
  <button class="cancel">取消</button>
  <script>
    function mjthrottle(fn, interval, {
      leading = true,
      trailing = false
    } = {}) {
      let startTime = 0
      let timer = null

      const _throttle = function(...args) {
        return new Promise((resovle, reject) => {
          try {
            // 获取当前时间
            const nowTime = new Date().getTime()
              // 对立即执行进行控制
            if (!leading && startTime === 0) startTime = nowTime

            // 计算等待时间执行函数
            const waitTime = interval - (nowTime - startTime)
            if (waitTime <= 0) {
              if (timer) clearTimeout(timer)
              res = fn.apply(this, args)
              resovle(res)
              startTime = nowTime
              timer = null
              return
            }

            // 是否需要执行尾部
            if (trailing && !timer) {
              timer = setTimeout(() => {
                res = fn.apply(this, args)
                resovle(res)
                startTime = new Date().getTime()
                timer = null
              }, waitTime)
            }
          } catch (error) {
            reject(error)
          }
        })
      }

      // 取消尾部执行
      _throttle.cancel = function() {
        if (timer) clearTimeout(timer)
        startTime = 0
        timer = null
      }

      return _throttle
    }
  </script>

  <script>
    // 1.获取input元素
    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector(".cancel")
    
    let counter = 1
    const throttleFn = mjthrottle(function() {
      console.log(`发送网络请求${counter++}:`, this.value)
    }, 3000, {
      trailing: true
    })

    throttleFn("aaaa")
  </script>

</body>

</html>

节流_返回值

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <button>按钮</button>

  <input type="text">
  <button class="cancel">取消</button>

  <script>
    function mjthrottle(fn, interval, {
      leading = true,
      trailing = false
    } = {}) {
      let startTime = 0
      let timer = null

      const _throttle = function(...args) {
        return new Promise((resovle, reject) => {
          try {
            // 获取当前时间
            const nowTime = new Date().getTime()
              // 对立即执行进行控制
            if (!leading && startTime === 0) startTime = nowTime

            // 计算等待时间执行函数
            const waitTime = interval - (nowTime - startTime)
            if (waitTime <= 0) {
              if (timer) clearTimeout(timer)
              const res = fn.apply(this, args)
              resovle(res)
              startTime = nowTime
              timer = null
              return
            }

            // 是否需要执行尾部
            if (trailing && !timer) {
              timer = setTimeout(() => {
                const res = fn.apply(this, args)
                resovle(res)
                startTime = new Date().getTime()
                timer = null
              }, waitTime)
            }
          } catch (error) {
            reject(error)
          }
        })
      }

      // 取消尾部执行
      _throttle.cancel = function() {
        if (timer) clearTimeout(timer)
        startTime = 0
        timer = null
      }

      return _throttle
    }
  </script>

  <script>
    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector(".cancel")

    let counter = 1
    const throttleFn = mjthrottle(function() {
      return "return value"
    }, 3000, {
      trailing: true
    })

    throttleFn("aaaa").then(res => {
      console.log(res);
    })
  </script>

</body>

</html>

节流_最终代码

function mjthrottle(fn, interval, {
  leading = true,
  trailing = false
} = {}) {
  let startTime = 0
  let timer = null

  const _throttle = function(...args) {
    return new Promise((resovle, reject) => {
      try {
        // 获取当前时间
        const nowTime = new Date().getTime()
          // 对立即执行进行控制
        if (!leading && startTime === 0) startTime = nowTime

        // 计算等待时间执行函数
        const waitTime = interval - (nowTime - startTime)
        if (waitTime <= 0) {
          if (timer) clearTimeout(timer)
          const res = fn.apply(this, args)
          resovle(res)
          startTime = nowTime
          timer = null
          return
        }

        // 是否需要执行尾部
        if (trailing && !timer) {
          timer = setTimeout(() => {
            const res = fn.apply(this, args)
            resovle(res)
            startTime = new Date().getTime()
            timer = null
          }, waitTime)
        }
      } catch (error) {
        reject(error)
      }
    })
  }

  // 取消尾部执行
  _throttle.cancel = function() {
    if (timer) clearTimeout(timer)
    startTime = 0
    timer = null
  }

  return _throttle
}

export default mjthrottle