想深入理解Vue的原理,和組織結(jié)構(gòu),這本
HuangYi寫的電子書是非常好的資料。
以備不時之需,特將主要脈絡(luò)記錄下來
作者經(jīng)常說: xx入口和 這里關(guān)鍵的代碼是, 所以我們分析源碼時,也要學會找重點
數(shù)據(jù)驅(qū)動
組裝Vue這個構(gòu)造函數(shù)的過程 platforms下某個平臺的入口文件比如web下的entry-runtime-with-compiler.js,這個文件再去runtime目錄下的index.js 這個文件再去code/index.js文件中找Vue,從平臺目錄跳出到core目錄了。core是Vue的核心代碼目錄。然后再去code/instance/index.js文件,這個文件才正式組裝Vue構(gòu)造函數(shù)
code/instance/index.js中Vue構(gòu)造函數(shù)通過prototype拓展功能被分散到多個模塊中去實現(xiàn),非常便于代碼的維護,直接的結(jié)果就是每個js文件代碼函數(shù)都不會很多。
然后是code/global-api.js文件向Vue添加一些全局靜態(tài)方法比如Vue.set。
Vue 初始化主要就干了幾件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,調(diào)用beforeCreate鉤子,初始化injections, 初始化 data、props、computed、watcher,初始化provide,調(diào)用created鉤子。最后如果有el屬性,調(diào)用$mount渲染成最終的DOM。
runtime的$mount方法最終會調(diào)用core/instance/lifecycle.js中的mountComponent, mountComponent會創(chuàng)建一個
Watcher實例,用來監(jiān)聽vm上的數(shù)據(jù)變化,初始化Watcher或者數(shù)據(jù)變化時,就會調(diào)用_updateComponent也就是先_render再_update,更新DOM。_render返回的是VNode。_render其實是調(diào)用vdom/create-element.js 中的creatElement創(chuàng)建的VNode然后通過core/instance/lifecycle.js中的
_update方法,將VNode轉(zhuǎn)換為DOM。_update用到的patch方法是由core/vdom/patch.js中的createPatchFunction創(chuàng)建,createPatchFunction返回的patch函數(shù)緩存了modules(平臺的一些模塊,會在整個patch過程的不同階段執(zhí)行相應(yīng)的鉤子函數(shù))和nodeOps (一些dom節(jié)點的操作方法)。patch函數(shù),如果接收第一個參數(shù)oldVnode是個dom元素(vue實例的一次path時候就是傳入一個dom元素),會執(zhí)行createElm方法創(chuàng)建一個dom元素并插入到他的父元素中。-
作者把初始化vue的過程畫了一張圖:
new Vue -> init -> $mount -> compile -> render -> vnode -> patch -> DOM
組件化
core/vdom/create-element.js 中的_createElement會判斷
tag參數(shù), 如果不是html字符串標簽,就會調(diào)用createComponent創(chuàng)建組建類型的VNode。createComponent在core/vdom/create-component.js中,他主要有3 個關(guān)鍵步驟:
構(gòu)造子類構(gòu)造函數(shù)。通過Vue.extend方法。
安裝組件鉤子函數(shù):installComponentHooks(data)。組件VNode在path過程中會執(zhí)行對于鉤子函數(shù)。
實例化組件VNode,不用傳children參數(shù)。patch時候組件型VNode會特殊對待。
-
自己關(guān)于vm.$vnode和vm._vnode的一些補充:
vm.$vnode 組件占位節(jié)點。 new Vue()創(chuàng)建的根組件,$vnode是null 。 <child> <div></div> </child> 定義的子組件childVm對應(yīng)一個childVm.$vnode。 這個childVm.$vnode會是他的父vnode(也就是vm._node)的一個孩子。 但是這個這個childVm.$vnode本身不會產(chǎn)生dom元素。我們實際調(diào)試頁面時候也沒有見過頁面里有和組件名一樣的元素標簽。 那這個vnode對應(yīng)的dom元素從哪來呢?其實是來源于他的孩子<div></div>會生成一個childVm._vnode。這個childVm._vnode會渲染出dom元素,并且把這個dom元素賦值給childVm.$vnode的dom元素變量。所以childVm.$vnode既是子組件最終渲染的childVm._vnode的父vnode,也是父組件vm的一個占位vnode(父組件某個孩子vnode)。 childVm._vnode.parent === childVm.$vnode childVm.$vnode.parent為 null vnode.js里清晰的注釋了 parent // component placeholder node 也就是說parent只是個占位node,如果一個vnode不需要占位vnode,那parent自然就是null
深入響應(yīng)式原理
src/core/instance/state.js中initState方法中initData和initProps方法對vue實例的$options屬性的上data和props屬性做初始化。
defineReactive方法把每個屬性對應(yīng)的值變成響應(yīng)式的。
observe(data, true) 會把data上的屬性變成響應(yīng)式的,同時開始觀察每個屬性。
defineReactive時候,發(fā)現(xiàn)對象屬性會遞歸調(diào)用observe方法,發(fā)現(xiàn)數(shù)組會調(diào)用對每個元素遞歸調(diào)用observe。
defineReactive通過Object.defineProperty重寫屬性的getter和setter。getter用來依賴收集,setter用來派發(fā)更新。
vue初始化時候會創(chuàng)建Watcher, Watcher構(gòu)造函數(shù)會調(diào)用傳遞給構(gòu)造函數(shù)的getter函數(shù),這個getter函數(shù)會觸發(fā)對vue data的訪問,就會觸發(fā)data上的每個屬性的getter,每個data屬性也對應(yīng)一個Dep對象,觸發(fā)屬性的getter,這個Dep對象就把這個watcher實例添加到自己的watcher數(shù)組里。data屬性訪問就完成了對watcher的收集。
-
為data屬性設(shè)置值時,就會觸發(fā)屬性的setter,屬性對應(yīng)的Dep對象就會通知他的watcher數(shù)組里的每個watcher,每個需要渲染的watcher就會被添加到更新隊列里,nexttick時候執(zhí)行watcher.run,最終執(zhí)行虛擬節(jié)點的patch。data屬性設(shè)置就完成了更新的派發(fā)。
image.png data以及data下的所有Object(包括多層次的,包括數(shù)組里通過指定方式添加的對象),都會有個
__ob__屬性,是一個Observer實例,在defineReactive時候,如果當前要變成響應(yīng)式的屬性的值是個Object,如果沒有__ob__屬性 就會添加__ob__屬性,__obj__屬性會有個Dep對象,這個Dep對象也把當前watcher添加到watcher數(shù)組里。這個就是為Vue.set 做好準備的。只需要調(diào)用每個Object上的__ob__.dep.notify(),就可通知相關(guān)watcher。core/observer/index.js 中Vue.set 方法的函數(shù)體set接收 3個參數(shù),
target可能是數(shù)組或者是普通對象,key代表的是數(shù)組的下標或者是對象的鍵值,val代表添加的值。target不能是data本身。必須是data的下一層或者下幾層屬性對象(或數(shù)組)。通過defineReactive讓這個新加的屬性變成響應(yīng)式的。然后調(diào)用targe對應(yīng)的observer ob.dep.notify()方法,通知watcher重新收集依賴,同時渲染DOM-
數(shù)組,這幾個方法是可以讓新加的元素(如果有的話)變成響應(yīng)式的,同時會觸發(fā)watcher更新,進而渲染相關(guān)DOM
const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] computed和用戶添加的watch也都是用Watcher實現(xiàn)的,個別邏輯有點區(qū)別
編譯
- 記得個ast(抽象語法樹)
擴展
略
Vue Router
略
Vuex
略
