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中的parseInt使用技巧
Sep 03 Javascript
js中设置元素class的三种方法小结
Aug 28 Javascript
Js中获取frames中的元素示例代码
Jul 30 Javascript
JavaScript判断用户是否对表单进行了修改的方法
Mar 18 Javascript
JavaScript中错误正确处理方式小结你用对了吗
Oct 10 Javascript
bootstrap 弹出框modal添加垂直方向滚轴效果
Jul 09 Javascript
详解JavaScript添加给定的标签选项
Sep 17 Javascript
jQuery轻量级表单模型验证插件
Oct 15 jQuery
webpack打包非模块化js的方法
Oct 24 Javascript
JS定义函数的几种常用方法小结
May 23 Javascript
Layui表格行工具事件与数据回填方法
Sep 13 Javascript
VUE递归树形实现多级列表
Jul 15 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判断远程图片或文件是否存在的实现代码
2014/02/20 PHP
PHP获取数组中重复最多的元素的实现方法
2014/11/11 PHP
PHP批量查询WordPress留言者E-mail地址实现方法
2015/02/15 PHP
php获得网站访问统计信息类Compete API用法实例
2015/04/02 PHP
浅谈php数组array_change_key_case() 函数和array_chunk()函数
2016/10/22 PHP
php 开发中加密的几种方法总结
2017/03/22 PHP
利用javascript移动div层-javascript 拖动层
2009/03/22 Javascript
jQuery一步一步实现跨浏览器的可编辑表格,支持IE、Firefox、Safari、Chrome、Opera
2009/08/28 Javascript
dul无法加载bootstrap实现unload table/user恢复
2016/09/29 Javascript
javascript实现一个网页加载进度loading
2017/01/04 Javascript
原生js实现倒计时--2018
2017/02/21 Javascript
微信小程序 自定义Toast实例代码
2017/06/12 Javascript
浅谈Vue页面级缓存解决方案feb-alive(上)
2019/04/14 Javascript
详解从0开始搭建微信小程序(前后端)的全过程
2019/04/15 Javascript
python获取标准北京时间的方法
2015/03/24 Python
Python调用C++程序的方法详解
2017/01/24 Python
破解安装Pycharm的方法
2018/10/19 Python
更改Python的pip install 默认安装依赖路径方法详解
2018/10/27 Python
对Python使用mfcc的两种方式详解
2019/01/09 Python
flask框架路由常用定义方式总结
2019/07/23 Python
python实现实时视频流播放代码实例
2020/01/11 Python
pandas和spark dataframe互相转换实例详解
2020/02/18 Python
在django项目中导出数据到excel文件并实现下载的功能
2020/03/13 Python
pytorch 把图片数据转化成tensor的操作
2021/03/04 Python
匈牙利超级网上商店和优惠:Alza.hu
2019/12/17 全球购物
Java面试题:为什么要用Java
2012/05/11 面试题
财务专业大学生职业生涯规划范文
2013/12/30 职场文书
房屋买卖协议书范本
2014/04/10 职场文书
银行青年文明号事迹材料
2014/05/31 职场文书
群众路线教育查摆剖析材料
2014/10/10 职场文书
卖房协议书样本
2014/10/30 职场文书
写给领导的感谢信
2015/01/22 职场文书
年度考核表个人总结
2015/03/06 职场文书
2015年公民道德宣传日活动总结
2015/03/23 职场文书
搬迁通知
2015/04/20 职场文书
纯html+css实现打字效果
2021/08/02 HTML / CSS