Understand the anti-shake function and throttling function

Image stabilization function

  • When an event fires, the corresponding function does not fire immediately, but instead waits for a period of time.
  • When events fire consecutively, the function’s trigger wait time is reset (delayed) continuously.

In layman’s terms, stabilization means that every time an event is triggered, it takes a certain amount of time to actually respond to that event.

There are many scenarios in which the anti-shake function can be used, and there are many situations in which we do not want these events to be triggered repeatedly:

  • If the input box changes every time, it will put a lot of pressure on the server. Therefore, we want to not send requests during continuous input until the user finishes typing or has not continued to input for a period of time.
  • Button clicking frequently triggers events (malicious behavior).
  • The resize event is frequently triggered when the user zooms the browser.

Throttling function

  • If events are started frequently, the throttling function will execute the function at a certain frequency.
  • The frequency at which the function is executed is always fixed, no matter how many times it is fired.

Realize the anti – shake function

Before implementing a stabilization function, let’s introduce the concept of closures. Many people may ask, what does a stabilization function have to do with a closure, and what is a closure? No hurry, we’ll get to that.

closure

Closure generation is related to JS execution.

  • Before executing the js code, it scans the global code and creates a GlobalObject with built-in classes and functions, as well as Windows.

  • If a variable declared by var is found while scanning the global code, a copy of the variable is mounted in the GO object with the value undefined.

  • If scan to function declarations, as a result of this function may not use, so only for function analytical v8 engine, in function of the resolution is not on the internal parse, will only create function objects in the heap memory, save for two things in the function object: the parent scope parentScope and blocks of code inside the function.

  • After pre-parsing, the function object is mounted in the GO object, which saves a reference to the function object’s address.

  • After scanning the code, the execution phase begins. Js code execution is performed in the execution context stack, so we will generate the execution context of the GO object. Generating the execution context consists of two steps: creating a Variable Object to point to GO and executing the JS code.

  • As code executes, variable declarations are assigned in turn.

  • If a function is executed while executing the code, the inside of the function is further parsed. The process is similar: An Activation Object is created, the code is scanned, and if a function is scanned, the function Object (including two parts: parentScope and code block) is also created in heap memory, and the address of the function Object is mounted to the AO.

  • After parsing the code inside the function, we need to execute the code inside the function. At this time, we will create the function execution context and push it into the stack. The creation process also includes two parts: create VO to point to AO, and execute the code. When the function is executed, the execution context is removed from the stack and the AO is destroyed.

  • There is, however, one case where an AO object cannot be destroyed: if the function returns a function declared inside the function, and the function is gradually found globally based on a variable, then the AO object cannot be destroyed. ParentScope is stored in the function object, if you can find this function object, then you must be able to find the AO object pointed to by parentScope, and can obtain the variable mounted in the AO object.

Simply put, a function is a closure if it can access variables in the outer scope. Closures tend to be great for holding private variables, but they can also cause memory leaks if used incorrectly…

Preliminary realization of anti – shake function

The implementation of a stabilization function is to pass in a function and a time, and return a stabilization function. Using the closure to save the timer, and then to achieve this anti – shake effect.

function debounce(fn,time){

    // Use the closure to define a timer that can be retrieved inside the function returned
    let timer 
    return function(. args){  // Returns a function that accepts arguments

        // Resets the timer each time before firing the function
        clearTimeout(timer)
        
        timer = setTimeout(() = >{
            fn.apply(this,args) // Execute the function
        },time)
    }
}
Copy the code

Execute immediately for the first time

If we want the function to respond immediately when it fires for the first time, and only respond when it fires later, we can use the following method.

function debounce(fn, time, immediate = true{
  let timer;
  // Whether the flag is called immediately
  let isInvoke = false
  return function (. args{
    // If it is not called immediately and needs to be called immediately
    if(! isInvoke && immediate){ fn.apply(this, args); 
        isInvoke = true
    }else{
        clearTimeout(timer);
        timer = setTimeout(() = > {
          fn.apply(this, args); 
          isInvoke = false  // Don't forget to reset isInvoke when the shock function ends}, time); }}; }Copy the code

Implement throttling function

Preliminary implementation of throttling functions

To implement the throttling function, you only need to record the timestamp of the last triggered function and the current timestamp, and compare.

function throttle (fn,time){
    // Save the timestamp of the last execution
    let lastTime = 0
    return function(. args){
        // Get the current timestamp
        let nowTime = new Date().getTime()
        // Execute if the interval between two triggers is longer than time
        if(nowTime - lastTime >= time){
            fn.apply(this. args) lastTime = nowTime } } }Copy the code

Whether to execute the first time immediately

As you can see in the throttling function we implemented above, since lastTime is 0, the return function is executed immediately the first time it is executed. If we want to be able to control whether we execute immediately the first time, we can do this.

// if leading is false, it does not want to trigger immediately
function throttle(fn, time, options = { leading: false }) {
    const { leading } = options
    let lastTime = 0
    let timer
    return function () {
        const nowTime = new Date().getTime()
        // Set lastTime to nowTime if you don't want the first trigger to be executed immediately
        // Then the interval is calculated from 0 until the interval is greater than the given time
        
        //lastTime === 0 can be used to indicate whether the first event in a sequence of events is triggered
        if(lastTime === 0 && leading === false) lastTime = nowTime 
        const remainTime = time - (nowTime - lastTime)
        if (remainTime <= 0) {
            fn.apply(this)
            lastTime = nowTime
        } 
    }
}
Copy the code

Whether to execute the last time

This is the question of whether to trigger if the last trigger point is in the middle of two trigger frequency nodes. In the above implementation, it does not fire if the last time it fires is in the middle of two cycles. As shown in figure.

So what do we do if we want to trigger the last time in the middle? We can set a timer at the beginning of each cycle, and if the last one is triggered between two cycles, the timer will be executed at the end of that time.

function throttle(fn, time, options = { leading: false, trailing: true }{
  const { leading, trailing } = options;
  let lastTime = 0;
  let timer;   // Save the timer
  return function (. args{
    const nowTime = new Date().getTime();
    if (lastTime === 0 && leading === false) lastTime = nowTime;
    const remainTime = time - (nowTime - lastTime);
    if (remainTime <= 0) {
      // Execute the function once if the interval is longer than time
      // If there are timers, we remove timers because we don't want timers to execute functions
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      lastTime = nowTime;
    } else if(trailing && ! timer) {// We just need to set a timer
      timer = setTimeout(() = > {
        fn.apply(this, args);
        timer = null// Do not forget to reset timer and lastTime when the timer triggers the function
        // Set lastTime to 0 if you don't want it to be triggered the first time next time
        // If the value is set to 0, it is reset to nowTimelastTime = ! leading ?0 : new Date().getTime() + time; }, remainTime); }}; }Copy the code