聊聊 vue 中的 watcher

ywwhack發表於2017-12-04

大家對於 watch 應該不陌生,專案中都用過下面這種寫法:

watch: {
  someProp () {
    // do something
  }
}

// 或者
watch: {
  someProp: {
    deep: true,
    handler () {
      // do something
    }
  }
}
複製程式碼

上面的寫法告訴 vue,我需要監聽 someProp 屬性的變化,於是 vue 在內部就會為我們建立一個 watcher 物件。(限於篇幅,我們不聊 watcher 的具體實現,感興趣的可以直接看原始碼 watcher

然而在 vue 中,watcher 的功能並沒有這麼單一,先上段程式碼:

<template>
  <div>
    <p>a: {{ a }}</p>
    <p>b: {{ b }}</p>
    <button @click="increment">+</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
    	a: 1
    }
  },
  computed: {
    b () {
    	return this.a * 2
    }
  },
  watch: {
    a () {
        console.log('a is changed')
    }
  },
  methods: {
    increment () {
    	this.a += 1
    }
  },
  created () {
    console.log(this._watchers)
  }
}
</script>
複製程式碼

線上demo

上面程式碼非常簡單,我們現在主要關注 created 鉤子中列印的 this._watchers,如下:

聊聊 vue 中的 watcher

分別展開三個 watcher,觀察每一個 expression,從上到下分別為:

  • b() {↵ return this.a * 2;↵ }
  • "a"
  • function () {↵ vm._update(vm._render(), hydrating);↵ }

上面三個 watcher 代表了三種不同功能的 watcher,我們將其按功能分為三類:

  • 在 watch 中定義的,用於監聽屬性變化的 watcher (第二個)
  • 用於 computed 屬性的 watcher (第一個)
  • 用於頁面更新的 watcher (第三個)

normal-watcher

我們在 watch 中定義的,都屬於這種型別,即只要監聽的屬性改變了,都會觸發定義好的回撥函式

computed-watcher

每一個 computed 屬性,最後都會生成一個對應的 watcher 物件,但是這類 watcher 有個特點,我們拿上面的 b 舉例:

屬性 b 依賴 a,當 a 改變的時候,b 並不會立即重新計算,只有之後其他地方需要讀取 b 的時候,它才會真正計算,即具備 lazy(懶計算)特性

render-watcher

每一個元件都會有一個 render-watcher, function () {↵ vm._update(vm._render(), hydrating);↵ }, 當 data/computed 中的屬性改變的時候,會呼叫該 render-watcher 來更新元件的檢視

三種 watcher 的執行順序

除了功能上的區別,這三種 watcher 也有固定的執行順序,分別是:

computed-render -> normal-watcher -> render-watcher

這樣安排是有原因的,這樣就能儘可能的保證,在更新元件檢視的時候,computed 屬性已經是最新值了,如果 render-watcher 排在 computed-render 前面,就會導致頁面更新的時候 computed 值為舊資料

小結

本文並不是原始碼解析類文章,只是從一個角度來聊聊,那些看似不相關的東西(computed/watch/頁面更新),內部卻有著緊密的聯絡,希望能拋磚引玉,讓大家更深入的去探索 vue

相關文章