1. Why should the function be shaken

There is the following code

window.onresize = (a)= > {
  console.log(Trigger window listening callback function)}Copy the code

When we zoom in and out of the browser window on the PC, we can easily trigger 30 events a second. The same is true when the phone triggers other Dom time listening callbacks.

The callback function here is just printing strings, and if the callback function were more complex, you can imagine that the browser would be very stressed and the user experience would be terrible.

Listening callbacks to Dom events such as REsize or Scroll are triggered frequently, so we need to limit them.

Second, implementation ideas

Function chattering is simply for a certain period of time for the continuous function call, let it be executed only once, the initial implementation idea is as follows:

The first time the function is called, a timer is created that runs the code after a specified interval. When the function is called a second time, it clears the previous timer and sets another. If the previous timer has already been executed, this operation is meaningless. However, if the previous timer has not been executed, it is simply replaced with a new timer. The goal is to execute only after the request to execute the function has stopped for some time.

3. Application scenario of Debounce

  • The resize/scroll statistics event is triggered every time
  • Validation of text input (send AJAX request for validation after continuous text input, once validation is fine)

Fourth, the final version of function anti-shake

Code to speak, there are errors kindly pointed out

function debounce(method, wait, immediate) {
  let timeout
  // Debmentioning is the return value
  // use Async/Await to process Async. If the function is executing asynchronously, wait for setTimeout to finish and return the value of the original function
  // args returns the argument passed to method when the function is called
  let debounced = function(. args) {
    return new Promise (resolve= > {
      // Record the result of the original function
      let result
      // Set this when method is executed to this when the function returned by debounce is called
      let context = this
      // Clear the timer if it exists
      if (timeout) {
        clearTimeout(timeout)
      }
      // Immediate execution requires two conditions: first, that immediate is true and timeout is not assigned or set to null
      if (immediate) {
        // If the timer does not exist, execute immediately and set a timer. Wait milliseconds and set the timer to null
        // This ensures that wait will not be triggered again within milliseconds of executing immediately
        letcallNow = ! timeout timeout = setTimeout((a)= > {
          timeout = null
        }, wait)
        // If the above two conditions are met, the execution is performed immediately and the execution result is recorded
        if (callNow) {
          result = method.apply(context, args)
          resolve(result)
        }
      } else {
        // If immediate is false, wait for the function to execute and record the result
        // And sets the Promise state to fullfilled so that the function continues
        timeout = setTimeout((a)= > {
          // args is an array, so use fn.apply
          // You can also write method.call(context,... args)
          result = method.apply(context, args)
          resolve(result)
        }, wait)
      }
    })
  }

  // Add a cancellation method to the returned debmentioning function
  debounced.cancel = function() {
    clearTimeout(timeout)
    timeout = null
  }

  return debounced
}
Copy the code

Note that if the return value of the original function is required, the outer function calling the buffeted function needs to wait for the result to return using Async/Await syntax

See the code for usage:

function square(num) {
  return Math.pow(num, 2)}let debouncedFn = debounce(square, 1000.false)

window.addEventListener('resize'.async() = > {let val
  try {
    val = await debouncedFn(4)}catch (err) {
    console.error(err)
  }
  // Stop scaling for 1S and output:
  // The original function returns: 16
  console.log(The return value of the original function is${val}`)},false)
Copy the code

For detailed implementation steps, see below

Implementation of Debounce

1. Implementation in JavaScript Advanced Programming (3rd edition)

function debounce(method, context) {
  clearTimeout(method.tId)
  method.tId = setTimeout((a)= > {
    method.call(context)
  }, 1000)}function print() {
  console.log('Hello World')}window.onresize = debounce(print)
Copy the code

We keep zooming in and out of the window, and when we stop for a second, we print Hello World.

One thing that can be optimized is that this implementation has the Side Effect of changing the input value of the method and adding new attributes to the method

2. Optimize the first version: Eliminate side effects and isolate timers

function debounce(method, wait, context) {
  let timeout
  return function() {
    if (timeout) {
      clearTimeout(timeout)
    }
    timeout = setTimeout((a)= > {
      method.call(context)
    }, wait)
  }
}
Copy the code

3. Optimize the second version: automatically adjust this to point correctly

Previously we had to manually pass in the function execution context. Now we optimize this to point to the correct object.

function debounce(method, wait) {
  let timeout
  return function() {
    // Set this when method is executed to this when the function returned by debounce is called
    let context = this
    if (timeout) {
      clearTimeout(timeout)
    }
    timeout = setTimeout((a)= > {
      method.call(context)
    }, wait)
  }
}
Copy the code

4. Optimization version 3: Functions can pass in arguments

Even though our function doesn’t need to pass parameters, remember that JavaScript provides the event object in the event handler, so we need to do that.

function debounce(method, wait) {
  let timeout
  // args returns the argument passed to method when the function is called
  return function(. args) {
    let context = this
    if (timeout) {
      clearTimeout(timeout)
    }
    timeout = setTimeout((a)= > {
      // args is an array, so use fn.apply
      // You can also write method.call(context,... args)
      method.apply(context, args)
    }, wait)
  }
}
Copy the code

5. Optimized version 4: Provides immediate execution options

Sometimes I don’t want to wait until the event stops firing. I want to execute the function immediately and then wait n milliseconds before I can fire again.

function debounce(method, wait, immediate) {
  let timeout
  return function(. args) {
    let context = this
    if (timeout) {
      clearTimeout(timeout)
    }
    // Immediate execution requires two conditions: first, that immediate is true and timeout is not assigned or set to null
    if (immediate) {
      // If the timer does not exist, execute immediately and set a timer. Wait milliseconds and set the timer to null
      // This ensures that wait will not be triggered again within milliseconds of executing immediately
      letcallNow = ! timeout timeout = setTimeout((a)= > {
        timeout = null
      }, wait)
      if (callNow) {
        method.apply(context, args)
      }
    } else {
      // If immediate is false, wait milliseconds
      timeout = setTimeout((a)= > {
        // Args is an array-like object, so use fn.apply
        // You can also write method.call(context,... args)
        method.apply(context, args)
      }, wait)
    }
  }
}
Copy the code

6. Optimized version 5: Provides cancellation functionality

Sometimes we need to be able to manually cancel the anti-shake during this period of time that cannot be triggered. The code implementation is as follows:

function debounce(method, wait, immediate) {
  let timeout
  // Assign the returned anonymous function to debmentioning so that a cancellation method will be added to it
  let debounced = function(. args) {
    let context = this
    if (timeout) {
      clearTimeout(timeout)
    }
    if (immediate) {
      letcallNow = ! timeout timeout = setTimeout((a)= > {
        timeout = null
      }, wait)
      if (callNow) {
        method.apply(context, args)
      }
    } else {
      timeout = setTimeout((a)= > {
        method.apply(context, args)
      }, wait)
    }
  }

  // Add or cancel the function
  // let myFn = debounce(otherFn)
  // myFn.cancel()
  debounced.cancel = function() {
    clearTimeout(timeout)
    timeout = null}}Copy the code

At this point, we have a fairly complete implementation of the Debounce function in underscore.

6. Remaining problems

The underscore method will return the return value of the function again in the debmentioning function, but this will have a problem. If the value of the immediate parameter is not true, the original function does not return a value when it is triggered for the first time. The original function is executed asynchronously within setTimeout, and the return value is undefined.

The value returned by the second firing is the value returned by the first execution, the value returned by the third firing is the value returned by the second execution, and so on.

1. Use the callback function to process the return value of the function

function debounce(method, wait, immediate, callback) {
  let timeout, result
  let debounced = function(. args) {
    let context = this
    if (timeout) {
      clearTimeout(timeout)
    }
    if (immediate) {
      letcallNow = ! timeout timeout = setTimeout((a)= > {
        timeout = null
      }, wait)
      if (callNow) {
        result = method.apply(context, args)
        // Use the callback function to handle the return value of the function
        callback && callback(result)
      }
    } else {
      timeout = setTimeout((a)= > {
        result = method.apply(context, args)
        // Use the callback function to handle the return value of the function
        callback && callback(result)
      }, wait)
    }
  }

  debounced.cancel = function() {
    clearTimeout(timeout)
    timeout = null
  }

  return debounced
}
Copy the code

We can then pass a callback function to handle the return value of the function when the function is buffered, using the following code:

function square(num) {
  return Math.pow(num, 2)}let debouncedFn = debounce(square, 1000.false, val => {
  console.log(The return value of the function is:${val}`)})window.addEventListener('resize', () => {
  debouncedFn(4)},false)

// Stop scaling for 1S and output:
// The original function returns: 16
Copy the code

2. Use Promise to handle the return value

function debounce(method, wait, immediate) {
  let timeout, result
  let debounced = function(. args) {
    // Return a Promise so that you can get the return value of the original function using then or Async/Await syntax
    return new Promise(resolve= > {
      let context = this
      if (timeout) {
        clearTimeout(timeout)
      }
      if (immediate) {
        letcallNow = ! timeout timeout = setTimeout((a)= > {
          timeout = null
        }, wait)
        if (callNow) {
          result = method.apply(context, args)
          // Pass the return value of the original function to resolve
          resolve(result)
        }
      } else {
        timeout = setTimeout((a)= > {
          result = method.apply(context, args)
          // Pass the return value of the original function to resolve
          resolve(result)
        }, wait)
      }
    })
  }

  debounced.cancel = function() {
    clearTimeout(timeout)
    timeout = null
  }

  return debounced
}
Copy the code

Use method 1: When calling a function that has been shaken, use then to get the return value of the original function

function square(num) {
  return Math.pow(num, 2)}let debouncedFn = debounce(square, 1000.false)

window.addEventListener('resize', () => {
  debouncedFn(4).then(val= > {
    console.log(The return value of the function is:${val}`)})},false)

// Stop scaling for 1S and output:
// The original function returns: 16
Copy the code

Use method 2: Call the outer function of the buffered function and wait for the result to return with Async/Await syntax

See the code for usage:

function square(num) {
  return Math.pow(num, 2)}let debouncedFn = debounce(square, 1000.false)

window.addEventListener('resize'.async() = > {let val
  try {
    val = await debouncedFn(4)}catch (err) {
    console.error(err)
  }
  console.log(The return value of the original function is${val}`)},false)

// Stop scaling for 1S and output:
// The original function returns: 16
Copy the code

7. Refer to the article

Underscore for JavaScript topics

An implementation of the underscore function to shake out

If there is any mistake or not precise place, please be sure to give correction, thank you very much.