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)
  })
})
相关推荐
Liu.7745 分钟前
vue3组件之间传输数据
前端·javascript·vue.js
|晴 天|5 分钟前
前端闭包:从概念到实战,解锁JavaScript高级技能
开发语言·前端·javascript
开发者小天7 分钟前
react的拖拽组件库dnd-kit
前端·react.js·前端框架
用户44455436542616 分钟前
在Android开发中阅读源码的指导思路
前端
用户542778485154018 分钟前
ESM 模块(ECMAScript Module)详解
前端
全栈前端老曹33 分钟前
【ReactNative】核心组件与 JSX 语法
前端·javascript·react native·react.js·跨平台·jsx·移动端开发
用户542778485154041 分钟前
JavaScript 闭包详解:由浅入深掌握作用域与内存管理的艺术
前端
小小黑00741 分钟前
快手小程序-实现插屏广告的功能
前端·javascript·小程序
用户542778485154042 分钟前
闭包在 Vue 项目中的应用
前端
TG:@yunlaoda360 云老大1 小时前
配置华为云国际站代理商OBS跨区域复制时,如何编辑委托信任策略?
java·前端·华为云