开发者社区> 小麋鹿666> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

JS魔法堂:函数节流(throttle)与函数去抖(debounce)

简介:
+关注继续查看

一、前言                                  

  以下场景往往由于事件频繁被触发,因而频繁执行DOM操作、资源加载等重行为,导致UI停顿甚至浏览器崩溃。

  1. window对象的resize、scroll事件

  2. 拖拽时的mousemove事件

  3. 射击游戏中的mousedown、keydown事件

  4. 文字输入、自动完成的keyup事件

  实际上对于window的resize事件,实际需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。

二、什么是debounce                            

   1. 定义

  如果用手指一直按住一个弹簧,它将不会弹起直到你松手为止。

      也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。

   接口定义

复制代码
/**
* 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 idle,action 才会执行
* @param idle   {number}    空闲时间,单位毫秒
* @param action {function}  请求关联函数,实际应用需要调用的函数
* @return {function}    返回客户调用函数
*/
debounce(idle,action)
复制代码

   2. 简单实现

复制代码
var debounce = function(idle, action){
  var last
  return function(){
    var ctx = this, args = arguments
    clearTimeout(last)
    last = setTimeout(function(){
        action.apply(ctx, args)
    }, idle)
  }
}
复制代码

三、什么是throttle                              

   1. 定义

  如果将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出。

  也就是会说预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。

      接口定义:

复制代码
/**
* 频率控制 返回函数连续调用时,action 执行频率限定为 次 / delay
* @param delay  {number}    延迟时间,单位毫秒
* @param action {function}  请求关联函数,实际应用需要调用的函数
* @return {function}    返回客户调用函数
*/
throttle(delay,action)
复制代码

   2. 简单实现

复制代码
var throttle = function(delay, action){
  var last = 0return function(){
    var curr = +new Date()
    if (curr - last > delay){
      action.apply(this, arguments)
last = curr
}
} }
复制代码

四、underscore v1.7.0相关的源码剖析                          

   1. _.throttle函数

复制代码
 _.throttle = function(func, wait, options) {
    /* options的默认值
     *  表示首次调用返回值方法时,会马上调用func;否则仅会记录当前时刻,当第二次调用的时间间隔超过wait时,才调用func。
     *  options.leading = true;
     * 表示当调用方法时,未到达wait指定的时间间隔,则启动计时器延迟调用func函数,若后续在既未达到wait指定的时间间隔和func函数又未被调用的情况下调用返回值方法,则被调用请求将被丢弃。
     *  options.trailing = true; 
     * 注意:当options.trailing = false时,效果与上面的简单实现效果相同
     */
    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;
      // 当到达wait指定的时间间隔,则调用func函数
      // 精彩之处:按理来说remaining <= 0已经足够证明已经到达wait的时间间隔,但这里还考虑到假如客户端修改了系统时间则马上执行func函数。
      if (remaining <= 0 || remaining > wait) {
        // 由于setTimeout存在最小时间精度问题,因此会存在到达wait的时间间隔,但之前设置的setTimeout操作还没被执行,因此为保险起见,这里先清理setTimeout操作
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        // options.trailing=true时,延时执行func函数
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };
复制代码
   按理来说remaining <= 0已经足够证明已经到达wait的时间间隔,至于remaining > wait的作用是什么,我现在也不太清楚。
   精彩之处:按理来说remaining <= 0已经足够证明已经到达wait的时间间隔,但这里还考虑到假如客户端修改了系统时间则马上执行func函数。这里谢谢@GreatFeng的提示!

 2. _.debounce函数 

复制代码
_.debounce = function(func, wait, immediate) {
    // immediate默认为false
    var timeout, args, context, timestamp, result;

    var later = function() {
      // 当wait指定的时间间隔期间多次调用_.debounce返回的函数,则会不断更新timestamp的值,导致last < wait && last >= 0一直为true,从而不断启动新的计时器延时执行func
      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();
      // 第一次调用该方法时,且immediate为true,则调用func函数
      var callNow = immediate && !timeout;
      // 在wait指定的时间间隔内首次调用该方法,则启动计时器定时调用func函数
      if (!timeout) timeout = setTimeout(later, wait);
      if (callNow) {
        result = func.apply(context, args);
        context = args = null;
      }

      return result;
    };
  };
复制代码

          _.debounce实现的精彩之处我认为是通过递归启动计时器来代替通过调用clearTimeout来调整调用func函数的延时执行。

五、总结                                  

   throttle和debounce均是通过减少实际逻辑处理过程的执行来提高事件处理函数运行性能的手段,并没有实质上减少事件的触发次数。两者在概念理解上确实比较容易令人混淆,结合各js库的具体实现进行理解效果将会更好。

   尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4147810.html ^_^肥子John

六、参考                                  

http://www.alloyteam.com/2012/11/javascript-throttle/

http://www.cnblogs.com/ambar/archive/2011/10/08/throttle-and-debounce.html

如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!

posted @ 2014-12-06 22:53 ^_^肥仔John 阅读(39925) 评论(12) 编辑 收藏
  
#1楼 2014-12-07 08:55 codezyc  
很实用,谢谢楼主的分享。
  
#2楼[楼主] 2014-12-07 09:39 ^_^肥仔John  
@ codezyc
不客气,共同进步啦
http://pic.cnblogs.com/face/347002/20141205140116.png
  
#3楼 2015-01-03 21:03 GreatFeng  
remaining>wait,表示客户端系统时间被调整过
  
#4楼[楼主] 2015-01-04 11:05 ^_^肥仔John  
@ GreatFeng
原来如此,谢谢了!
http://pic.cnblogs.com/face/347002/20141205140116.png
  
#5楼 2015-11-22 16:36 暗夜精灵^  
怎么感觉throttle的简单实现不对,last = curr; 应该在if语句里面
  
#6楼[楼主] 2015-11-25 15:04 ^_^肥仔John  
@ 暗夜精灵^
确实是,谢谢指正!
http://pic.cnblogs.com/face/347002/20141205140116.png
  
#7楼 2016-08-10 14:42 Code2Life  
非常详细. 刚去看了一下underscore1.8.3的源码debounce的实现方式, 作者改成调整timeout实现, 而不是递归setTimeout了, 博主怎么看
  
#8楼 2016-10-25 20:34 明星120  
阔以的
http://pic.cnblogs.com/face/983944/20160712113551.png
  
#9楼 2017-02-20 12:57 就让往事都随风  
throttle:触发-上次动作执行时间〉大于限制时间->执行动作,记录执行时间
debounce:触发-记录触发时间-上次动作触发时间〉大于限制时间-执行动作
  
#10楼 2017-04-13 15:36 mynext  
写得挺好的
  
#11楼[楼主] 2017-04-24 08:52 ^_^肥仔John  
@ mynext
谢谢!
http://pic.cnblogs.com/face/347002/20141205140116.png
  
#12楼37995422017/9/28 15:49:03 2017-09-28 15:49 liris  
debounce的简单实现是不是有点问题?
http://pic.cnblogs.com/face/1018217/20170216113608.png

公告

肥仔John@github
作品:
 
本文转自
^_^肥仔John博客园博客,原文链接:http://www.cnblogs.com/fsjohnhuang/p/4147810.html/,如需转载请自行联系原作者
 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
JavaScript函数篇之ES6箭头函数与匿名函数
对于箭头函数,this 关键字始终表示定义箭头函数的对象。
0 0
JavaScript学习笔记(一),js变量、以及函数的调用
JavaScript学习笔记(一),js变量、以及函数的调用
0 0
重温js——函数基础
函数的作用,减少重复代码,实现代码的解耦。把某个功能点给抽离出来。使得代码阅读增加
0 0
JavaScript的函数(二)
JavaScript的函数之自执行函数和闭包 前面讲到js函数的基本分类及使用,今天我们来聊一下自执行函数的原理以及简单闭包原理。 1.自执行函数 说到自执行函数,其实就是函数不用被调用,自身执行代码块代码。整体思路如下代码: 1.按照我们的想法,先声明一个匿名函数,然后在函数后面加上(),但是会发现报错。如下图: 在这里插入图片描述 按照上面的思路去理解,在匿名函数后面直接加上()应该是可以立即执行的,但这个函数报错了,而且报了两个错误,是因为浏览器在解析js代码的时候,遇到function会当做函数声明,然后检查语法,函数声明必须要有函数名,所以报错;第二个括号处的语法错误在于函数
0 0
JavaScript的函数解析(一)
JavaScript的函数 今天和大家说一下js中一个比较重要的内容,函数。 JavaScript 函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。 JavaScript 函数是被设计为执行特定任务的代码块。 JavaScript 函数会在某代码调用它时被执行。 JavaScript 函数由 function 声明。 JavaScript 函数声明后需要被调用才会执行。 1.无参函数 // 声明函数 function func1(){ console.log("这是一个无参函数"); }
0 0
JavaScript组合函数
JavaScript组合函数
0 0
JavaScript函数柯里化的实现原理,进来教你完成一个自己的自动实现柯里化方法
JavaScript函数柯里化的实现原理,进来教你完成一个自己的自动实现柯里化方法
0 0
【JavaScript】函数式编程——函数柯里化
【JavaScript】函数式编程——函数柯里化
0 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Javascript中的函数
立即下载
JavaScript函数
立即下载
JS零基础入门教程(上册)
立即下载