用吃巧克力来理解 防抖和节流!再也不忘了!

简介: 用吃巧克力来理解 防抖和节流!再也不忘了!

用吃巧克力来理解 防抖和节流!再也不忘了!


词语不陌生,但是回回看,回回忘记,究其根本就是没理解。

让我们先忘掉这两个词语,进入一个新的情境~

现在你是一个 5 岁的宝宝,今天有小朋友要来,妈妈准备了很多巧克力。

你一看,赶紧就说,妈妈我要吃巧克力,妈妈给了你一块,你又叨叨了一次,又给了一块。

第三次叨叨的时候,妈妈就不太高兴了,你已经吃了 2 块了,剩下的巧克力要等小朋友来吃。

作为一个 5 岁的宝宝,你难以接受,于是,一直跟着妈妈后面叨叨,“妈妈,我要吃巧克力,我好想吃巧克力”。

妈妈被你烦的不行,就妥协的说,“我定个半小时的计时器,计时结束就给你一块,还想要的话,那我们再设置半小时定时器”。

于是,妈妈就设置了半小时定时器~

但是你只是 5 岁的宝宝啊,心里还是很想很想吃,于是,还是在后面叨叨,但妈妈无视你,带上耳塞了。

半小时计时结束,计时器响了,妈妈果然就你一块了!

吃完之后,你又去叨叨了,于是妈妈重新设置了半小时的定时器。

节流 throttle

好,现在来根据上面的例子,说说妈妈的策略

你一直跟妈妈要巧克力,如果说一次就给一次,妈妈就要给你无数个巧克力,显然妈妈觉得很累。

于是妈妈拿了个定时器,你叨叨的时候,如果定时器还在计时,就无视你,如果计时器不在计时的话,重新计时。计时结束就给一次。

这样大大减少了满足的频次。

现在切换角色,你是开发者,网页里有个查询的按钮,每次点击就会请求接口,返回新的查询数据。

但有些用户(测试),可能无聊,一直点着查询提交按钮,一秒点个十次,服务器就得返回十次数据。

<button id="btn">提交</button>
<script>
  let times = 0;
  const fn = () =>
    console.log(`发送请求${++times}次`, new Date().toLocaleTimeString());
  btn.onclick = fn;
</script>

网络异常,图片无法展示
|

测试就来找你了,“老铁,控制下频次,一秒发一次请求吧”。

这时候,就是节流,你不一定需要记住名词,但你记得怎么控制就行

let times = 0;
const fn = console.log(`发送请求${++times}次`, new Date().toLocaleTimeString());
// 拿一个计时器
var timer;
/* 这里的点击就相当于是叨叨要巧克力
 * 每次叨叨的时候,看下计时器在不在计时,
 * 如果正在计时的话,就无视
 * 如果不在计时的话,就重新开始计时
 * 计时结束,妈妈会主动给巧克力,然后关掉计时器
 */
btn.onclick = () => {
  // 计时器还在计时,就无视
  if (timer) {
    return;
  }
  // 计时器不在计时,就重新开始计时
  timer = setTimeout(() => {
    // 计时结束的时候,妈妈主动给巧克力,然后关掉计时器
    fn();
    timer = null;
  }, 1000);
};

节流的通用版

把后面的函数用另外一个函数生成,就成了节流的简短版

function throttle(fn, delay) {
  // 拿一个计时器
  var timer;
  return (...args) => {
    // 计时器还在计时,就无视
    if (timer) {
      return;
    }
    // 计时器不在计时,就重新开始计时
    timer = setTimeout(() => {
      // 计时结束的时候,妈妈主动给巧克力,然后关掉计时器
      fn(...args);
      timer = null;
    }, delay);
  };
}
btn.onclick = throttle(fn, 1000);

网络异常,图片无法展示
|

防抖

重新回到情境里,你已经连续两个小时都在叨叨了,妈妈想要清净会,于是改了主意~

“这是个定时器,如果你叨叨的话,我就重新开始计时;计时结束,我就给你一个巧克力”

你一听这话,就赶紧闭嘴了

let times = 0;
const fn = console.log(`发送请求${++times}次`, new Date().toLocaleTimeString());
// 拿一个计时器
var timer;
/* 这里的点击就相当于是叨叨要巧克力
 * 每次叨叨的时候,计时器就重新计时
 * 计时结束,妈妈会主动给巧克力
 */
btn.onclick = () => {
  // 只要叨叨,就重新计时
  timer && clearTimeout(timer);
  timer = setTimeout(() => {
    // 计时结束,妈妈主动给巧克力,关掉计时器
    fn();
    timer = null
  }, 1000);
};

网络异常,图片无法展示
|

这就是防抖,叨叨就重新计时,闭嘴了一段时间,才会给巧克力

防抖的通用版

同理,把后面的函数用另外一个函数生成,就成了防抖的简短版

function debounce(fn, delay) {
  // 拿一个计时器
  var timer = null;
  return (...args) => {
    // 只要叨叨,就重新计时
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
      // 计时结束,妈妈主动给巧克力,关掉计时器
      fn(...args);
      timer = null
    }, delay);
  };
}
btn.onclick = debounce(fn, 1000);

防抖和节流的异同

其实通过上面的情景,我觉得你基本能感受到了这两的异同点。

原始情况:只要叨叨,就满足要求。 缺点就是:满足的太频繁。

现在用节流和防抖的话:

  • 节流是拿一个定时器,你叨叨的时候,如果计时器还在计时的话,就无视;如果不在计时的话,就开始计时;
    计时结束,主动满足要求。
    结果就是:你一直叨叨的话,显然每隔一段时间,你就能满足要求
  • 防抖是拿一个定时器,你叨叨的时候,就重新计时;
    计时结束,主动满足要求。
    结果就是:你一直叨叨的话,显示不会被满足要求,只有停止叨叨,隔一段时间才会被满足要求

相同点:都拿个计时器,计时结束,主动满足要求。

不同点:叨叨的时候,隔段时间就满足要求 还是 停止叨叨隔段时间之后,才满足要求。

怎么选择呢

其实想选择哪个,看业务情况和你的心情了!

比如,页面滚动的时候,你想实时知道滚动的高度,但这个实时其实没必要到 10 毫秒的程度,那就想着 50 毫秒获取一次,也就是页面在滚动的时候,你希望每隔 50ms 就获取一次,那就显然是节流~

如果你是希望,页面停止滚动的时候,才需要获取,那就显然是防抖~

滚动可以类比为,一直叨叨要巧克力,获取页面高度相当于给巧克力满足你~

如果没有特别的需求只是单纯的想控制获取次数,随你开心啦~~

常用的场景:

  • 点击按钮发请求的时候
  • 页面滚动的时候
  • 用户在输入文字实时发请求的时候

怎么记忆不混淆

防抖就理解为,防止你嘴唇不停的抖动,只有不抖了,隔段时间才满足。

知道了防抖,另一个就是节流了。

实际怎么使用

虽然简版的能满足一些场景,但一般不建议使用自己手写的,你知道这么个逻辑就行

实际使用的时候,直接使用库里封装好的比较稳妥,以lodash为例:

// npm i lodash 或者 https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js
btn.onclick = _.throttle(fn, 1000);
btn.onclick = _.debounce(fn, 1000);

这里额外注意两个参数:

  • leading,表示开始计时之前,要不要先给你一个巧克力,默认是false,也就是计时开始之前不给巧克力
  • trailing,表示计时结束,要不要给你巧克力,默认是true,也就是计时结束给巧克力

这两个参数,根据场景使用,文中的例子就是默认的情况,其他的情况,同理知道,不再细说~

引用

目录
相关文章
|
负载均衡 监控 前端开发
云原生Istio架构和组件介绍 1
云原生Istio架构和组件介绍
395 0
|
编解码 人工智能 运维
南加大提出全新通用时间序列基础模型TimeDiT!基于扩散模型创新物理约束机制
 【10月更文挑战第10天】南加大提出TimeDiT模型,创新融合扩散模型与Transformer架构,针对真实世界时间序列数据的复杂性,如多分辨率、缺失值等问题,提供高效解决方案。该模型通过新颖的掩码机制和无微调编辑策略,实现多任务处理及物理知识集成,显著提升预测和异常检测的准确性和鲁棒性。
395 3
|
11月前
|
存储 数据可视化 编译器
【C语言】union 关键字详解
联合体(`union`)是一种强大的数据结构,在C语言中具有广泛的应用。通过共享内存位置,联合体可以在不同时间存储不同类型的数据,从而节省内存。在嵌入式系统、硬件编程和协议解析等领域,联合体的使用尤为常见。理解和正确使用联合体可以使代码更加高效和灵活,特别是在内存受限的系统中。
702 3
【C语言】union 关键字详解
|
Ubuntu 网络协议 Android开发
使用ruri快速构建跨架构chroot容器
【7月更文挑战第10天】使用ruri快速构建跨架构chroot容器:先确认binfmt_misc支持;安装qemu-user-static;用rootfstool脚本获取rootfs;下载最新ruri二进制;解压rootfs并启动容器;配置DNS。完成这些步骤后,可在x86_64上运行arm64的Ubuntu容器。注意,ruri处于实验阶段,使用前需评估风险,并根据需求调整与优化。此流程提供基础参考,具体操作可能需微调。
408 6
|
人工智能 前端开发 JavaScript
低代码到底是前端的 “ 福 ” 还是 “ 孽 ”???
低代码到底是前端的 “ 福 ” 还是 “ 孽 ”???
|
运维 Cloud Native 应用服务中间件
阿里云微服务引擎 MSE 及 云原生 API 网关 2024 年 09 月产品动态
阿里云微服务引擎 MSE 面向业界主流开源微服务项目, 提供注册配置中心和分布式协调(原生支持 Nacos/ZooKeeper/Eureka )、云原生网关(原生支持Higress/Nginx/Envoy,遵循Ingress标准)、微服务治理(原生支持 Spring Cloud/Dubbo/Sentinel,遵循 OpenSergo 服务治理规范)能力。API 网关 (API Gateway),提供 APl 托管服务,覆盖设计、开发、测试、发布、售卖、运维监测、安全管控、下线等 API 生命周期阶段。帮助您快速构建以 API 为核心的系统架构.满足新技术引入、系统集成、业务中台等诸多场景需要
|
存储 负载均衡 安全
虚拟桌面和云桌面办公系统
虚拟桌面和云桌面办公系统
|
算法 定位技术
连连看核心算法与基本思想(附全部项目代码链接与代码详细注释)
连连看核心算法与基本思想(附全部项目代码链接与代码详细注释)
733 0
|
小程序 测试技术 数据库
基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖微信小程序端(十二)
基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖微信小程序端(十二)
|
应用服务中间件 nginx
Nginx源码阅读:共享内存ngx_shm_t和它的组织方式ngx_shm_zone_t、ngx_list_t
Nginx源码阅读:共享内存ngx_shm_t和它的组织方式ngx_shm_zone_t、ngx_list_t
199 0