vue 踩坑小記 - 如何正確的使用 debounce

咱們常常會在頁面 resize 的時候作些操做,好比從新渲染一個圖表組件,使其自適應當前頁面大小, 可是 window.resize 事件觸發的很頻繁,因此咱們通常都會加 debounce 作個「去抖」操做,代碼以下:html

import debounce from 'lodahs/debounce'

export default {
  methods: {
    resize: debounce(function () {
      // do something
    }, 100)
  },

  mounted () {
    window.addEventListener('resize', this.resize)
  },

  beforeDestroy () {
    window.removeEventListener('resize', this.resize)
  }
}複製代碼

然而,上面的代碼是有深坑的(在坑中爬了半天 - . -),下面聊聊個人爬坑歷程。vue

先看個例子

<template>
  <div id="app">
    <chart></chart>
    <chart></chart>
  </div>
</template>

<script>
const Chart = {
  template: '<span>chart</span>',

  methods: {
    resize: _.debounce(function () {
      console.log('resize')
    }, 1000 /* 時間設長一點,便於後面的觀察 */)
  },

  mounted () {
    window.addEventListener('resize', this.resize)
  },

  beforeDestroy () {
    window.removeEventListener('resize', this.resize)
  }
}

new Vue({
  el: '#app',
  components: {
    Chart
  }
})
</script>複製代碼

頁面中有兩個 Chart 組件,他們會監聽 window.resize 事件,而後在控制檯輸出 "resize"。bash

如今我拖動頁面,改變其大小,1s 後(debounce 設的延遲爲 1s),控制檯會輸出幾回 "resize" ?app

這還不簡單,難道不是每一個 Chart 組件各輸出 1 次,共計 2 次?ide

這裏提供一個線上 demo,你們能夠去把玩一下,實際上每次改變頁面大小,控制檯只輸出了 1 次 "resize",是否是很詭異?函數

methods 提及

假設咱們在組件 Chart 中定義了以下方法:ui

{
  methods: {
    resize () {}
  }
}複製代碼

那麼有一個與本文很重要的點須要弄清楚:全部該 Chart 組件的實例,調用 this.resize() 時,最後都會調用 this.$options.methods.resize(),在 vue 內部,組件實例化的時候其實就幹了下面這個事:this

// 綁定 this
this.resize = this.options.methods.resize.bind(this)複製代碼

這種關係以下圖:spa

而後咱們來解釋下詭異現象的緣由:.net

兩個 Chart 實例中的 resize 會調用同一個 debounce 函數,所以當兩個組件同時執行 resize 方法的時候,前者被 debounce 掉了,因此咱們只看到輸出了 1 次 "resize"。

將 resize 方法獨立出來

因爲 methods 中定義的方法是共享的,形成 debounce 效果在組件中相互影響,致使 bug,那麼只要避免共享,每一個 resize 相互獨立就能夠了,改進後的代碼以下:

<template>
  <div id="app">
    <chart></chart>
    <chart></chart>
  </div>
</template>

<script>
const Chart = {
  template: '<span>chart</span>',

  created () {
    // 將 resize 的定義從 methods 中拿到這裏來
    // 這樣就能保證 resize 只歸某個實例擁有
    this.resize = _.debounce(function () {
      console.log('resize')
    }, 1000 /* 時間設長一點,便於後面的觀察 */)
  },

  mounted () {
    window.addEventListener('resize', this.resize)
  },

  beforeDestroy () {
    window.removeEventListener('resize', this.resize)
  }
}

new Vue({
  el: '#app',
  components: {
    Chart
  }
})
</script>複製代碼

改進後的 demo

最後說兩句

細心的小夥伴可能會發現,不對呀,官方的例子 vuejs.org/v2/guide/mi… 就是將 debounce 放到了 methods 中。在官方的例子中,確實沒什麼問題,由於一個頁面不可能同時有兩個輸入框在輸入,同時調用 expensiveOperation 方法。可是,假設把 debounce 的延遲設大一點,而後快速在兩個輸入框中切換輸入(雖然這個場景幾乎不存在),就會出現我一開始說的那個詭異現象。

相關文章
相關標籤/搜索