new Vue() => _init() => initState:javascript
function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
判斷該vue實例是否存在props
、methods
、data
、computed
、watch
進行調用相應的初始化函數html
主要工做是調用defineProperty
給屬性分別掛載get(觸發該鉤子時,會將當前屬性的dep實例推入當前的Dep.target也就是當前watcher的deps中即它訂閱的依賴,Dep.target下文會講到。且該dep實例也會將當前watcher即觀察者推入其subs數組中)、set方法(通知該依賴subs中全部的觀察者watcher去調用他們的update方法)。vue
它的做用是將computed對象中全部的屬性遍歷,並給該屬性new一個computed watcher(計算屬性中定義了個dep依賴,給須要使用該計算屬性的watcher訂閱)。也會經過調用defineProperty
給computed掛載get(get方法)、set方法(set方法會判斷是否傳入,若是沒傳入會設置成noop空函數)computed
屬性的get方法是下面函數的返回值函數java
function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { watcher.depend() return watcher.evaluate() } } }
注意其中的watcher.depend()
,該方法讓用到該屬性的watcher觀察者訂閱該watcher中的依賴,且該計算屬性watcher會將訂閱它的watcher推入他的subs中(當計算屬性值改變的時候,通知訂閱他的watcher觀察者)watcher.evaluate()
,該方法是經過調用watcher的get方法(其中須要注意的是watcher的get方法會調用pushTarget將以前的Dep.target實例入棧,並設置Dep.target爲該computed watcher,被該計算屬性依賴的響應式屬性會將該computed watcher推入其subs中,因此當被依賴的響應式屬性改變時,會通知訂閱他的computed watcher,computed watcher 再通知訂閱該計算屬性的watcher調用update方法),get方法中調用計算屬性key綁定的handler函數計算出值。數組
該watcher 爲user watcher(開發人員本身在組件中自定義的)。
initWatch的做用是遍歷watch中的屬性,並對每一個watch監聽的屬性調用定義的$watchapp
Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {} options.user = true // 表明該watcher是用戶自定義watcher const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { cb.call(vm, watcher.value) } return function unwatchFn () { watcher.teardown() } }
代碼中調用new Watcher的時候,也會同render watcher同樣,執行下watcher的get方法,調用pushTarget
將當前user watcher賦值給Dep.target,get()中value = this.getter.call(vm, vm)
這個語句會觸發該自定義watcher監聽的響應式屬性的get方法,並將當前的user watcher推入該屬性依賴的subs中,因此當user watcher監聽的屬性set觸發後,通知訂閱該依賴的watcher去觸發update,也就是觸發該watch綁定的key對應的handler。而後就是調用popTarget出棧並賦值給Dep.target。dom
initState初始化工做大體到這裏過,接下去會執行$mount開始渲染工做
$mount主要工做:new了一個渲染Watcher,並將updateCompent做爲callback傳遞進去並執行函數
updateComponent = () => { vm._update(vm._render(), hydrating) } new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */)
三種watcher中new Watcher的時候,只有computed watcher不會一開始就執行它的get()方法。$mount裏面new的這個render watcher會調用get()
方法,調用pushTarget
將當前render watcher賦值給Dep.target。接下去重頭戲來了,調用updateComponent
,該方法會執行vm._update(vm._render(), hydrating)
,其中render函數會觸發html中使用到的響應式屬性的get鉤子。get鉤子會讓該響應式屬性的依賴實例dep將當前的render watcher推入其subs數組中,因此當依賴的響應式屬性改變以後,會遍歷subs通知訂閱它的watcher去調用update()。oop
可能你們對watcher和dep調來調去一頭霧水,我講個實例this
<div id="app"> <div>{{a}}</div> <div>{{b}}</div> </div> new Vue({ el: "#app", data() { return { a:1, } }, computed:{ b() { return a+1 } }, })
我直接從渲染開始講,只講跟dep跟watcher有關的
$mount:new一個渲染watcher(watcher的get方法中會將渲染watcher賦值給Dep.target)的時候會觸發 vm._update(vm._render(), hydrating)
,render的時候會獲取html中用到的響應式屬性,上面例子中先用到了a,這時會觸發a的get鉤子,其中dep.depend()
會將當前的渲染watcher推入到a屬性的dep的subs數組中。
接下去繼續執行,訪問到b(b是計算屬性的值),會觸發計算屬性的get方法。計算屬性的get方法是調用createComputedGetter
函數後的返回函數computedGetter
,computedGetter
函數中會執行watcher.depend()
。Watcher的depend方法是專門留給computed watcher使用的。剛纔上面說過了除了computed watcher,其餘兩種watcher在new 完以後都會執行他們的get方法,那麼computed watcher在new完以後幹嗎呢,它會new一個dep。回到剛纔說的專門爲computed watcher開設的方法watcher.depend()
,他的做用是執行this.dep.depend()
(computed watcher定義的dep就是在這裏使用到的)。this.dep.depend()
會讓當前的渲染watcher訂閱該計算屬性依賴,該計算屬性也會將渲染watcher推入到它本身的subs([render watcher])中,當計算屬性的值修改以後會通知subs中的watcher調用update()
,因此計算屬性值變了頁面能刷新。回到前面說的觸發b計算屬性的get鉤子那裏,get鉤子最後會執行watcher.evaluate()
,watcher.evaluate()
會執行computed watcher的get()
方法。這時候重點來了,會將Dep.target(render watcher)推入targetStack棧中(存入以後以便待會兒取出繼續用),而後將這個計算屬性的computed watcher賦值給Dep.target。get方法中value = this.getter.call(vm, vm)
,會執行computed屬性綁定的handler。如上面例子中return a + 1。使用了a那麼就必定會觸發a的get鉤子,get鉤子又會調用dep.depend()
,dep.depend()會讓computed watcher將dep存入它的deps數組中,a的dep會將當前的Dep.target(computed watcher)存入其subs數組中,當前例子中a的subs中就會是[render watcher,computed watcher],因此a值變化會遍歷a的subs中的watcher調用update()
方法,html中用到的a會刷新,計算屬性watcher調用update()
方法會通知他本身的subs([render watcher])中render watcher去調用update方法,html中用到的計算屬性b纔會刷新dom(這裏提個醒,我只是粗略的講,計算屬性依賴的屬性變化後他不必定會觸發更新,他會比較計算完以後的值是否變化)。computed watcher的get()方法最後會調用popTarget()
,將以前存入render watcher出棧並賦值給Dep.target,這時候我例子中targetStack就變成空數組了。render watcher的get方法執行到最後也會出棧,這時候會將Dep.target賦值會空。