回憶
????????這里我們將對(duì)render函數(shù)把template轉(zhuǎn)化成vnode的過程進(jìn)行介紹。
????????Vue.prototype._render方法中調(diào)用vm.$options.render.call(vm_renderProxy, vm.$creatElement),$options.render本身是個(gè)函數(shù),以creatElement方法為參數(shù)。傳入的參數(shù)vm.$creatElement是個(gè)function createElement(vm,a,x,c,d,true)函數(shù)。createElement最終會(huì)調(diào)用??_createElement。
????????轉(zhuǎn)化的最終方法為_createElement(context(VNode 的上下文環(huán)境,它是?Component?類型),tag(標(biāo)簽,它可以是一個(gè)字符串,也可以是一個(gè)?Component),data(VNode 的數(shù)據(jù),?VNodeData?類型),children(當(dāng)前 VNode 的子節(jié)點(diǎn),它是任意類型的,它接下來需要被規(guī)范為標(biāo)準(zhǔn)的 VNode 數(shù)組),normalizationType(子節(jié)點(diǎn)規(guī)范的類型,類型不同規(guī)范的方法也就不一樣,它主要是參考?render?函數(shù)是編譯生成的還是用戶手寫的))。
????????主要邏輯分為兩個(gè)部分?children?的規(guī)范化以及 VNode 的創(chuàng)建。
? ?????1、?children?的規(guī)范化,會(huì)調(diào)用simpleNormalizeChildren或者normalizeChildren把children由樹狀結(jié)構(gòu)打平成一維數(shù)組。2、通過vnode=new VNode()創(chuàng)建vnode。
render

????????Vue 的?_render?方法是實(shí)例的一個(gè)私有方法,它用來把實(shí)例渲染成一個(gè)虛擬 Node。它的定義在src/core/instance/render.js文件中。

initRender函數(shù)主要部分($creatElement)

? ??????最關(guān)鍵的是?render?方法的調(diào)用,我們?cè)谄綍r(shí)的開發(fā)工作中手寫?render?方法的場(chǎng)景比較少,而寫的比較多的是?template?模板,在之前的?mounted?方法的實(shí)現(xiàn)中,會(huì)把?template?編譯成?render?方法,但這個(gè)編譯過程是非常復(fù)雜的,我們不打算在這里展開講,之后會(huì)專門花一個(gè)章節(jié)來分析 Vue 的編譯過程。
????????可以看到,render?函數(shù)中的?createElement?方法就是?vm.$createElement?方法。
????????實(shí)際上,vm.$createElement?方法定義是在執(zhí)行?initRender?方法的時(shí)候,可以看到除了?vm.$createElement方法,還有一個(gè)?vm._c?方法,它是被模板編譯成的?render?函數(shù)使用,而vm.$createElement?是用戶手寫?render?方法使用的, 這倆個(gè)方法支持的參數(shù)相同,并且內(nèi)部都調(diào)用了?createElement?方法。
render渲染實(shí)際步驟
? ???????在 Vue 的官方文檔中介紹了?render?函數(shù)的第一個(gè)參數(shù)是?createElement,那么結(jié)合之前的例子。

相當(dāng)于我們編寫如下?render?函數(shù):

再回到?_render?函數(shù)中的?render?方法的調(diào)用:? ? ? ??

繼續(xù)看看vm._renderProxy,定義在instance/init.js中

我們繼續(xù)往下找,instance/proxy中

????????看完render渲染函數(shù) vnode = render.call(vm._renderProxy, vm.$createElement),我們接著往下看。

????????vm._render?最終是通過執(zhí)行?createElement?方法并返回的是?vnode,它是一個(gè)虛擬 Node。Vue 2.0 相比 Vue 1.0 最大的升級(jí)就是利用了 Virtual DOM。因此在分析?createElement?的實(shí)現(xiàn)前,我們先了解一下 Virtual DOM 的概念。
Virtual DOM
????????Virtual DOM 這個(gè)概念相信大部分人都不會(huì)陌生,它產(chǎn)生的前提是瀏覽器中的 DOM 是很“昂貴"的,為了更直觀的感受,可以簡(jiǎn)單的把一個(gè)簡(jiǎn)單的 div 元素的屬性都打印出來,很多屬性非常龐大,因?yàn)闉g覽器的標(biāo)準(zhǔn)就把 DOM 設(shè)計(jì)的非常復(fù)雜。當(dāng)我們頻繁的去做 DOM 更新,會(huì)產(chǎn)生一定的性能問題。
????????而 Virtual DOM 就是用一個(gè)原生的 JS 對(duì)象去描述一個(gè) DOM 節(jié)點(diǎn),所以它比創(chuàng)建一個(gè) DOM 的代價(jià)要小很多。在 Vue.js 中,Virtual DOM 是用?VNode?這么一個(gè) Class 去描述,它是定義在src/core/vdom/vnode.js中的。
????????Vue.js 中的 Virtual DOM 的定義還是略微復(fù)雜一些的,因?yàn)樗@里包含了很多 Vue.js 的特性。這里千萬不要被這些茫茫多的屬性嚇到,實(shí)際上 Vue.js 中 Virtual DOM 是借鑒了一個(gè)開源庫(kù)snabbdom的實(shí)現(xiàn),然后加入了一些 Vue.js 特色的東西。我建議大家如果想深入了解 Vue.js 的 Virtual DOM 前不妨先閱讀這個(gè)庫(kù)的源碼,因?yàn)樗雍?jiǎn)單和純粹。
? ??????其實(shí) VNode 是對(duì)真實(shí) DOM 的一種抽象描述,它的核心定義無非就幾個(gè)關(guān)鍵屬性,標(biāo)簽名、數(shù)據(jù)、子節(jié)點(diǎn)、鍵值等,其它屬性都是都是用來擴(kuò)展 VNode 的靈活性以及實(shí)現(xiàn)一些特殊 feature 的。由于 VNode 只是用來映射到真實(shí) DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常輕量和簡(jiǎn)單的。
????????Virtual DOM 除了它的數(shù)據(jù)結(jié)構(gòu)的定義,映射到真實(shí)的 DOM 實(shí)際上要經(jīng)歷 VNode 的 create、diff、patch 等過程。那么在 Vue.js 中,VNode 的 create?是通過之前提到的?createElement?方法創(chuàng)建的,我們接下來分析這部分的實(shí)現(xiàn)。
createElement

????????接下來分析這個(gè)creatElement, Vue.js 利用 createElement 方法創(chuàng)建 VNode,它定義在src/core/vdom/create-elemenet.js中。

????????_createElement?方法有 5 個(gè)參數(shù),context?表示 VNode 的上下文環(huán)境,它是?Component?類型;tag?表示標(biāo)簽,它可以是一個(gè)字符串,也可以是一個(gè)?Component;data?表示 VNode 的數(shù)據(jù),它是一個(gè)?VNodeData?類型,可以在?flow/vnode.js?中找到它的定義,這里先不展開說;children表示當(dāng)前 VNode 的子節(jié)點(diǎn),它是任意類型的,它接下來需要被規(guī)范為標(biāo)準(zhǔn)的 VNode 數(shù)組;normalizationType表示子節(jié)點(diǎn)規(guī)范的類型,類型不同規(guī)范的方法也就不一樣,它主要是參考?render?函數(shù)是編譯生成的還是用戶手寫的。
????????createElement?函數(shù)的流程略微有點(diǎn)多,我們接下來主要分析 2 個(gè)重點(diǎn)的流程 ——?children?的規(guī)范化以及 VNode 的創(chuàng)建。



children 的規(guī)范化
????????由于 Virtual DOM 實(shí)際上是一個(gè)樹狀結(jié)構(gòu),每一個(gè) VNode 可能會(huì)有若干個(gè)子節(jié)點(diǎn),這些子節(jié)點(diǎn)應(yīng)該也是 VNode 的類型。_createElement?接收的第 4 個(gè)參數(shù) children 是任意類型的,因此我們需要把它們規(guī)范成 VNode 類型。
????????這里根據(jù)?normalizationType?的不同,調(diào)用了?normalizeChildren(children)?和?simpleNormalizeChildren(children)?方法,它們的定義都在src/core/vdom/helpers/normalzie-children.js?中
? ??????simpleNormalizeChildren?方法調(diào)用場(chǎng)景是?render?函數(shù)當(dāng)函數(shù)是編譯生成的。理論上編譯生成的?children?都已經(jīng)是 VNode 類型的,但這里有一個(gè)例外,就是?functional component?函數(shù)式組件返回的是一個(gè)數(shù)組而不是一個(gè)根節(jié)點(diǎn),所以會(huì)通過?Array.prototype.concat?方法把整個(gè)?children?數(shù)組打平,讓它的深度只有一層。
? ???????normalizeChildren方法的調(diào)用場(chǎng)景有 2 種,一個(gè)場(chǎng)景是?render?函數(shù)是用戶手寫的,當(dāng)?children?只有一個(gè)節(jié)點(diǎn)的時(shí)候,Vue.js 從接口層面允許用戶把?children?寫成基礎(chǔ)類型用來創(chuàng)建單個(gè)簡(jiǎn)單的文本節(jié)點(diǎn),這種情況會(huì)調(diào)用?createTextVNode?創(chuàng)建一個(gè)文本節(jié)點(diǎn)的 VNode;另一個(gè)場(chǎng)景是當(dāng)編譯?slot、v-for?的時(shí)候會(huì)產(chǎn)生嵌套數(shù)組的情況,會(huì)調(diào)用?normalizeArrayChildren?方法,接下來看一下它的實(shí)現(xiàn)。
? ??????normalizeArrayChildren接收 2 個(gè)參數(shù),children?表示要規(guī)范的子節(jié)點(diǎn),nestedIndex?表示嵌套的索引,因?yàn)閱蝹€(gè)?child?可能是一個(gè)數(shù)組類型。?normalizeArrayChildren?主要的邏輯就是遍歷?children,獲得單個(gè)節(jié)點(diǎn)?c,然后對(duì)?c?的類型判斷,如果是一個(gè)數(shù)組類型,則遞歸調(diào)用?normalizeArrayChildren; 如果是基礎(chǔ)類型,則通過?createTextVNode?方法轉(zhuǎn)換成 VNode 類型;否則就已經(jīng)是 VNode 類型了,如果?children?是一個(gè)列表并且列表還存在嵌套的情況,則根據(jù)?nestedIndex?去更新它的 key。這里需要注意一點(diǎn),在遍歷的過程中,對(duì)這 3 種情況都做了如下處理:如果存在兩個(gè)連續(xù)的?text?節(jié)點(diǎn),會(huì)把它們合并成一個(gè)?text?節(jié)點(diǎn)。
? ??????經(jīng)過對(duì)?children?的規(guī)范化,children?變成了一個(gè)類型為 VNode 的 Array。

VNode 的創(chuàng)建
????????回到?createElement?函數(shù),規(guī)范化?children?后,接下來會(huì)去創(chuàng)建一個(gè) VNode 的實(shí)例。
????????這里先對(duì)?tag?做判斷,如果是?string?類型,則接著判斷如果是內(nèi)置的一些節(jié)點(diǎn),則直接創(chuàng)建一個(gè)普通 VNode,如果是為已注冊(cè)的組件名,則通過?createComponent?創(chuàng)建一個(gè)組件類型的 VNode,否則創(chuàng)建一個(gè)未知的標(biāo)簽的 VNode。 如果是?tag?一個(gè)?Component?類型,則直接調(diào)用?createComponent?創(chuàng)建一個(gè)組件類型的 VNode 節(jié)點(diǎn)。對(duì)于?createComponent?創(chuàng)建組件類型的 VNode 的過程,我們之后會(huì)去介紹,本質(zhì)上它還是返回了一個(gè) VNode。
? ??????那么至此,我們大致了解了?createElement?創(chuàng)建 VNode 的過程,每個(gè) VNode 有?children,children?每個(gè)元素也是一個(gè) VNode,這樣就形成了一個(gè) VNode Tree,它很好的描述了我們的 DOM Tree。
????????回到?mountComponent?函數(shù)的過程,我們已經(jīng)知道?vm._render?是如何創(chuàng)建了一個(gè) VNode,接下來就是要把這個(gè) VNode 渲染成一個(gè)真實(shí)的 DOM 并渲染出來,這個(gè)過程是通過?vm._update?完成的,接下來分析一下這個(gè)過程。