JavaScript性能优化(一)实现防抖、节流

101 阅读2分钟

highlight: a11y-light theme: fancy

一、防抖

在一些高频率事件触发的场景下我们不希望对应的事件处理函数多次执行,我们只希望识别一次点击,可以人为的是第一次或者是最后一次,简单的来说就是多次点击,只触发一次
场景:

  • 滚动事件
  • 输入的模糊匹配
  • 轮播图切换
  • 点击操作
  • ......

废话不多说,直接上代码

     /**
       * handle 最终需要执行的事件监听
       * wait 事件触发之后多久开始执行
       * immediate 控制是执行第一次还是最后一次,false 执行最后一次
       */
 function myDebounce(handle, wait, immediate) {
        // 1. 参数类型判断及默认值处理
        if (typeof handle !== "function")
          throw new Error("handle must be an function");
        if (typeof wait === "undefined") wait = 300;
        if (typeof wait === "boolean") {
          immediate = wait;
          wait = 300;
        }
        if (typeof immediate !== "boolean") immediate = false;

        // 防抖
        let timer = null;
        return function proxy(...args) {
          const self = this
          const init = immediate && !timer
          clearTimeout(timer)
          timer = setTimeout(() => {
            timer = null
            !immediate && handle.call(self, ...args)
          }, wait)

          // 如果当前传递进来的是 true 就表示我们需要立即执行
          // 如果想要实现只在第一次执行,那么可以添加上 timer 为 null 做为判断
          // 因为只要 timer 为 Null 就意味着没有第二次....点击
          init && handle.call(self, ...args)
        };
      }
  1. 首先我们需要对传进来的参数进行判断处理。
    (1)如果handel不是一个函数,抛出异常。
    (2)如果wait为undefined,我们需要给wait设置一个默认值。
    (3)wait为boolean类型,说明传参时wait没有传参,直接传入的 immediate,所以我们把wait的值赋值给immediate,再给wait设置默认值。
    (4)immediate不是boolean类型,设置默认值为false。
  2. 传入参数的类型的两种情况
    (1)第一种immediate传false需要控制最后一次事件触发,第二种传true控制第一次就触发。
    (2)false:通过传入的immediate进行判断即可,每一次事件触发都会清除定时器。
    (3)true: 因为想要实现只在第一次执行,那么可以添加上 timer 为 null 做为判断,每一次点击时还会判断timer,因为只有第一次为null,所以只有第一次时才会执行。

二、节流

在自定义的一段时间内让事件进行触发,在一个时间段,只触发一次
场景:

  • 监听滚动事件
  • 拖拽事件...
function myThrottle(handle, wait) {
      if (typeof handle !== 'function') throw new Error('handle must be an function')
      if (typeof wait === 'undefined') wait = 400

      let previous = 0  // 定义变量记录上一次执行时的时间 
      let timer = null  // 用它来管理定时器

      return function proxy(...args) {
        let now = new Date() // 定义变量记录当前次执行的时刻时间点
        let self = this
        let interval = wait - (now - previous)

        if (interval <= 0) {
          // 此时就说明是一个非高频次操作,可以执行 handle 
          clearTimeout(timer)
          timer = null
          handle.call(self, ...args)
          previous = new Date()
        } else if (!timer) {
          // 当我们发现当前系统中有一个定时器了,就意味着我们不需要再开启定时器
          // 此时就说明这次的操作发生在了我们定义的频次时间范围内,那就不应该执行 handle
          // 这个时候我们就可以自定义一个定时器,让 handle 在 interval 之后去执行 
          timer = setTimeout(() => {
            clearTimeout(timer) // 这个操作只是将系统中的定时器清除了,但是 timer 中的值还在
            timer = null
            handle.call(self, ...args)
            previous = new Date()
          }, interval)
        }
      }

    }