龙空技术网

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

前端西瓜哥 84

前言:

此时小伙伴们对“js截流函数throttle”大约比较关心,姐妹们都需要学习一些“js截流函数throttle”的相关资讯。那么小编也在网络上汇集了一些对于“js截流函数throttle””的相关资讯,希望我们能喜欢,你们快快来了解一下吧!

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

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

节流函数是什么?

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

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

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

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

// 节流后1    3    5    6    8---------------------
节流函数的常见使用场景

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

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

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

搜索框根据用户输入的内容,进行智能补全提示。需要通过节流来减少对服务器的压力网页底部滚动加载。如果不使用节流,用户滚动到底部时发送第一个请求时,数据还没返回,就会持续触发滚动到底部的事件,导致同一时间发送大量请求。我们可以考虑加上节流,这个时间间隔也记得调大一点,处理弱网环境。通过 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:

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

原函数最后一次被调用的时间戳 lastInvoke;定时器 id,当函数被调用时,清除原来的定时器,再执行一个新的定时器,确保任何时刻只有一个定时器在运行要被调用的原函数 fn;调用的时间间隔 wait

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

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

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

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

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

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

lodash.throttle 考虑了以下细节:

第一次执行时,使用同步方式执行;支持中途取消执行(cancel);支持中途立即执行(flush);返回上一次原函数执行时的返回值;可以选择是否执行一轮的 leading 和 trailing 边界情况;...结尾

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

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

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

标签: #js截流函数throttle