vue源碼分析:渲染篇(轉載)

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.jsdom

其中/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時,三種寫法:

  1. 自定義Render函數

    Vue.component('anchored-heading', {
          render: function (createElement) {
              return createElement(
                  'h' + this.level,   // tag name 標籤名稱
                  this.$slots.default // 子組件中的陣列
              )
          },
          props: {
              level: {
                  type: Number,
                  required: true
              }
          }
      })
  2. template寫法

    var vm = new Vue({

    data: {
          // 以一個空值聲明 `msg`
          msg: ''
      },
      template: '<div>{{msg}}</div>'

    })

  3. 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有多種實現方式,但基本思路都是同樣的,分爲兩步:

  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的渲染小結

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

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

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

做者:Generon
來源:CSDN
原文:https://blog.csdn.net/generon... 版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索