Today I encountered a problem when using the throttling function. It took me a long time to find the cause, so HERE is a summary.

Throttling function

Browser events, such as: resize, Scroll, Mousemove, etc. These events are triggered too often, and the callbacks bound to these events will be called over and over again, burdening the browser and causing a terrible user experience. So the sage invented the throttling function, a simple version of which is as follows:

function throttle (f, wait = 200) {
  let last = 0
  return function (. args) {
    let now = Date.now()
    if (now - last > wait) {
      last = now
      f.apply(this, args)
    }
  }
}
Copy the code

Suppose you have a VUE component, svgMark. The rendered elements in this component are redrawn when the page window size changes, and when redrawn, throttling functions are used to prevent performance loss. The normal code is as follows:

<template>
  <div>{{ index }}</div>
</template>

<script>
import { throttle } from 'lodash'
export default {
  name: 'SvgMark'.data() {
    return {
      index: 0}},mounted() {
    window.addEventListener('resize'.this.reDraw)
  },
  beforeDestroy() {
    window.removeEventListener('resize'.this.reDraw)
  },
  methods: {
    reDraw: throttle(function() {
      this.index++
    }, 500)}}</script>
</script>
Copy the code

In general, this is fine. However, there is a scenario where throttling fails when the component is loaded many times by the V-for loop:

<template>
  <div>
    <svgMark v-for="item in 10" :key="item.id" />
  </div>
</template>
Copy the code

No matter how many svgMark components are rendered, only the first component and the NTH cut are redrawn when the window size changes. Why are the other components not redrawn? It’s back to the beginning.

  • Throttling function

When initialized, the throttling function generates a closure that holds the variable last, which records the last time f was executed. The next time the throttling function is triggered, f will not be executed if the time now minus last is less than wait.

Obviously, the first child generated a last when the throttling function was triggered, and the second generated a now when the throttling function was triggered that did not satisfy the now-last > wait condition, so no redraw code was performed. By the time the NTH component triggers the throttling function, the condition of now – last > wait is satisfied, so the redraw is successful.

  • Vue components

ReDraw: throttle(function() {this.index++}, 500); reDraw: throttle(function() {this.index++}, 500);

The reDraw: ƒ (... args) {let now = Date.now()
  if (now - last > wait) {
    last = now
    f.apply(this, args)
  }
}
Copy the code

Note that the last variable of the reDraw function is stored in the closure, which means that the reDraw methods in all subsequent components generated by V-for refer to the last variable in the same closure. So each component’s reDraw method is called against the last variable in the closure to determine whether now-last > wait is true.

So how do we solve the problem? The solution is to have each component generate its own throttling function without sharing. The following code

Child components:

<template>
  <div>{{ index }}</div>
</template>

<script>
import { throttle } from 'lodash'
export default {
  name: 'SvgMark'.data() {
    return {
      index: 0}},mounted() {
    this.reDraw = throttle(() = > {
      this.index++
    }, 500)
    window.addEventListener('resize'.this.reDraw)
  },
  beforeDestroy() {
    window.removeEventListener('resize'.this.reDraw)
  }
}
</script>
Copy the code

In mounted, we manually declare reDraw instead of reDraw, so that each component initialization generates its own throttling closure. Note that the parameter to the throttling function uses the arrow function, so that this points to the component instance.

The above is the throttle function brought me the pit, now share with you. [Off duty][Applause]