JavaScript技术JavaScript

javascript 实现防抖函数

2022-01-17  本文已影响0人  踏莎行

有点啰嗦,要源码直接拉倒最后就行啦

什么是防抖??

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间;

举个最常见的例子,就是输入框的联想


?.png

我们在输入搜索关键词的时候,下面就会立刻联想出其他的关键词,它的实现无非就是通过监控文本框的变化,每次输入框发生变化就会重新请求联想出来的关键词再渲染到页面中
而input事件在监控输入框的变化时,哪怕一个字母或者输一个拼音时它都会监控到并执行input对应的回调函数,向后端发送数据请求联想的关键词。
所以这就有些浪费性能了,防抖函数的作用就是在input变化时延迟回调函数的执行,我的例子都是围绕这个例子来说的

实现防抖函数

/*
 * fn: 回调函数
 * delay:延迟执行时间
 */
function debounce(fn, delay){
  
}

那么在使用的时候

input.addEventListener('input', debounce(fn, delay))

这样是不行的,addEventListener里面的回调其实是debounce函数的返回值,所以debounce函数需要在return出去一个函数才能被addEventListener监听回调,不然debounce的返回值就是undefined,
return出去的这个函数才是实现防抖功能的函数

function debounce(fn, delay){
  function _debounce(){}
  return _debounce
}
base.png
function debounce(fn, delay) {
  // 存储定时标识符,以便清除定时器
  let timer = null
  return function _debounce() {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn()
    }, delay)
  }
}
function debounce(fn, delay) {
  let timer = null
  // 所以这个函数就可以使用...运算符收集js自动添加的参数到一个数组中
  return function _debounce(...arg) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      // 通过apply绑定this和传递参数,apply第二个参数正好是传数组嘛
      fn.apply(this, arg)
    }, delay)
  }
}
function debounce(fn, delay, immdiate = false) {
  let timer = null
  let isInvoke = false
  return function _debounce(...arg) {
    if (timer) clearTimeout(timer)
    if (immdiate && !isInvoke) {
      fn.apply(this, arg)
      isInvoke = true
    } else {
      timer = setTimeout(() => {
        fn.apply(this, arg)
        isInvoke = false
      }, delay)
    }
  }
}
function debounce(fn, delay, immdiate = false) {
  let timer = null
  let isInvoke = false
  function _debounce(...arg) {
    if (timer) clearTimeout(timer)
    if (immdiate && !isInvoke) {
      fn.apply(this, arg)
      isInvoke = true
    } else {
      timer = setTimeout(() => {
        fn.apply(this, arg)
        isInvoke = false
        timer = null
      }, delay)
    }
  }

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

  }
  return _debounce
}

那么调用方法就变了
要执行取消,就得先获取到_debounce函数对象

// 获取dom的代码未给出,相信你懂

// 要延迟的函数
const inputChange = function () {
  console.log("输入框发生变化")
}

// 获取_debounce函数对象
const debounceChange = debounce(inputChange, 3000, false)
// 进行input事件监听
inputDom.addEventListener('input', debounceChange )
// 点击按钮取消
buttonDom.addEventListener('click', function () {
  debounceChange.cancel()
})
const inputChange = function () {
  console.log("输入框发生变化")
  return "想不到吧,产品经理让你得到我的返回值"
}

获取fn的返回值有两种方法;
Promise和传入回调函数,经过实践这里使用Promise的方法来实现,用户使用时不太优雅就不说了,说说用回调吧

因为函数中存在定时器,不能使用return返回值的方法,因为函数都执行完了定时器都还没执行,return也不会执行了

debounce函数添加第四个参数resultCallback,类型是一个函数,不管是立即执行还是定时器执行fn都获取到fn的返回值,然后调用传进来的回调resultCallback,将这个结果作为回调resultCallback的参数,然后使用者那么就能拿到这个结果了,下面附上使用方法

function debounce(fn, delay, immdiate = false, resultCallback) {
  let timer = null
  let isInvoke = false
  function _debounce(...arg) {
    if (timer) clearTimeout(timer)
    if (immdiate && !isInvoke) {
      const result = fn.apply(this, arg)
      if (resultCallback && typeof resultCallback === "function") resultCallback(result)
      isInvoke = true
    } else {
      timer = setTimeout(() => {
        const result = fn.apply(this, arg)
        if (resultCallback && typeof resultCallback === "function") resultCallback(result)
        isInvoke = false
        timer = null
      }, delay)
    }

  }

  _debounce.cancel = function () {
    if (timer) clearTimeout(timer)
    timer = null
    isInvoke = false
  }

  return _debounce
}

使用方法

const inputChange = function () {
  console.log("输入框发生变化")
  return "想不到吧,产品经理让你得到我的返回值"
}

const debounceChange = debounce(inputChange, 3000, false, (res) => {
  console.log("fn的返回值是:" + res);
})

inputDom.addEventListener('input', debounceChange)

ok了,哪有写错了,请不吝指正
最后附上完整代码

function debounce(fn, delay, immdiate = false, resultCallback) {
  let timer = null
  let isInvoke = false
  function _debounce(...arg) {
    if (timer) clearTimeout(timer)
    if (immdiate && !isInvoke) {
      const result = fn.apply(this, arg)
      if (resultCallback && typeof resultCallback === "function") resultCallback(result)
      isInvoke = true
    } else {
      timer = setTimeout(() => {
        const result = fn.apply(this, arg)
        if (resultCallback && typeof resultCallback === "function") resultCallback(result)
        isInvoke = false
        timer = null
      }, delay)
    }

  }

  _debounce.cancel = function () {
    if (timer) clearTimeout(timer)
    timer = null
    isInvoke = false
  }

  return _debounce
}
上一篇 下一篇

猜你喜欢

热点阅读