Vue源碼之虛擬DOM

我的博客地址vue

什麼是虛擬DOM

虛擬DOM是隨着時代發展而誕生的產物。node

在web早期,都是命令式的操做DOM,雖然簡單好用,可是不會維護。web

如今,三大主流框架都是聲明式的操做DOM,經過描述狀態和DOM之間的映射關係,來渲染成視圖。狀態怎麼生成視圖,不須要你來關心,框架會幫你搞定。算法

當某個狀態發生改變時,如何只更新與這個狀態相關聯的DOM節點。框架

虛擬DOM的解決方式是:根據狀態生成一個虛擬節點樹,而後使用虛擬節點樹進行渲染。在渲染前會將新的虛擬節點樹和舊的虛擬節點樹進行對比,只渲染不一樣的地方。async

虛擬節點樹是由組件樹創建起來的整個虛擬節點(vnode)樹。函數

vnode是JavaScript中一個很普通的對象,這個對象上保存了生成DOM節點須要的一些數據。性能

爲何要引入虛擬DOM

在Angular和 React中,它們只知道有狀態(state)變化了,可是不是到具體是哪一個或者哪些狀態(state)變化了,因此就須要進行比較暴力的對比,React是經過虛擬Dom進行對比,Angular是使用髒檢查流程。ui

Vue的變化偵測和這兩個都不同,Vue在必定程度是知道哪些狀態發生了變化。可是若是細粒度太細,每個綁定都會有一個watcher實例來觀察狀態的變化,這樣就會有一些內存開銷以及一些依賴追蹤的開銷。當狀態越多的節點被使用時,開銷就越大。this

所以在Vue2把細粒度調整到中,組件級別是一個watcher實例,無論組件內部使用了多少次,所以當狀態變化時,只通知到組件,在組件內部使用虛擬DOM去進行對比更新。

Vue.js 中的虛擬DOM

映射關係

在vue中使用模板來描述狀態和DOM之間的映射關係,根據模板生成render渲染函數,執行render渲染函數生成虛擬節點樹,而後再與舊的虛擬節點樹對比,最後渲染DOM。

虛擬DOM執行流程

虛擬DOM在vue中主要作了兩件事:

  • 提供與真實DOM相對應的vnode
  • 把新的虛擬節點樹和舊的虛擬節點樹進行對比,更新不一樣的地方

對兩個虛擬節點進行對比是整個虛擬DOM中最核心的算法,它能夠判斷出那個節點發生了變動,從而只更新變動的節點。

vnode

Vue 中有一個 VNode 類,能夠實例化不一樣類型的 vnode,不一樣類型的 vnode 表示不一樣類型的 DOM 元素:

class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  fnContext: Component | void; // real context vm for functional nodes
  fnOptions: ?ComponentOptions; // for SSR caching
  devtoolsMeta: ?Object; // used to store functional render context for devtools
  fnScopeId: ?string; // functional scope id support

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.fnContext = undefined
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }
}

複製代碼

簡單的說,vnode 能夠理解爲節點描述對象,描述了怎麼去建立一個真實的DOMDOM元素上的全部屬性堵在 vnode 上有對應的屬性。

vnode 表示一個真正的 DOM 元素全部真實的 DOM 元素都使用 vnode 建立,並插入到視圖中去。

vnode建立DOM並插入到視圖

vnode的做用

每次渲染視圖時,都會建立新的 vnode ,與舊的 vnode 進行對比,找出不同的地方並更新。

Vue 目前採用的是中等密度的細粒度,當狀態變化時,只會通知到組件,在組件內部使用虛擬DOM來渲染視圖。

也就是說只要組件內部有一個狀態發生了改變了,整個組件都會從新渲染。若是組件只變化了一個節點,卻要從新渲染全部的節點,這會形成性能浪費。

所以對比新舊 vnode ,只更新不一樣的部分,就顯得尤其重要。

VNode的類型

  • 註釋節點

    export const createEmptyVNode = (text: string = '') => {
      const node = new VNode()
      node.text = text
      node.isComment = true
      return node
    }
    複製代碼
  • 文本節點

    export function createTextVNode (val: string | number) {
      return new VNode(undefined, undefined, undefined, String(val))
    }
    複製代碼
  • 元素節點

    元素節點一般包括一下4種屬性:

    • tag 節點名稱,例如:div
    • data 節點數據,例如:class style
    • children 當前節點的子節點列表
    • context 當前組件的Vue實例
  • 組件節點

    和元素節點差很少,有兩個特有屬性:

    • componentOptions 組件節點的選項參數,包括:propsData、tag、children
    • componentInstance 組件實例。在 Vue 中每一個組件都是一個Vue實例。
  • 函數式節點

    和組件節點相似,有兩個特有屬性:

    • functionalContext
    • functionalOptions
  • 克隆節點

    克隆節點是將現有節點的全部屬性都複製到新節點下面,直接複用現有的 vnode ,出了首次渲染外,後續更新都不須要執行渲染函數生成相同的 vnode ,從而提高必定程度的性能。

    export function cloneVNode (vnode: VNode): VNode {
      const cloned = new VNode(
        vnode.tag,
        vnode.data,
        // #7975
        // clone children array to avoid mutating original in case of cloning
        // a child.
        vnode.children && vnode.children.slice(),
        vnode.text,
        vnode.elm,
        vnode.context,
        vnode.componentOptions,
        vnode.asyncFactory
      )
      cloned.ns = vnode.ns
      cloned.isStatic = vnode.isStatic
      cloned.key = vnode.key
      cloned.isComment = vnode.isComment
      cloned.fnContext = vnode.fnContext
      cloned.fnOptions = vnode.fnOptions
      cloned.fnScopeId = vnode.fnScopeId
      cloned.asyncMeta = vnode.asyncMeta
      cloned.isCloned = true
      return cloned
    }
    複製代碼

總結:

Vue 中,VNode 是一個類,不一樣類型的 VNode 實例表明不一樣類型的元素節點。

Vue2 對狀態的偵測策略採用了中等粒度,當狀態發生變化時,只會通知到組件,組件內部再使用虛擬DOM進行更新。組件內不是全部的節點都須要更新,因此將渲染函數新生成的 vnode 和舊的 vnode 進行對比,只更新不一樣的部分。

下一篇會介紹虛擬DOM最核心的算法:patch。

相關文章
相關標籤/搜索