【JavaScript】面试手撕节流

简介: 上篇我们讲了防抖,这篇我们就谈谈防抖的好兄弟 -- 节流。这里在老生常谈般的提一下他们两者之间的区别,顺带给读者巩固下。

引入

上篇我们讲了防抖,这篇我们就谈谈防抖的好兄弟 -- 节流。这里在老生常谈般的提一下他们两者之间的区别,顺带给读者巩固下。

PS: 开源节流中节流与这个技术上的节流,个人认为本质上是一样的。

  • 开源节流的节流指的是节省公司的金钱开支。
  • 前端技术上的节流指的是稀释函数的调用频率,节省CPU的开支。

区别

  • 节流: N 秒内只运行一次,若在N秒内重复触发,只有第一次生效
  • 防抖: N 秒后在执行该事件,若在N秒内被重复触发,则重新计时

不过我认为还是防抖那篇文章有个读者的评论更显生动 🐶, 在此对该读者表示感谢🙏

  • 节流: 可以看做攻击间隔,点的再快没打出来也不会同时攻击两次。
  • 防抖: 可以理解为回城,每点一下就要重新跑.

节流例子

这里我举两个常见的🌰,大家有什么更好的🌰可以在评论里回复

这个王者荣耀的攻击例子也算是节流,不过这个我就不举了。🐶

生活例子

这里跟大家举一个生活例子,更显生动。

我们知道一般女神回复舔🐶的概率都是比较低的,假设女神每天回复舔🐶一次。也就是说如果今天女生已经回复过了,那么无论舔🐶发再多的信息对方也是不会回的。今天的次数已经消耗完毕了,也只能等明天才能刷新。

短信验证

我们知道短信验证是在生活中很常见,其实短信验证便用到了节流的技术。

因为发短信其实是要钱的,为了避免一个用户重复点击导致发出多条短信就要使用节流。比如获取一次验证码的有效时间为60秒,则60秒内这个发送短信的方法不能再次触发。

手撕代码

xdm,接下来开始手撕代码了。这里有两种实现方式,分别是时间戳实现和定时器实现。

时间戳实现

原理

每次事件触发都会检查距离上次执行的时间间隔,如果超过指定的等待时间delay,则执行函数。并且更新上一次执行时间为为当前的时间戳。

代码

function throttleByTimestamp(fn, wait) {
  let lastTime = 0; // 用于保存上一次执行的时间戳
  return function () {
    const currentTime = new Date().getTime();
    // 判断当前时间与上次执行时间差是否大于等待时间
    if (currentTime - lastTime > wait) {
      // 如果满足条件,则执行原函数,并传递参数
      fn.apply(this, arguments);
      // 更新上一次执行的时间戳为当前时间
      lastTime = currentTime;
    }
  };
}
// 使用
const throttledFn = throttleByTimestamp(function () {
  console.log("节流函数被执行");
}, 500); // 每隔500毫秒执行一次
// 等待时间
const waitTime = async (time) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
};
const main = async() => {
    throttledFn();
    // 换成 < 500ms的则只会执行一次
    await waitTime(600);
    throttledFn();
}
main()
/**
 * 输出:
 * 节流函数被执行
 * 节流函数被执行
 */

定时器实现

原理

我们在事件触发时设置一个定时器。

  • 首次事件触发时,我们设置一个定时器,在等待一段时间后执行函数。
  • 定时器触发前,如果有新的事件触发,我们会检查是否存在定时器,如果存在则跳过。
  • 当定时器触发时,会清除当前定时器,确保下一次事件能重新触发。

代码

注: 测试例子跟上面的一样,此处不在贴啦。

function throttleByTimer(fn, delay) {
  let timer;
  return function () {
    const context = this;
    const args = arguments;
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(context, args);
        clearTimeout(timer);
        timer = null;
      }, delay);
    }
  };
}

时间戳+定时器实现

我们回顾这两个实现方式,大家有没有发现这两个实现方式的区别?

  • 时间戳: 因为是拿当前时间减上一次触发的时间,所以一旦满足该条件,事件会立即执行。
  • 定时器: 由于fn.apply的函数写在了setTimeout里,所以触发了,也得等delay后才能执行,于是就有了这种事件停止触发后依然会再一次执行的效果。

如果我们要实现这样的一个需求,完成一个事件触发时立即执行,触发完毕还能执行一次的节流函数该如何做呢?

原理

此处借鉴掘金网友《6个瑞士卷》的分析,个人觉得逻辑写的很好。于是摘录了下来。

  • 需要在每个delay时间中一定会执行一次函数,因此在节流函数内部使用开始时间、当前时间与delay来计算remaining
  • remaining <= 0时表示该执行函数了,如果还没到时间的话就设定在remaining时间后再触发。
  • 当然在remaining这段时间中如果又一次发生事件,那么会取消当前的计时器,并重新计算一个remaining来判断当前状态。

代码

function throttle(fn, delay) {
  let timer;
  let lastTime = 0;
  return function () {
    let currentTime = Date.now();
    // 计算距离上次执行fn到现在过去了多少时间,与delay做比较
    let remaining = delay - (currentTime - lastTime);
    const context = this;
    const args = arguments;
    // 如果在remaining这段时间在发生,会取消当前的计时器
    clearTimeout(timer);
    // 当remaining <= 0时表示该执行函数了
    if (remaining <= 0) {
      fn.apply(context, args);
      lastTime = Date.now();
    } else {
      // 如果还没到时间的话就设定在remaining时间后再触发
      timer = setTimeout(fn, remaining);
    }
  };
}

借鉴文章

  1. JS简单实现防抖和节流
目录
相关文章
|
21天前
|
前端开发 JavaScript 网络协议
前端最常见的JS面试题大全
【4月更文挑战第3天】前端最常见的JS面试题大全
44 5
|
1月前
|
JavaScript
JS封装节流函数
JS封装节流函数
15 0
|
2天前
|
测试技术
js_防抖与节流(闭包的使用)
js_防抖与节流(闭包的使用)
7 0
|
10天前
|
JavaScript 前端开发 测试技术
「一劳永逸」送你21道高频JavaScript手写面试题(上)
「一劳永逸」送你21道高频JavaScript手写面试题
36 0
|
1月前
|
JavaScript
|
1月前
|
设计模式 JavaScript 前端开发
最常见的26个JavaScript面试题和答案
最常见的26个JavaScript面试题和答案
47 1
|
1月前
|
存储 JavaScript 前端开发
【JavaScript】面试手撕浅拷贝
引入 浅拷贝和深拷贝应该是面试时非常常见的问题了,为了能将这两者说清楚,于是打算用两篇文章分别解释下深浅拷贝。 PS: 我第一次听到拷贝这个词,有种莫名的熟悉感,感觉跟某个英文很相似,后来发现确实Copy的音译,感觉这翻译还是蛮有意思的
45 6
|
2月前
|
前端开发 JavaScript UED
【JavaScript】面试手撕防抖
防抖: 首先它是常见的性能优化技术,主要用于处理频繁触发的浏览器事件,如窗口大小变化、滚动事件、输入框内容改变等。在用户连续快速地触发同一事件时,防抖机制会确保相关回调函数在一个时间间隔内只会被执行一次。
36 0
|
2月前
|
前端开发 JavaScript 算法
【JavaScript】面试手撕数组排序
这章主要讲的是数组的排序篇,我们知道面试的时候,数组的排序是经常出现的题目。所以这块还是有必要进行一下讲解的。笔者观察了下前端这块的常用算法排序题,大概可以分为如下
24 2