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 相关文章推荐
表单(FORM)的一些实用效果代码
Mar 25 Javascript
JavaScript初学者需要了解10个小技巧
Aug 25 Javascript
只需20行代码就可以写出CSS覆盖率测试脚本
Apr 24 Javascript
JQuery获取表格数据示例代码
May 26 Javascript
JavaScript实现检查页面上的广告是否被AdBlock屏蔽了的方法
Nov 03 Javascript
JavaScript中用let语句声明作用域的用法讲解
May 20 Javascript
Bootstrap安装环境配置教程分享
May 27 Javascript
javascript获取select标签选中的值
Jun 04 Javascript
jQuery zTree搜索-关键字查询 递归无限层功能实现代码
Jan 25 jQuery
Angular6笔记之封装http的示例代码
Jul 27 Javascript
vue2.0 + ele的循环表单及验证字段方法
Sep 18 Javascript
Vue  webpack 项目自动打包压缩成zip文件的方法
Jul 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
用PHP实现递归循环每一个目录
2010/08/08 PHP
如何利用php array_multisort函数 对数据库结果进行复杂排序
2013/06/08 PHP
php获取目标函数执行时间示例
2014/03/04 PHP
php生成图片验证码-附五种验证码
2015/08/19 PHP
ExtJS 2.0实用简明教程 之ExtJS版的Hello
2009/04/29 Javascript
js null,undefined,字符串小结
2010/08/21 Javascript
js实现在页面上弹出蒙板技巧简单实用
2013/04/16 Javascript
js实现点击注册按钮开始读秒倒计时的小例子
2013/05/11 Javascript
js之ActiveX控件使用说明 new ActiveXObject()
2014/03/03 Javascript
jquery复选框checkbox实现删除前判断
2014/04/20 Javascript
JavaScript提高性能知识点汇总
2016/01/15 Javascript
jQuery实现字符串全部替换的方法【推荐】
2017/03/09 Javascript
BootStrap中Table隐藏后显示问题的实现代码
2017/08/31 Javascript
javascript流程控制语句集合
2017/09/18 Javascript
使用JQuery实现图片轮播效果的实例(推荐)
2017/10/24 jQuery
Echart折线图手柄触发事件示例详解
2018/12/16 Javascript
python3.5仿微软计算器程序
2020/03/30 Python
Python win32com 操作Exce的l简单方法(必看)
2017/05/25 Python
python 给DataFrame增加index行名和columns列名的实现方法
2018/06/08 Python
python中自带的三个装饰器的实现
2019/11/08 Python
numpy创建单位矩阵和对角矩阵的实例
2019/11/29 Python
python itsdangerous模块的具体使用方法
2020/02/17 Python
PyCharm 2020 激活到 2100 年的教程
2020/03/25 Python
基于Keras中Conv1D和Conv2D的区别说明
2020/06/19 Python
荷兰网上买鞋:MooieSchoenen.nl
2017/09/12 全球购物
Room Mate Hotels美国:西班牙酒店品牌
2018/04/10 全球购物
波兰家具和室内装饰品购物网站:Vivre
2018/04/10 全球购物
Beach Bunny Swimwear官网:设计师泳装和性感比基尼
2019/03/13 全球购物
Traffic People官网:女式花裙、上衣和连身裤
2020/10/12 全球购物
入党积极分子自我鉴定
2014/02/18 职场文书
机电系毕业生求职信
2014/07/11 职场文书
欢迎词范文
2015/01/27 职场文书
档案接收函格式
2015/01/30 职场文书
学校国庆节活动总结
2015/03/23 职场文书
民事诉讼答辩状范文
2015/05/21 职场文书
Redis 限流器
2022/05/15 Redis