上一節,我們講到了在創建一個vu實例時候,vue的構造函數會執行this._init()函數,然後啟動函數的最後會執行_init方法裡面執行vm.$mount(vm.$options.el),將實例掛載在dom上,至此啟動函數結束。可以看出vue.$mount為vue render主要函數。

本節我們會主要著重於vue整個render過程,過於細節化的東西會再以後章節詳細描述。

首先我們通過流程圖看下vue獨立構建渲染過程:

為了了解vue渲染過程我們必須知道vue有幾種構建方式:

1 獨立構建 (包含template編譯器)

渲染過程:html字元串 → render函數 → vnode → 真實dom節點

2 運行中構建 (不包含template編譯器)渲染過程: render函數 → vnode → 真實dom節點

產生兩種構建方式原因是由於歷史版本中vue1.0中編譯過程依賴瀏覽器的dom,無法將編譯器和運行中分開,編譯器和運行中是打包在一起的,都在瀏覽器中執行,但是在vue2.0中,為了支持服務端渲染,必須將編譯器和運行時分開,就形成了獨立構建(編譯器+運行時)和運行時構建,可以看到運行時構建打包出來必定比獨立構建中小的多。

綜上所述,我們說了這麼多,接下來我們來看下vue源碼的分析過程。

Vue 中我們是通過$mount實例方法去掛載vm的,$mount 方法在多個文件中都有定義,如src/platform/web/entry-runtime-with-complier.js , src/platform/web/runtime/index.js src/platform/weex/runtime/index.js 因為$mount 這個方法的實現是和平台、構建方式都相關的。接下來我們分析運行時版本的$mount 實現,因為拋開 webpack 的 vue-loader,我們在純前端瀏覽器環境分析 Vue 的工作原理 。

在src/platforms/web/entry-runtime-with-compiler,$mount函數在vue的原型上被定義

可以看到el可以是字元串也可以是個element,調用了query方法(platfomrs/web/util/index.js):

可以看到當傳入字元串時候回篩選出字元對應的dom節點,如果是dom節點話直接返回

至此,el就轉化為dom對象然後做了一個判斷,判斷el不能是body以及html,為了防止覆蓋

截下來就是判斷是否有render函數以及template,若vue實例中沒有render,則將template編譯成render,也就是說vue只認render函數,同時,因為template可以寫成多掙形式,因此el也會轉換成template(使用getOutHTML函數),再轉換成render。本段不好描述邏輯,故代碼加備註

在getOuterHTML中,主要是獲得el所對應的dom及其內容

最後,調用mount函數完成渲染。

其中這個mount指的是 platform/runtime/index.js裡面的方法

上面的方法主要是做一些判斷,並且執行mountcomponent方法,mountComponent核心就是先調用vm._render方法先生成虛擬 Node,再實例化一個渲染Watcher,在它的回調函數中會調用updateComponent方法,最終調用vm._update更新 DOM。那我們來看看這個方法是在core/instance/lifecycle裡面:

裡面的邏輯主要是判斷是否有render函數,如果沒有,執行createEmptyVNode,並且假如當我們使用了runtime-only版本,我們又使用template語法,不是直接寫render函數,還有一種情況就是既沒有寫render函數和template函數會報錯:

vm.$options.template && vm.$options.template.charAt(0) !== #) || vm.$options.el || el會報出警告,

最後定義updateComponent函數,該函數完成的功能是渲染和更新。至於什麼時候調用這個函數,這個過程是使用觀察者模式,由wacher一起完成的。new watcher第一個參數當前vm實例,第三個函數是空函數,第四個參數是個配置,第五個參數是布爾值。在src/core/observer/watach.js中定義了Watcher類,在構造函數中,傳入的參數中有一個是否為渲染watcher,如果是,則將初始化實例的_watche,Watcher在這裡起到兩個作用,一個是初始化的時候會執行回調函數,另一個是當 vm 實例中的監測的數據發生變化的時候執行回調函數,函數最後判斷為根節點的時候設置vm._isMountedtrue, 表示這個實例已經掛載了,同時執行mounted鉤子函數。 這裡注意vm.$vnode表示 Vue 實例的父虛擬 Node,所以它為Null則表示當前是根 Vue 的實例。

將getter賦值給expOrFn,這裡傳給expOrFn的是updateComponent,隨後,調用get(),在get函數中,主要是收集一些依賴,然後在初始化或者有更新時,調用this.getter(對應著updateComponent函數),在$mount中,首先是根據template將其轉換為render(在沒有render的情況下),然後調用mountComponent函數,該函數主要調用的是updateComponent函數。updateComponent函數使用渲染watcher,在初次渲染或者值發生改變的時候調用該函數(這個過程由wacher完成),使用了觀察者模式。

看到這裡大家可能有一些迷糊,因為我們上述只是簡單把$mount的過程簡單介紹,並沒有對render函數的編譯過程做具體的介紹。那麼 render函數的編譯過程是怎麼樣的?

render函數的編譯的主要幾個步驟,這可以幫助我們從整體上把握它:

1 給編譯options添加web平台特性

2 將template字元串解析成ast

3 優化:將那些不會被改變的節點(statics)打上標記

4 生成render函數字元串,並用with包裹(最新版本有改為buble)

5 通過new Function的方式生成render函數並緩存

本章只是簡單介紹render函數過程,以後會有專門章節介紹render函數具體編譯步驟。

下節,我們會繼續介紹 render函數編譯的5個步驟具體源碼實現分析。敬請期待.....


推薦閱讀:
相关文章