Skip to content
Inspire - Capo Productions

防抖、节流

当持续高频触发事件,如果回调函数很复杂或者有 Ajax 请求,就会导致响应跟不上触发,出现页面卡顿,假死现象。

概念描述场景
防抖憋到最后一次按钮防误触
节流按节奏一直做接口限流

防抖

事件频繁触发时,只在“最后一次触发后的一段时间”才执行。

如下图,持续触发 scroll 事件时,并不执行 handle 函数,当 1000 毫秒内没有触发 scroll 事件时,才会延时触发 scroll 事件。

Preview

应用场景

  • 搜索框输入查询
  • 窗口大小调整
  • 按钮点击

实现原理

简版

用于理解防抖函数原理。

js
function debounce(func, wait) {
  let timeout

  return function (...args) {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

const btn = document.getElementById('btn')
btn.addEventListener('click', debounce(() => {
  console.log('点击')
}, 2000))

完整版

添加对 thisevent 对象的正确解析,以及立刻执行。

js
/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表立即执行,false 表非立即执行
 */
function debounce(func, wait, immediate) {
  let timeout

  return function () {
    const context = this
    const args = argments

    if (timeoue)
      clearTimeOut(timeout)
    if (immediate) {
      const callNow = !timeout
      timeout = setTimeout(() => {
        timeout = null
      }, wait)
      if (callNow)
        fnc.apply(context, args)
    }
    else {
      timeout = setTimeout(() => {
        func.apply(context, args)
      }, wait)
    }
  }
}

使用 lodash.debounce

例如,下面是一个带有搜索框的页面,并且需要在用户停止输入 500 毫秒后才开始搜索:

js
import { debounce } from 'lodash-es'

// 根据关键词进行搜索
function search(keyword) {
  console.log(`Searching for '${keyword}'...`)
}

const inputEl = document.getElementById('search-input')

// 创建 debounce 函数,最多每 500 毫秒执行一次 search 函数
const debouncedSearch = _.debounce(search, 500)

// 监听 input 变化,如果有变化则调用 debouncedSearch 函数
// 每当出现输入变化时,我们会将输入内容传递给 `debouncedSearch` 函数,这个函数会将搜索操作延迟 500 毫秒后执行。因此,只有用户停止输入 500 毫秒之后,才会真正执行搜索操作
inputEl.addEventListener('input', (evt) => {
  const keyword = evt.target.value.trim()
  debouncedSearch(keyword)
})

节流

事件频繁触发时,每隔一段时间执行一次。

就像水龙头放水,如果阀门开的很大,水会一直流出来,如果阀门开的很小,水会一滴一滴的流出来。

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

Preview

应用场景

鼠标移动事件 onmousemove, 滚动滚动条事件 onscroll,窗口大小改变事件 onresize

实现原理

js
function throttle(fn, wait) {
  let lastTime = Date.now()

  return function (...args) {
    const now = Date.now()

    if (now - lastTime > wait) {
      func.apply(this, args)
      lastTime = now
    }
  }
}

使用 lodash.throttle

例如,下面是一个点击按钮时每隔 1 秒输出一次 log 的示例:

js
function log() {
  console.log('Clicked!')
}

const btnEl = document.getElementById('click-button')

// 创建 throttle 函数,最多每 1 秒执行一次 log 函数
const throttledLog = _.throttle(log, 1000)

// 监听 click 事件,如果有点击则调用 throttledLog 函数
// 每当用户点击按钮时,我们会调用 `throttledLog` 函数,这个函数会通过限制函数的执行频率,保证每隔 1 秒钟输出一条日志。即使用户连续点击按钮,也只有第一次点击可以触发函数的执行,后续的点击都会被忽略
btnEl.addEventListener('click', (evt) => {
  throttledLog()
})