【JavaScript】函数节流与函数防抖

函数节流(throttle)

函数节流:在指定的间隔时间内只执行一次

有个需要频繁触发函数,出于优化性能角度,在规定时间内,只让函数触发的第一次生效,后面不生效。

比如下面的例子,在不加函数节流的时候,每当滚动条滚动的时候都会触发一次,造成大量的性能浪费

// 未添加节流函数
document.onscroll = function () {
  console.log('scroll事件被触发了')
}

添加了节流函数后

// 添加了节流函数
document.onscroll = throttle(function () {
  console.log('scroll事件被触发了')
}, 300)

具体代码实现

/**
 * @description 函数节流
 * @param {Function} fn 需要执行函数节流的函数
 * @param {Number} interval 指定间隔时间
 */
function throttle(fn, interval = 300) {
  let canRun = true // 通过闭包保存一个标记
  return function () {
    if (!canRun) return // 第一次调用执行
    canRun = false // setTimeout未执行时,后续fn函数调用都不会再执行
    // setTimeout 定时器延时执行
    setTimeout(() => {
      fn.apply(this, arguments)
      canRun = true // 标记为true,节流完成
    }, interval)
  }
}

代码解释

原理:将即将被执行的函数用setTimout延迟一段时间执行,如果该次延迟执行还没有完成,则忽略接下来调用该函数的请求。

简单来说,函数的节流就是通过闭包保存一个标记(canRun = true), 在函数的开头判断这个标记是否为true,如果这个标记为true的话就继续执行,否则就return掉,判断完标记后立即把这个标记设置为false,然后把外部传入的函数的执行包在一个setTimout中,最后在定时器执行完毕之后再把标记设置为true,表示本次延迟执行完毕,可以执行下一次循环了。当定时器还未执行完毕的时候,canRun这个标记始终未false,故在开头的判断中总是被return掉,函数并未执行

应用场景

监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次等

函数防抖(debounce)

函数防抖: 一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效。

比如点击一个按钮,每点击一次就会触发一次事件,在没有加防抖函数的情况下,快速点击会导致多次触发

// 未加防抖函数
document.getElementById('btn').onclick = function(){
  console.log('我被点击了');
}

在加了防抖函数后,只会在规定时间后触发一次

// 加了防抖函数
document.getElementById('btn').onclick = debounce(function(){
  console.log('我被点击了');
},300)

具体代码实现

/**
 * @description 函数防抖
 * @param {Function} fn 需要执行函数防抖的函数
 * @param {Number} interval 指定间隔时间
 */
function debounce(fn, interval = 300) {
  let timeout = null // 通过闭包保存一个标记
  return function () {
    clearTimeout(timeout) // 把前一个定时器去掉
    // 又创建一个新的定时器
    timeout = setTimeout(() => {
      fn.apply(this, arguments) // 指定的时间间隔之后运行fn
    }, interval)
  }
}

代码解释

其原理就第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器,然后延迟一定时间再执行。

应用场景

文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)

更新代码 1

function showTop() {
  var scrollTop = document.body.scrollTop || document.documentElement.scrollTop
  console.log('滚动条位置:' + scrollTop)
}
// window.onscroll = debounce(showTop, 1000)
window.onscroll = throttle(showTop, 1000)

function debounce(fn, delay) {
  let timer = null
  return function () {
    let context = this
    let args = arguments
    if (timer) {
      clearTimeout(timer)
      timer = setTimeout(fn, delay) // 重新计时
    } else {
      timer = setTimeout(() => {
        fn.apply(context, args)
      }, delay)
    }
  }
}
function throttle(fn, delay) {
  let flag = false
  return function () {
    if (flag) {
      return false
    }
    let context = this
    let args = arguments
    flag = true
    setTimeout(() => {
      fn.apply(context, args)
      // 本次执行完再执行
      flag = false
    }, delay)
  }
}

更新代码 2

const throttle = (fn, time) => {
  let tiemr = null
  return (...args) => {
    if (tiemr) {
      return
    }
    fn.call(undefined, ...args)
    tiemr = setTimeout(() => {
      tiemr = null
    }, time)
  }
}

const debounce = (fn, time) => {
  let timer = null
  return (...args) => {
    if (timer) {
      clearTimeout(timer)
    }

    timer = setTimeout(() => {
      fn.call(undefined, ...args)
    }, time)
  }
}

posted @ 2020-07-21 23:21  努力挣钱的小鑫  阅读(348)  评论(2编辑  收藏  举报