深入vue2.0底層思想–模板渲染

發表於2017-07-19

初衷

在使用vue2.0的過程,有時看API很難理解vue作者的思想,這促使我想要去深入瞭解vue底層的思想,瞭解完底層的一些思想,才能更好的用活框架,雖然網上已經有很多原始碼解析的文件,但我覺得只有自己動手了,才能更加深印象。

vue2.0和1.0模板渲染的區別

Vue 2.0 中模板渲染與 Vue 1.0 完全不同,1.0 中採用的 DocumentFragment (想了解可以觀看這篇文章),而 2.0 中借鑑 React 的 Virtual DOM。基於 Virtual DOM,2.0 還可以支援服務端渲染(SSR),也支援 JSX 語法。

知識普及

在開始閱讀原始碼之前,先了解一些相關的知識:AST 資料結構,VNode 資料結構,createElement 的問題,render函式。

AST 資料結構

AST 的全稱是 Abstract Syntax Tree(抽象語法樹),是原始碼的抽象語法結構的樹狀表現形式,計算機學科中編譯原理的概念。而vue就是將模板程式碼對映為AST資料結構,進行語法解析。

我們看一下 Vue 2.0 原始碼中 AST 資料結構 的定義:

我們看到 ASTNode 有三種形式:ASTElement,ASTText,ASTExpression。用屬性 type 區分。

VNode資料結構

下面是 Vue 2.0 原始碼中 VNode 資料結構 的定義 (帶註釋的跟下面介紹的內容有關):

真實DOM存在什麼問題,為什麼要用虛擬DOM

我們為什麼不直接使用原生 DOM 元素,而是使用真實 DOM 元素的簡化版 VNode,最大的原因就是 document.createElement 這個方法建立的真實 DOM 元素會帶來效能上的損失。我們來看一個 document.createElement 方法的例子

開啟 console 執行一下上面的程式碼,會發現列印出來的屬性多達 228 個,而這些屬性有 90% 多對我們來說都是無用的。VNode 就是簡化版的真實 DOM 元素,關聯著真實的dom,比如屬性elm,只包括我們需要的屬性,並新增了一些在 diff 過程中需要使用的屬性,例如 isStatic。

render函式

這個函式是通過編譯模板檔案得到的,其執行結果是 VNode。render 函式 與 JSX 類似,Vue 2.0 中除了 Template 也支援 JSX 的寫法。大家可以使用 Vue.compile(template)方法編譯下面這段模板。

方法會返回一個物件,物件中有 render 和 staticRenderFns 兩個值。看一下生成的 render函式

要看懂上面的 render函式,只需要瞭解 _c,_m,_v,_s 這幾個函式的定義,其中 _c 是 createElement(建立元素),_m 是 renderStatic(渲染靜態節點),_v 是 createTextVNode(建立文字dom),_s 是 toString (轉換為字串)

除了 render 函式,還有一個 staticRenderFns 陣列,這個陣列中的函式與 VDOM 中的 diff 演算法優化相關,我們會在編譯階段給後面不會發生變化的 VNode 節點打上 static 為 true 的標籤,那些被標記為靜態節點的 VNode 就會單獨生成 staticRenderFns 函式

模板渲染過程(重要的函式介紹)

瞭解完一些基礎知識後,接下來我們講解下模板的渲染過程
image

$mount 函式,主要是獲取 template,然後進入 compileToFunctions 函式。

compileToFunctions 函式,主要將 template 編譯成 render 函式。首先讀快取,沒有快取就呼叫 compile 方法拿到 render 函式 的字串形式,再通過 new Function 的方式生成 render 函式。

compile 函式就是將 template 編譯成 render 函式的字串形式,後面一小節我們會詳細講到。

完成render方法的生成後,會進入 _mount 中進行DOM更新。該方法的核心邏輯如下:

首先會new一個watcher物件(主要是將模板與資料建立聯絡),在watcher物件建立後,會執行傳入的方法 vm._update(vm._render(), hydrating) 。其中的vm._render()主要作用就是執行前面compiler生成的render方法,並返回一個vNode物件。vm.update() 則會對比新的 vdom 和當前 vdom,並把差異的部分渲染到真正的 DOM 樹上。

推薦個圖,響應式工程流程

images
(想深入瞭解watcher的背後實現原理的,可以觀看這篇文章 Vue2.0 原始碼閱讀:響應式原理

compile

上文中提到 compile 函式就是將 template 編譯成 render 函式 的字串形式。

這個函式主要有三個步驟組成:parse,optimize 和 generate,分別輸出一個包含 AST,staticRenderFns 的物件和 render函式 的字串。

parse 函式,主要功能是將 template字串解析成 AST。前面定義了ASTElement的資料結構,parse 函式就是將template裡的結構(指令,屬性,標籤等)轉換為AST形式存進ASTElement中,最後解析生成AST。

optimize 函式(src/compiler/optimizer.js)主要功能就是標記靜態節點,為後面 patch 過程中對比新舊 VNode 樹形結構做優化。被標記為 static 的節點在後面的 diff 演算法中會被直接忽略,不做詳細的比較。

generate 函式(src/compiler/codegen/index.js)主要功能就是根據 AST 結構拼接生成 render 函式的字串。

其中 genElement 函式(src/compiler/codegen/index.js)是會根據 AST 的屬性呼叫不同的方法生成字串返回。

以上就是 compile 函式中三個核心步驟的介紹,compile 之後我們得到了 render 函式 的字串形式,後面通過 new Function 得到真正的渲染函式。資料發現變化後,會執行 Watcher 中的 _update 函式(src/core/instance/lifecycle.js),_update 函式會執行這個渲染函式,輸出一個新的 VNode 樹形結構的資料。然後在呼叫 patch 函式,拿這個新的 VNode 與舊的 VNode 進行對比,只有發生了變化的節點才會被更新到真實 DOM 樹上。

patch

patch.js 就是新舊 VNode 對比的 diff 函式,主要是為了優化dom,通過演算法使操作dom的行為降到最低,diff 演算法來源於 snabbdom,是 VDOM 思想的核心。snabbdom 的演算法為了 DOM 操作跨層級增刪節點較少的這一目標進行優化,它只會在同層級進行, 不會跨層級比較。

想更加深入VNode diff演算法原理的,可以觀看(解析vue2.0的diff演算法

總結

  • compile 函式主要是將 template 轉換為 AST,優化 AST,再將 AST 轉換為 render函式;
  • render函式 與資料通過 Watcher 產生關聯;
  • 在資料發生變化時呼叫 patch 函式,執行此 render 函式,生成新 VNode,與舊 VNode 進行 diff,最終更新 DOM 樹。

相關文章