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