vue渲染過程解析 VDOM DOM

2021-10-10 05:30:09 字數 4163 閱讀 1098

前言

大家知道乙個複雜的頁面會包含大量的dom節點,為了高效地更新這些dom節點,vue設計了虛擬dom的概念。虛擬dom是對真實dom節點資訊的描述。在vue中,每乙個dom節點都會有乙個虛擬dom節點與之對應。這個虛擬dom節點,我們也稱之為vnode,而由vnode所組成的整個vnode樹就是虛擬dom。

經過編譯後我們可以得到如下render函式:

function render() 

}

函式_c是在初始化render環境的時候新增到vue例項上,用來建立vnode的全域性例項方法。它可以通vue例項直接呼叫,主要是給vue內部使用的vnode建立方法。其底層實現上與vue對外向使用者暴露的api$createelement一樣都是呼叫了內部的_createelement方法。_createelement核心**如下:

function _createelement(context, tag, data, children, normalizationtype)
乙個vnode節點主要包含如下資訊:

方法_v也是vue例項方法,內部用以建立文字型別的vnode,在本例中,}是乙個文字節點,所以需要使用_v來建立文字vnode。不過無論是文字型別的vnode還是非文字型別的vnode都是vnode物件的例項。兩者的區別在於,文字型別的vnode不存在tagchildren

// 建立乙個文字型別的vnode

function createtextvnode (val)

方法_s同樣也是vue的例項方法,內部用來將接收的引數變成字串返回,對於字串和數值使用object.tostring()轉換,如果接收到的是乙個物件,則使用json.stringify()轉換。

function tostring (val)
vnode 通過parentchildren連線父節點和子節點,組成vnode樹。

有了vnode後,vue還需要根據vnode來建立dom節點。如果是首次渲染,那麼vue會走建立的邏輯。如果是資料的更新導致的重新渲染,那麼vue會走更新的邏輯。

因為是首次渲染,所以不存在先前老的vnode,因此無需進行比較。vue直接呼叫createelm方法建立dom元素。具體的建立步驟如下:

首先為vnode建立dom元素。

如果vnode有子節點,逐個為其子節點建立dom元素,並將子dom元素插入到vnode的dom元素上。

呼叫setattribute為vnode的dom元素新增屬性。

將vnode的dom元素插入到其父元素上。

createelm方法的主要**如下:

function createelm (vnode, insertedvnodequeue, parentelm, refelm, nested, ownerarray, index) 

const data = vnode.data

const children = vnode.children

const tag = vnode.tag

// 普通元素

if (isdef(tag))

// 4. 將vnode的dom節點插入到父元素上(根節點的父元素是body)

insert(parentelm, vnode.elm, refelm)

} else if (istrue(vnode.iscomment)) else

// ...省略其他**

}

我們都知道,data資料物件中儲存著與dom元素有關的屬性,如id,class,style,event,ref等。vue有專門的模組負責給dom元素新增、更新或刪除這些屬性。因此,我們在**中可以看到,如果vnode節點存在data資料物件時,vue會呼叫invokecreatehooks分別使用相應的處理模組來處理data中的屬性。

如果不是首次渲染,而是由資料變化所觸發的重新渲染,那麼vue會最大限度地復用已建立的dom元素。而復用的前提就是通過比較新老vnode,找出需要更新的內容,然後最小限度地進行替換。這也是vue設計vnode的核心用途。

function patchvnode (

oldvnode,

vnode,

insertedvnodequeue,

ownerarray,

index,

removeonly

) const elm = vnode.elm = oldvnode.elm

let i

const data = vnode.data

const oldch = oldvnode.children

const ch = vnode.children

// 1. 更新dom元素的屬性

if (isdef(data) && ispatchable(vnode))

// 2. 更新子元素

// 如果vnode不是文字節點,則更新子元素。

if (isundef(vnode.text)) else if (isdef(ch)) else if (isdef(oldch)) else if (isdef(oldvnode.text))

} else if (oldvnode.text !== vnode.text)

// ...省略其他**

}

從原始碼中可以看到,當新老vnode完全相等的情況下,vue不會對該節點重新渲染,直接跳過了。

如果新vnode發生了變化,那麼vue會遵循以下步驟更新dom元素:

更新dom元素的屬性。這個在首次渲染那部分提到了一些。vue內實現了若干個屬性處理模組,專門用於dom元素屬性的建立和更新。這些模組中基本都實現了createupdate這兩個處理函式。create負責dom元素屬性的建立,update負責dom元素屬性的更新。cbs.update[i](oldvnode, vnode)的意思就是逐個呼叫這些模組上的update方法,以更新發生改變的dom元素屬性。

更新dom元素的子元素。關於dom子元素的更新分為幾種情況

如果新老vnode的子節點都是文字節點且文字內容不同,處理方式更新dom元素的textcontent屬性值。

如果新老vnode的子節點都是非文字節點,需要呼叫updatechildren遞迴地去更新子節點。

如果新vnode的子節點是非文字節點,而老vnode的子節點是文字節點,需要清除dom元素的文字,並建立子vnode的dom元素插入到其父節點的dom元素上。

如果新vnode的子節點不存在,但老vnode的子節點存在,那麼呼叫removevnode刪除老vnode的子節點對應的dom元素。

如果老vnode的子節點是文字節點,而新vnode的子節點不存在,則清空老dom元素的文字。

大量的dom操作會極損耗瀏覽器效能。vue在每次資料發生變化後,都會重新生成vnode節點。通過比較新老vnode節點,找出需要進行操作的最小dom元素子集。根據變化點,進行dom元素屬性、dom子節點的更新。這種設計方式大大減少了dom操作的次數。

推薦閱讀

好文我在看????

vue渲染過程

把模板編譯為render函式 例項進行掛載,根據根節點render函式的呼叫,遞迴的生成虛擬dom 對比虛擬dom,渲染到真實dom 元件內部data發生變化,元件和子元件引用data作為props重新呼叫render函式,生成虛擬dom,返回到步驟3 第一步 模板變成render函式 hello ...

二 Vue 頁面渲染過程

上篇博文我們依葫蘆畫瓢已經將hello world 展現在介面上啦,但是是不是感覺新虛虛的,總覺得這麼多檔案,專案怎麼就啟動起來了呢?怎麼訪問到8080 埠就能進入到我們的首頁呢。整個的流程是怎麼樣的呢?我也是剛剛接觸,所以就會有這樣的困惑,所以這篇就簡單的理解一下專案頁面渲染的過程。我們上篇文章說...

Vue 的首次渲染的過程

vue版本 2.6.10 首先進行vue的初始化,初始化vue的例項成員和靜態成員 呼叫建構函式中的 init 方法,作為整個vue的入口 在 init 中呼叫 mount 方法 呼叫mountcomponent src core instance lifecycle.js中定義 判斷render選...