带你“深入”节流

2023-03-04 17:55:11 浏览数 (3)

说白话:

节流就像我们喝水上厕所一样... Emm咱们还是换个例子吧。。。

比如说吃饭。吃饭说明规定,五个小时吃一次。吃了一次饭,小狗蹦蹦哒哒地玩了五个小时。五个小时一到, 小狗再回来吃饭。依次类推,每五小时回来吃一次饭。

说人话:

定义:如果持续触发事件,单位时间内执行一次函数。

节流模样:

代码语言:javascript复制
<!-- <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.1/underscore-umd-min.js"></script>
<div class="box"></div>
<script>
let obox = document.querySelector('.box')
function todo(e) {
  console.log(this, e);
}
let throttleFn = _.throttle(todo, 1000)
obox.onmousemove = throttleFn
</script>

1.gif

我们可以直接使用lodash.js或者underscore.js中的节流函数,查看节流的效果。我的鼠标一直在div中移动,节流就会每个一段时间打印一次。

扒开面具见真相

对于我们而言,光知其然,是远远不够的;我们更要知其所以然! 老样子,咱们给自己上一课吧!

对于节流函数,与防抖的形参类似。它有三个参数:节流的执行函数fn;需要延迟的毫秒数wait;第三个参数options禁用第一次首先执行可以传递{leading: false},禁用最后一次执行{trailing: false}

那么根据第三个,可以传递两个属性参数。可能会出现三种情况:

  • 第一次先执行,最后一次不执行
  • 第一次不执行,最后一次执行
  • 第一次先执行,最后一次也执行

有头的

第一次先执行。

代码语言:javascript复制
function throttle(fn, wait) {
  let previous = 0
  return function (...args) {
    let now = new Date().getTime() || Date.now()
    if (now - previous > wait) {
      fn.apply(this, args)
      previous = now
    }
  }
}

我们可以使用时间戳的方法去实现第一次触发先执行。先记录默认时间点(一开始为0),在执行函数时,求得当前的时间戳。两者间隔大于等待时间时,就执行fn函数。这样就能够保证第一次触发就能够先执行。 但是一定要将记录当前时间点的值赋给默认时间点,不然,鼠标移动时会一直触发函数执行。

2.gif

有尾巴的

在时间段尾部执行。

代码语言:javascript复制
function throttle(fn, wait) {
  let timer = null
  return function (...args) {
    if (timer) return
    timer = setTimeout(() => {
      fn.apply(this, args)
      timer = null
    }, wait)
  }
}

考虑在尾部执行的情况。我们可以联想到定时器setTimeout,每隔一段时间触发执行一次。如果刚进来的话,正好在上一次执行的时间间隔内,就直接返回。只有当达到预定时间时(没有定时器,刚好要执行),执行函数,但是执行完成后一定要记得清空定时器,以免耽误下一次事件触发。

有头又有尾

嘿,这部就像我们做人一样嘛,有始有终! 根据第三个参数进行判断头尾。再把时间戳版和定时器版两者东拼拼西凑凑,大体模型就出来拉。

代码语言:javascript复制
function throttle(fn, wait, options) {
  let previous = 0
  let timer = null
  return function (...args) {
        let now = Date.now()
        // 先执行
        // 先执行里面要判断leading情况
        if (!previous && options.leading === false) previous = now // 如果options.leading是后执行 在判断时间间隔时忽略
        if (now - previous > wait) {
          if (timer) {
            clearTimeout(timer)
            timer = null
          }
          fn.apply(this, args)
          previous = now
        }
        // 后执行
        if (!timer && options.trailing) { // 无定时器
          timer = setTimeout(() => {
            // 后执行里面要判断leading的情况
            previous = options.leading ? Date.now() : 0
            fn.apply(this, args)
            timer = null
          }, wait)
        }
      }
  console.log(previous);
}
let obtn = document.querySelector('#btn')
let obox = document.querySelector('.box')  // 按钮
function todo(e) {
  console.log(this, e);
}
let throttleFn = throttle(todo, 1000, { leading: false, trailing: false })
obox.onmousemove = throttleFn

加点装饰

上面已经算基本完成了节流的实现。此外,和防抖类似,还有执行函数有返回值的结果、取消节流的功能。

返回值的话,可以使用一个变量去接收执行函数返回值。取消节流在函数上添加一个取消功能函数(取消时清除定时器并且将一开始时间置0)。

代码语言:javascript复制
function throttle(fn, wait, options) {
  let timer = null, previous = 0, result
  let throttled = function (...args) {
    let now = Date.now()
    if (!previous && options.leading == false) previous = now
    if (now - previous > wait) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      result = fn.apply(this, args)
      previous = now
    }
    if (!timer && options.trailing) {
      timer = setTimeout(() => {
        previous = options.leading ? Date.now() : 0
        result = fn.apply(this, args)
        timer = null
      }, wait);
    }
    return result
  }
  throttled.cancel = function () {
    if (timer) clearTimeout(timer)
    timer = null
    previous = 0
  }
  return throttled
}

如果需要同步结果可以使用promise,此处略过。

有什么用

节流的作用主要用于,在频繁触发某个事件的情况下,将其控制成一段时间请求一次。

  • 鼠标不断点击触发(单位时间内只触发一次)
  • 滚动监听,滚动到底部是否加载更多
  • input输入框输入监听(节流防抖都可)

节流防抖区别

防抖和节流都是减少用户调用频率。 防抖:一段时间内,鼠标一直不停地移动,以最后一次函数执行为准(后执行)。将多次触发,变为最后一次为准。 节流:一段时间执行函数,再过一段时间在执行函数。将多次触发,变为每隔一段时间触发。

1 人点赞