本文轉載自 http://blog.csdn.net/generon/article/details/72482844 ,看過之后覺得,實在太精彩了
一、前言
-
Vue.js框架是目前比較火的MVVM框架之一,簡單易上手的學習曲線,友好的官方文檔,配套的構建工具,讓Vue.js在2016大放異彩,大有趕超React之勢。前不久Vue.js 2.0正式版已出,在體積優化(相比1.0減少了50%)、性能提升(相比1.0提升60%)、API優化等各方面都更上一層樓;
-
本文是系列文章,主要想通過對於Vue.js 2.0源碼的分析,從代碼層面解析Vue.js的實現原理,幫助讀者能夠更深入地理解整個框架的思想。此篇文章主要介紹前端渲染部分;
-
不足之處還請批評指正,歡迎一起交流學習。
二、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的渲染小結
回過頭來看,這里的渲染邏輯並不是特別復雜,核心關鍵的幾步流程還是非常清晰的:
- new Vue,執行初始化
- 掛載
$mount
方法,通過自定義Render方法、template、el等生成Render函數 - 通過Watcher監聽數據的變化
- 當數據發生變化時,Render函數執行生成VNode對象
- 通過patch方法,對比新舊VNode對象,通過DOM Diff算法,添加、修改、刪除真正的DOM元素
至此,整個new Vue的渲染過程完畢。