Vue 2.0 中模板渲染與 Vue 1.0 完全不同,1.0 中采用的 DocumentFragment,而 2.0 中借鑒 React 的 Virtual DOM。基于 Virtual DOM,2.0 還可以支持服務端渲染(SSR),也支持 JSX 語法。
相關概念介紹
AST (abstract syntax tree)
AST即抽象語法樹,是源代碼的抽象語法結構的樹狀表現形式。Vue在mount的過程中,template會被編譯成AST語法樹。
Vue中的AST數據結構定義如下:
declare type ASTNode = ASTElement | ASTText | ASTExpression
declare type ASTElement = { // 有關元素的一些定義
type: 1;
tag: string;
attrsList: Array{ name: string; value: string }>;
attrsMap: { [key: string]: string | null };
parent: ASTElement | void;
children: ArrayASTNode>;
//......
}
declare type ASTExpression = {
type: 2;
expression: string;
text: string;
static?: boolean;
}
declare type ASTText = {
type: 3;
text: string;
static?: boolean;
}
可以看到Vue中的AST數據結構有三種類型,以type區分:
- ASTElement 標簽
- ASTExpression 包含字面量表達式的文本節點
- ASTText 普通文本節點或注釋節點
Virtual DOM - 輕量級的模擬DOM結構
- 結構為有序的二叉樹
- 與真實DOM樹每個位置的屬性一一對應
- 某一時刻真實DOM狀態的內存映射
VNode - Vue中的VDOM對象
數據結構定義:
constructor {
this.tag = tag //元素標簽
this.data = data //屬性
this.children = children //子元素列表
this.text = text
this.elm = elm //對應的真實 DOM 元素
this.ns = undefined
this.context = context
this.functionalContext = 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
}
為什么使用VDOM?
-
創建真實DOM的代價高
真實的DOM節點實現的屬性很多,僅一個<div>
就有多達200種屬性,而VNode僅僅實現一些必要的屬性,相比起來,創建一個VNode的成本比較低 - 真實DOM頻繁排版與重繪的效率是相當低的
- 虛擬DOM進行頻繁修改,然后一次性比較并修改真實DOM中需要改的部分
模板渲染流程解析
模板渲染大致上分為圖中的三個階段,其中
$mount()
函數是下面所有函數的入口函數,所做的工作主要分為三步:
- 如果你的option里面沒有自定義
render()
函數,那么,通過compileToFunctions()
將HTML模板編譯成可以生成VNode的render函數 - new 一個
Watcher()
實例,觸發updateComponent()
方法 - 生成VNode,經過
patch()
,把VNode更新到DOM上
模板編譯
模板編譯階段涉及兩個函數,compileToFunctions()
和compile()
,其中compileToFunctions()
是compile()
的入口函數,模板編譯的結果是生成render函數和staticRenderFns函數的字符串,其中staticRenderFns函數包含被標記為靜態節點的 VNode,與后續的diff算法優化相關
在進入
compileToFunctions()
以后,會先檢查緩存中是否有已經編譯好的結果,如果有結果則直接從緩存中讀取,這樣做防止每次同樣的模板都要進行重復的編譯工作。若無可用緩存,則調用compile()
,最后對編譯結果進行緩存
compile()
做了三件事情,分別是生成AST、優化靜態內容、生成render函數,對應三個函數:
parse ()
主要功能是將 template字符串解析成 AST,采用了 jQuery 作者 John Resig 的 HTML Parser。前面定義了ASTElement的數據結構,parse 函數就是將template里的結構(指令,屬性,標簽等)轉換為AST形式存進ASTElement中,最后解析生成ASToptimize()
主要功能就是標記靜態節點,獲得AST對象的最大靜態子樹,為后面 patch 過程中對比新舊 VNode 樹形結構做優化。被標記為 static 的節點在后面的 diff 算法中會被直接忽略,不做詳細的比較
最大靜態子樹:
不包含參數data屬性的dom節點,每次data數據改變導致頁面重新渲染的時候,最大靜態子樹不需要重新計算生成
-
generate()
根據 AST 結構拼接生成 render 函數的字符串,另外還會生成staticRenderFns函數字符串
DOM更新
Vue中的DOM更新是一個響應式工作流程,當數據發現變化后,會執行 Watcher 中的update()
函數,update()
函數會執行這個渲染函數,輸出一個新的 VNode 樹形結構的數據。然后再調用patch()
函數,拿這個新的 VNode 與舊的 VNode 進行對比,只有發生了變化的節點才會被更新到真實 DOM 樹上
patch.js 就是新舊 VNode 對比的 diff 函數,主要是為了優化dom,通過算法使操作dom的行為降到最低,diff 算法來源于 snabbdom,是 VDOM 思想的核心。snabbdom 的算法為了 DOM 操作跨層級增刪節點較少的這一目標進行優化,它只會在同層級進行, 不會跨層級比較。
總結
- VDOM
VDOM總損耗 = 虛擬DOM增刪改 + diff增刪改 + (較少的節點)排版與重繪
真實DOM總損耗 = 真實DOM完全增刪改 + (可能較多的節點)排版與重繪
目的是為了避免頻繁引發大面積的DOM操作,提升性能
模板編譯
將 template 轉換為 AST,優化 AST,再將 AST 轉換為 render函數
目的是為了得到render函數DOM更新
render函數通過Watcher與數據產生關聯,在數據發生變化時調用patch函數,
執行此render函數,生成新VNode,與舊VNode進行diff,最終更新DOM樹
參考鏈接
- Vue技術內幕
http://hcysun.me/vue-design/ - Vue渲染原理
https://www.cnblogs.com/zhaodagang8/p/7819414.html - Vue源碼--解讀Vue響應式原理
https://geniuspeng.github.io/2018/01/05/vue-reactivity/