Underscore.js throttle vs debounce example

An example test visually demonstrating the difference between Underscore.js's throttle() and debounce(), the latter with the "immediate" parameter set true.

by Nhat Nguyen

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<p>Click the meters repeatedly. Events throttled/filtered by 1 second.</p>
<ul class="meter meter1">
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
  <li>7</li>
  <li>8</li>
</ul>
<ul class="meter meter2">
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
  <li>7</li>
  <li>8</li>
</ul>
<p>
  <code>_.throttle()</code> will fire once, and subsequent clicks will fire only after 1 second has passed&mdash;but the subsequent click will happen.
  <b>This is not good for filtering double-clicks.</b>
</p>
<p>
  <code>_.debounce()</code> (with a <code>true</code> third parameter) will <i>filter</i> subsequent clicks.
  <b>This <i>is</i> good for filtering accidental clicks.</b>
</p>

CSS

p {
  margin: 20px 20px 0px;
  clear: both;
}

.meter {
  margin: 10px 20px;
  padding: 5px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-family: sans-serif;
  display: block;
  float: left;
  clear: left;
  box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2);
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.meter:hover {
  background: #ff8;
  cursor: pointer;
}

.meter li {
  border: 1px solid #ccc;
  background: #fff;
  width: 25px;
  height: 20px;
  text-align: center;
  padding-top: 8px;
  float: left;
  border-radius: 25px;
  margin-right: 6px;
  display: inline-block;
  color: #ccc;
}

.meter li:last-child {
  float: none;
  margin: 0;
}

.meter li.on {
  background: #2f2;
  box-shadow: inset 0px 1px 0px 2px rgba(0, 0, 0, 0.1);
  border: 1px solid #777;
  color: #000;
  font-weight: bold;
  text-shadow: 0px 1px #fff;
}

.meter1:after {
  content: "_.throttle()";
  padding: 0px 5px;
}

.meter2:after {
  content: "_.debounce()";
  padding: 0px 5px;
}

.blink:after {
  font-weight: bold;
}

JavaScript

$(function() {
  function changeMeter(ev) {
    var $ul = $(ev.target).closest("ul"),
      numLi = $("li", $ul).length,
      $on = $("li.on", $ul),
      numOn = $on.length;
    if (numLi == numOn) {
      // All filled! Reset!
      $on.removeClass("on");
    } else {
      // Add a new $on
      if (numOn == 0) {
        $("li:first", $ul).addClass("on");
      } else {
        $("li.on:last", $ul).next().addClass("on");
      }
    }
  }

  $(".meter1").on("click", _.throttle(changeMeter, 1000));
  $(".meter2").on("click", _.debounce(changeMeter, 1000, true));
  $(".meter").on("click", function(ev) {
    var $meter = $(ev.target).closest("ul");
    $meter.addClass("blink");
    setTimeout(function() {
      $meter.removeClass("blink");
    }, 50);
  });
});