vue資料入口initSate開始分析資料驅動更新原理

陳鳳娟發表於2019-01-30

前言

讀這篇文章前,最好是先讀vue資料繫結原始碼,因為本篇是接這章寫的,放在一篇文章裡,篇幅太大,我只好分成兩章了。

初始化vue例項

Vue.prototype._init = function (options) {
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm) // 初始化資料的入口
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    
    if (vm.$options.el) {
      vm.$mount(vm.$options.el) // 掛載元件
    }
}

// mountComponent是在掛載元件時呼叫的方法
export function mountComponent (vm, el ,hydrating) {
  callHook(vm, 'beforeMount')

  let updateComponent = () => {
      vm._update(vm._render(), hydrating)
  }

  new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
  
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  
  return vm
}
複製程式碼

initState

上一篇文章,我們已經瞭解了ObserverDepWatcher都是負責什麼?如何互相協作? 接下來,我們從資料的入口開始,瞭解vue是如何使用這幾個類完成的資料驅動檢視更新的? 初始化state,就是初始化這幾個屬性。

vue資料入口initSate開始分析資料驅動更新原理

先講下什麼是可觀察者物件呢? 具備兩個條件: 1、取值的時候,能把要取值的watcher(觀察者物件)加入它的dep(依賴,也可叫觀察者管理器)管理的subs列表裡(即觀察者列表); 2、設定值的時候,有了變化,所有依賴於它的物件(即它的dep裡收集到的觀察者watcher)都得到通知。 watcher裡面儲存它都觀察了誰(dep),dep裡面儲存了都誰觀察了自己。

initProps

vue資料入口initSate開始分析資料驅動更新原理

迴圈每個props屬性,對每個屬性呼叫defineReactive,把每個屬性加上getset裝飾器,變為可觀察者物件,如果屬性值是物件也會遞迴轉化。

initData

vue資料入口initSate開始分析資料驅動更新原理

observe就是迴圈把data中的所有項都轉換成可觀察者物件,如果子項是物件或陣列就遞迴轉化,確保data裡的每一項及其後代都轉化成了可觀察者物件 初始化資料時,不管data裡的資料渲染用沒用到,都會轉成可觀察者物件。 對於propsdata,只有哪個watcher用到了去讀取時,才會把該watcher加到他的觀察者列表中。

dataprops裡都呼叫了proxy這個方法,他是做什麼的呢? proxy(vm,’_data’,key) proxy就是把dataprops下的屬性都代理到了vm例項下,vm._data.a等價於vm.a 原理就是Object.definePropertyvmkey屬性設定getset方法,當訪問get的時候,返回的是vm._data[key];當訪問set的時候,設定的是vm._data[key]的值。

initComputed

vue資料入口initSate開始分析資料驅動更新原理

初始化計算屬性,就是為每一個計算屬性定義一個Watcher觀察者物件。這個物件是lazy的,不會立即就去執行計算(即get方法),等到用的時候才會去計算,這個時候就會去讀取這個計算屬性依賴的可觀察屬性的值來計算,讀取的時候就會把這些依賴新增進這個計算watcher裡,同時這些依賴的訂閱者列表也會加入這個計算watcher。所以當依賴變化時,通知到他的所有訂閱watcher。計算watcher接到依賴發生變化了,不會立即計算新值,而是標記dirtytrue,讀取這個計算屬性的時候,發現dirtytrue,就是說資料已經不是最新的了,需要重新計算,然後才去計算,否則直接取上一次計算的值value

如果某個data通過計算屬性間接的被用到了渲染裡,那麼這個data也會被加入到渲染watcher的依賴列表,它的訂閱者列表也會儲存渲染watcher。 只有當模版裡使用了該計算屬性,這個計算屬性依賴的可觀察者才會被加入到渲染watcher的依賴列表。如果模版裡沒有直接或間接用到可觀察者物件屬性,那麼當你set它的時候,也就不會觸發更新操作。

initWatch

vue資料入口initSate開始分析資料驅動更新原理

初始化watch,就是為每個watch屬性建立一個觀察者物件,這個expOrFn解析取值表示式去取值,然後就會呼叫相關data/prop屬性的get方法,get方法又會在他的觀察者列表里加上該watcher,一旦這些依賴屬性值變化就會通知該watcher執行update方法。即會執行他的回撥方法cb,也就是watch屬性的handler方法。

到這裡,資料的初始化就完成了。

mountComponent

vue資料入口initSate開始分析資料驅動更新原理

這個方法是在,所有的資料初始化完成後,執行掛載元件時呼叫,建立一個渲染watcher,每個元件有且僅有一個渲染watcherupdateComponentwatchergetter屬性,建立後,立即呼叫get方法,即是呼叫updateComponent,也就是呼叫render方法,render裡使用了的資料就會讀取相關的dataget方法,就會把data的依賴加進這個渲染watcher的依賴列表裡,datasubs也會加入渲染watcher,如此,當設定data時攔截的set方法就會通知渲染watcher呼叫update方法。

我總結下: 一個資料變更後,通知他的Watcher去執行update。 不同型別的Watcher職責不同,vue裡的Watcher可以分為3類: 渲染Watcher、計算Watcher、偵聽器Watcher,注意這3中Watcher的getter屬性分別是什麼。

  • 渲染Watcher的update,負責重新渲染,執行render;getter是updateComponent。
  • 計算Watcher的update,負責標記dirty,告訴它資料不是最新的需要重新計算了;getter是計算屬性的get方法。
  • 偵聽器Watcher的update,負責執行回撥方法,也就是watch的handler;getter是watch的取值表示式。

相關文章