原文:medium.com/js-imaginea…html
說到 JavaScript 框架,Vue.js 絕對是個熱門的 UI 框架(譯註:截至本文翻譯時其 Github 155k ⭐️ & 23k 🍴, 關注數已經超過了 React)。於我來講 Vue.js 最吸引人的地方在於 -- 其學習曲線,很是之低。我的角度來說,我感受就像正在作着 jQuery 一類的事情。鼓搗幾天以後,你就能開始創建應用了。vue
一年前我開始探索 Vue.js 並創建了一些應用。可是幾天前,一股深刻了解 Vue.js 代碼的渴望在我心中升騰。我翻閱了 Github 上的源碼並進行了多輪調試以瞭解其底層運行機制。這也是本文中我要寫的東西。git
因此,讓咱們來點乾貨,本文將嘗試給你以下 4 個問題的答案:github
Vue 組件中包含一個模板(template),而模板在出如今瀏覽器裏以前必須經歷多個階段。咱們來編寫一個短小的模板,並以之做爲一個例子驅動本文的進行。算法
<div id="app">
<span v-if="dynamic">Dynamic text</span>
<span><p>Static text</p></span>
<button @click="toggleFlag">Toggle Dynamic</button>
</div>
複製代碼
組件的 JS logic 就不寫出來了,由於模板自己已經能夠自解釋。瀏覽器
Vue compiler 讀取一個組件的模板,使之經歷下圖所示的 parsing、optimizing、codegen 階段並最終建立一個渲染函數。該渲染函數的職責就是建立一個 VNode,而該 VNode 會被 Virtual DOM 的 patch 過程用來建立真實 DOM。緩存
解析階段app
在編譯的這個階段對特定組件中的置標語言模板進行解析。正如你能在下圖中見到的,首先 parser 會將模板解析成 HTML parser,隨後轉成 AST(即 抽象語法樹)。框架
AST 包含了諸如 attributes、parent、children、tag 等等的信息。解析過程當中也會將 directives 以相似元素的方式處理。諸如 v-for、v-if、v-once 等結構化的 directives 會被表現爲一個特定元素 AST 中的 key-value 對。如咱們模板中的 v-if,在解析後將被推入 attrsMap 中變成形如 {v-if: 「dynamic」} 的對象。dom
優化階段
optimizer 的目標就是遍歷生成的 AST 並探測純靜態的子樹,即 DOM 中不會改變的那些部分。以下圖所示,這些元素將被標記爲 static。
一旦檢測到靜態子樹,Vue 便將其提高爲常量,從而不會在每次從新渲染時爲其生成新鮮的節點。這些節點也會在 Virtual DOM 的 patch 過程當中被徹底地跳過。
Codegen 階段
編譯的最後一個階段就是 Codegen,該階段將建立真正的渲染函數以用於 patch 過程。
在上圖中,能夠看到模板的層次結構已經被轉換成了渲染函數的層次結構。基於 optimizer 打過的 static 標記,Codegen 將渲染函數分叉爲兩個獨立的函數。一個是普通的渲染函數,另外一個是靜態渲染函數。
最後,當真正的渲染過程觸發時,渲染函數將被用於建立 VNode。
注意:若是你使用了一個構建步驟,如單文件組件時,模板的編譯將提早發生。
Observer
Vue 會在底層遍歷全部咱們定義在 data 中的屬性,並經過 Object.defineProperty 將它們轉換爲 getter/setters。
當任何 data 屬性獲得一個新值時,set 函數將會通知 Watchers。
Watcher
當一個 Vue 應用被初始化時,會爲每一個組件建立一個 Watcher。Watcher 會解析一個表達式,收集訂閱者並在表達式的值變化時觸發回調。這個作法被同時用在了 $watch API 和 directives 上。每一個組件實例都有一個相應的 watcher 實例,用以將渲染組件期間「觸及」的任何屬性記錄爲依賴項(譯註:在 getter 裏收集會訪問到的依賴數據)。其後,當一個依賴項的 setter 被觸發,它就會通知到 watcher,並最終觸發 patch 過程。
不管什麼時候,當一個數據的改變被觀察到,就會開啓一個隊列並緩存本輪事件循環中發生的全部數據改變。全部 watchers 都被添加到此隊列中。每一個 watcher 有一個獨特的自增 Id,這樣若是相同的 watcher 被觸發屢次,它只會在被使用前被推送到隊列中一次。由於 watchers 要以從 parent 到 child 的順序運行,因此隊列也會被排序。
在內部,Vue 會爲異步排隊嘗試使用原生的 Promise.then 和 MessageChannel,實在不行就用 setTimeout(fn, 0)。
nextTick 函數會消耗掉隊列中的全部 watchers。在那以後,渲染過程將經過 watcher 的 run() 函數被初始化。
patch 過程基本上就是一個使用 Virtual DOM 和真實 DOM 高效交互的過程。一個 Virtual DOM 就是表示一個 DOM(文檔對象模型 - Document Object Model) 的 JavaScript 對象。Vue.js 在內部使用了 snabbdom 庫。因此,讓咱們看看 patch 過程當中到底發生了什麼。
整個過程就是個關於兩相對比新舊 VNode (Virtual DOM Node) 的遊戲。
其算法將以以下方式運行 --
相同的過程會遞歸式地應用到全部節點上。
此外,我得提醒你一些事情 -- 靜態節點,咱們在優化階段討論過的。靜態節點樹並不會被觸及,並被原樣使用。這意味着 -- 咱們並不須要對這種樹與真實 DOM 交互。
讓咱們來討論一下特定組件的生命跨度,並嘗試把它們帶入本文討論的話題。
組件生命週期可被分爲四個節段 --
一旦 Vue 的新實例被執行,建立組件的過程就啓動了。
beforeCreation: 收集組件所需的事件、數據以前。換句話說 -- 在收集 watchers/dependencies 的過程當中。
created: 當 Vue 設置好 data 和 watchers 的時候。
beforeMount: 早於 patch 過程。VNode 正在基於 data 和 watchers 被建立。
mount: patch 過程以後。
beforeUpdate: 若是數據改變,watcher 會更新 VNode 並從新開始一次 patch 過程。
update: patch 過程完成時。
beforeDestroy: 卸載組件以前。此時,組件還是全須全尾的。
destroyed: 銷燬 watchers 並刪除附加其上的事件監聽器或子組件時。
搜索 fewelife 關注公衆號
轉載請註明出處