1、前言
Vue.js框架是目前比較火的MVVM框架之一,簡單易上手的學習曲線,友好的官方文檔,配套的構建工具,讓Vue.js在2016大放異彩,大有趕超React之勢。前不久Vue.js 2.0正式版已出,在體積優化(相比1.0減小了50%)、性能提高(相比1.0提高60%)、API優化等各方面都更上一層樓;前端
本文是系列文章,主要想經過對於Vue.js 2.0源碼的分析,從代碼層面解析Vue.js的實現原理,幫助讀者可以更深刻地理解整個框架的思想。此篇文章主要介紹前端渲染部分;node
不足之處還請批評指正,歡迎一塊兒交流學習。web
2、Vue的初始化
咱們在使用Vue.js的時候,最基本的一個使用,就是在HTML引入Vue.js的庫文件,並寫以下一段代碼:算法
var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })
new Vue,本質就是生成一個Vue的對象,咱們來了解一下這個生成Vue對象的過程是怎樣的:數據結構
首先,Vue的入口是/src/entries/web-runtime-with-compiler.js
,這是由config.js
配置文件決定的。app
這個入口文件中import了不少文件,其中有一條主要的脈絡:框架
/src/entries/web-runtime-with-compiler.js
引用了/src/entries/web-runtime.js
引用了/src/core/index.js
引用了/src/core/instance/index.js
dom
其中/src/core/instance/index.js是最核心的初始化代碼,其中:函數
紅框部分,就是整個Vue的類的核心方法。其含義給讀者解讀一下:工具
//初始化的入口,各類初始化工做 initMixin(Vue) //數據綁定的核心方法,包括經常使用的$watch方法 stateMixin(Vue) //事件的核心方法,包括經常使用的$on,$off,$emit方法 eventsMixin(Vue) //生命週期的核心方法 lifecycleMixin(Vue) //渲染的核心方法,用來生成render函數以及VNode 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時,三種寫法:
自定義Render函數
Vue.component('anchored-heading', { render: function (createElement) { return createElement( 'h' + this.level, // tag name 標籤名稱 this.$slots.default // 子組件中的陣列 ) }, props: { level: { type: Number, required: true } } })
template寫法
var vm = new Vue({
data: { // 以一個空值聲明 `msg` msg: '' }, template: '<div>{{msg}}</div>'
})
el寫法(這個就是入門時最基本的寫法)
var app = new Vue({
el: '#app', data: { message: 'Hello Vue!' }
})
這三種渲染模式最終都是要獲得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中有這麼一段代碼:
vm._watcher = new Watcher(vm, () => { vm._update(vm._render(), hydrating) }, 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
有多種實現方式,但基本思路都是同樣的,分爲兩步:
在Vue.js 2.0中Javascript模擬DOM模型樹就是VNode,Render函數執行後都會返回VNode對象,爲下一步操做作準備。在/src/core/vdom/vnode.js中,咱們能夠看到VNode的具體數據結構:
VNode的數據結構中還有VNodeData、VNodeDirective、VNodeComponentOptions,這些數據結構都是對DOM節點的一些描述,本文不一一介紹。讀者能夠根據源碼來理解這些數據結構。(PS:Vue.js使用了flow,標識了參數的靜態類型,對理解代碼頗有幫助^_^)
咱們知道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的渲染小結
回過頭來看,這裏的渲染邏輯並非特別複雜,核心關鍵的幾步流程仍是很是清晰的:
new Vue,執行初始化
掛載$mount
方法,經過自定義Render
方法、template、el
等生成Render
函數
經過Watcher
監聽數據的變化
當數據發生變化時,Render
函數執行生成VNode
對象
經過patch
方法,對比新舊VNode
對象,經過DOM Diff
算法,添加、修改、刪除真正的DOM元素
做者:Generon
來源:CSDN
原文:https://blog.csdn.net/generon... 版權聲明:本文爲博主原創文章,轉載請附上博文連接!