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 相关文章推荐
一些常用的JS功能函数(2009-06-04更新)
Jun 04 Javascript
js onclick事件传参讲解
Nov 06 Javascript
一个简单的实现下拉框多选的插件可移植性比较好
May 05 Javascript
jquery动态添加删除一行数据示例
Jun 12 Javascript
基于jquery的手风琴图片展示效果实现方法
Dec 16 Javascript
javascript插件开发的一些感想和心得
Feb 28 Javascript
AngularJs IE Compatibility 兼容老版本IE
Sep 01 Javascript
Vue.js中数组变动的检测详解
Oct 12 Javascript
JavaScript reduce和reduceRight详解
Oct 24 Javascript
COM组件中调用JavaScript函数详解及实例
Feb 23 Javascript
Angular简单验证功能示例
Dec 22 Javascript
怎么使用javascript深度拷贝一个数组
Jun 06 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
《DOTA3》开发工作已经开始 《DOTA3》将代替《DOTA2》
2021/03/06 DOTA
常用PHP数组排序函数归纳
2016/08/08 PHP
PHP生成zip压缩包的常用方法示例
2019/08/22 PHP
php在linux环境中如何使用redis详解
2020/12/15 PHP
解析jquery中的ajax缓存问题
2013/12/19 Javascript
jquery简单实现幻灯片的方法
2015/08/03 Javascript
jquery结婚电子请柬特效源码分享
2015/08/21 Javascript
JavaScript  cookie 跨域访问之广告推广
2016/04/20 Javascript
JavaScript的Backbone.js框架环境搭建及Hellow world示例
2016/05/07 Javascript
jQuery中hover方法搭配css的hover选择器,实现选中元素突出显示方法
2017/05/08 jQuery
解决ztree搜索中多级菜单展示不全问题
2017/07/05 Javascript
React Native 环境搭建的教程
2017/08/19 Javascript
详解微信小程序审核不通过的解决方法
2018/01/17 Javascript
浅谈JS对象添加getter与setter的5种方法
2018/06/09 Javascript
深入了解响应式React Native Echarts组件
2019/05/29 Javascript
ES6 Map结构的应用实例分析
2019/06/26 Javascript
vue 中 命名视图的用法实例详解
2019/08/14 Javascript
python数据结构之链表详解
2017/09/12 Python
python3+PyQt5实现拖放功能
2018/04/24 Python
windows下 兼容Python2和Python3的解决方法
2018/12/05 Python
windows安装TensorFlow和Keras遇到的问题及其解决方法
2019/07/10 Python
python matplotlib实现将图例放在图外
2020/04/17 Python
Python常用GUI框架原理解析汇总
2020/12/07 Python
详解HTML5 Canvas绘制时指定颜色与透明度的方法
2016/03/25 HTML / CSS
戛纳奢侈品商店:Jacques Loup法国
2019/11/04 全球购物
如何向接受结构参数的函数传入常数值
2016/02/17 面试题
高级文秘工作总结的自我评价
2013/09/28 职场文书
竞聘书模板
2014/03/31 职场文书
《去年的树》教学反思
2014/04/11 职场文书
农业生产宣传标语
2014/10/08 职场文书
2015年关爱留守儿童工作总结
2015/05/22 职场文书
公司保密管理制度
2015/08/04 职场文书
解析Redis Cluster原理
2021/06/21 Redis
Python借助with语句实现代码段只执行有限次
2022/03/23 Python
Java实现经典游戏泡泡堂的示例代码
2022/04/04 Java/Android
Valheim服务器 Mod修改安装教程 【ValheimPlus】
2022/12/24 Servers