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)
  })
})
相关推荐
啃火龙果的兔子3 分钟前
安全有效的 C 盘清理方法
前端·css
海天胜景7 分钟前
vue3 数据过滤方法
前端·javascript·vue.js
天生我材必有用_吴用12 分钟前
深入理解JavaScript设计模式之策略模式
前端
海上彼尚14 分钟前
Vue3 PC端 UI组件库我更推荐Naive UI
前端·vue.js·ui
述雾学java14 分钟前
Vue 生命周期详解(重点:mounted)
前端·javascript·vue.js
洛千陨20 分钟前
Vue实现悬浮图片弹出大图预览弹窗,弹窗顶部与图片顶部平齐
前端·vue.js
咚咚咚ddd22 分钟前
微前端第四篇:qiankun老项目渐进式升级方案(jQuery + React)
前端·前端工程化
螃蟹82725 分钟前
作用域下的方法如何调用?
前端
独立开阀者_FwtCoder27 分钟前
TypeScript 杀疯了,开发 AI 应用新趋势!
前端·javascript·github
汪子熙33 分钟前
QRCode.js:一款轻量级、跨浏览器的 JavaScript 二维码生成库
前端·javascript·面试