debounce与throttle实现与原理

2023-11-03

前言

前端时间在面试中,面试官让我写一个关于input输入框,并且实时搜索的问题,我就当然用keyup事件来写,写完面试官还是挺满意的。又问我一个问题,如何减少每次输入频繁请求的性能开销。这个我就犯难了。事后,我百度了下,查到了throttle(节流)和debounce(去抖)。
我还百度到,这些事件的运用场景:

  • 鼠标事件:mousemove(拖曳)/mouseover(划过)/mouseWheel(滚屏)
  • 键盘事件:keypress(基于ajax的用户名唯一性校验)/keyup(文本输入检验、自动完成)/keydown(游戏中的射击)
  • window的resize/scroll事件(DOM元素动态定位)

debounce实现

throttle:连续的时间间隔(每隔一定时间间隔执行callback)。

debounce:空闲的时间间隔(callback执行完,过一定空闲时间间隔再执行callback)。

自己实现一个debounce:

主要思路是通过一个不断清除定时器,创建定时器的过程。

/**
 *
 * @param func {Function}   实际要执行的函数
 * @param wait {Number}  延迟时间,单位是毫秒(ms)
 * @param wait {Boolean}  一定时间内,先执行/后执行
 * 
 * @return {Function}     返回一个“去抖”了的函数
 */

var debounce = function(func, wait, immediate) {
    // 设置定时器
    let timeout;
    return (...args) => {
        const later = () => {
            timeout = null;
            if (!immediate) func.apply(this, args);
        };
        const callNow = immediate && !timeout;
        // 进入先清除定时器 
        claerTimeout(timeout);
        // 重新设置一个定时器,如果没有连续触发函数,就执行定时器,也是就是核心原理
        timeout = setTimeout(later, wait);
        if (callNow) 
            func.apply(this, args);
    }
};

我们下面来看一个实例:

window.addEventListener('resize',debounce(function(){
        console.log(1);
    },1000,true));

可以看到,你如果一直快速的改变浏览器的大小,console.log(1);只会执行一次。只有在你规定的时间内,不发生第二次改变,他才会再一次地输出console.log。

_的.debounce()分析

underscore(v1.8.3):

_.debounce = function(func, wait, immediate) {
    var timeout, args, context, timestamp, result;

    var later = function() {

        var last = _.now() - timestamp;

        if (last < wait && last >= 0) {
            timeout = setTimeout(later, wait - last);
            // wait-last 用来补充触发时的时间,从而达到从触发到下一次触发这又一个设定的闭环时间。
        } else {
            timeout = null;
            if (!immediate) {
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            }
        }
    };

    return function() {
        context = this;
        args = arguments;
        timestamp = _.now();
        var callNow = immediate && !timeout;
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
            result = func.apply(context, args);
            context = args = null;
        }
        return result;
    };
};

可以看到underscore的实有些不同,他的每一次定时器不清除timeout = setTimeout(later, wait);这个语句,每隔wait时间,会触发一次,但是在later函数中,会进行判断,如果在规定时间内再次触发这个函数,是不会触发func的。
lodash:

在lodash中的实现和我自己实现的异曲同工。想要看细节,请移步lodash官网吧。

throttle实现

自己实现一个throttle

核心思想,通过记录时间差,来判断是否执行func。

/**
*
* @param fn {Function}   实际要执行的函数
* @param delay {Number}  执行间隔,单位是毫秒(ms)
*
* @return {Function}     返回一个“节流”函数
*/

function throttle(fn, threshhold) {

  // 记录上次执行的时间
  var last

  // 定时器
  var timer

  // 默认间隔为 250ms
  threshhold || (threshhold = 250)

  // 返回的函数,每过 threshhold 毫秒就执行一次 fn 函数
  return function () {

    // 保存函数调用时的上下文和参数,传递给 fn
    var context = this
    var args = arguments

    var now = +new Date()

    // 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃
    // 执行 fn,并重新计时
    if (last && now < last + threshhold) {
      clearTimeout(timer)

      // 保证在当前时间区间结束后,再执行一次 fn
      timer = setTimeout(function () {
        last = now
        fn.apply(context, args)
      }, threshhold)

    // 在时间区间的最开始和到达指定间隔的时候执行一次 fn
    } else {
      last = now
      fn.apply(context, args)
    }
  }
}

_的.underscore()分析

underscore(v1.8.3):

_.throttle = function(func, wait, options) {
  var context, args, result;
  var timeout = null;
  var previous = 0;
  if (!options) options = {};
  var later = function() {
    previous = options.leading === false ? 0 : _.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  return function() {
    var now = _.now();
    // 记录第一次进入时间
    if (!previous && options.leading === false) previous = now;
    // 剩余时间
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    // 判断是否间隔规定时间
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
};

lodash中的实现只是将debounce封装了一层,就不进行讲解了。

参考文章:http://www.qidiantong.com/javascript/3876.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

debounce与throttle实现与原理 的相关文章

随机推荐

  • 【MySQL】不就是子查询

    前言 今天我们来学习多表查询的下一个模块 子查询 子查询包括了标量子查询 列子查询 行子查询 表子查询 话不多说我们开始学习 目录 前言 目录 一 子查询 1 子查询的概念 2 子查询语法格式 2 1 根据子查询结果不同可以分为 2 2 根
  • 51单片机开发系列一-51单片机开发环境搭建以及入门汇编代码

    51单片机开发系列一 51单片机开发环境搭建以及入门汇编代码 象棋小子 1048272975 1 51单片机概述 51单片机是对所有兼容Intel 8031指令系统的单片机的统称 目前教科书基本都是以早期的MCS 51为原型 讲解微机的原理
  • Java上传Excel文件并解析入库,数据生成Excel文档(知识点满满)

    原因 我们经常会遇到甲方给数据是excel格式的数据 当然Navicat是支持导入excel的但是遇到客户想要自己上传Excel入库数据就要手动进行写代码了 一 解析Excel入库 param request param response
  • YOLOV7训练自己的数据集以及训练结果分析(手把手教你)

    YOLOV7训练自己的数据集以及训练结果分析 手把手教你 YOLOV7训练自己的数据集整个过程主要包括 环境安装 制作数据集 参数修改 模型测试 模型推理 一 环境安装 conda create n yolov7 python 3 8 co
  • python---的各种算法

    今天来聊聊python中的算法 比如AES DES RSA 1 MD5加密 md5是一个大的hash算法 它不存在解密的逻辑 市面上所为的解密是通过撞库来实现的 我们可以简单的理解为生活中的防伪码 1 不加salt 简单理解为密钥 from
  • 【语义分割】综述——一文搞定语义分割

    本文记录了博主阅读的关于语义分割 Semantic Segmentation 的综述类文章的笔记 更新于2019 02 19 语义分割 综述 一文搞定语义分割 参考文献网址 An overview of semantic image seg
  • 如何是matlab中的折线图变得更加的光滑?

    原来的代码如下 y2 Convergence2 m figure Name y2 Position 200 200 500 500 plot y2 title E2 xlabel Iteration ylabel Objective Fun
  • 网上学自动化测试靠谱吗 自己亲自试听一下去感受下就知道了

    众所周知 目前我国软件测试每年都要新增大量岗位 但学校还没有开展软件测试相关的课程 而企业培养的人才远远不足需求 因此自学和培训就成为目前主流的从事软件测试的两个最有效的途径 但相对自学来说 参与培训能够在较短时间内学到软件测试的相关知识
  • Claude2 AI实战:重新认识我们自己

    交流源于内心本真的需要 通过交流来降低信息的不对称 今天的交流对象是一个集大成者的老学者 当然是由 Claude2 扮演 相信会有不一样的收获 角色设定 你是一名集大成者的年迈学者 在哲学 社会学 历史 心理学等方面都有很高的造诣 现在我们
  • 【华为OD机试真题 Python】加扰字符串

    前言 本专栏将持续更新华为OD机试题目 并进行详细的分析与解答 包含完整的代码实现 希望可以帮助到正在努力的你 关于OD机试流程 面经 面试指导等 如有任何疑问 欢迎联系我 wechat steven moda email nansun09
  • 到底什么是Cortex、ARMv8、arm架构、ARM指令集、soc?一文帮你梳理基础概念【科普】

    更多信息请关注公众号 一口Linux 一 到底什么是Cortex ARMv8 arm架构 ARM指令集 soc 有粉丝问我到底什么是ARM 搞不清楚Cortex arm内核 arm架构 ARM指令集 soc这些概念都是什么关系 下面一口君给
  • xxljob 定时任务执行 报:job handler not found

    用xxljob做一个定时任务调度 执行以后出现执行失败 提示的找不到执行器 一般情况下是因为管理中心的JobHandler名称与代码中定义的不匹配 或者与pom文件引入的xxljob 版本不匹配 先检查一下这两个地方 在定时任务代码的入口文
  • 钩子函数(HOOK)完整的教程

    基本概念 钩子 Hook 是Windows消息处理机制的一个平台 应用程序可以在上面设置子程以监视指定窗口的某种消息 而且所监视的窗口可以是其他进程所创建的 当消息到达后 在目标窗口处理函数之前处理它 钩子机制允许应用程序截获处理windo
  • MySQL-分库分表详解(七)

    作者 小刘在C站 个人主页 小刘主页 努力不一定有回报 但一定会有收获加油 一起努力 共赴美好人生 学习两年总结出的运维经验 以及思科模拟器全套网络实验教程 专栏 云计算技术 小刘私信可以随便问 只要会绝不吝啬 感谢CSDN让你我相遇 前言
  • ClickHouse的WITH-ALIAS是如何实现的

    ClickHouse的WITH ALIAS是如何实现的 WITH ALIAS包含相似但不同的两个特性 WITH lt 表达式 gt as lt 别名 gt WITH lt 别名 gt as lt 子查询 gt WITH lt 表达式 gt
  • centos安装apt-get

    apt get众所周知是ubantu的安装工具 经常被类比于centos的yum 不过有的时候经常遇到安装文档出现apt get安装 这个时候并不是替换成yum都能成功的 有的源yum是没有的 这个时候就很头疼 搜了一圈都是大同小异的 都是
  • c语言中 %d与%2d与%02d的区别在哪里

    在c语言中 1 d代表直接输出整数a 例如 int main int a 1 printf d n a 输出结果为1 return 0 2 2d代表如果我们输出的数字位数为2 例如 int main int a 1 printf 2d a
  • LaserScan数据转PointCloud2

    通过stage ros中rosbag record a记录的数据 里面的坐标系分别是odom gt gt base foot print gt gt base link gt gt base laser link 通过 rosrun tf
  • DBeaver设置背景色

    如图 设置sql编辑器背景色 方法 点击 窗口 gt 首选项 选择背景色 设置自定义颜色 最后应用并关闭
  • debounce与throttle实现与原理

    前言 前端时间在面试中 面试官让我写一个关于input输入框 并且实时搜索的问题 我就当然用keyup事件来写 写完面试官还是挺满意的 又问我一个问题 如何减少每次输入频繁请求的性能开销 这个我就犯难了 事后 我百度了下 查到了thrott