
回憶
Observe觀察者(建立響應(yīng)式對(duì)象)
? ? ? ? 概括:它給數(shù)據(jù)通過(guò)defineProperty進(jìn)行響應(yīng)式化。依賴收集的入口:在每個(gè)數(shù)據(jù)初始化dep實(shí)例后,通過(guò)get方法在當(dāng)前dep實(shí)例的subs數(shù)組中收集當(dāng)前渲染watcher(當(dāng)前watcher對(duì)數(shù)據(jù)實(shí)現(xiàn)訂閱),在當(dāng)前渲染watcher的newDeps中加入當(dāng)前dep實(shí)例。派發(fā)更新的入口:通過(guò)set方法在數(shù)據(jù)變化時(shí)通過(guò)dep實(shí)例的subs數(shù)組中遍歷所有訂閱了數(shù)據(jù)變化的watcher執(zhí)行watcher.run實(shí)現(xiàn)重現(xiàn)渲染。其中優(yōu)化:派發(fā)更新的過(guò)程中會(huì)把所有要執(zhí)行update的watcher推入隊(duì)列queue中,在nextTick后統(tǒng)一依次執(zhí)行flush進(jìn)行watcher.run()。
依賴收集過(guò)程:
? ??????_init()時(shí)候會(huì)有一個(gè)initState()方法主要是對(duì)props、methods、data、computed?和?wathcer?等屬性做了初始化操作。
????????我們先說(shuō)Data的響應(yīng)式原理,initData會(huì)調(diào)用observe方法,判斷data下有無(wú)_ob_對(duì)象,有的話直接返回data._ob_,沒(méi)有的話new Observe(data)對(duì)象。
? ??????Observe類會(huì)判斷data是數(shù)組還是對(duì)象,數(shù)組就對(duì)每一個(gè)子元素調(diào)用observe(),否則就執(zhí)行walk()對(duì) 對(duì)象每一個(gè)屬性執(zhí)行defineReactive()方法。
? ? ? ??defineReactive()中new Dep()初始化一個(gè)訂閱器。如果子屬性是個(gè)對(duì)象的話再對(duì) 子屬性執(zhí)行observe()否則對(duì)子屬性通過(guò)object.defineProperty() 定義get和set函數(shù),get用來(lái)收集依賴,set用來(lái)派發(fā)更新。
? ? ? ? 當(dāng)訂閱者Watcher中執(zhí)行updateComponent,_render開(kāi)始實(shí)例化Vnode時(shí),會(huì)獲取data數(shù)據(jù),觸發(fā)get函數(shù)。后續(xù)做了兩件事,1、把當(dāng)前數(shù)據(jù)屬性dep實(shí)例加入到當(dāng)前渲染watcher的newDeps中。2、在當(dāng)前數(shù)據(jù)屬性dep實(shí)例中的subs屬性中加入訂閱當(dāng)前渲染watcher。接下來(lái)我們看看過(guò)程:
????????如果Dep.target存在(此時(shí)watcher已經(jīng)實(shí)例化,Dep.target存在),執(zhí)行dep.depend(),間接執(zhí)行Dep.target.addDep(this)相當(dāng)于當(dāng)前Watcher.addDep(this)(把這個(gè)屬性在defineproperty中new的dep作為this)。這時(shí)候會(huì)做一些邏輯判斷(保證同一dep實(shí)例不會(huì)被添加多次)后執(zhí)行?dep.addSub(this),那么該dep實(shí)例就會(huì)執(zhí)行?this.subs.push(sub),也就是說(shuō)把當(dāng)前的?watcher?訂閱到這個(gè)數(shù)據(jù)持有的?dep?的?subs?中,這個(gè)目的是為后續(xù)數(shù)據(jù)變化時(shí)候能通知到哪些?subs?做準(zhǔn)備。
派發(fā)更新過(guò)程:
? ? ? ? 值更新改變觸發(fā)set方法。其中,把新值替代舊值(如果新值是一個(gè)對(duì)象,也對(duì)它使用observe());執(zhí)行dep.notify()派發(fā)更新。
????????dep.notify(),遍歷dep實(shí)例的subs數(shù)組(即Watcher實(shí)例數(shù)組)調(diào)用每一個(gè)watcher的update方法。
????????update(),即執(zhí)行queueWatcher隊(duì)列方法(把watcher push到 quequ中形成隊(duì)列,通過(guò)id防止多次添加同一個(gè)watcher到quequ中),再通過(guò)nextTick異步執(zhí)行flushSchedulerQueue方法。
????????flushSchedulerQueue(),把隊(duì)列根據(jù)id從小到大排列后,遍歷隊(duì)列拿到每一個(gè)watcher執(zhí)行watcher.run(),重置初始狀態(tài),執(zhí)行update鉤子。
? ??????watcher.run(),通過(guò)this.get()取得新值并觸發(fā)updateComponent = () => {vm._update(vm._render(), hydrating) } 進(jìn)行重新渲染。
????????關(guān)于用戶定義的watch或者$.watch訂閱數(shù)據(jù)的派發(fā)更新。例如,有一個(gè)響應(yīng)式msg數(shù)據(jù),通過(guò)a.click可以改變它,同時(shí)watch對(duì)它監(jiān)聽(tīng)執(zhí)行 也改變它。那么當(dāng)a.click改變它時(shí),遍歷dep.subs下訂閱該數(shù)據(jù)的watcher(有兩個(gè),一個(gè)userWatcher和一個(gè)渲染watcher),通過(guò)queueWatcher把watcher都添加到queue隊(duì)列中,再異步統(tǒng)一依次執(zhí)行flushSchedulerQueue。首先執(zhí)行userWatcher(用戶定義的watch比渲染watch先執(zhí)行),其中會(huì)執(zhí)行watcher.run(),watcher.run()中會(huì)執(zhí)行watcher.get()取得新值(因?yàn)閡serWatcher是有回調(diào)函數(shù)的即watch內(nèi)定義的函數(shù)),若與舊值不同則執(zhí)行回調(diào)函數(shù)。執(zhí)行回調(diào)函數(shù)改變了msg,訂閱msg的userWatcher和渲染W(wǎng)atcher又通過(guò)queueWatcher被添加到queue隊(duì)列中(userWatcher能添加,渲染watcher因?yàn)樯弦粋€(gè)相同的渲染watcher還沒(méi)執(zhí)行id還沒(méi)清空所以不能添加),此時(shí)queue隊(duì)列中有3個(gè)watcher,2個(gè)相同的userWatcher和1個(gè)渲染watcher。然后又執(zhí)行第二個(gè)userWatcher的watcher.run()又執(zhí)行回調(diào)函數(shù),又添加同一個(gè)userWatcher,一直循環(huán)添加下去,渲染watcher輪不到執(zhí)行。那么當(dāng)循環(huán)次數(shù)超過(guò)一定值,vue會(huì)報(bào)錯(cuò)。
Dep訂閱器(管理訂閱者)
????????defineReactive()中會(huì)new Dep(),Dep?是一個(gè) Class,它定義了一些屬性和方法,這里需要特別注意的是它有一個(gè)靜態(tài)屬性?target,這是一個(gè)全局唯一?Watcher,這是一個(gè)非常巧妙的設(shè)計(jì),因?yàn)?b>在同一時(shí)間只能有一個(gè)全局的?Watcher?被計(jì)算,另外它的自身屬性?subs?也是?Watcher?的數(shù)組。
Watcher訂閱者(實(shí)例化和派發(fā)更新)
實(shí)例化
????????在執(zhí)行$mount觸發(fā)的mountComponent方法中,定義了方法updateComponent = () => {vm._update(vm._render(), hydrating)}(把render函數(shù)實(shí)例化Vnode進(jìn)行patch),和vm._watcher = new Watcher(vm, updateComponent, noop)兩段關(guān)鍵代碼。
? ? ? ? 實(shí)例化訂閱者Watcher,會(huì)執(zhí)行Watcher類的get方法。
????????其中pushTarget(this),Dep.target存在情況下,會(huì)往targetStack(一個(gè)數(shù)組)pushDep.target。另外會(huì)把當(dāng)前Watcher實(shí)例賦值給Dep.target()(此時(shí)開(kāi)始,Dep.target中有值了)。
? ? ? ? 其中value = this.getter.call(vm, vm),即執(zhí)行updateComponent方法。其中vm._render()方法調(diào)用vnode = render.call(vm._renderProxy, vm.$createElement)實(shí)例化vnode時(shí)會(huì)去獲取data中數(shù)據(jù),會(huì)觸發(fā)觀察者Observe的defineReactive()中對(duì)應(yīng)數(shù)據(jù)定義的get函數(shù),進(jìn)行依賴收集了。
? ? ? ? 接下來(lái),if (this.deep) { traverse(value)}這個(gè)是要遞歸去訪問(wèn)?data,觸發(fā)它所有子項(xiàng)的?getter,這個(gè)之后會(huì)詳細(xì)講。
????????接下來(lái)執(zhí)行:popTarget(),即Dep.target = targetStack.pop(),實(shí)際上就是把?Dep.target?恢復(fù)成上一個(gè)狀態(tài),因?yàn)楫?dāng)前 vm 的數(shù)據(jù)依賴收集已經(jīng)完成,那么對(duì)應(yīng)的渲染Dep.target?也需要改變。
? ? ? ? watcher中的deps[]、newDeps[]、depIdsp[]、newDepIds[]是干嘛的?是用來(lái)維護(hù)dep.subs的。依賴收集的時(shí)候,會(huì)把當(dāng)前dep實(shí)例加入到newDeps中。watcher的get()執(zhí)行完會(huì)執(zhí)行cleanupDeps(), 遍歷deps中dep和newDeps中dep做比較,如果deps中有的dep實(shí)例在newDeps卻沒(méi)有,就在該dep實(shí)例中的subs中把當(dāng)前訂閱者watcher移除(該wathcer不再訂閱該dep實(shí)例所對(duì)應(yīng)數(shù)據(jù))。并把newDeps賦值給deps,再把newDeps清除。主要目的是觸發(fā)當(dāng)重新渲染時(shí),在當(dāng)前渲染watcher的newDeps收集這次渲染存在的響應(yīng)式數(shù)據(jù),和deps中上一次該watcher渲染收集的響應(yīng)式數(shù)據(jù)對(duì)比,現(xiàn)在不存在的而上次卻存在的dep實(shí)例,把該dep實(shí)例的subs中把該watcher取消掉,即當(dāng)前watcher取消對(duì)該dep實(shí)例對(duì)應(yīng)數(shù)據(jù)的訂閱。
? ??????最后執(zhí)行:this.cleanupDeps(),考慮到 Vue 是數(shù)據(jù)驅(qū)動(dòng)的,所以每次數(shù)據(jù)變化都會(huì)重新 vm._render() ,并再次觸發(fā)數(shù)據(jù)的 getters,所以?Wathcer?在構(gòu)造函數(shù)中會(huì)初始化 2 個(gè)?Dep?實(shí)例數(shù)組,newDeps?表示新添加的?Dep?實(shí)例數(shù)組,而?deps?表示上一次添加的?Dep?實(shí)例數(shù)組。在執(zhí)行cleanupDeps函數(shù)的時(shí)候,會(huì)首先遍歷?deps,移除對(duì)?dep?的訂閱,然后把?newDepIds?和?depIds?交換,newDeps?和?deps?交換,并把?newDepIds?和?newDeps?清空。
????????那么為什么需要做?deps?訂閱的移除呢,在添加?deps?的訂閱過(guò)程,已經(jīng)能通過(guò)?id?去重避免重復(fù)訂閱了。
????????考慮到一種場(chǎng)景,我們的模板會(huì)根據(jù)?v-if?去渲染不同子模板 a 和 b,當(dāng)我們滿足某種條件的時(shí)候渲染 a 的時(shí)候,會(huì)訪問(wèn)到 a 中的數(shù)據(jù),這時(shí)候我們對(duì) a 使用的數(shù)據(jù)添加了 getter,做了依賴收集,那么當(dāng)我們?nèi)バ薷?a 的數(shù)據(jù)的時(shí)候,理應(yīng)通知到這些訂閱者。那么如果我們一旦改變了條件渲染了 b 模板,又會(huì)對(duì) b 使用的數(shù)據(jù)添加了 getter,如果我們沒(méi)有依賴移除的過(guò)程,那么這時(shí)候我去修改 a 模板的數(shù)據(jù),會(huì)通知 a 數(shù)據(jù)的訂閱的回調(diào),這顯然是有浪費(fèi)的。
????????因此 Vue 設(shè)計(jì)了在每次添加完新的訂閱,會(huì)移除掉舊的訂閱,這樣就保證了在我們剛才的場(chǎng)景中,如果渲染 b 模板的時(shí)候去修改 a 模板的數(shù)據(jù),a 數(shù)據(jù)訂閱回調(diào)已經(jīng)被移除了,所以不會(huì)有任何浪費(fèi),真的是非常贊嘆 Vue 對(duì)一些細(xì)節(jié)上的處理。
概述
????????前面 2 章介紹的都是 Vue 怎么實(shí)現(xiàn)數(shù)據(jù)渲染和組件化的,主要講的是初始化的過(guò)程,把原始的數(shù)據(jù)最終映射到 DOM 中,但并沒(méi)有涉及到數(shù)據(jù)變化到 DOM 變化的部分。而 Vue 的數(shù)據(jù)驅(qū)動(dòng)除了數(shù)據(jù)渲染 DOM 之外,還有一個(gè)很重要的體現(xiàn)就是數(shù)據(jù)的變更會(huì)觸發(fā) DOM 的變化。
????????其實(shí)前端開(kāi)發(fā)最重要的 2 個(gè)工作,一個(gè)是把數(shù)據(jù)渲染到頁(yè)面,另一個(gè)是處理用戶交互。Vue 把數(shù)據(jù)渲染到頁(yè)面的能力我們已經(jīng)通過(guò)源碼分析出其中的原理了,但是由于一些用戶交互或者是其它方面導(dǎo)致數(shù)據(jù)發(fā)生變化重新對(duì)頁(yè)面渲染的原理我們還未分析。

????????在分析前,我們先直觀的想一下,如果不用 Vue 的話,我們會(huì)通過(guò)最簡(jiǎn)單的方法實(shí)現(xiàn)這個(gè)需求:監(jiān)聽(tīng)點(diǎn)擊事件,修改數(shù)據(jù),手動(dòng)操作 DOM 重新渲染。這個(gè)過(guò)程和使用 Vue 的最大區(qū)別就是多了一步“手動(dòng)操作 DOM 重新渲染”。這一步看上去并不多,但它背后又潛在的幾個(gè)要處理的問(wèn)題:
????????我需要修改哪塊的 DOM?我的修改效率和性能是不是最優(yōu)的?我需要對(duì)數(shù)據(jù)每一次的修改都去操作 DOM 嗎?我需要 case by case 去寫修改 DOM 的邏輯嗎?
????????如果我們使用了 Vue,那么上面幾個(gè)問(wèn)題 Vue 內(nèi)部就幫你做了,那么 Vue 是如何在我們對(duì)數(shù)據(jù)修改后自動(dòng)做這些事情呢,接下來(lái)我們將進(jìn)入一些 Vue 響應(yīng)式系統(tǒng)的底層的細(xì)節(jié)。