以前寫了三篇vue的源碼解析,響應式,虛擬dom,和模板編譯組件化.
這三篇是比較細的,這裏作個總結,先看總結,再看那三篇應該會更好,
這裏是把大概流程和前面的例子總結下.
一,首次渲染過程
首先咱們導入vue時會初始化實例成員,靜態成員vue
- 全局靜態例如config,options,內部工具方法,一些靜態方法例如set,nextTick,組件,指令,過濾器方法,而後原型方法例如:mount(內部調用mountComponent掛載),init,_render(方法裏默認調用了options裏的render,默認傳遞vm.$createElement提供給用戶傳入的render當 h函數,生成虛擬dom ,模板編譯出來的render內部使用的vm._c 不用傳遞進去這個),_update等,
- 在init初始化實例成員,例如options,_isVue,uid記錄, Vue.extend()初始化組件的構造函數,它繼承自vue全部原型方法,合併配置options.
- 實例化 new Vue(),這裏會調用 原型上定義的init方法;
this._init()node
- 在這裏合併options配置,初始化生命週期變量,事件監聽自定義事件.
- 執行initRender函數(生成vm._c處理編譯生成render,生成vm.$createElement處理用戶傳入render)
執行鉤子回調,對傳入的data數據作響應式處理數組
- 劫持屬性
- 生成各個屬性節點的dep對象,dep對象用來通知watcher更新,而且劫持數組原型方法.
- 若是有計算屬性生成計算watcher,有偵聽器,生成偵聽watcher
- 生成watcher時會根據傳入的方法來決定是否去 取對應data中的值,若是傳入方法裏獲取值了,會觸發對應的咱們前面數據劫持的get方法,從而把咱們當前watcher添加到對應屬性的dep的subs數組中,若是當前屬性是子對象,對應子對象dep也須要添加watcher(set和數組時會用到).
- 而後觸發created建立完成的鉤子函數.
- 最後執行$mount掛載.
vm.$mount();promise
- 這個方法會先查找options.render函數,看用戶有沒有傳入,沒有傳入的話,使用傳入的模板,調用compileToFunctions把模板轉換成render函數,這個render函數內部調用的是vm._c來處理模板編譯生成vNode
- 把生成的render賦值給options.render,,後續調用_render()時會從options取出render來調用,這個須要vue的編譯器版本.
- 用戶傳入了render的話,後續調用_render時就會直接調用用戶傳入的render,從options.render上獲取執行這會會使用傳入的vm.$createElement來當h函數生成虛擬dom,最後調用mountComponent來進行掛載.
mountComponent主要功能瀏覽器
定義updateComponentapp
- 這個方法做用是更新界面_update(_render()) //render中編譯出的_c或 用戶傳入_$createElement生成虛擬dom
- _render()生成虛擬dom,_update()內部調用patchVnode 用來對比新舊vNode 來進行dom更新
- _render中會調用了對應的編譯vm._c或者vm._createElement,生成虛擬dom,在這個過程當中,會判斷若是裏面有自定義組件會調用 createComponent ,createComponent內部會調用extend()返回組件構造函數, 而且建立組件vnode, 而後註冊插件init鉤子,init鉤子裏作實例化組件,而後會調用繼承自Vue的init初始化方法,最後再調用mount(),生成渲染watcher,並把組件掛載到頁面上(vnode.elm,這裏驗證了一個組件對應一個渲染watcher)
建立渲染watcher實例,傳遞updateComponentdom
- 建立watcher實例時會傳入updateComponent方法,這裏初始化會調用傳入的函數,也就是updateComponent來更新界面.
- 在這個過程當中會 獲取 咱們前面data進行屬性劫持中的屬性,而後會觸發對應的get,來把渲染watcher添加到 對應屬性的dep的subs數組中.造成屬性dep和渲染watcher的互相依賴.(這樣就造成了一個觀察關係,在這裏一個渲染watcher,可能放入多個屬性dep的subs數組中,由於一個渲染watcher對應一個組件, 一個屬性中的dep的subs數組中也可能會放入多個不一樣watcher,例如同時存在渲染和計算||偵聽屬性的watcher)
- 在這裏聲明下,一個組件對應一個渲染watcher.
- mounted最後執行這個鉤子,總體渲染完成
- 到此爲止 vue的首次渲染就完成了
二,響應式原理
前面講到了,咱們在new Vue()時調用init作了對數據data的劫持生成屬性對應的dep發佈者和對應的get和set方法,在實例化watcher時會把自身賦給Dep.target,而後獲取屬性值時再觸發對應的get,經過dep.depend()和 childOb.dep.depend(),來把當前的watcher添加到自身和子對象的dep的subs數組中. 同時watcher也記錄一下dep.id防止後續觸發get時重複添加.而後改變data中的屬性賦值時會觸發對應的set,set會判斷值是否改變,改變了的話賦給val,而後set 裏會判斷新賦值的值是不是對象,是的話繼續進行數據劫持observe,而後調用dep的notify方法,來調用dep的subs數組中的watcher的update方法.異步
異步promsie 若是瀏覽器不支持的話會降級成setTimeout
這裏也就體現了vue中的更新是異步的,批量的
這裏咱們用段僞代碼來推理一下它的更新流程
<div id="app">
<p id="p" ref="p1">{{ msg }}</p>
{{ name }}<br>
{{ title }}<br>
</div>
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello nextTick',
name: 'Vue.js',
title: 'Title'
},
mounted() {
this.msg = 'Hello Worlds'
this.name = 'Hello snabbdom'
this.title = 'Vue.js'
Vue.nextTick(() => {
console.log(this.$refs.p1.textContent)
})
this.msg = 'Hello'
}
})
</script>
更新值,而後msg的dep.notify()//派發更新
- msg的dep.subs數組裏的watcher.update()
- 執行queueWatcher方法拿到watcher.id用個對象,記錄防止重複,不重複的話這個watcher放到queue隊列裏,(當前這個watcher是渲染watcher)
- 執行nextTick(flushSchedulerQueue) 而後僞代碼傳入的函數放入 callbacks.push(()=>{ flushSchedulerQueue() })
- 這時callbacks數組[執行queue中隊列watcher更新的方法也就是flushSchedulerQueue,]
- 而後這時pending爲false,改成true標記爲本次的tick處理 定義一個微任務promise掛在本次tick的最後,等待未來執行(flushCallbacks是then回調函數).
更新值,而後name.dep.notify()//派發更新
- name的dep.subs數組裏的watcher.update()
- 同上面同樣,可是watcher.id重複,同一個渲染watcher,退出queueWatcher方法 (這裏沒添加劇復的watcher,可是值已經更新了 val = 新值)
更新值,而後title的dep.notify()//派發更新
- title的dep.subs數組裏的watcher.update()
- 同上面同樣,可是watcher.id重複,同一個渲染watcher,退出queueWatcher方法(這裏沒添加劇復的watcher,可是值已經更新了 val = 新值)
Vue.nextTick(回調)
- 執行nextTick(回調) 而後僞代碼傳入的函數放入 callbacks.push(()=>{ 回調() })
- 這時callbacks數組[執行queue中隊列watcher更新的方法也就是flushSchedulerQueue, 回調方法]
- 而後這時pending爲true 退出
更新值,而後msg的dep.notify()//派發更新
- msg的dep.subs數組裏的watcher.update()
- 同上面同樣,可是watcher.id重複,同一個渲染watcher,退出queueWatcher方法(這裏沒添加劇復的watcher,可是值已經更新了 val = 新值,這時的msg已是Hello 而不是Hello words)
本次tick最後了來執行屬於本次tick的微任務
- 執行flushCallbacks方法 callbacks數組中第一個方法是flushSchedulerQueue,這個方法執行queue隊列中的全部watcher的更新,咱們如今裏面就一個渲染watcher(由於id是相同的),執行渲染wtcher.
- 以後執行 步驟4 Vue.nextTick傳入的回調,這時渲染watcher已經執行完成了,內容已經改變了,而後執行回調再去獲取對應dom的textContent時就是咱們最後一次給msg賦值Hello.
這就是vue數據響應式的原理,以及它的更新過程,以及Vue.nextTick爲何能獲取到更新以後的dom值
三,Vue 中模板編譯的過程
咱們開篇提到過,在調用$mount 掛載時 會調用compileToFunctions 把template模板轉換成render函數(內部調用_c()生成虛擬dom)
這個方法主要做用:
這就是模板的編譯渲染.
四,虛擬 DOM 中 Key 的做用和好處。
虛擬dom中的key 主要用來標記兩個節點是不是同一個,而後作新舊vNode對比使用,對比vnode的不一樣來更新老vNode,從而更新 對應的dom,由於在虛擬dom節點的對比時,節點對比規則會根據key來比較,新舊開始,新舊結束,舊開始新結束,舊結束新開始.
若是都不符合而後新的開始 在老的沒對比完的同級開始結束位置區間 找相同的 vnode 來跟新差別並根據須要移動位置.
這樣看的話 假如沒有帶Key,舉個例子
<ul>
<li v-for="value in arr"
:key="value"
>{{value}}</li>
</ul>
[a,b,c,d]
//更新爲
[a,x,b,c,d]
沒key的時候,對比開始第一個相同,key時undefined相等,標籤相同, 第2,3,4標籤相同,內容不一樣更新dom內容,第5個生成dom 插入d
也就是說 沒key時有 一個生成插入操做, 三個更新dom
有key的時候,會對比key,不會key出現undefined的狀況, 根據咱們上面說的規則,只須要執行一次x的生成插入操做.
從這個例子看出來,若是咱們 使用的列表,若是往不一樣位置插入數據時,沒有key的時候,更新的次數要遠遠大於有key的時候.因此使用列表時儘可能來使用Key.
此次總結就到這裏結束了.