ZooMze's World

vuePress-theme-reco ZooMze    2018 - 2021
ZooMze's World ZooMze's World

Choose mode

  • dark
  • auto
  • light
主页
分类
  • 基础
  • 备忘
  • 教程
  • 扩展
  • 框架
  • 组件
  • 季度分享
标签
时光轴
GitHub
author-avatar

ZooMze

35

Article

23

Tag

主页
分类
  • 基础
  • 备忘
  • 教程
  • 扩展
  • 框架
  • 组件
  • 季度分享
标签
时光轴
GitHub
  • 节流 Throttle

    • 前言
      • 节流的定义
        • 定时器的实现
          • 时间戳的实现
            • 魔法卡: 融合

            节流 Throttle

            vuePress-theme-reco ZooMze    2018 - 2021

            节流 Throttle


            ZooMze 2020-07-08 JavaScript

            # 前言

            上一篇说到 防抖 Debounce, 对于性能优化还有另一种方案: 那就是本篇内容介绍的 节流 Throttle, 节流与防抖有着共同的目的, 都是防止事件一直触发, 但是他们存在着根本上的差异, 一个是直接阻止, 一个是减少数量。

            # 节流的定义

            同防抖一样, 都是用来处理连续的事件触发, 但是节流是按照单位时间 n 秒进行限制, 以 n 秒为间隔来触发事件。

            意思就是说在这 n 秒内允许触发有且只有 1 次, 想要再次触发得等下一个 n 秒开始!

            这里要考虑一下边缘情况: 首次是否执行? 计时结束后是否还要再执行一次?

            这里可以用两个布尔变量来控制这种情况:

            • lead 首次是否执行
            • tail 结束后是否在执行一次

            提到时间, 你是不是又想到了防抖里用过的 setTimeout 了? 那么我们就从定时器的实现开始

            # 定时器的实现

            思路很简单: 创建一个定时器, 如果再次有同样的的事件触发, 则根据计时器的情况决定是否能继续执行, 然后直到定时器计时结束, 清空计时器, 等待下一次事件触发:














             
             
             
             
             
             
             




            var container = document.getElementsByTagName('body')[0]
            var count = 1
            
            function getUserAction() {
              console.log(`这是第${count++}次调用这个事件了!`);
            }
            
            function throttle(func, wait) {
              var timeout
            
              return function() {
                var context = this
                var args = arguments
                if(!timeout) {
                  timeout = setTimeout(function() {
                    timeout = null // 清空定时器即可
                    func.apply(context, args) // 希望你没忘记这儿, 忘了的话去看看防抖的this指向小节
                  }, wait)
                }
              }
            }
            
            container.onmousemove = throttle(getUserAction, 1000);
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23

            这里是沿用了防抖的例子, 现在功能写好了, 鼠标在屏幕上移动, 事件永远保持每秒一次的调用, 成功!

            小瑕疵

            事件不会在刚开始(鼠标开始移动时)就执行, 他只会在开始后的第1秒后才开始, 这就是 节流的定义 中提到的 lead, 我们后面会解决它

            # 时间戳的实现

            现在来换个思路, 用时间戳来实现: 事件开始触发时记录一个时间戳 previous, 之后每次执行重复事件时校验新时间戳 now 与 previous 的间隔是否是处于 n 秒以外, 也就是 now - previous > n , 如果是则重写时间戳 previous, 重新循环上述过程。

            说完就开整:










             




            function throttle(func, wait) {
              var previous = 0
              
              return function() {
                var now = +new Date()
                var context = this;
                var args = arguments;
                if(now - previous > wait) {
                  func.apply(context, args)
                  previous = now // 重写previous
                }
              }
            }
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13

            现在可以实现需求啦! 但是同样有个小问题

            小瑕疵

            如果间隔给的较长能感知到, 如果在间隔时间还没结束就停止触发的话, 那么真正意义上的最后一次其实并未真的执行, 而是在前一个单位时间后执行, 例如 在第3.6s停止触发, 其实真正只执行了第3秒的那一次事件。

            # 魔法卡: 融合

            上面两种实现方式都有点瑕疵, 用户比任何人都挑剔: 我希望我开始移动鼠标时就开始执行第一次, 当我在任意时间结束触发时就在此刻再执行一次!

            那就把刚刚两种实现方式融合到一起好了!

            function throttle(func, wait) {
              var timeout, context, args, result;
              var previous = 0;
            
              // 定义一个需要被延迟执行的函数
              var later = function() {
                previous = +new Date();
                timeout = null;
                func.apply(context, args)
              };
            
              var throttled = function() {
                  var now = +new Date();
                  // 下次触发 func 剩余的时间
                  var remaining = wait - (now - previous);
                  context = this;
                  args = arguments;
                    // 如果没有剩余的时间了或者你改了系统时间
                  if (remaining <= 0 || remaining > wait) {
                    if (timeout) {
                        clearTimeout(timeout);
                        timeout = null;
                    }
                    previous = now;
                    func.apply(context, args);
                  } else if (!timeout) {
                    timeout = setTimeout(later, remaining);
                  }
              };
              return throttled;
            }
            
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31