The original link

preface

Generally for listening to some intensive keyboard, mouse, gesture events need to interact with the back-end request, modify DOM, tremble, throttling is very necessary.

Image stabilization

Usage scenarios

  • Keyword Remote search drop-down box
  • resize

For this type of operation, you want to get the final keyword entered by the user, determine the drag size, and then interact with the server. In order to reduce the pressure on the server and avoid wasting resources on the server, it is necessary to prevent shaking.

case

  • Input box anti – shake
// Record the time
let last = new Date().getTime();
  // Simulate an Ajax request
function ajax(content) {
  const d = new Date().getTime();
  const span = d - last;
  console.log(`${content}interval${span}ms`);
  last = d;
}

const noActionInput = document.getElementById('noAction');

noActionInput.addEventListener('keyup'.function(e) {
  ajax(e.target.value);
});
Copy the code
  • Not being

You can see that too many requests are being sent for too many intermediate states.

/** * Generally uses closures to store and privatize timer 'timer' ** when called again within 'delay' time to clear the unexecuted timer * retimer * @param fn * @param delay * @returns {Function} */
function debounce(fn, delay) {
  let timer = null;
  return function() {
    // all intermediate states are cleared
    timer && clearTimeout(timer);
    // Just need the final state, execute
    timer = setTimeout((a)= > fn.apply(this.arguments), delay);
  };
}
    
const debounceInput = document.getElementById('debounce');

let debounceAjax = debounce(ajax, 100);

debounceInput.addEventListener('keyup'.function(e) {
  debounceAjax(e.target.value);
});
Copy the code
  • After being

It can be found:

  1. If the input is slow, about everydelay 100msExecute once;
  2. If the input is too fast, it indicates that the user is continuously typing. The callback is executed after the user finishes typing and slows down.

The throttle

Usage scenarios

  • Sliding scroll bar
  • Shooting games shoot bullets
  • Flow rate of water from faucet

For some continuous events, in order to show smooth transitions, we also need to care about intermediate states. But attenuating the frequency of intensive events is still a performance optimization killer.

errata

Two very common errors, so popular that I can’t help correcting them.

// Time stamp version
function throttleError1(fn, delay) {
  let lastTime = 0;
  return function () {
    const now = Date.now();
    const space = now - lastTime; // Time interval, the first time will be a large value
    if (space > delay) {
      lastTime = now;
      fn.apply(this.arguments); }}; }}// Timer version
function throttleError2(fn, delay) {
  let timer;
  return function () {
    if(! timer) { timer = setTimeout((a)= > {
        timer = null;
        fn.apply(this.arguments); }, delay); }}; }Copy the code

Both versions have problems. First, assume delay=100ms, and assume that timers are executed on time.

  • The time stamp version
  1. Because for the first timenow - lastTime === nowThis value is very large, the first 0ms immediately executed, the user within 0-100ms to execute the interaction is invalid, if the user stayed in 99ms, the last lost.
  2. For example, if you want to set the style by the height of the scroll bar from the top, you can’t handle the scroll bar from 0 to 100px in 99ms.
  • PS: time-stamped version, with an application scenario, to prevent repeated submissions within a certain period of time.

  • The timer version

  1. The first 0ms will not be executed immediately with a 100ms delay, like the first shot takes 100ms for the bullet to come out.
  • Smart readers, as you might imagine, can combine the two to solve the problem.
  1. First 0ms immediate execution without delay;
  2. Get the last state to ensure the last execution.

case

  • Visually continuous adjustment while scrolling the sliderdom
* @param fn * @param delay * @returns {Function} */
function throttle(fn, delay) {
  let timer, lastTime;
  return function() {
    const now = Date.now();
    const space = now - lastTime; // Interval time
    if( lastTime && space < delay ) { // In response to the user's last operation
      // It is not the first time that the timer has not reached the time.
      timer && clearTimeout(timer);
      // Reset the timer
      timer = setTimeout((a)= > {
        lastTime = Date.now(); // Don't forget to record the time
        fn.apply(this.arguments);
      }, delay);
      return;
    }
    // Time is up
    lastTime = now;
    fn.apply(this.arguments);
  };
}

const throttleAjax = throttle(ajax, 100);

window.addEventListener('scroll'.function() {
  const top = document.body.scrollTop || document.documentElement.scrollTop;
  throttleAjax('scrollTop: ' + top);
});
Copy the code
  • Before the throttle

The pullback was too dense. (PS: It is often heard that scroll has its own throttle, which means that it is triggered once at about 16ms per frame.)

  • After the throttle

As you can see, whether you ski slowly or fast, it’s like a timed trigger.

  • Careful readers may notice that if the interaction is stuck at 199ms and the timer is executed at 300ms, with an interval of about 200ms, the timer delay should not be set to the originaldelay.

* @param fn * @param delay * @returns {Function} */ Function throttle(fn, delay) { let timer, lastTime; return function() { const now = Date.now(); const space = now - lastTime; / / time interval between the if (lastTime && space < delay) {/ / the last time in response to user action / / not for the first time, haven't to time, remove the timer, timing again. timer && clearTimeout(timer); // Reset the timer timer = setTimeout(() => {- lastTime = now; // This was wrong before, thanks for pointing it out in the comments section
+ lastTime = Date.now(); // Don't forget to record the time
        fn.apply(this, arguments);
- }, delay);
+ }, delay - space);return; } // time = now; fn.apply(this, arguments);+ // If the timer is not cleared, there will be redundant intermediate execution
+ timer && clearTimeout(timer);
  };
}
Copy the code
  • If you forget to clear at the end

  • The final result

  • The actual production is achieved by using LoDash’s mature and reliable anti-shake and throttling capabilities.

  • Lodash effect

  • Trailing controls the execution of 99ms. Trailing controls the execution of 100ms. MaxWait controls the execution of 99ms.

  • Here are some common ways to use Lodash.

  1. Achieve similarscrollComes with a frame16msEffect:throttle(fn, /* wait = undefined */)It’s actually used internallyrequestAnimationFrame.
  2. Very common, sending requests to prevent repeated submissions, such as the first click to execute,500msAll clicks inside will not be executed:debounce(fn, 500, { leading: true, trailing: false })
  3. For example, adomCleared,debounced.cancel()To cancel the trailing call to avoid a dom error.
  4. And so on.

conclusion

Both anti-shake and throttling use closures to achieve internal data acquisition and maintenance. Anti – shake is easier to understand, throttling needs to be thought about a little bit. There is still a difference between the two, do not make a mistake again, paste the problem code. Anti – shake and throttling are indispensable for performance optimization of frequent DOM events.

reference

  1. In this paper, the demo
  2. 7 minutes to understand the throttling, anti-shaking and application scenarios of JS