VueUse 是如何封装函数节流的?

函数节流

基本概念

函数节流是高频率执行函数的一种优化方式,另一种方式是函数防抖,函数节流和函数防抖的区别是:

  • 函数节流是在一定时间内只执行一次函数。
  • 函数防抖是在函数被触发一定时间后再执行,如果在这时间段内又被触发,则重新计时。

实现

throttleFn 实现了函数节流,返回的新函数执行时就会有节流效果。

throttleFn 函数有两个参数:要执行的函数 fn 和时间段 delay, 返回的新函数会设置一个定时器,在 delay 时间之后才会执行 fn 并清空 timeout

如果在定时器到期之前这个新函数再次被调用,fn 不会执行,这里定时器没有销毁,加上会更加严谨些。

优化

定时器

定时器仅仅只是计划代码在未来的某个时间执行(但是并不保证在该时间点一定执行)。执行时机是不能保证的,因为定时器只是在指定时间后将执行的函数放入执行队列中等待执行,只有当进程空闲时才会从执行队列中取出执行。

如图,添加了 200ms 的定时器,在 600ms 时会将要执行的函数放入执行队列中,这时主进程是空闲状态,就会从执行队列中取出要执行的函数执行。

如果在 250ms 时添加了 100ms 的定时器,那么 350ms 时将要执行的函数放入执行队列中,但此时主进程还在执行JavaScript代码,定时器代码最终是在 400ms 主进程空闲后才执行。

时间戳来实现函数节流

相对定时器有以下优势:

  • 实时性:定时器的执行时间可能会延后执行,时间戳能更精确的控制函数的执行。
  • 性能消耗:频繁的创建定时器有一定的性能消耗,时间戳的方式不用管理定时器。
  • 执行方式:定时器是异步执行的,时间戳是同步执行的。

缺点:

  • 执行函数如果是很耗时的操作,时间戳的同步执行反而会阻塞进程而使得页面卡顿。
  • 时间戳的方式在最后一次执行的函数可能会被忽略,导致没有记录到最后一次的状态。比如在图片懒加载中用的话,就会出现问题。

图片懒加载的实现是 onscroll 事件中判断图片是否在可视区域内,onscroll 事件可以采用函数节流来进行优化,如果不记录最后一次 onscroll 事件的触发执行,就不能准确的判断图片最后是否在可视区域内。

useThrottleFn

VueUse 中使用 useThrottleFn 来实现函数节流。

使用

基本实现

首先 useThrottleFn 是采用时间戳的方式来实现函数节流。

上面提到时间戳的方式有两个问题:

第一个问题是执行函数如果是很耗时的操作的话,时间戳的同步执行返回会阻塞进程而使得页面卡顿,这个问题可以将函数的执行转为异步来解决。

第二个问题是最后一次执行的函数可能会被忽略,导致没有记录到最后一次的状态,解决这个问题可以采用定时器来设置最后一次执行。

函数加了 trailing 参数,为 true 时会执行间隔小于 delay 的加一个定时器作为最后一次执行。

异步处理

现在异步操作一般都是用 Promise 更好一些,因此对定时器做一层 Promise 封装方便获取返回值。

封装完之后,返回的新函数的返回值可能是 Promise 或者其他值,这里统一返回 Promise 更方便使用者处理。

功能拆分

我们可以将 throttleFn 函数里面的功能拆分封装。

节流的部分进行封装:

Promise 部分进行封装:

配置项扩展

添加this绑定

类似 Function.prototype.call 一样将 this 绑定的对象作为第一个参数:

使用:

首次不执行

有些场景下是希望节流的函数第一次不执行,比如在 loading 延迟赋值场景下。

loading 延迟赋值:

一般我们在发送请求获取数据的时会显示一个 loading 的效果,目的告知用户当前正在加载数据,请等待。以提升用户体验,但如果获取数据的时间很快的话,loading 的效果一闪而过反而体验不好。因此对loading 进行延迟赋值,也就是抽出一小段时间用来网络请求,超出这段时间再显示 loading,没有超出就不显示 loading,如果这一小段时间够短,那么用户是几乎感知不到的。

第一次 throttledFn(true) 不马上执行,而是等超过 500ms 才执行,这里就需要函数节流首次调用不马上执行。useThrottleFn 通过 leading 配置首次不执行。

ms 可能 Ref 对象或函数

完整代码:

rejectOnCancel 配置

rejectOnCancel 用于取消定时器后触发 Promisereject 方法。

细节优化

throttleFn 改名为 useThrottleFn,并调整参数顺序方便使用。

相关推荐
腾讯TNTWeb前端团队38 分钟前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试