JS throttling and anti – shake

Function throttling: effective for the first time within a certain interval

It specifies a time interval in which only a callback that triggers an event can be executed. If the event is triggered more than once within the time interval, no callback is executed. Application scenario: Can reduce the trigger frequency of some events, such as lazy loading to listen to calculate the position of the scroll bar, click lottery, buy

By definition, let’s write a throttling function according to the understanding of small white, Vue as an example:

  1. Suppose we use the throttling function that is wrapped around the button bindingclickMeMethod inside
  2. Declare an event state variabletimerDeclare the anti-shake functionthrottle
  3. The stabilization function takes two arguments, one of which is passed the method that should be executedcb, one is the time interval for shaking preventiondelay
  4. Enter the method iftimerIs true, thenreturnDon’t follow the logic that follows
  5. iftimernull, starts a timer and assigns a value totimer, the waiting time of the timer isdelayParameter, timer after execution, clear timer, and willtimerRestore to the initial valuenull
  6. Finally, the passed method is executedcb
  7. So the first time we click the button,timernull, execute timer, executecbAfter,1sIf I click on it again,timerIf true, no subsequent logic is executed,1sAnd then I click again,timernull, the command can be executed again
<template>
  <button @click="clickMe('Jerry', 'Tom')">PM me!</button>
</template>

<script>
  / / throttling
  // declare an event state variable
  let timer = null
  // 3. Two parameters: one is the method of transmission, the other is the time interval of anti-shake
  function throttle (cb, delay) {
    // if timer is true, return
    if (timer) return
    Otherwise, start a timer and assign a value to timer. The waiting time of the timer is the delay parameter. After the timer is executed, clear the timer and restore the timer to the initial value null
    timer = setTimeout(() = > {
      clearTimeout(timer)
      timer = null
    }, delay)
    // Execute the pass method cb
    return cb()
  }
  export default {
    methods: {
      clickMe (name, name2) {
        // 1. Suppose we use the throttling function wrapped in the clickMe method we bind to the button
        throttle(() = > {
          console.log('the point! ', name)
          console.log('the point! ', name2)
        }, 1000)}}}</script>
Copy the code

However, this implementation is problematic because timers are defined in the global scope, so they are improved in the form of closures:

  1. nowthrottleBind directly toclickMeOn,
  2. When the current component is initialized,clickMeThe binding ofthrottleWill be executed once,throttleWill pass us oncbMethod for packaging, and thenreturnA new way to giveclickMe
  3. At this timetimerThe scope of thethrottle
<template>
  <button @click="clickMe('Jerry', 'Tom')">PM me!</button>
</template>

<script>
/ / throttling
function throttle (cb, delay) {
  / / timer
  let timer
  // When throttle is called, cb is wrapped, executed, and returned to the caller
  return function () {
    // If the timer exists, the user is prevented from operating
    if (timer) return
    // Otherwise start a timer
    timer = setTimeout(() = > {
      // The interval ends, the timer ends
      clearTimeout(timer)
      // Initialize the timer
      timer = null
    }, delay)
    // Perform the callbackcb(... arguments) } }export default {
  methods: {
    // Throttle is now tied directly to clickMe, and throttle will return a new method
    clickMe: throttle((name, name2) = > {
      console.log('the point! ', name)
      console.log('the point! ', name2)
      console.log(this) // undefined
    }, 1000)}}</script>
Copy the code

Here are a few points:

  1. argumentsAnd residual parameters
  • In ordinary functionsfunctionTo obtain all parameter objects, use the keywordargumentsYou can get
  • In the arrow function, noarguments, can pass the remaining parameters(... args)To obtain

However, we can’t get this in the present method, so we are executing cb(… Arguments) need to modify this pointer inside cb, there are three methods: Modify this to point to

  • cb.call(this, ... arguments)
  • cb.apply(this, arguments)
  • cb.bind(this)(... arguments)

So we can get the “this” pointer correctly.

We can use date.now () instead of a timer to improve performance. Here is the complete code:

<template>
  <button @click="clickMe('Jerry', 'Tom')">PM me!</button>
</template>

<script>
/ / throttling
function throttle (cb, delay) {
  // The time of the last operation
  let lastTime = 0
  return function () {
    const triggerTime = Date.now()
    // Current operation time - Last operation time > Interval
    if (triggerTime - lastTime > delay) {
      cb.call(this. arguments) lastTime = triggerTime } } }export default {
  methods: {
    clickMe: throttle(function (name, name2) {
      console.log('the point! ', name)
      console.log('the point! ', name2)
      console.log('the point! '.this)},1000)}}</script>
Copy the code

Two, function tremble: within a certain interval, the last effective

If the event is triggered again within this time interval, the timer is reset and the callback is not executed.

Application scenario: Search box (if the user does not enter the content again after n seconds, search), zoom in and out of the listening window resize, scroll listening

function debounce(cb, delay = 500) {
  / / timer
  let timer = null
  return function (. args) {
    // Clear the timer if it exists and reset the timer (if the user keeps typing, reset the timer)
    if (timer) clearTimeout(timer)
    // Start timer (after delay time, the user does not enter, then start search)
    timer = setTimeout(() = > {
      // When the interval is reached, the callback is executed
      cb.call(this, args)
    }, delay)
  }
}
Copy the code

Use: Use anti-shake in onScorll

// Wrap the scroll callback with debounce
const scroll = debounce(() = > {
  console.log("Triggered a scroll event.")},1000)
document.addEventListener("scroll", scroll)
Copy the code

ThrottlePlus: Use Debounce to optimize throttle

The problem with Throttle is that it’s too patient, assuming:

  • First we are going todelaySet to3sIs executed when the user clicks it for the first timecb
  • Users went crazy, but it didn’t happen3sThe click stops before this time point is reached
  • So except for the user’s first click it’s going to executecbIn addition, none of the subsequent clicks will be executedcb
  • As a result, users never get feedback on their actions and feel “stuck” on the page.

So, to solve this problem, we need to combine throttle and debounce, so that if the user finishes before 3s, cb is executed once the timer reaches 3s

function throttlePlus (cb, delay) {
  // The time of the last operation
  let lastTime = 0
  / / timer
  let timer = null
  return function () {
    /** @ not in the interval **/
    // The current operation time
    const triggerTime = Date.now()
    // If the time between the current operation and the time of the last operation is greater than the interval, go to cb
    if (triggerTime - lastTime > delay) {
      lastTime = triggerTime
      cb.call(this. arguments) }else {
      /** @ within the interval **/
      // If the user keeps operating, the timer is reset
      timer && clearTimeout(timer)
      // Set the timer and execute it once when the delay interval is reached
      timer = setTimeout(() = > {
        lastTime = triggerTime
        clearTimeout(timer)
        cb.call(this. arguments) }, delay) } } }Copy the code

Use: Enhanced throttlePlus in onScorll

// Wrap scroll callback with throttlePlus
const scroll = throttlePlus(() = > {
  console.log("Triggered a scroll event.")},1000)
document.addEventListener("scroll", scroll)
Copy the code