JS 面试题:手写节流(throttle)函数

本文由前端开发者分享了如何手写节流函数,讲解了节流函数的作用和常见应用场景,如搜索框智能补全、滚动加载和窗口大小改变事件处理。并提供了节流函数的实现代码,通过维护最后调用时间戳和定时器来控制函数执行频率。文章还提及lodash库中的throttle实现更为完善,考虑了更多边界情况和额外功能。
摘要由CSDN通过智能技术生成

大家好,我是前端西瓜哥,今天带大家来手写(throttle)节流函数。

在这之前,我们简单了解下节流函数是什么。

节流函数是什么?

节流函数,就是降低一个函数的执行频率。每隔一段时间,才执行一次函数

假设我们 1s 中执行了 8 次函数:

1  2 3 4  5    6 78  
---------------------

添加节流能力后,我们让函数只在特定的时间间隔执行,且其中的一部分函数并没有被真正调用:

// 节流后
1    3    5    6    8
---------------------

节流函数的常见使用场景

使用节流的场景通常为一些会高频触发的事件,包括滚动、改变窗口大小、输入内容、光标移动事件等。

它们的触发频率非常高,不进行限制的话很容易产生一些性能问题。

下面是一些常见的使用节流函数的场景:

  1. 搜索框根据用户输入的内容,进行智能补全提示。需要通过节流来减少对服务器的压力

  2. 网页底部滚动加载。如果不使用节流,用户滚动到底部时发送第一个请求时,数据还没返回,就会持续触发滚动到底部的事件,导致同一时间发送大量请求。我们可以考虑加上节流,这个时间间隔也记得调大一点,处理弱网环境。

  3. 通过 JS 监听窗口大小改变 resize 事件,改变某个元素的宽度,可以通过节流来减少浏览器的重绘操作。

实现

我们需要实现一个 throttle 函数,这个函数接受原函数 fn 和时间间隔 wait,然后返回一个支持节流的新函数。

用法如下:

const printNum = (num) => {
  console.log(num);
}

// 设置
const throttled = throttle(printNum, 300);

throttled(0);
setTimeout(() => { throttled(1); }, 100);
setTimeout(() => { throttled(2); }, 200);
setTimeout(() => { throttled(3); }, 250);

// 0
// 3 (300ms+ 左右后输出)

实现上,只要做到将高频的函数连续调用,变成均匀且低频的调用,就算是节流函数了。

实现的核心在于 利用时间戳控制好定时器的调用时机

我们先看代码实现:

function throttle(fn, wait = 0) {
  let timerId;
  let lastInvoke = Number.MIN_SAFE_INTEGER; // 上次调用时间

  return function(...args) {
    // 当前时间
    const currTime = new Date().getTime();
    // 距离下次执行的剩余时间
    const remain = Math.max(lastInvoke + wait - currTime, 0);
    // 更新定时器,确保同一时间只有一个定时器在运行
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      lastInvoke = new Date().getTime();
      fn(...args);
    }, remain);
  }
}

// 使用方式
const throttled = throttle(printNum, 300);
throttle(0);

演示 demo:

https://codepen.io/F-star/pen/GRxvRRd?editors=0010

首先我们需要通过闭包保存一些私有变量:

  1. 原函数最后一次被调用的时间戳 lastInvoke;

  2. 定时器 id,当函数被调用时,清除原来的定时器,再执行一个新的定时器,确保任何时刻只有一个定时器在运行

  3. 要被调用的原函数 fn;

  4. 调用的时间间隔 wait

当调用 throttle 返回的增强的函数时,

先计算调用下一个函数的剩余时间 remain:首先通过 lastInvoke + wait 计算出下一次应该执行的时间戳,然后用这个时间戳减去当前时间戳 currTime,就能得到剩余时间了。

剩余时间可能是负数,比如我们调用 throttled 后过了很长时间再次执行的场景。这种情况下我们就将其设置为 0,接着将这个剩余时间传到 setTimeout 里执行。

定时器执行时,我们在执行原函数前,先更新 lastInvoke。如果放后面,可能会因为原函数执行报错,导致 lastInvoke 更新失败。

以上代码的实现其实是比较粗糙的,有一些 case 没能处理到

如果你想要实现一个比较完美节流函数,可以参考 lodash.throttle 的实现,它考虑了更多的边界情况,并提供了一些额外功能,代码实现也较为复杂。

lodash.throttle 考虑了以下细节:

  1. 第一次执行时,使用同步方式执行;

  2. 支持中途取消执行(cancel);

  3. 支持中途立即执行(flush);

  4. 返回上一次原函数执行时的返回值;

  5. 可以选择是否执行一轮的 leading 和 trailing 边界情况;

结尾

实现一个简单的节流函数,关键在于维护好最后一次调用原函数的时间戳,通过它来计算下一次执行时机,并使用定时器来执行。

一个比较完善的节流函数的实现并不简单,需要考虑一些边界情况,我更推荐你使用知名 lodash 工具库提供的 throttle 方法。

我是前端西瓜哥,欢迎关注我,学习更多前端面试题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值