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 相关文章推荐
20个非常有用的PHP类库 加速php开发
Jan 15 Javascript
javascript实现复选框超过限制即弹出警告框的方法
Feb 25 Javascript
详解jQuery简单的表单应用
Dec 16 Javascript
JS正则子匹配实例分析
Dec 22 Javascript
AjaxUpLoad.js实现文件上传
Mar 05 Javascript
在vue中使用v-bind:class的选项卡方法
Sep 27 Javascript
Cocos2d实现刮刮卡效果
Dec 20 Javascript
VSCode使用之Vue工程配置eslint
Apr 30 Javascript
微信小程序request请求封装,验签代码实例
Dec 04 Javascript
如何使用gpu.js改善JavaScript的性能
Dec 01 Javascript
javascript中layim之查找好友查找群组
Feb 06 Javascript
浅谈vue2的$refs在vue3组合式API中的替代方法
Apr 18 Vue.js
如何实现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读取EXCEL文件 php excelreader读取excel文件
2012/12/06 PHP
Zend Framework 2.0事件管理器(The EventManager)入门教程
2014/08/11 PHP
javascript下阻止表单重复提交、防刷新、防后退
2007/08/17 Javascript
jQuery 源代码显示控件 (Ajax加载方式).
2009/05/18 Javascript
jquery 打开窗口返回值实现代码
2010/03/04 Javascript
jQuery温习篇 强大的JQuery选择器
2010/04/24 Javascript
jquery attr 设定src中含有&amp;(宏)符号问题的解决方法
2011/07/26 Javascript
JavaScript实现x秒后自动跳转到一个页面
2013/01/03 Javascript
js简单实现删除记录时的提示效果
2013/12/05 Javascript
js读取配置文件自写
2014/02/11 Javascript
jQuery中size()方法用法实例
2014/12/27 Javascript
js实现精美的银灰色竖排折叠菜单
2015/05/16 Javascript
jQuery切换所有复选框选中状态的方法
2015/07/02 Javascript
jquery Easyui快速开发总结
2015/08/20 Javascript
解决angular的$http.post()提交数据时后台接收不到参数值问题的方法
2015/12/10 Javascript
EasyUI Pagination 分页的两种做法小结
2016/07/09 Javascript
require、backbone等重构手机图片查看器
2016/11/17 Javascript
vue  自定义组件实现通讯录功能
2018/09/30 Javascript
vue模块移动组件的实现示例
2020/05/20 Javascript
python随机生成指定长度密码的方法
2015/04/04 Python
windows下安装python的C扩展编译环境(解决Unable to find vcvarsall.bat)
2018/02/21 Python
python xlsxwriter库生成图表的应用示例
2018/03/16 Python
python判断数字是否是超级素数幂
2018/09/27 Python
pandas.read_csv参数详解(小结)
2019/06/21 Python
浅谈TensorFlow中读取图像数据的三种方式
2020/06/30 Python
python分布式爬虫中消息队列知识点详解
2020/11/26 Python
欧舒丹加拿大官网:L’Occitane加拿大
2017/10/29 全球购物
澳大利亚波西米亚风连衣裙在线商店:Fortunate One
2019/04/01 全球购物
集世界奢侈品和设计师品牌的意大利精品买手店:Tessabit
2019/08/17 全球购物
在加拿大在线租赁和购买电子游戏:Game Access
2019/09/02 全球购物
世界上最大的皮肤科医生拥有和经营的美容网站:LovelySkin
2021/01/03 全球购物
企业管理毕业生求职信范文
2014/03/07 职场文书
家长会主持词开场白
2014/03/18 职场文书
无子女夫妻离婚协议书(4篇)
2014/10/20 职场文书
MySQL系列之三 基础篇
2021/07/02 MySQL
Python Pygame实战在打砖块游戏的实现
2022/03/17 Python