《Vue.js揭秘》讀后感

想深入理解Vue的原理,和組織結(jié)構(gòu),這本HuangYi寫的電子書是非常好的資料。

以備不時之需,特將主要脈絡(luò)記錄下來

作者經(jīng)常說: xx入口這里關(guān)鍵的代碼是, 所以我們分析源碼時,也要學會找重點

數(shù)據(jù)驅(qū)動

  1. 組裝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ù)

  2. code/instance/index.js中Vue構(gòu)造函數(shù)通過prototype拓展功能被分散到多個模塊中去實現(xiàn),非常便于代碼的維護,直接的結(jié)果就是每個js文件代碼函數(shù)都不會很多。

  3. 然后是code/global-api.js文件向Vue添加一些全局靜態(tài)方法比如Vue.set。

  4. Vue 初始化主要就干了幾件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,調(diào)用beforeCreate鉤子,初始化injections, 初始化 data、props、computed、watcher,初始化provide,調(diào)用created鉤子。最后如果有el屬性,調(diào)用$mount渲染成最終的DOM。

  5. 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。

  6. _render返回的是VNode。 _render其實是調(diào)用vdom/create-element.js 中的creatElement創(chuàng)建的VNode

  7. 然后通過core/instance/lifecycle.js中的_update方法,將VNode轉(zhuǎn)換為DOM。

  8. _update用到的patch方法是由core/vdom/patch.js中的createPatchFunction創(chuàng)建,createPatchFunction返回的patch函數(shù)緩存了modules(平臺的一些模塊,會在整個 patch 過程的不同階段執(zhí)行相應(yīng)的鉤子函數(shù))和nodeOps (一些dom節(jié)點的操作方法)。

  9. patch函數(shù),如果接收第一個參數(shù)oldVnode是個dom元素(vue實例的一次path時候就是傳入一個dom元素),會執(zhí)行createElm方法創(chuàng)建一個dom元素并插入到他的父元素中。

  10. 作者把初始化vue的過程畫了一張圖:

    new Vue -> init -> $mount -> compile -> render -> vnode -> patch -> DOM
    

組件化

  1. core/vdom/create-element.js 中的_createElement會判斷tag參數(shù), 如果不是html字符串標簽,就會調(diào)用createComponent創(chuàng)建組建類型的VNode。

  2. createComponent在core/vdom/create-component.js中,他主要有3 個關(guān)鍵步驟:

  3. 構(gòu)造子類構(gòu)造函數(shù)。通過Vue.extend方法。

  4. 安裝組件鉤子函數(shù):installComponentHooks(data)。組件VNode在path過程中會執(zhí)行對于鉤子函數(shù)。

  5. 實例化組件VNode,不用傳children參數(shù)。patch時候組件型VNode會特殊對待。

  6. 自己關(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)式原理

  1. src/core/instance/state.js中initState方法中initData和initProps方法對vue實例的$options屬性的上data和props屬性做初始化。

  2. defineReactive方法把每個屬性對應(yīng)的值變成響應(yīng)式的。

  3. observe(data, true) 會把data上的屬性變成響應(yīng)式的,同時開始觀察每個屬性。

  4. defineReactive時候,發(fā)現(xiàn)對象屬性會遞歸調(diào)用observe方法,發(fā)現(xiàn)數(shù)組會調(diào)用對每個元素遞歸調(diào)用observe。

  5. defineReactive通過Object.defineProperty重寫屬性的getter和setter。getter用來依賴收集,setter用來派發(fā)更新。

  6. 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的收集。

  7. 為data屬性設(shè)置值時,就會觸發(fā)屬性的setter,屬性對應(yīng)的Dep對象就會通知他的watcher數(shù)組里的每個watcher,每個需要渲染的watcher就會被添加到更新隊列里,nexttick時候執(zhí)行watcher.run,最終執(zhí)行虛擬節(jié)點的patch。data屬性設(shè)置就完成了更新的派發(fā)。


    image.png
  8. 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。

  9. 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

  10. 數(shù)組,這幾個方法是可以讓新加的元素(如果有的話)變成響應(yīng)式的,同時會觸發(fā)watcher更新,進而渲染相關(guān)DOM

    const methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ]
    
  11. computed和用戶添加的watch也都是用Watcher實現(xiàn)的,個別邏輯有點區(qū)別

編譯

  1. 記得個ast(抽象語法樹)

擴展

Vue Router

Vuex

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容