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

ywwhack發表於2017-11-27

我們經常會在頁面 resize 的時候做些操作,比如重新渲染一個圖表元件,使其自適應當前頁面大小, 但是 window.resize 事件觸發的很頻繁,所以我們一般都會加 debounce 做個「去抖」操作,程式碼如下:

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)
  }
}複製程式碼

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

先看個例子

<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"。

現在我拖動頁面,改變其大小,1s 後(debounce 設的延遲為 1s),控制檯會輸出幾次 "resize" ?

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

這裡提供一個線上 demo,大家可以去把玩一下,實際上每次改變頁面大小,控制檯只輸出了 1 次 "resize",是不是很詭異?

methods 說起

假設我們在元件 Chart 中定義瞭如下方法:

{
  methods: {
    resize () {}
  }
}複製程式碼

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

// 繫結 this
this.resize = this.options.methods.resize.bind(this)複製程式碼

這種關係如下圖:

然後我們來解釋下詭異現象的原因:

兩個 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 的延遲設大一點,然後快速在兩個輸入框中切換輸入(雖然這個場景幾乎不存在),就會出現我一開始說的那個詭異現象。

相關文章