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


本文轉載自 http://blog.csdn.net/generon/article/details/72482844  ,看過之后覺得,實在太精彩了

一、前言

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

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

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

二、Vue的初始化

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

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

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

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

圖片描述

這個入口文件中import了很多文件,其中有一條主要的脈絡:

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

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

圖片描述

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

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方法繼續走下去。

三、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函數相當於人已經將邏輯翻譯給程序,能夠勝任復雜的邏輯,靈活性高,但對於用戶的理解相對差點。

四、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樹。

五、Vue的渲染小結

回過頭來看,這里的渲染邏輯並不是特別復雜,核心關鍵的幾步流程還是非常清晰的:

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM