手撕防抖和节流

122 阅读3分钟

前言

本篇文章主要记录学习防抖、节流封装的过程,从简单到复杂封装,在此分享给掘友们学习,共同进步!

一. 防抖

封装之前简单介绍一下什么是防抖

当我们输入框输入内容进行搜索,输入的过程中会不断的根据输入的内容,向后台发起请求,这时候会造成性能压力,就会需要用到防抖来处理,在我们输入完内容后,确定最后不再输入,等待几秒后再发起请求。

1.简单封装:

此方法主要是记录计时器的时间,如果不断地输入内容则会一直调用取消上一次定时器时间,直到不输入为止。

function debounce(fn, delay) {
  // 1.定义一个定时器, 保存上一次的定时器
  let timer = null;
  console.log("初始值", timer);

  const _debounce = function (...args) {
    // 取消上一次的定时器
    console.log("hhh", timer);
    if (timer) clearTimeout(timer);
    // 延迟执行
    timer = setTimeout(() => {
      // 外部传入的真正要执行的函数
      fn.apply(this, args);
    }, delay);
  };

  return _debounce;
}

上方代码可点击 code.juejin.cn/pen/7185375…

2. 复杂封装

如果需要控制第一次是否立即执行、执行的规程中取消方法,返回传入执行函数的返回值,则修改为如下:

function debounce(fn, delay, immediate = false, resultCallback) {
  // 1.定义一个定时器, 保存上一次的定时器
  let timer = null
  let isInvoke = false

  // 2.真正执行的函数
  const _debounce = function(...args) {
    // 取消上一次的定时器
      if (timer) clearTimeout(timer)

      // 判断是否需要立即执行
      if (immediate && !isInvoke) {
        const result = fn.apply(this, args)
        if (resultCallback) resultCallback(result)
        isInvoke = true
      } else {
        // 延迟执行
        timer = setTimeout(() => {
          // 外部传入的真正要执行的函数
          const result = fn.apply(this, args)
          if (resultCallback) resultCallback(result)
          isInvoke = false
          timer = null
        }, delay)
      }
  }

  // 封装取消功能
  _debounce.cancel = function() {
    if (timer) clearTimeout(timer)
    timer = null
    isInvoke = false
  }

  return _debounce
}

调用方式:

    const inputEl = document.querySelector("input")
    let count = 0
    const inputChange = function(event) {
      console.log(`发送了第${++count}次网络请求`, this, event)
      // 返回值
      return "aaaaaaaaaaaa"
    }

    // 防抖处理
    const debounceChange = debounce(inputChange, 3000, false, (res) => {
      console.log("拿到真正执行函数的返回值:", res)
    })

上方为什么会定义一个变量isInvoke,如果只控制immediate,会导致每次多次输入搜索内容,则会导致下方直接不执行下方else函数,所以需定义该变量控制。

二. 节流

节流简单来说举例:当你打游戏发射时,无论你点击多少下,都控制该时间段内只执行一次发射。

1.简单封装

function throttle(fn, interval, options) {
  // 1.记录上一次的开始时间
  let lastTime = 0

  // 2.事件触发时, 真正执行的函数
  const _throttle = function() {

    // 2.1.获取当前事件触发时的时间
    const nowTime = new Date().getTime()

    // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
    const remainTime = interval - (nowTime - lastTime)
    if (remainTime <= 0) {
      // 2.3.真正触发函数
      fn()
      // 2.4.保留上次触发的时间
      lastTime = nowTime
    }
  }

  return _throttle
}

2. 复杂封装

第一次不执行:该方法默认是立即执行的(new Date().getTime()默认很大),因为如果希望第一次不执行,需定义一个变量lastTime, 记录上一次触发时间,设置lastTime= 0,即第一次lastTime = nowTimeleading=false,则第一次不会执行

最后一次执行:加上timer变量控制是否有定时器,没有才会执行if (trailing && !timer)下函数

函数返回值: 上方节流返回值是穿的callback,本次使用promise返回结果

function throttle(fn, interval, options = { leading: true, trailing: false }) {
  // 1.记录上一次的开始时间
  const { leading, trailing } = options
  let lastTime = 0
  let timer = null 

  // 2.事件触发时, 真正执行的函数
  const _throttle = function(...args) {

    //这边用promise获取传入的函数返回值
    return new Promise((resolve, reject) => {
      // 2.1.获取当前事件触发时的时间
      const nowTime = new Date().getTime()
      if (!lastTime && !leading) lastTime = nowTime

      // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
      const remainTime = interval - (nowTime - lastTime)
      if (remainTime <= 0) {
        if (timer) {
          clearTimeout(timer)
          timer = null
        }

        // 2.3.真正触发函数
        const result = fn.apply(this, args)
        resolve(result)
        // 2.4.保留上次触发的时间
        lastTime = nowTime
        return
      }
      

      if (trailing && !timer) {
        timer = setTimeout(() => {
          timer = null //设置为初始值
          lastTime = !leading ? 0: new Date().getTime()
          const result = fn.apply(this, args)
          resolve(result)
        }, remainTime)
      }
    })
  }

  _throttle.cancel = function() {
    if(timer) clearTimeout(timer)
    timer = null
    lastTime = 0
  }

  return _throttle
}

上方代码可点击code.juejin.cn/pen/7187223…