javascript性能优化之防抖和节流

244 阅读2分钟

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

防抖debounce

连续的多次动作内,只执行最后一次动作

:star: 设想下面场景:

  • 有一个提交按钮,每次点击提交之后,就会向后台发起ajax请求

  • 这样设计会有问题:用户在一定时间内多次点击提交,按钮就会像多次向后台发送请求!!

  • 这时候,防抖就非常有用:用户在一定时间内无论多点击多少次提交,按钮只会提交一次

:key: 防抖有两种执行模式:

  1. 在一定动作内执行最后一次动作

  2. 在一定动作内只执行首次动作

    在这里插入图片描述

    流程图地址:processon.com/diagraming/…

    // 防抖函数
    function debounce(func, wait = 300, immediately) {
      let timer, result // timer为定时器,result为传入的函数的返回值
    
      let debounce = function (...args) {
        // 清楚定时器
        if (timer) clearTimeout(timer)
        // 是否立即执行事件
        if (immediately) {
          let callNow = !timer	// 判断是否有无定时器
          timer = setTimeout(() => {
            timer = null	// 把定时器变量清空
          }, wait)
    
          if (callNow) result = func.apply(this, args)
        } else {
          new Promise((resolve, reject) => {
            timer = setTimeout(() => {
              resolve(func.apply(this, args))
              timer = null
            }, wait)
          })
        }
        return result	// 返回值
      }
    
      // 终止防抖事件
      debounce.cancel = function () {
        clearTimeout(timer)
        timer = null
      }
    
      // 返回
      return debounce
    }
    

:european_castle: 案例

应用场景:

  1. scroll事件滚动触发

  2. 搜索框输入查询

  3. 表单验证

  4. 按钮提交事件

  5. 浏览器窗口缩放,resize事件

-节流throttle

连续的多次动作内,在固定周期内,一个周期执行一次动作

:star: 还是举一个例子:

  • 我现在有一个图片预览网站,里面有很多图片,需求是实现图片懒加载的监听
  • 这里的问题是:每次拉动滚动条,就会触发事件去对比图片是否到达可视区域,触发非常频繁
  • 这时候,节流就非常有用:在用户拉动滚动条的时候,我只在500ms这个周期内触发事件

:key: 节流有两种执行模式:

  1. 刚触发事件时会执行一次,后续周期会触发事件,最后一次事件不触发(顾头不顾尾)

  2. 刚触发事件时不执行,后续周期会触发事件,最后一次事件会触发(顾尾不顾头)

  3. 刚触发事件时会执行一次,后续周期会触发事件,最后一次事件会触发(顾头顾尾)

    在这里插入图片描述

    流程图地址:processon.com/diagraming/…

    function throttle(func, wait, opt = {leading: true, trailing: true}) {
      let timer, // 定时器
          old = 0 // 时间戳
    
      return function (...args) {
        let now = new Date().valueOf()
        let later = () => {
          old = new Date().valueOf() // 更新时间戳
          func.apply(this, args)
          timer = null
        }
    
        if(opt.leading === false) old = now  // leading=false 永远不执行第一次
    
        if (now - old > wait) {
          if (timer) {
            clearTimeout(timer) // 下面的定时器时间是不准的,有可能这个执行快
            timer = null
          }
          func.apply(this, args)
          old = now
        } else if (!timer && opt.trailing !== false) {
          timer = setTimeout(later, wait)
        }
      }
    }
    

:european_castle: 案例

  • DOM元素拖拽
  • 飞机每隔一段事件射击导弹
  • 监听scroll滚动事件
  • 计算鼠标移动的距离