什麼是虛擬DOM
虛擬DOM是隨着時代發展而誕生的產物。
在web早期,都是命令式的操作DOM,雖然簡單好用,但是不會維護。
現在,三大主流框架都是聲明式的操作DOM,通過描述狀態和DOM之間的映射關係,來渲染成視圖。狀態怎麼生成視圖,不需要你來關心,框架會幫你搞定。
當某個狀態發生改變時,如何只更新與這個狀態相關聯的DOM節點。
虛擬DOM的解決方式是:根據狀態生成一個虛擬節點樹,然後使用虛擬節點樹進行渲染。在渲染前會將新的虛擬節點樹和舊的虛擬節點樹進行對比,只渲染不同的地方。
虛擬節點樹是由組件樹建立起來的整個虛擬節點(vnode)樹。
vnode是JavaScript中一個很普通的對象,這個對象上保存了生成DOM節點需要的一些數據。
爲什麼要引入虛擬DOM
在Angular和 React中,它們只知道有狀態(state)變化了,但是不是到具體是哪個或者哪些狀態(state)變化了,所以就需要進行比較暴力的對比,React是通過虛擬Dom進行對比,Angular是使用髒檢查流程。
Vue的變化偵測和這兩個都不一樣,Vue在一定程度是知道哪些狀態發生了變化。但是如果細粒度太細,每一個綁定都會有一個watcher實例來觀察狀態的變化,這樣就會有一些內存開銷以及一些依賴追蹤的開銷。當狀態越多的節點被使用時,開銷就越大。
因此在Vue2把細粒度調整到中,組件級別是一個watcher實例,不管組件內部使用了多少次,因此當狀態變化時,只通知到組件,在組件內部使用虛擬DOM去進行對比更新。
Vue.js 中的虛擬DOM
在vue中使用模板來描述狀態和DOM之間的映射關係,根據模板生成render渲染函數,執行render渲染函數生成虛擬節點樹,然後再與舊的虛擬節點樹對比,最後渲染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 可以理解爲節點描述對象,描述了怎麼去創建一個真實的DOM,DOM元素上的所有屬性堵在 vnode 上有對應的屬性。
vnode 表示一個真正的 DOM 元素所有真實的 DOM 元素都使用 vnode 創建,並插入到視圖中去。
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。