SoFunction
Updated on 2025-03-03

A brief discussion on JavaScript throttling and anti-shake functions

concept

Throttle function

Methods for executing incoming at fixed intervals

The purpose is to prevent the function execution frequency from being too fast and affecting performance. It is commonly found in functions that bind to scrolling and mouse movement events.

Anti-shake function

For people who have been exposed to hardware, it may be better understood that when the hardware button is pressed, the user presses on a different length of time, and the current fluctuation will be triggered multiple times. Adding an anti-shake function will only trigger once, preventing the problems caused by meaningless current fluctuations.

Why do you need to shake when pressing the buttons to prevent debounce? When the mechanical button is pressed, it does not come in contact very well. Especially for mechanical switches with reeds, they will be opened and closed many times during the contact moments until the switch state completely changes.

When applied in the front end, a common scenario is to trigger query/search/check after the input box typing action has ended for a period of time, instead of triggering every word, causing meaningless ajax query, etc., or functions bound to adjust the window size, actually only need to execute the action after the final window size is fixed.

Your own implementation

Anti-shake function

The key point is that the handle of the delay function is cleared every time it is triggered. Only the last trigger will not clear the handle, so the last trigger will wait for the default 1s to execute the parameter function f passed in debounce. The closure function returned inside debounce is the function triggered every time it is called, and is no longer the original f. Therefore, the arguments here take the arguments in the closure function environment variable and pass it to f when executing f, and get it outside the setTimeout function.

let debounce = function(f, interval = 1000) {
 let handler = null;
 return function() {
  if (handler) {
  clearTimeout(handler);
  }
  let arg = arguments;
  handler = setTimeout(function() {
  (this, arg);
  clearTimeout(handler);
  }, interval)
 }
 }

application:

let input = ('#input');
 ('input', debounce(function(e) {
 ("Your input is",)
 }))

More advanced implementations will also consider using leading and trailing as parameters, starting with the function and eliminating the subsequent jitter, or executing the function at the end to eliminate the previous jitter, as in my example. The loadash anti-shake function will be analyzed in detail later.

Throttle function

let throttle = function(f,gap = 300){
  let lastCall = 0;
  return function(){
  let now = ();
  let ellapsed = now - lastCall;
  if(ellapsed < gap){
   return
  }
  (this,arguments);
  lastCall = ();
  }
 }

During the period when the closure function is constantly called, it records the time from the last call interval. If the interval time is less than the time set by the throttling, it will be returned directly, and the function f that is actually wrapped is not executed. Only when the interval time is greater than the time set by the throttling function will it be called f and the call time is updated.

application:

('scroll', throttle(function (e) {
 // Logic for determining whether to scroll to the bottom (e,);
 }));

lodash source code analysis

The above is the most basic and simple implementation of the throttling anti-shake function. Let’s analyze the analysis of the throttling anti-shake function in the lodash library.

Use of throttling functions

$(window).on('scroll', _.debounce(doSomething, 200));
function debounce(func, wait, options) {
 var lastArgs,
  lastThis,
  result,
  timerId,
  lastCallTime = 0,
  lastInvokeTime = 0,
  leading = false,
  maxWait = false,
  trailing = true;

 if (typeof func != 'function') {
  throw new TypeError(FUNC_ERROR_TEXT);
 }
 wait = wait || 0;
 if (isObject(options)) {
  leading = !!;
  maxWait = 'maxWait' in options && (() || 0, wait);
  trailing = 'trailing' in options ? !! : trailing;
 }

 function invokeFunc(time) {
  var args = lastArgs,
  thisArg = lastThis;

  lastArgs = lastThis = undefined;
  lastInvokeTime = time;
  result = (thisArg, args);
  return result;
 }

 function leadingEdge(time) {
  ("leadingEdge setTimeout")
  // Reset any `maxWait` timer.
  lastInvokeTime = time;
  // Start the timer for the trailing edge.
  timerId = setTimeout(timerExpired, wait);
  // Invoke the leading edge.
  return leading ? invokeFunc(time) : result;
 }

 function remainingWait(time) {
  var timeSinceLastCall = time - lastCallTime,
  timeSinceLastInvoke = time - lastInvokeTime,
  result = wait - timeSinceLastCall;
  ("remainingWait",result)
  return maxWait === false ? result : (result, maxWait - timeSinceLastInvoke);
 }

 function shouldInvoke(time) {
  ("shouldInvoke")
  var timeSinceLastCall = time - lastCallTime,
  timeSinceLastInvoke = time - lastInvokeTime;
  ("time",time,"lastCallTime",lastCallTime,"timeSinceLastCall",timeSinceLastCall)
  ("time",time,"lastInvokeTime",lastInvokeTime,"timeSinceLastInvoke",timeSinceLastInvoke)
  ("should?",(!lastCallTime || (timeSinceLastCall >= wait) ||
  (timeSinceLastCall < 0) || (maxWait !== false && timeSinceLastInvoke >= maxWait)))
  // Either this is the first call, activity has stopped and we're at the
  // trailing edge, the system time has gone backwards and we're treating
  // it as the trailing edge, or we've hit the `maxWait` limit.
  return (!lastCallTime || (timeSinceLastCall >= wait) ||
  (timeSinceLastCall < 0) || (maxWait !== false && timeSinceLastInvoke >= maxWait));
 }

 function timerExpired() {
  ("timerExpired")
  var time = ();
  if (shouldInvoke(time)) {
  return trailingEdge(time);
  }
  ("Restart the timer.",time,remainingWait(time))
  // Restart the timer.
  ("timerExpired setTimeout")
  timerId = setTimeout(timerExpired, remainingWait(time));
 }

 function trailingEdge(time) {
  clearTimeout(timerId);
  timerId = undefined;

  // Only invoke if we have `lastArgs` which means `func` has been
  // debounced at least once.
  ("trailing",trailing,"lastArgs",lastArgs)
  if (trailing && lastArgs) {
  return invokeFunc(time);
  }
  lastArgs = lastThis = undefined;
  return result;
 }

 function cancel() {
  if (timerId !== undefined) {
  clearTimeout(timerId);
  }
  lastCallTime = lastInvokeTime = 0;
  lastArgs = lastThis = timerId = undefined;
 }

 function flush() {
  return timerId === undefined ? result : trailingEdge(());
 }

 function debounced() {
  var time = (),
  isInvoking = shouldInvoke(time);
  ("time",time);
  ("isInvoking",isInvoking);
  lastArgs = arguments;
  lastThis = this;
  lastCallTime = time;

  if (isInvoking) {
  if (timerId === undefined) {
   return leadingEdge(lastCallTime);
  }
  // Handle invocations in a tight loop.
  clearTimeout(timerId);
  ("setTimeout")
  timerId = setTimeout(timerExpired, wait);
  return invokeFunc(lastCallTime);
  }
  return result;
 }
  = cancel;
  = flush;
 return debounced;
 }

ref

/debouncing-throttling-explained-examples/

/lodash/lodash/blob/4.7.0/#L9840

/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/

The above is a brief discussion on JavaScript throttling and anti-shake functions. For more information about JavaScript throttling and anti-shake functions, please pay attention to my other related articles!