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 相关文章推荐
Javascript 更新 JavaScript 数组的 uniq 方法
Jan 23 Javascript
javascript 的Document属性和方法集合
Jan 25 Javascript
javascript中类的定义及其方式(《javascript高级程序设计》学习笔记)
Jul 04 Javascript
js关于字符长度限制的问题示例探讨
Jan 24 Javascript
使用javascript实现判断当前浏览器
Apr 14 Javascript
jQuery实现瀑布流布局详解(PC和移动端)
Sep 01 Javascript
深入学习jQuery Validate表单验证(二)
Jan 18 Javascript
js console.log打印对像与数组用法详解
Jan 21 Javascript
关于layui的动态图标不显示的解决方法
Sep 04 Javascript
ElementUI Tree 树形控件的使用并给节点添加图标
Feb 27 Javascript
javascript设计模式 ? 中介者模式原理与用法实例分析
Apr 20 Javascript
解决Antd 里面的select 选择框联动触发的问题
Oct 24 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
完美解决phpexcel导出到xls文件出现乱码的问题
2016/10/29 PHP
Thinkphp 空操作、空控制器、命名空间(详解)
2017/05/05 PHP
Laravel5框架添加自定义辅助函数的方法
2018/08/01 PHP
从父页面读取和操作iframe中内容方法
2009/07/25 Javascript
让IE6支持min-width和max-width的方法
2010/06/25 Javascript
js检测输入内容全为空格的方法
2014/05/03 Javascript
关于JS数组追加数组采用push.apply的问题
2014/06/09 Javascript
jquery ajax 如何向jsp提交表单数据
2015/08/23 Javascript
jQuery获取当前点击的对象元素(实现代码)
2016/05/19 Javascript
React Native之prop-types进行属性确认详解
2017/12/19 Javascript
理理Vue细节(推荐)
2019/04/16 Javascript
VueJs里利用CryptoJs实现加密及解密的方法示例
2019/04/29 Javascript
jquery插件实现轮播图效果
2020/10/19 jQuery
Vue 简单实现前端权限控制的示例
2020/12/25 Vue.js
解决python3在anaconda下安装caffe失败的问题
2017/06/15 Python
读取本地json文件,解析json(实例讲解)
2017/12/06 Python
Python列表解析配合if else的方法
2018/06/23 Python
selenium+python实现自动化登录的方法
2018/09/04 Python
Python打开文件、文件读写操作、with方式、文件常用函数实例分析
2020/01/07 Python
tensorflow 实现自定义梯度反向传播代码
2020/02/10 Python
python list的index()和find()的实现
2020/11/16 Python
印度尼西亚综合购物网站:Lazada印尼
2016/09/07 全球购物
美的官方商城:Midea
2016/09/14 全球购物
美国波西米亚风格服装品牌:Show Me Your Mumu
2018/01/05 全球购物
梅西百货官网:Macy’s
2020/08/04 全球购物
学校门卫工作职责
2013/12/07 职场文书
四年大学生活的自我评价范文
2014/02/07 职场文书
六查六看剖析材料
2014/02/15 职场文书
《千年梦圆在今朝》教学反思
2014/02/24 职场文书
公开服务承诺制度
2014/03/26 职场文书
团代会主持词
2014/04/02 职场文书
离职感谢信
2015/01/21 职场文书
2015年乡镇纪委工作总结
2015/05/26 职场文书
mybatis调用sqlserver存储过程返回结果集的方法
2021/05/08 SQL Server
正确使用MySQL INSERT INTO语句
2021/05/26 MySQL
vmware虚拟机打不开vmx文件怎么办 ?vmware虚拟机vmx文件打开方法
2022/04/08 数码科技