傻傻分不清楚的防抖和节流

362 阅读4分钟

前言

在实际开发过程中, 我们在进行窗口的resizescroll, input的内容校验、表单提交按钮的触发等操作时,如果事件处理函数的调用频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。

此时我们可以采用防抖(debouce)和节流(throttle)的方式来减少调用频率,同时又不影响实际效果。

函数防抖

定义

当持续触发事件时,在一定时间内没有再触发事件, 则执行一次处理函数,如果设定的时间未到来前又触发了事件,则重新计时。

实例解析

如下图, 当我们持续触发scroll事件,并不执行handle函数,当1000毫秒内没有再触发scroll事件时, 才开始执行一次handle函数(即延时触发scroll事件)。

截屏2023-04-04 11.25.55.png

应用场景

  • 在点赞、输入框校验、取消点赞、创建订单等发送网络请求的时候,如果我们连续点击按钮,可能会发送多次请求。这个对于后台来说是不允许的。(涉及到会频繁调用接口的情景下, 延迟时间一般设置300ms)
  • 在鼠标每次 resize/scroll 触发统计事件

函数封装

es5写法

/**
* 防抖函数
* @param {function} func 需要执行的函数
* @param {Number} wait 延迟时间
* @param {Boolean} immediate 是否立即执行
*/
function debounce (func, wait = 0, immediate) {
  var timer

  if (typeof func !== 'function') {
    throw new TypeError('debounce的第一个参数类型为funtion')
  }

  return function () {
    var context = this
    var args = arguments

    if (timer) clearTimeout(timer) // 清除定时器

    // 立即执行
    if (immediate) {
      var callNow = !timer

      timer = setTimeout(function () {
        func.apply(context, args)
      }, wait)

      callNow && func.apply(context, args)
    } else {
      // 非立即执行
      timer = setTimeout(function () {
        func.apply(context, args)
      }, wait)
    }
  }
}

es6(剪头函数写法)

/**
* 防抖函数
* @param {function} func 需要执行的函数
* @param {Number} wait 延迟时间
* @param {Boolean} immediate 是否立即执行
*/
function debouce(func, wait = 0, immediate) {
    // 判断传递的参数类型
    if(typeof func !== 'function') {
        throw new TypeError('debounce的第一个参数类型为funtion')
    }
    
    let timer;
    return (...arg) => {
        // 清除定时器
        timer && clearTimeout(timer)
        // 立即执行
        if(immediate) {
            const callNow = !timer
            timer = setTimeout(() => {
                func(...arg)
            }, wait)
            callNow && func(...arg)
        } else {
        // 非立即执行
            timer = setTimeout(() => {
                func(...arg)
            }, wait)
        } 
    }
}

使用

// 处理函数
function handle() {}

// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));

函数节流

定义

当持续触发事件时, 在一定时间内只调用一次事件处理函数。

实例解析

如下图,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。

截屏2023-04-04 11.34.14.png

简单粗暴的解释就是在一段时间内不管你触发多少次事件, 我就只执行一次事件处理函数。

应用场景

  • DOM 元素的拖拽功能实现(mousemove)
  • 搜索联想(keyup)
  • 计算鼠标移动的距离(mousemove)
  • Canvas 模拟画板功能(mousemove)
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  • 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次.

函数封装

es5-时间戳-非立即执行版本

/**
 * 节流函数
 * @param {*} func 事件处理函数
 * @param {*} wait 延迟事件
 * @returns
 */
function throttle (func, wait = 0) {
  var prev = Date.now()

  return function () {
    var context = this
    var args = arguments

    var now = Date.now()
    if (now - prev >= wait) {
      func.apply(context, args)
      prev = Date.now()
    }
  }
}

es5-定时器版本

/**
 * 节流函数
 * @param {*} func 事件处理函数
 * @param {*} wait 延迟事件
 * @returns
 */
function throttle (func, wait = 0) {
  var timer = null

  return function () {
    var context = this
    var args = arguments

    if (!timer) {
      timer = setTimeout(function () {
        func.apply(context, args)
        timer = null
      }, wait)
    }
  }
}

总结

函数防抖: 将几次操作合并为一次操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

函数节流: 使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。

区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。