js手写防抖节流

370 阅读4分钟

前言

git地址

一、debounce(防抖)

1.过程

  • 当事件触发时,相应的函数不会立即触发,而是会等待一定的时间。
  • 事件触发越频繁,函数的触发就会频繁的推迟。
  • 等待了一段时间也没有事件触发,才会真正的执行响应函数。

一句话总结:距离上一次事件触发达到了约定的时间,再执行响应函数。

2.应用场景

  • 输入框中频繁的输入内容,搜索或者提交信息。
  • 频繁的点击按钮,触发某个事件。
  • 监听浏览器滚动事件,完成某些特定的操作。
  • 用户缩放浏览器的resize事件。

注:项目中使用防抖是性能优化的方案之一。

3.实现防抖

3.1 基本实现(核心)

思考过程:

1. 需要接收什么参数?
- 参数1:回调函数;
- 参数2:延迟时间(`delay`)。
2. 有什么返回值?
- 返回一个函数。
3. 内部怎么实现?
- 延时操作需要使用`setTimeout`函数。
- 在执行响应函数前,需要取消前一次的预执行响应函数。

实现:

// 防抖的核心
function debounce (fn, delay) {
  // 1.用于记录上一次事件触发的timer
  let timer = null

  // 2.触发事件时执行的函数
  const _debounce = () => {
    // 2.1.如果有再次触发事件(更多次触发)事件,取消上一次事件
    timer && clearTimeout(timer)

    // 2.2.延迟执行对应的fn函数(传入的回调函数)
    timer = setTimeout(() => {
      fn()
      // 执行函数之后,timer初始化
      timer = null
    }, delay)
  }
  return _debounce
}

涉及知识点:

- `setTimeout`的使用
- 闭包

3.2 优化1:this和参数绑定

function debounce (fn, delay) {
  let timer = null
  // 参数传递,不能使用箭头函数
  const _debounce = function (...args) {
    timer && clearTimeout(timer)
    timer = setTimeout(() => {
      // 新增:绑定this,传递参数
      fn.apply(this, args)
      timer = null
    }, delay)
  }
  return _debounce
}

涉及知识点:

- `apply`的使用
- 函数的参数`arguments`

3.3 优化2:取消功能实现

function debounce (fn, delay) {
  let timer = null
  const _debounce = function (...args) {
    timer && clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
      timer = null
    }, delay)
  }
  // 新增:给_debounce函数绑定一个取消的函数
  _debounce.cancel = function () {
    timer && clearTimeout(timer)
  }
  return _debounce
}

涉及知识点:

- 在不改变返回值的形式下增加返回内容(向后兼容)

3.4 优化3:立即执行功能实现

// 原则:一个函数只做一件事,一个变量只用于记录一个状态
// 新增:新增立即执行标识参数,默认为false
function debounce (fn, delay, immediate = false) {
  let timer = null
  // 新增:使用另一个变量用于记录执行状态
  let isInvoke = false
  const _debounce = function (...args) {
    timer && clearTimeout(timer)
    // 新增:立即执行
    if (immediate && !isInvoke) {
      fn.apply(this, args)
      isInvoke = true
      return
    }
    timer = setTimeout(() => {
      fn.apply(this, args)
      timer = null
      // 新增:状态初始化
      isInvoke = false
    }, delay)
  }
  _debounce.cancel = function () {
    timer && clearTimeout(timer)
    // 新增:状态初始化
    timer = null
    isInvoke = false
  }
  return _debounce
}

优化初始化模块:

function debounce (fn, delay, immediate = false) {
  let timer = null
  let isInvoke = false
  // 优化:封装初始化模块
  const initData = function () {
    timer = null
    isInvoke = false
  }
  const _debounce = function (...args) {
    timer && clearTimeout(timer)
    if (immediate && !isInvoke) {
      fn.apply(this, args)
      isInvoke = true
      return
    }
    timer = setTimeout(() => {
      fn.apply(this, args)
      initData()
    }, delay)
  }
  _debounce.cancel = function () {
    timer && clearTimeout(timer)
    initData()
  }
  return _debounce
}

涉及知识点:

- 一个函数只做一件事,一个变量只用于记录一个状态

3.5 优化4:获取返回值实现

function debounce (fn, delay, immediate = false) {
  let timer = null
  let isInvoke = false
  const initData = function () {
    timer = null
    isInvoke = false
  }
  const _debounce = function (...args) {
    // 新增:返回promise
    return new Promise((resolve, reject) => {
      // 新增:使用try/catch
      try {
        timer && clearTimeout(timer)
        // 新增:变量保存执行结果
        let res = null
        if (immediate && !isInvoke) {
          res = fn.apply(this, args)
          // 新增:返回结果
          resolve(res)
          isInvoke = true
          return
        }
        timer = setTimeout(() => {
          res = fn.apply(this, args)
          // 新增:返回结果
          resolve(res)
          initData()
        }, delay)
      } catch (error) {
        reject(error)
      }
    })
  }
  _debounce.cancel = function () {
    timer && clearTimeout(timer)
    initData()
  }
  return _debounce
}

涉及知识点:

- `Promise`
- `try/catch`

二、throttle(节流)

1.过程

  • 当事件触发时,会执行这个事件的响应函数。
  • 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数。
  • 不管在这个中间有多少次触发这个事件,执行函数的频率总是固定的。

一句话总结:从事件触发开始,一定时间内只执行一次响应函数。

2.应用场景

  • 监听页面的滚动事件。
  • 鼠标移动事件。
  • 用户频繁点击操作按钮。
  • 游戏中的一些设计,如王者荣耀的攻击动作。

3.实现节流

3.1 基本实现(核心)

思考过程:

1. 需要接收什么参数?
- 参数1:回调函数;
- 参数2:间隔时间(`interval`)。
2. 有什么返回值?
- 返回一个函数。
3. 内部怎么实现?
- 满足公式:waitTime = interval - (nowTime - startTime)
- 满足条件:if (waitTime <= 0) {
-             fn()
-             starTime = nowTime
-          }

实现:

// 节流的核心
function throttle (fn, interval) {
  let startTime = 0
  const _throttle = function () {
    // 1.获取当前时间
    const nowTime = new Date().getTime()

    // 2.计算需要等待的时间执行函数
    const waitTime = interval - (nowTime - startTime)
    if (waitTime <= 0) {
      fn()
      startTime = nowTime
    }
  }
  return _throttle
}

涉及知识点:

- 闭包

3.2 优化1:this和参数绑定

function throttle (fn, interval) {
  let startTime = 0
  // 新增:参数传递,不能使用箭头函数
  const _throttle = function (...args) {
    const nowTime = new Date().getTime()
    const waitTime = interval - (nowTime - startTime)
    if (waitTime <= 0) {
      // 新增:绑定this,传递参数
      fn.apply(this, args)
      startTime = nowTime
    }
  }
  return _throttle
}

涉及知识点:

- `apply`的使用
- 函数的参数`arguments`

3.3 优化1:立即执行的控制

可以设置首次是否立即执行。默认是执行。

// 新增:新增立即执行标识参数leading,默认为true
function throttle (fn, interval, leading = true) {
  let startTime = 0
  const _throttle = function (...args) {
    const nowTime = new Date().getTime()
    // 新增:立即执行的控制
    if (!leading && (startTime === 0)) {
      startTime = nowTime
    }
    const waitTime = interval - (nowTime - startTime)
    if (waitTime <= 0) {
      fn.apply(this, args)
      startTime = nowTime
    }
  }
  return _throttle
}

3.4 优化2:尾部执行的控制

常规实现是立即执行和尾部不执行。

待更新......有点复杂

3.5 优化2:取消功能实现

待更新......和尾部执行有关

3.6 优化3:获取返回值实现

function throttle (fn, interval, leading = true) {
  let startTime = 0
  const _throttle = function (...args) {
    // 新增:返回Promise
    return new Promise((resolve, reject) => {
      try {
        const nowTime = new Date().getTime()
        if (!leading && (startTime === 0)) {
          startTime = nowTime
        }
        const waitTime = interval - (nowTime - startTime)
        if (waitTime <= 0) {
          const res = fn.apply(this, args)
          // 新增:返回
          resolve(res)
          startTime = nowTime
        }
      } catch (error) {
        reject(error)
      }
    })
  }
  return _throttle
}

涉及知识点:

- `Promise`
- `try/catch`