1. Why should the function be shaken

There is the following code

window.onresize = () => {
  console.log(Trigger window listening callback function} copy the codeCopy 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) {
  letTimeout // debmentioning is the return value // Async/A is usedwaitHandle asynchrony, if the function executes asynchronously, waitset// args returns the parameter passed in when the function is called, and passes it to methodlet debounced = function(... args) {returnNew Promise (resolve => {// record the result of the original function executionletResult // Sets this when method is executed to this when the function returned by debounce is calledletContext = this // Clear the timer if it existsif(timeout) {clearTimeout(timeout)} // Two conditions are required for immediate executiontrueTimeout is not assigned or set to nullif(immediate) {// If the timer does not exist, run it immediately and set a timer.waitSet the timer to null after milliseconds to ensure immediate executionwaitIt won't be triggered again for millisecondsletcallNow = ! timeout timeout =setTimeout(() => {
          timeout = null
        }, wait) // If the above two conditions are met, the execution is carried out immediately and the results are recordedif (callNow) {
          result = method.apply(context, args)
          resolve(result)
        }
      } else{// If immediate isfalse, waits for the function to execute and records its results // and sets the Promise state to fullfilled so that the function continues with timeout =setTimeout(() => {// args is an array, so use fn.apply // method.call(context,... args) result = method.apply(context, args) resolve(result) },wait)}})} // Add the cancelling method debmentioning. Cancel = to the returned debmentioning functionfunction() {
    clearTimeout(timeout)
    timeout = null
  }

  returnDebmentioning} Copy the codeCopy 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 () => {
  letVal try {val = await debouncedFn(4)} catch (err) {console.error(err)} 16 console.log(' The original function returns a value of${val}`)},false) Copy codeCopy 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(() => {
    method.call(context)
  }, 1000)
}

function print() {
  console.log('Hello World')
}

window.onresize = debounce(print) Copy codeCopy 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(() => {
      method.call(context)
    }, wait)}} copy the codeCopy 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 calledlet context = this
    if (timeout) {
      clearTimeout(timeout)
    }
    timeout = setTimeout(() => {
      method.call(context)
    }, wait)}} copy the codeCopy 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) {
  letTimeout // args returns the argument passed to method when the function is calledreturn function(... args) {let context = this
    if (timeout) {
      clearTimeout(timeout)
    }
    timeout = setTimeout(() => {// args is an array, so use fn.apply // method.call(context,... args) method.apply(context, args) },wait)}} copy the codeCopy 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)} // Two conditions are required for immediate executiontrueTimeout is not assigned or set to nullif(immediate) {// If the timer does not exist, run it immediately and set a timer.waitSet the timer to null after milliseconds to ensure immediate executionwaitIt won't be triggered again for millisecondsletcallNow = ! timeout timeout =setTimeout(() => {
        timeout = null
      }, wait)
      if (callNow) {
        method.apply(context, args)
      }
    } else{// If immediate isfalse, the functionwaitAfter milliseconds, execute timeout =setTimeout(() => {// args is a class array object, so use fn.apply // method.call(context,... args) method.apply(context, args) },wait)}}} copy the codeCopy 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) {
  letTimeout // Assign the returned anonymous function to debmentioning so that a cancellation method can be added to itlet debounced = function(... args) {let context = this
    if (timeout) {
      clearTimeout(timeout)
    }
    if (immediate) {
      letcallNow = ! timeout timeout =setTimeout(() => {
        timeout = null
      }, wait)
      if (callNow) {
        method.apply(context, args)
      }
    } else {
      timeout = setTimeout(() => {
        method.apply(context, args)
      }, wait}} // Add and cancel the function, use the following method //let myFn = debounce(otherFn)
  // myFn.cancel()
  debounced.cancel = function() {clearTimeout(timeout) timeout = null}} Copies the codeCopy 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(() => {
        timeout = null
      }, wait)
      if(callNow) {result = method.apply(context, args) // Callback && callback(result)}}else {
      timeout = setTimeout(() => {result = method.apply(context, args) // callback && callback(result)},wait)
    }
  }

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

  returnDebmentioning} Copy the codeCopy 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 original function returns:${val}`)
})

window.addEventListener('resize', () => {
  debouncedFn(4)
}, false// Stop scaling for 1S output: // The return value of the original function is: 16 copy the codeCopy 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 it can be usedthenOr Async/AwaitThe syntax gets the return value of the original functionreturn new Promise(resolve => {
      let context = this
      if (timeout) {
        clearTimeout(timeout)
      }
      if (immediate) {
        letcallNow = ! timeout timeout =setTimeout(() => {
          timeout = null
        }, wait)
        if(callNow) {result = method.apply(context, args)}else {
        timeout = setResolve resolve(result) {result = method.apply(context, args)}wait)
      }
    })
  }

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

  returnDebmentioning} Copy the codeCopy 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('${val}`)})},false// Stop scaling for 1S output: // The return value of the original function is: 16 copy the codeCopy 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 () => {
  letVal try {val = await debouncedFn(4)} catch (err) {console.error(err)} console.log(' The original function returns a value of${val}`)},false// Stop scaling for 1S and output: // Return value of the original function is: 16Copy the code