Javascript节流函数throttle和防抖函数debounce


Posted in Javascript onDecember 03, 2020

问题的引出

在一些场景往往由于事件频繁被触发,因而频繁地进行DOM操作、资源加载,导致UI停顿甚至浏览器崩溃。

在这样的情况下,我们实际上的需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。

1. resize事件

2. mousemove事件

3. touchmove事件

4.scroll事件

throttle 与 debounce

在现在很多的javascript框架中都提供了这两个函数。例如 jquery中有throttle和debounce插件, underscore.js ,Lodash.js 等都提供了这两个函数。

原理:

首先我们会想到设置一定的时间范围delay,每隔delayms 执行不超过一次。

事件处理函数什么时候执行能? 这里有两个选择,一是先执行,再间隔delayms来等待;或者是先等待delayms,然后执行事件处理函数。

操作过程中的事件全不管,反正只执行一次事件处理。

相同低,这一次的事件处理可以是先执行一次,然后后面的事件都不管; 或者前面的都不管,最后操作完了再执行一次事件处理。

区别:

1. throttle

如果将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出。

也就是会说预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。

2.debounce

如果用手指一直按住一个弹簧,它将不会弹起直到你松手为止。

也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。

简单代码实现及实验结果

那么下面我们自己简单地实现下这两个函数:

throttle 函数:

window.addEventListener("resize", throttle(callback, 300, {leading:false}));
 window.addEventListener("resize", callback2);
 function callback () { console.count("Throttled"); }
 function callback2 () { console.count("Not Throttled"); }
 /**
 * 频率控制函数, fn执行次数不超过 1 次/delay
 * @param fn{Function}   传入的函数
 * @param delay{Number}  时间间隔
 * @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false},
 *             如果想忽略结束边界上的调用则传入 {trailing:false},
 * @returns {Function}   返回调用函数
 */
 function throttle(fn,delay,options) {
   var wait=false;
   if (!options) options = {};
   return function(){
     var that = this,args=arguments;
     if(!wait){
       if (!(options.leading === false)){
         fn.apply(that,args);
       }
       wait=true;
       setTimeout(function () {
         if (!(options.trailing === false)){
           fn.apply(that,args);
         }
         wait=false;
       },delay);
     }
   }
 }

将以上代码贴入浏览器中运行,可得到:

Javascript节流函数throttle和防抖函数debounce

下面再看debounce函数的情况,

debounce 函数:

window.addEventListener("resize", throttle(callback, 300, {leading:false}));
window.addEventListener("resize", callback2);
function callback () { console.count("Throttled"); }
function callback2 () { console.count("Not Throttled"); }
/**
 * 空闲控制函数, fn仅执行一次
 * @param fn{Function}   传入的函数
 * @param delay{Number}  时间间隔
 * @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false},
 *             如果想忽略结束边界上的调用则传入 {trailing:false},
 * @returns {Function}   返回调用函数
 */
function debounce(fn, delay, options) {
  var timeoutId;
  if (!options) options = {};
  var leadingExc = false;

  return function() {
    var that = this,
      args = arguments;
    if (!leadingExc&&!(options.leading === false)) {
      fn.apply(that, args);
    }
    leadingExc=true;
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    timeoutId = setTimeout(function() {
      if (!(options.trailing === false)) {
        fn.apply(that, args);
      }
      leadingExc=false;
    }, delay);
  }
}

将以上代码贴入浏览器中运行,分三次改变窗口大小,可看到,每一次改变窗口的大小都会把开始和结束边界的事件处理函数各执行一次:

Javascript节流函数throttle和防抖函数debounce

如果是一次性改变窗口大小,会发现开始和结束的边界各执行一次时间处理函数,请注意与一次性改变窗口大小时 throttle 情况的对比:

Javascript节流函数throttle和防抖函数debounce

underscore.js 的代码实现

_.throttle函数

/**
 * 频率控制函数, fn执行次数不超过 1 次/delay
 * @param fn{Function}   传入的函数
 * @param delay{Number}  时间间隔
 * @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false},
 *             如果想忽略结束边界上的调用则传入 {trailing:false},
 * @returns {Function}   返回调用函数
 */
_.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) {
      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;
  };
};

_.debounce函数

/**
 * 空闲控制函数, fn仅执行一次
 * @param fn{Function}   传入的函数
 * @param delay{Number}  时间间隔
 * @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false},
 *             如果想忽略结束边界上的调用则传入 {trailing:false},
 * @returns {Function}   返回调用函数
 */
_.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);
    } 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;
  };
};

参考的文章

Debounce and Throttle: a visual explanation
jQuery throttle / debounce: Sometimes, less is more!
underscore.js

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
childNodes.length与children.length的区别
May 14 Javascript
javascript 动态调整图片尺寸实现代码
Dec 28 Javascript
IE8 chrome中table隔行换色解决办法
Jul 09 Javascript
js返回上一页并刷新代码整理
Dec 21 Javascript
jQuery position() 函数详解以及jQuery中position函数的应用
Dec 14 Javascript
纯js实现手风琴效果
Apr 17 Javascript
jQuery实现鼠标选中文字后弹出提示窗口效果【附demo源码】
Sep 05 Javascript
详谈js中标准for循环与foreach(for in)的区别
Nov 02 Javascript
微信小程序wx.request实现后台数据交互功能分析
Nov 25 Javascript
vue2.0+vue-dplayer实现hls播放的示例
Mar 02 Javascript
Vue中props的详解
May 16 Javascript
JS实现数组去重的11种方法总结
Apr 04 Javascript
如何实现vue的tree组件
Dec 03 #Vue.js
Vue实现图书管理小案例
Dec 03 #Vue.js
基于ajax实现上传图片代码示例解析
Dec 03 #Javascript
JQuery绑定事件四种实现方法解析
Dec 02 #jQuery
WebPack工具运行原理及入门教程
Dec 02 #Javascript
Vue router安装及使用方法解析
Dec 02 #Vue.js
js前端对于大量数据的展示方式及处理方法
Dec 02 #Javascript
You might like
PHP If Else(elsefi) 语句
2013/04/07 PHP
PHP使用正则表达式获取微博中的话题和对象名
2015/07/18 PHP
php简单日历函数
2015/10/28 PHP
Symfony2联合查询实现方法
2016/03/18 PHP
解决Laravel自定义类引入和命名空间的问题
2019/10/15 PHP
(仅IE下有效)关于checkbox 三态
2007/05/12 Javascript
Javascript和Ajax中文乱码吐血版解决方案
2009/12/21 Javascript
Javascript学习笔记之函数篇(四):arguments 对象
2014/11/23 Javascript
使用Plupload实现直接上传附件至七牛云存储
2014/12/26 Javascript
jQuery Select下拉框操作小结(推荐)
2016/07/22 Javascript
BootStrap 可编辑表Table格
2016/11/24 Javascript
JS实现页面中所有img对象添加onclick事件及新窗口查看图片的方法
2016/12/27 Javascript
validationEngine 表单验证插件使用实例代码
2017/06/15 Javascript
vue1.0和vue2.0的watch监听事件写法详解
2018/09/11 Javascript
基于Vue2实现简易的省市区县三级联动组件效果
2018/11/05 Javascript
简单了解JavaScript中的执行上下文和堆栈
2019/06/24 Javascript
Jquery如何使用animation动画效果改变背景色的代码
2020/07/20 jQuery
通过实例了解Render Props回调地狱解决方案
2020/11/04 Javascript
Vue中使用JsonView来展示Json树的实例代码
2020/11/16 Javascript
Python Mysql自动备份脚本
2008/07/14 Python
python如何在列表、字典中筛选数据
2018/03/19 Python
Python图像处理之图像的缩放、旋转与翻转实现方法示例
2019/01/04 Python
python实现合并两个排序的链表
2019/03/03 Python
python 实现矩阵填充0的例子
2019/11/29 Python
python numpy库np.percentile用法说明
2020/06/08 Python
墨西哥网上购物:Linio墨西哥
2016/10/20 全球购物
G-Form护具官方网站:美国运动保护装备
2019/09/04 全球购物
有影响力的人、名人和艺术家的官方商品:Represent
2019/11/26 全球购物
台湾屈臣氏网路商店:Watsons台湾
2020/12/29 全球购物
打架检讨书800字
2014/01/10 职场文书
运动会加油稿100字
2014/09/19 职场文书
2014年学生会干事工作总结
2014/11/07 职场文书
普宁寺导游词
2015/02/04 职场文书
欠款起诉书范文
2015/05/19 职场文书
Python OpenCV之常用滤波器使用详解
2022/04/07 Python
Python Pandas解析读写 CSV 文件
2022/04/11 Python