前言
讀這篇文章前,最好是先讀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
上一篇文章,我們已經瞭解了Observer
、Dep
、Watcher
都是負責什麼?如何互相協作?
接下來,我們從資料的入口開始,瞭解vue是如何使用這幾個類完成的資料驅動檢視更新的?
初始化state,就是初始化這幾個屬性。
先講下什麼是可觀察者物件呢?
具備兩個條件:
1、取值的時候,能把要取值的watcher
(觀察者物件)加入它的dep
(依賴,也可叫觀察者管理器)管理的subs
列表裡(即觀察者列表);
2、設定值的時候,有了變化,所有依賴於它的物件(即它的dep
裡收集到的觀察者watcher
)都得到通知。
watcher
裡面儲存它都觀察了誰(dep
),dep
裡面儲存了都誰觀察了自己。
initProps
迴圈每個props
屬性,對每個屬性呼叫defineReactive
,把每個屬性加上get
和set
裝飾器,變為可觀察者物件,如果屬性值是物件也會遞迴轉化。
initData
observe
就是迴圈把data
中的所有項都轉換成可觀察者物件,如果子項是物件或陣列就遞迴轉化,確保data
裡的每一項及其後代都轉化成了可觀察者物件
初始化資料時,不管data裡的資料渲染用沒用到,都會轉成可觀察者物件。
對於props
和data
,只有哪個watcher
用到了去讀取時,才會把該watcher
加到他的觀察者列表中。
data
和props
裡都呼叫了proxy
這個方法,他是做什麼的呢?
proxy(vm,’_data’,key)
proxy
就是把data
和props
下的屬性都代理到了vm例項下,vm._data.a
等價於vm.a
原理就是Object.defineProperty
給vm
的key
屬性設定get
和set
方法,當訪問get
的時候,返回的是vm._data[key]
;當訪問set
的時候,設定的是vm._data[key]
的值。
initComputed
初始化計算屬性,就是為每一個計算屬性定義一個Watcher
觀察者物件。這個物件是lazy
的,不會立即就去執行計算(即get方法),等到用的時候才會去計算,這個時候就會去讀取這個計算屬性依賴的可觀察屬性的值來計算,讀取的時候就會把這些依賴新增進這個計算watcher
裡,同時這些依賴的訂閱者列表也會加入這個計算watcher
。所以當依賴變化時,通知到他的所有訂閱watcher
。計算watcher
接到依賴發生變化了,不會立即計算新值,而是標記dirty
為true
,讀取這個計算屬性的時候,發現dirty
為true
,就是說資料已經不是最新的了,需要重新計算,然後才去計算,否則直接取上一次計算的值value
。
如果某個data
通過計算屬性間接的被用到了渲染裡,那麼這個data
也會被加入到渲染watcher
的依賴列表,它的訂閱者列表也會儲存渲染watcher
。
只有當模版裡使用了該計算屬性,這個計算屬性依賴的可觀察者才會被加入到渲染watcher
的依賴列表。如果模版裡沒有直接或間接用到可觀察者物件屬性,那麼當你set
它的時候,也就不會觸發更新操作。
initWatch
初始化watch
,就是為每個watch
屬性建立一個觀察者物件,這個expOrFn
解析取值表示式去取值,然後就會呼叫相關data/prop
屬性的get
方法,get
方法又會在他的觀察者列表里加上該watcher
,一旦這些依賴屬性值變化就會通知該watcher
執行update
方法。即會執行他的回撥方法cb
,也就是watch
屬性的handler
方法。
到這裡,資料的初始化就完成了。
mountComponent
這個方法是在,所有的資料初始化完成後,執行掛載元件時呼叫,建立一個渲染watcher
,每個元件有且僅有一個渲染watcher
。updateComponent
是watcher
的getter
屬性,建立後,立即呼叫get
方法,即是呼叫updateComponent
,也就是呼叫render
方法,render
裡使用了的資料就會讀取相關的data
的get
方法,就會把data
的依賴加進這個渲染watcher
的依賴列表裡,data
的subs
也會加入渲染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的取值表示式。