前言
大家知道乙個複雜的頁面會包含大量的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不存在tag
和children
。
// 建立乙個文字型別的vnode
function createtextvnode (val)
方法_s
同樣也是vue的例項方法,內部用來將接收的引數變成字串返回,對於字串和數值使用object.tostring()
轉換,如果接收到的是乙個物件,則使用json.stringify()
轉換。
function tostring (val)
vnode 通過parent
和children
連線父節點和子節點,組成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元素屬性的建立和更新。這些模組中基本都實現了create
、update
這兩個處理函式。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選...