JS防抖节流——电梯关门与地铁发车

类比

  • 防抖:电梯关门。最后一个人进入后,等待一段时间后才关门
  • 节流:地铁发车。每隔固定时间发一班车,不等人。

作用

间接影响 TTI(time to interactive),减少不必要的JS执行,释放主线程 => 加快页面响应。

防抖:通过将【一段时间内】,【高频且耗时的操作】合并为一次执行。

节流:【固定频率】执行操作,稀释执行密度。

应用场景

防抖:【搜索框用户输入联想】【窗口频繁的resize事件】

节流:【滚动事件监听】【动画渲染,保持 16ms 一帧的节奏】【用户滚动相关的一系列事件】

具体实现

防抖(debounce)

javascript 复制代码
export const debounce = (fn, delay) => {
  let timer = null

  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(null, args)
    }, delay)
  }
}

防抖(立即执行)

增加一个第一次立即执行的标识即可

javascript 复制代码
export const debounceImmediate = (fn, delay) => {
  let timer = null
  let firstFlag = true
  return function (...args) {
    if (firstFlag) {
      fn.apply(this, args)
      firstFlag = false
      return
    }
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, args)
      timer = null
    }, delay)
  }
}

节流(throttle)date版本

因为时间比较符合直觉,一般用 date 实现。

javascript 复制代码
export const throttle = (fn, delay) => {
  let lastTime = 0
  return function (...args) {
    const now = +new Date()
    if (now - lastTime >= delay) {
      fn.apply(this, args)
      lastTime = now
    }
  }
}

节流(throttle)timer版本

javascript 复制代码
export const throttleTimer = (fn, delay) => {
  let timer = null
  return function (...args) {
    if (timer) {
      return
    }
    timer = setTimeout(() => {
      fn.apply(this, args)
      timer = null
    }, delay)
  }
}

节流尾部调用版本

记录用户最后一次滚动常用此版本

除第一次外,都往加 timer的方向思考(因为除第一次,每一次都有可能是最后一次)

javascript 复制代码
export const throttleTrailing = (fn, delay, trailing = true) => {
  let lastTime = 0
  let timer = null
  return function (...args) {
    const curDate = +new Date()
    if (curDate - lastTime >= delay) {
      lastTime = curDate
      if (timer) {
        timer = null
        clearTimeout(timer)
      }
      fn.apply(this, args)
      return
    }
    if (!timer && trailing) {
      timer = setTimeout(
        () => {
          fn.apply(this, args)
          timer = null
        },
        delay - (curDate - lastTime),
      )
    }
  }
}

上述所有函数的测试代码

mocha+ chai

scss 复制代码
import {
  debounce,
  debounceImmediate,
  throttle,
  throttleTimer,
  throttleTrailing,
} from '../src/js/throttle-debounce.js'
import { expect } from 'chai'
import { describe } from 'mocha'

let count = 0
let trailingCount = 0

describe('throttle-debounce', () => {
  beforeEach(() => {
    count = 0
  })
  it('debounce normal', (done) => {
    const debounceFn = debounce(() => {
      ++count
    }, 100)
    debounceFn()
    debounceFn()
    debounceFn()
    setTimeout(() => {
      expect(count).to.equal(1)
      done()
    }, 500)
  })

  it('debounce immediate', (done) => {
    const debounceImmediateFn = debounceImmediate(() => {
      ++count
    }, 100)
    debounceImmediateFn()
    debounceImmediateFn()
    debounceImmediateFn()
    setTimeout(() => {
      expect(count).to.equal(2)
    }, 200)
    done()
  })

  it('throttle normal', (done) => {
    const throttleFn = throttle(() => {
      ++count
    }, 300)
    throttleFn() // 1
    setTimeout(() => {
      throttleFn()
      setTimeout(() => {
        throttleFn() // 2
        done()
      }, 300)
    }, 200)
  })

  it('throttle timer', (done) => {
    const throttleFn = throttleTimer(() => {
      ++count
    }, 300)
    throttleFn() // 1
    setTimeout(() => {
      throttleFn()
      setTimeout(() => {
        throttleFn() // 2
        done()
      }, 300)
    }, 200)
  })

  it('throttle trailing', (done) => {
    const throttleFn = throttleTrailing(() => {
      ++trailingCount
    }, 300)
    throttleFn() // 1
    setTimeout(() => {
      throttleFn() // 200 ms 之后又进行了执行,应该算是最后一次,理应执行
      setTimeout(() => {
        expect(trailingCount).to.equal(2)
        done()
      }, 500)
    }, 200)
  })
})
相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax