Summary of common interview questions

Debounce and throttle of js function

Original text: Function anti chattering and throttling;

preface:

During our normal development, there are many scenarios that frequently trigger events, such as real-time request from search box, onmousemove, resize, onscroll, etc. sometimes, we can't or don't want to trigger events frequently. What should we do? At this time, function anti shake and function throttling should be used!

Preparation of materials:

<div
  id="content"
  style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"
></div>

<script>
  let num = 1;
  let content = document.getElementById("content");

  function count() {
    content.innerHTML = num++;
  }
  content.onmousemove = count;
</script>

In this code, if the mouse moves freely in the gray area, the count() function will continue to be triggered, resulting in the following effects:

Next, we limit frequent operations through anti chattering and throttling.

Function debounce

The same event is triggered multiple times in a short time. Only the last or the first one is executed, and the middle one is not executed.

// Non immediate version
function debounce(func, wait) {
  let timer;
  return function () {
    let context = this; // Note that this points to
    let args = arguments; // e is stored in arguments

    if (timer) clearTimeout(timer);

    timer = setTimeout(() => {
      func.apply(this, args);
    }, wait);
  };
}

We use it this way:

content.onmousemove = debounce(count, 1000);

The non immediate execution version means that the function will not execute immediately after the event is triggered, but will execute after n seconds. If the event is triggered again within n seconds, the function execution time will be recalculated. The effects are as follows:

// Execute now
function debounce(func, wait) {
  let timer;
  return function () {
    let context = this; // Who does this point to here?
    let args = arguments; // e is stored in arguments

    if (timer) clearTimeout(timer);

    let callNow = !timer;

    timer = setTimeout(() => {
      timer = null;
    }, wait);

    if (callNow) func.apply(context, args);
  };
}

The immediate execution version means that the function will execute immediately after the event is triggered, and then the function can continue to execute only if the event is not triggered within n seconds. The usage is the same as above, and the effect is as follows:

// Synthetic version
/**
 * @desc Function anti shake
 * @param func objective function 
 * @param wait Milliseconds of delayed execution
 * @param immediate true - Execute now, false - delay execution
 */
function debounce(func, wait, immediate) {
  let timer;
  return function () {
    let context = this,
      args = arguments;

    if (timer) clearTimeout(timer);
    if (immediate) {
      let callNow = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, wait);
      if (callNow) func.apply(context, args);
    } else {
      timer = setTimeout(() => {
        func.apply(context, args);
      }, wait);
    }
  };
}

Throttle

A function that triggers events continuously but executes only once in N seconds. That is, execute twice in 2n seconds. Throttling, literally, dilutes the frequency of function execution.

There are also two versions, timestamp and timer.

// Timestamp version
function throttle(func, wait) {
  let previous = 0;
  return function () {
    let now = Date.now();
    let context = this;
    let args = arguments;
    if (now - previous > wait) {
      func.apply(context, args);
      previous = now;
    }
  };
}

The usage is as follows:

content.onmousemove = throttle(count, 1000);

The effects are as follows:

It can be seen that during the continuous triggering event, the function will execute immediately and every 1s.

// Timer version
function throttle(func, wait) {
  let timeout;
  return function () {
    let context = this;
    let args = arguments;
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null;
        func.apply(context, args);
      }, wait);
    }
  };
}

The usage is the same as above, and the effect is as follows:

It can be seen that in the process of continuously triggering events, the function will not be executed immediately, and it will be executed every 1s. After stopping triggering events, the function will be executed again.

It should be easy to find that the difference between the throttling function of the timestamp version and the timer version is that the function trigger of the timestamp version is at the beginning of the time period, while the function trigger of the timer version is at the end of the time period.

Similarly, we can combine the throttling function of timestamp version and timer version to realize the throttling function of double sword version.

/**
 * @desc Function throttling
 * @param func function
 * @param wait Milliseconds of delayed execution
 * @param type 1 Table timestamp version, 2 table timer version
 */
function throttle(func, wait, type) {
  if (type === 1) {
    let previous = 0;
  } else if (type === 2) {
    let timeout;
  }
  return function () {
    let context = this;
    let args = arguments;
    if (type === 1) {
      let now = Date.now();

      if (now - previous > wait) {
        func.apply(context, args);
        previous = now;
      }
    } else if (type === 2) {
      if (!timeout) {
        timeout = setTimeout(() => {
          timeout = null;
          func.apply(context, args);
        }, wait);
      }
    }
  };
}

Appendix:

Analysis on the direction of context (this) in throttling / anti chattering function:

First, when executing the line of throttle(count, 1000), there will be a return value, which is a new anonymous function, so content onmousemove = throttle(count,1000); This sentence can finally be understood as follows:

content.onmousemove = function() {
    let now = Date.now();
    let context = this;
    let args = arguments;
    ...
    console.log(this)
}

So far, only the event function has been bound and has not been actually executed, and the specific direction of this needs to be determined at the real run time. So at this time, if we put the front content Replace onMouseMove with var fn and execute FN (). At this time, the internal this will print out as a window object.

Secondly, when we trigger the onMouseMove event, we really execute the anonymous function mentioned above, that is, content onmousemove() . At this time, the above anonymous function is executed through the object Function name (), then this inside the function naturally points to the object.

Finally, if the calling method of func inside an anonymous function is the most common direct execution of func(), then this inside func must point to window. Although there is no exception when the code is simple (the result is the same as normal), it will be a hidden bug and I have to pay attention to it! So, we capture this through an anonymous function, and then through func Apply () to achieve content OnMouseMove = func.

It can be said that we should pay attention to the binding of this inside higher-order functions.

Input anti shake throttle

<!DOCTYPE html>
<html lang="en">
  <head>
    
    
    
    <title>Document</title>
  </head>
  <body>
    <input class="input" value="" type="text" />
    <button class="btn">click</button>
    <script>
      document
        .querySelector(".input")
        .addEventListener("input", throttle(handleFn, 500, true));
      let index = 1;
      function handleFn(e) {
        console.log("index", index);
        console.log("handleFn", e, this.value);
        index++;
      }
      // The anti shake interval is triggered multiple times within the delay time, and only the first or last time is executed
      function debounce(fn, delay, triggerNow = false) {
        let timer = null;
        return function (...arg) {
          const context = this;
          // clearTimeout can only be executed for the first or last time, otherwise it will be cleaned up within the specified delay time
          if (timer) clearTimeout(timer);
          // The immediate execution version is triggered multiple times in a delay time, and only the first time is executed
          if (triggerNow) {
            let first = !timer;
            if (first) fn.call(context, ...arg);
            timer = setTimeout(() => {
              // Restore timer for first execution
              timer = null;
            }, delay);
            // The last execution version is triggered many times in a delay time, and only the last execution is executed
          } else {
            timer = setTimeout(() => {
              fn.call(context, ...arg);
            }, delay);
          }
        };
      }
      // Throttling a delay time starts multiple times and only executes once. fristTrigger executes the first time and the last time
      function throttle(fn, delay, fristTrigger = false) {
        let timer = null; // The timer defaults to null
        let last = 0; // The last timestamp defaults to 0
        return function (...arg) {
          const context = this;
          if (fristTrigger) {
            // The first time in a certain period of time
            // Current timestamp - previous timestamp > delay re execution
            const cur = Date.now();
            if (cur - last > delay) {
              fn.call(context, ...arg);
              last = cur;
            }
          } else {
            // If there is no timer, execute the emptying timer again, and execute the last time within a certain time
            if (!timer) {
              timer = setTimeout(() => {
                timer = null;
                fn.call(context, ...arg);
              }, delay);
            }
          }
        };
      }
    </script>
  </body>
</html>

Added by bdichiara on Wed, 05 Jan 2022 10:50:17 +0200