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)
  })
})
相关推荐
zwjapple1 小时前
docker-compose一键部署全栈项目。springboot后端,react前端
前端·spring boot·docker
像风一样自由20203 小时前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
aiprtem4 小时前
基于Flutter的web登录设计
前端·flutter
浪裡遊4 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
why技术4 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
GISer_Jing4 小时前
0704-0706上海,又聚上了
前端·新浪微博
止观止4 小时前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
whale fall4 小时前
npm install安装的node_modules是什么
前端·npm·node.js
烛阴5 小时前
简单入门Python装饰器
前端·python
袁煦丞5 小时前
数据库设计神器DrawDB:cpolar内网穿透实验室第595个成功挑战
前端·程序员·远程工作