Vue是如何渲染頁面的,渲染過程以及原理代碼

本文轉載自 http://blog.csdn.net/generon/article/details/72482844  ,看過以後以爲,實在太精彩了

1、前言

  1. Vue.js框架是目前比較火的MVVM框架之一,簡單易上手的學習曲線,友好的官方文檔,配套的構建工具,讓Vue.js在2016大放異彩,大有趕超React之勢。前不久Vue.js 2.0正式版已出,在體積優化(相比1.0減小了50%)、性能提高(相比1.0提高60%)、API優化等各方面都更上一層樓;javascript

  2. 本文是系列文章,主要想經過對於Vue.js 2.0源碼的分析,從代碼層面解析Vue.js的實現原理,幫助讀者可以更深刻地理解整個框架的思想。此篇文章主要介紹前端渲染部分;前端

  3. 不足之處還請批評指正,歡迎一塊兒交流學習。java

2、Vue的初始化

咱們在使用Vue.js的時候,最基本的一個使用,就是在HTML引入Vue.js的庫文件,並寫以下一段代碼:node

1.var app = new Vue({ 2. el: '#app', 3. data: { 4. message: 'Hello Vue!' 5. } 6.})

new Vue,本質就是生成一個Vue的對象,咱們來了解一下這個生成Vue對象的過程是怎樣的:react

首先,Vue的入口是/src/entries/web-runtime-with-compiler.js,這是由config.js配置文件決定的。web

圖片描述

這個入口文件中import了不少文件,其中有一條主要的脈絡:算法

/src/entries/web-runtime-with-compiler.js 
引用了/src/entries/web-runtime.js 
引用了/src/core/index.js 
引用了/src/core/instance/index.jsmarkdown

其中/src/core/instance/index.js是最核心的初始化代碼,其中:數據結構

圖片描述

紅框部分,就是整個Vue的類的核心方法。其含義給讀者解讀一下:app

1.//初始化的入口,各類初始化工做 2.initMixin(Vue) 3.//數據綁定的核心方法,包括經常使用的$watch方法 4.stateMixin(Vue) 5.//事件的核心方法,包括經常使用的$on,$off,$emit方法 6.eventsMixin(Vue) 7.//生命週期的核心方法 8.lifecycleMixin(Vue) 9.//渲染的核心方法,用來生成render函數以及VNode 10.renderMixin(Vue)

其中new Vue就是執行下面的這個函數:

圖片描述

_init方法就是initMixin中的_init方法。

圖片描述

至此,程序沿着這個_init方法繼續走下去。

3、Vue的渲染邏輯——Render函數

在定義完成Vue對象的初始化工做以後,本文主要是講渲染部分,那麼咱們接上面的邏輯,看Vue.js是如何渲染頁面的。在上圖中咱們看到有一個initRender的方法:

圖片描述

在該方法中會執行紅框部分的內容:

圖片描述

$mount方法就是整個渲染過程的起始點。具體定義是在/src/entries/web-runtime-with-compiler.js中,根據代碼整理成流程圖:

圖片描述

由此圖能夠看到,在渲染過程當中,提供了三種渲染模式,自定義Render函數、template、el都可以渲染頁面,也就是對應咱們使用Vue時,三種寫法:

1. 自定義Render函數

1.Vue.component('anchored-heading', { 2. render: function (createElement) { 3. return createElement( 4. 'h' + this.level, // tag name 標籤名稱 5. this.$slots.default // 子組件中的陣列 6. ) 7. }, 8. props: { 9. level: { 10. type: Number, 11. required: true 12. } 13. } 14.})

2. template寫法

1.var vm = new Vue({
2. data: { 3. // 以一個空值聲明 `msg` 4. msg: '' 5. }, 6. template: '<div>{{msg}}</div>' 7.})

3. el寫法(這個就是入門時最基本的寫法)

1.var app = new Vue({ 2. el: '#app', 3. data: { 4. message: 'Hello Vue!' 5. } 6.})

這三種渲染模式最終都是要獲得Render函數。只不過用戶自定義的Render函數省去了程序分析的過程,等同於處理過的Render函數,而普通的template或者el只是字符串,須要解析成AST,再將AST轉化爲Render函數。

記住一點,不管哪一種方法,都要獲得Render函數。

咱們在使用過程當中具體要使用哪一種調用方式,要根據具體的需求來。

  • 若是是比較簡單的邏輯,使用template和el比較好,由於這兩種都屬於聲明式渲染,對用戶理解比較容易,但靈活性比較差,由於最終生成的Render函數是由程序經過AST解析優化獲得的;

  • 而使用自定義Render函數至關於人已經將邏輯翻譯給程序,可以勝任複雜的邏輯,靈活性高,但對於用戶的理解相對差點。

4、Vue的渲染邏輯——VNode對象&patch方法

根據上面的結論,咱們不管怎麼渲染,最終會獲得Render函數,而Render函數的做用是什麼呢?咱們看到在/src/core/instance/lifecycle.js中有這麼一段代碼:

1.vm._watcher = new Watcher(vm, () => { 2. vm._update(vm._render(), hydrating) 3.}, noop);

意思就是,經過Watcher的綁定,每當數據發生變化時,執行_update的方法,此時會先執行vm._render(),在這個vm._render()中,咱們的Render函數會執行,而獲得VNode對象。

圖片描述

VNode對象是什麼?VNode就是Vue.js 2.0中的Virtual DOM,在Vue.js 2.0中,相較Vue.js 1.0引入了Virtual DOM的概念,這也是Vue.js 2.0性能提高的一大關鍵。Virtual DOM有多種實現方式,但基本思路都是同樣的,分爲兩步:

1. JavaScript模擬DOM模型樹

在Vue.js 2.0中javascript模擬DOM模型樹就是VNode,Render函數執行後都會返回VNode對象,爲下一步操做作準備。在/src/core/vdom/vnode.js中,咱們能夠看到VNode的具體數據結構

圖片描述

VNode的數據結構中還有VNodeData、VNodeDirective、VNodeComponentOptions,這些數據結構都是對DOM節點的一些描述,本文不一一介紹。讀者能夠根據源碼來理解這些數據結構。(PS:Vue.js使用了flow,標識了參數的靜態類型,對理解代碼頗有幫助^_^)

2. DOM模型樹經過DOM Diff算法查找差別,將差別轉爲真正DOM節點

咱們知道Render函數執行生成了VNode,而VNode只是Virtual DOM,咱們還須要經過DOM Diff以後,來生成真正的DOM節點。在Vue.js 2.0中,是經過/src/core/vdom/patch.js中的patch(oldVnode, vnode ,hydrating)方法來完成的。

該方法有三個參數oldVnode表示舊VNode,vnode表示新VNode,hydrating表示是否直接使用服務端渲染的DOM元素,這個本文不做討論,在服務端渲染篇再詳細介紹。

其主要邏輯爲當VNode爲真實元素或舊的VNode和新的VNode徹底相同時,直接調用createElm方法生成真實的DOM樹,當VNode新舊存在差別時,則調用patchVnode方法,經過比較新舊VNode節點,根據不一樣的狀態對DOM作合理的添加、刪除、修改DOM(這裏的Diff算法有興趣的讀者能夠自行閱讀patchVnode方法,鑑於篇幅再也不贅述),再調用createElm生成真實的DOM樹。

5、Vue的渲染小結

回過頭來看,這裏的渲染邏輯並非特別複雜,核心關鍵的幾步流程仍是很是清晰的:

  1. new Vue,執行初始化
  2. 掛載$mount方法,經過自定義Render方法、template、el等生成Render函數
  3. 經過Watcher監聽數據的變化
  4. 當數據發生變化時,Render函數執行生成VNode對象
  5. 經過patch方法,對比新舊VNode對象,經過DOM Diff算法,添加、修改、刪除真正的DOM元素

至此,整個new Vue的渲染過程完畢。

相關文章
相關標籤/搜索