javascript防抖(Debouncing)和节流阀(Throttling)

Posted 刘翾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javascript防抖(Debouncing)和节流阀(Throttling)相关的知识,希望对你有一定的参考价值。

中文原文链接: https://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/
英文原文链接: https://css-tricks.com/debouncing-throttling-explained-examples/

1. 序言

防抖(Debounce)和节流(throttle)都是用来控制某个函数在一定时间内执行多少次的技巧,两者相似而又不同。

当我们给 DOM 绑定事件的时候,加了防抖和节流的函数变得特别有用。为什么呢?因为我们在事件和函数执行之间加了一个控制层。记住,我们是无法控制 DOM 事件触发频率的。

看下滚动事件的例子:

// html
<h1>Number of scroll events </h1>
<a href="#" class="reset">Reset</a>
<div id="counter">0</div>
// css
body 
   background: #444444;
   color: white;
    font: 15px/1.51 system, -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
   margin:0 auto;
   max-width:600px;
   padding:20px;
   min-height:1000vh; /* 100 times viewport height */

#counter 
  position:fixed;
  top:100px;
  left:40%;
  font-size:50px;

.reset 
  color:white;
  text-decoration:none;
  border:1px solid white;
  padding:10px 20px;
  background:rgba(0,0,0,0.1);

// js
var i = 0;
var $counter = $('#counter');
$(document).ready(function()
  $(document).on('scroll', function()
    $counter.html(i);
    i++; 
  );
);

$('.reset').on('click', function()
  $counter.html('');
  i = 0;
)

演示图:

gif抓取帧率太低,可以自己本地写下代码试试

当使用触控板,滚动滚轮,或者拖拽滚动条的时候,一秒可以轻松触发30次事件。经我的测试,在智能手机上,慢慢滚动一下,一秒可以触发事件100次之多。这么高的执行频率,你的滚动回调函数压力大吗?

有个大佬建议的解决方案是,在onScroll事件外部,每 250ms 循环执行一次。简单的技巧,避免了影响用户体验。

现如今,有一些稍微高端的方式处理事件。我来结合用例介绍下 Debounce,Throttle 和 requestAnimationFrame 吧。

2. 防抖动(Debounce)

防抖技术可以把多个顺序地调用合并成一次。 下面看个例子

<a class="trigger-area">Trigger area</a>
<a class="reset">Reset</a>
<div class="visualizations">
<h2>Raw events over time</h2>
<div id="raw-events" class="events"></div>
<h2>Debounced events
  <span class="details"> 400ms, trailing</span></h2>
<div id="debounced-events" class="events"></div>
</div>
body 
   background: #444444;
   color: white;
   font: 15px/1.51 system, -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
   margin:0 auto;
   max-width:700px;
   padding:20px;


.events
  padding:0px 20px 10px 20px;
  height: 23px;

.events span 
  height:17px;
  width:6px;
  display:inline-block;
  border-right:1px solid #111;


.events span:last-of-type 
  border:2px solid black;
  border-bottom: 4px solid #AAA;
  border-top: 0px;
  margin-bottom:-17px;
  margin-left:-2px;

h2 
  margin:10px 0 5px 0;
  clear:both;
  font-weight: normal;
  font-size:14px;
  padding:6px 20px;


.trigger-area 
  margin: 0;
  display:inline-block;
  width: 200px;
  height:50px;
  border: 1px solid #5ed1ff;
  padding: 28px 0 0 0;
  text-align: center;
  background-color: transparent;
  cursor:pointer;
  font-size:17px;
  -webkit-user-select: none;  /* Chrome  / Safari */
  -moz-user-select: none;     /* Firefox all */
  -ms-user-select: none;      /* IE 10+ */
  user-select: none;          /* Likely future */    

.trigger-area.active 
  background:#2F5065;

.clickme:hover,
.clickme:active
  background-color: #333;

.clickme:active
  padding: 4px 5px;

.reset 
  display:inline-block;
  width: 120px;
  padding: 10px 0 0 0;
  text-align: center;
  font-size:14px;
  cursor:pointer;
  color:#eee;

.visualizations 
  margin-top:10px;
  background:rgba(0,0,0,0.2);

.details 
  font-size:13px;
  color:#999;


/* stating the obvious: color0 represents our empty color */
.color0  transparent

.color1  background-color: #FFE589
.color2  background-color: #B9C6FF
.color3  background-color: #99FF7E
.color4  background-color: #FFB38A
.color5  background-color: #A5FCFF
.color6  background-color: #FF8E9B
.color7  background-color: #E3FF7E
.color8  background-color: #FFA3D8
.color9  background-color: #5ca6ff
.color10  background-color: #9BFFBB
$(document).ready(function()

  var $rawDiv = $('#raw-events'),
      $debounceDiv = $('#debounced-events'),
      $triggerArea = $('.trigger-area'),
      initialized = false,
      frequency = 100,
      barLength = 0,
      globalColor = 2,
      colorNeedChange = false,
      interval_id,
      rawColor = 0,
      debounceColor = 0,
      maxBarLength = 87;
  
  var drawDebouncedEvent = _.debounce(function(div)
   debounceColor = globalColor;
  , frequency*4, leading:false, trailing:true);
  

  var changeDebouncedColor = _.debounce(function(div)
    // Change colors, to visualize easier the "group of events" that is reperesenting this debounced event
    
    globalColor++;
    if (globalColor > 9)
      globalColor = 2;
     
  , frequency*4, leading:false, trailing:true);
  

  function draw_tick_marks()

      // every x seconds, draw a tick mark in the bar
      interval_id = setInterval(function()
      barLength++;   
      $rawDiv.append('<span class="color' + rawColor + '" >');
      $debounceDiv.append('<span class="color' + debounceColor + '" >');
      rawColor = 0; // make it transparent again
      debounceColor = 0; // make it transparent again
        
      if (barLength > maxBarLength)
        clearInterval(interval_id);
      
      
    , frequency);
  ;
  
  
  // Track Mouse movement or clicks for mobile
  $triggerArea.on('click mousemove', function ()  
    if (!initialized) 
      initialized = true;
      draw_tick_marks();
      $(this).addClass('active');
     
    rawColor = globalColor;
    drawDebouncedEvent();
    changeDebouncedColor();
  );

  $('.reset').on('click', function()
    initialized = false;
    $triggerArea.removeClass('active');
    $rawDiv.empty();
    $debounceDiv.empty();
    barLength = 0;
    clearInterval(interval_id);
  );

);

效果图:

你可以看到连续快速的事件是如何被一个 debounce 事件替代的。但是如果事件触发的时间间隔过长,debounce 则不会生效。

2.1. 前缘(或者“immediate”)

你会发现,直到事件停止快速执行以后,debounce 事件才会触发相应功能。为何不立即触发呢?那样的话就跟原本的非 debounce 处理无异了。

直到两次快速调用之间的停顿结束,事件才会再次触发。

前缘 debounce 的例子,如下方代码:

<a class="trigger-area">Trigger area</a>
<a class="reset">Reset</a>
<div class="visualizations">
<h2>Raw events over time</h2>
<div id="raw-events" class="events"></div>
<h2>Debounced events
  <span class="details"> 400ms, trailing</span></h2>
<div id="debounced-events" class="events"></div>
</div>
body 
   background: #444444;
   color: white;
   font: 15px/1.51 system, -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
   margin:0 auto;
   max-width:700px;
   padding:20px;


.events
  padding:0px 20px 10px 20px;
  height: 23px;

.events span 
  height:17px;
  width:6px;
  display:inline-block;
  border-right:1px solid #111;


.events span:last-of-type 
  border:2px solid black;
  border-bottom: 4px solid #AAA;
  border-top: 0px;
  margin-bottom:-17px;
  margin-left:-2px;

h2 
  margin:10px 0 5px 0;
  clear:both;
  font-weight: normal;
  font-size:14px;
  padding:6px 20px;


.trigger-area 
  margin: 0;
  display:inline-block;
  width: 200px;
  height:50px;
  border: 1px solid #5ed1ff;
  padding: 28px 0 0 0;
  text-align: center;
  background-color: transparent;
  cursor:pointer;
  font-size:17px;
  -webkit-user-select: none;  /* Chrome  / Safari */
  -moz-user-select: none;     /* Firefox all */
  -ms-user-select: none;      /* IE 10+ */
  user-select: none;          /* Likely future */    

.trigger-area.active 
  background:#2F5065;

.clickme:hover,
.clickme:active
  background-color: #333;

.clickme:active
  padding: 4px 5px;

.reset 
  display:inline-block;
  width: 120px;
  padding: 10px 0 0 0;
  text-align: center;
  font-size:14px;
  cursor:pointer;
  color:#eee;

.visualizations 
  margin-top:10px;
  background:rgba(0,0,0,0.2);

.details 
  font-size:13px;
  color:#999;


/* stating the obvious: color0 represents our empty color */
.color0  transparent

.color1  background-color: #FFE589
.color2  background-color: #B9C6FF
.color3  background-color: #99FF7E
.color4  background-color: #FFB38A
.color5  background-color: #A5FCFF
.color6  background-color: #FF8E9B
.color7  background-color: #E3FF7E
.color8  background-color: #FFA3D8
.color9  background-color: #5ca6ff
.color10  background-color: #9BFFBB
$(document).ready(function()

  var $rawDiv = $('#raw-events'),
      $debounceDiv = $('#debounced-events'),
      $triggerArea = $('.trigger-area'),
      initialized = false,
      frequency = 100,
      barLength = 0,
      globalColor = 2,
      colorNeedChange = false,
      interval_id,
      rawColor = 0,
      debounceColor = 0,
      maxBarLength = 87;
  
  var drawDebouncedEvent = _.debounce(function(div)
   debounceColor = globalColor;
  , frequency*4, leading:true, trailing:false);
  // 在 underscore.js 中,选项叫 immediate ,而不是 leading:
 

  var changeDebouncedColor = _.debounce(function(div)
    // Change colors, to visualize easier the "group of events" that is reperesenting this debounced event
    
    globalColor++;
    if (globalColor > 9)
      globalColor = 2;
     
  , frequency*4, leading:false, trailing:true);
  

  function draw_tick_marks()

      // every x seconds, draw a tick mark in the bar
      interval_id = setInterval(function()
      barLength++;   
      $rawDiv.append('<span class="color' + rawColor + '" >');
      $debounceDiv.append('<span class="color' + debounceColor + '" >');
      rawColor = 0; // make it transparent again
      debounceColor = 0; // make it transparent again
        
      if (barLength > maxBarLength)
        clearInterval(interval_id);
      
      
    , frequency);
  ;
  
  
  // Track Mouse movement or clicks for mobile
  $triggerArea.on('click mousemove', function ()  
    if (!initialized) 
      initialized = true;
      draw_tick_marks();
      $(this).addClass('active');
     
    rawColor = globalColor;
    drawDebouncedEvent();
    changeDebouncedColor();
  );

  $('.reset')无敌秘籍之 — JavaScript手写代码

节流(Throttling)和去抖(Debouncing)详解

JavaScript节流和防抖

来聊聊JavaScript中的防抖和节流

JavaScript防抖与节流

JavaScript防抖与节流