【源码共读】 | 跟着underscore学防抖

218 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

【若川视野 x 源码共读】第25期 | 跟着underscore学防抖 点击了解本期详情一起参与

本文涉及

需要防抖的场景

防抖的定义

实现一个简单的防抖

源码分析功能点

源码地址:github.com/jashkenas/u…

使用场景

节流和防抖本质上是用来优化高频率执行事件的一种手段

高频的执行事件,如浏览器的 resizescrollkeypressmousemove 等事件,当这些事件触发时,会不停的调用回调函数


我们以地铁为例,

  • 节流就是只要人一上车,开始倒计时,时间一到就走

  • 防抖就是有人上车,开始倒计时,在计时过程中如果还有人上车,则重新开始计时,直到 没有人上车 && 计时器到点

我们尝试着自己实现一个debounce函数

// debounce.js
export function debounce(func, wait) {
  let timeout, result
  return function (...args) {
    if (timeout) {
      clearTimeout(timeout)
    }
    timeout = setTimeout(() => {
      result = func.apply(this, args)
    }, wait)
    return result
  }
}

测试用例

// debounce.test.js
import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest'
import { debounce } from './debounce'

describe('test debounce', () => {
  beforeEach(() => {
    // 使用 mock 时间
    vi.useFakeTimers()
  })

  afterEach(() => {
    // 重置时间
    vi.useRealTimers()
  })

  it('5秒内只执行一次', () => {
    const mock = vi.fn(() => console.log(`Hello JueJin`))
    const debounced = debounce(mock, 1000 * 5)
    debounced()
    debounced()
    vi.runAllTimers()
    expect(mock).toHaveBeenCalledTimes(1)
  })
})

image-20220919140617616


源码分析

我们来看下源码是怎么实现的

import restArguments from './restArguments.js'
import now from './now.js'

export default function debounce(func, wait, immediate) {
  var timeout, previous, args, result, context

  var later = function () {
    // 是否超时
    // 没有超时:重置计时器并设定此时的时间为 wait - passed
    // 超时:清空计时器,执行func
    var passed = now() - previous
    if (wait > passed) {
      timeout = setTimeout(later, wait - passed)
    } else {
      timeout = null
      if (!immediate) result = func.apply(context, args)
      // 避免func中再次调用debounced
      if (!timeout) args = context = null
    }
  }

  var debounced = restArguments(function (_args) {
    context = this
    args = _args
    previous = now()
    if (!timeout) {
      timeout = setTimeout(later, wait)
      // 立即执行func
      if (immediate) result = func.apply(context, args)
    }
    return result
  })
  
    // 手动取消
  debounced.cancel = function () {
    clearTimeout(timeout)
    timeout = args = context = null
  }

  return debounced
}

源码上考虑到了几点:

  • 事件可以立即执行
  • func中再次调用debounce的问题
  • 可以手动取消

总结

源码考虑到的使用场景更多,涉及到的功能点也更加完善。

  • 需要防抖的场景

    • 优化高频率执行事件的一种手段
  • 防抖的定义

    • 在事件触发后,在一定时间后执行回调函数,如果在一定时间内再次触发,则重新计算时间
  • 实现一个简单的防抖

  • 源码分析功能点

参考文章:

JavaScript专题之跟着underscore学防抖