Vue基於snabbdom作了哪些事

文章首發於 http://shuaizhang.tophtml

前言

以前有簡單看過 Vue patch 部分的源碼,瞭解了是基於 Snabbdom 庫實現的。最近想詳細瞭解下 Vue 處理 vnode patch 的整個過程,想知道它在 Snabbdom 之上作了哪些事情?因此帶着這個問題,寫了這篇文章來記錄。vue

Snabbdom 作了哪些事?

A virtual DOM library with focus on simplicity, modularity, powerful features and performance.
(一個虛擬 DOM 庫,專一於簡單性,模塊化,強大的功能和性能。)

Snabbdom 核心代碼大約只有 200 行。它提供了模塊化架構,具備豐富的功能,可經過自定義模塊進行擴展。在瞭解核心 patch 前,須要先了解 snabbdom 的模塊化架構思想。node

modules

在節點的生命週期裏作一些任務,來擴展 Snabbdom ,就能夠稱之爲模塊。
Snabbdom 在 patch 的過程當中會注入不少鉤子(hooks)。模塊實現就是基於這些鉤子,鉤子能夠理解爲 vnode 節點的生命週期。git

好比 eventlisteners 模塊:github

  1. create: 節點建立時添加時間監聽(addEventListener)
  2. update: 節點更新時移除老節點事件,添加新事件
  3. destroy: 節點銷燬時,移除老節點事件

Hooks

Hooks 是一種掛載 vnode 生命週期的方式。軟件開發領域有相似這樣的設計思想,好比版本管理工具 git。Snabbdom 中的模塊就是基於此來實現擴展的。固然,也能夠傳遞 hook 配置,實如今 vnode 生命週期裏作一些事(這種只針對單個節點,而模塊針對全部節點),例如:web

h('div.row', {
  key: movie.rank,
  hook: {
    insert: vnode => {
      movie.elmHeight = vnode.elm.offsetHeight
    }
  }
})

Vue 中的指令就是基於此實現的。具體的生命週期能夠參考官方文檔:https://github.com/snabbdom/snabbdom#hooks數組

patch(oldVnode, newVnode) 函數

Snabbdom 的核心部分,patch 函數由 snabbdom.init 建立,初始化時提供綁定 hook 的模塊。
若是 oldVnode 是具備父節點的 DOM 元素,則 newVnode 將變爲 DOM 節點,而且傳遞的元素將被建立的 DOM 節點替換。若是傳遞的是個 vnode 實例,則會比對此節點和遞歸對比它的子節點,並作 dom 更新。這一塊網上的源碼解析文章也比較多,這裏就很少介紹了。性能優化

snabbdom/h

h 函數用來建立 vnode 實例,類比 Vue render 函數中的參數 h,Vue 中擴展了入參。好比第一個參數 tag,Vue 能夠是對象或函數;第二個參數 data,Vue 增長了一些特有的功能好比:scopedSlots, slot, directive, ref... 等。架構

Vue 相比 Snabbdom,patch 過程當中有哪些不同的地方?

組件

  • Vue 中有組件的概念,組件一樣是能夠嵌套的,全部就存在父組件和子組件。父組件 render 函數執行後,子組件並無被初始化,僅僅是建立了一個特殊的 vnode 節點,而這個節點上會綁定一些 hooks: init, prepatch, insert, destroy。當父組件 patch 執行的過程當中,遇到子組件的話,會調用 hook: init 方法,此方法中才會初始化組件實例。後續 insert hook 觸發時,相應的會調用組件 mounted 生命週期鉤子。這一塊源碼能夠查看 https://github.com/vuejs/vue/blob/dev/src/core/vdom/create-component.js#L36-L97
  • setScope 也是在 patch 執行過程當中調用的,它會往 dom 節點上增長 ${scopedId} 屬性,用來實現 scoped 樣式功能。
  • patch 過程當中會更新 ref 到上下文實例上。
  • 由於有組件概念,因此 Vue 中建立 vnode 第一個參數 tag 能夠是對象或函數,Snabbdom 上只能是字符串。

生命週期

  • Vue 去掉了 prepost 2 個鉤子,這 2 個鉤子分別用來在 patch 執行先後調用的
  • Vue 中 init 鉤子只對組件節點生效,而 Snabbdom 中全部節點均可定義 init hooks
  • Vue 新增了 active 鉤子,用來處理 <keep-alive> 嵌套 <transition> 組件產生的邊界問題

靜態節點優化

Vue 會對靜態節點作性能優化。dom

  • 編譯時:Vue compiler 會對 template 中的靜態節點特殊處理:建立靜態節點的方法會存放在 staticRenderFns 屬性裏。除了純 html 語法產生的靜態節點外,v-once, v-pre 也會產生靜態節點。
  • 運行時:當組件初始化時,靜態節點會被保存到實例 _staticTrees 數組中。組件更新從新觸發 render 時,不會從新建立 vnode 節點,直接使用以前已有的靜態節點。進而不會觸發 patchVnode 操做。

hydrate

對於 Vue 服務端渲染輸出的 html,客戶端初始化掛載節點時,會把已經渲染好的 dom 和 vnode 一一綁定,以達到同構的效果,hydrate 函數就作了這個任務。

patch 總體流程

我整理了一份比較詳細的 patch 流程圖。
一些細節沒有寫入,好比:updateChildren diff 過程、異步組件、keep-alive、hydrate...

Vue patch process

Vue 中有哪些功能是基於 patch 中的 Hooks 實現的?

directives

Vue 2 中的指令就是基於 hooks 實現的,從 directive 的生命週期來看:

  1. bind:節點上綁定新的指令時調用 -> hooks: create, update
  2. inserted:節點上綁定新的指令,若 hooks == create ,則在節點被 insert 時調用;若 hooks == update,則直接調用
  3. update:節點上已存在指令時調用 -> hooks: update
  4. componentUpdated: 節點上已存在指令,且在 hooks: postpatch 觸發時調用,此時指令所在組件的 VNode 及其子 VNode 已所有更新。
  5. unbind:節點被銷燬時調用 -> hooks: destroy

ref

vnode 建立、更新、銷燬時都會更新所在組件上的 $refs 屬性。
patch 過程當中處理了子組件爲空時,父組件指向的 ref 爲 undefined 的邊界狀況

style

對於動態綁定 style,Vue 還會智能的往不支持的屬性前加廠商前綴。

const normalize = cached(function(prop) {
  emptyStyle = emptyStyle || document.createElement('div').style
  prop = camelize(prop)
  if (prop !== 'filter' && prop in emptyStyle) {
    return prop
  }
  const capName = prop.charAt(0).toUpperCase() + prop.slice(1)
  for (let i = 0; i < vendorNames.length; i++) {
    const name = vendorNames[i] + capName
    if (name in emptyStyle) {
      return name
    }
  }
})

Vue 還支持 value 數組寫法。好比 {display: ["-webkit-box", "-ms-flexbox", "flex"]}

Snabbdom 中對 style 增長了 hooks: remove,這個是爲了實現節點被移除時的過渡動效,而 Vue 對過渡動效的處理封裝在了 <transition> 組件中。

寫在後面

其實 Vue 中還有不少功能都依賴 vnode 節點 patch 的過程,transition 的功能也比較多,這裏暫時不深刻了。
因爲本人理解有限,文中若有任何問題歡迎留言指正。

參考

相關文章
相關標籤/搜索