Vue3中虛擬dom轉(zhuǎn)成真實(shí)dom的過(guò)程詳解
前言
Vue.js 在其運(yùn)行過(guò)程中會(huì)將模板編譯成虛擬 DOM (VNode),然后再將 VNode 渲染成實(shí)際的 DOM 節(jié)點(diǎn)。這個(gè)過(guò)程是由 Vue 內(nèi)部的編譯器和渲染系統(tǒng)完成的.
雖然Vue 3 的虛擬 DOM 編譯過(guò)程對(duì)于開(kāi)發(fā)者來(lái)說(shuō)通常是透明的,但了解這些內(nèi)部機(jī)制有助于更好地理解和優(yōu)化應(yīng)用程序。
如果你對(duì) Vue 3 的內(nèi)部實(shí)現(xiàn)感興趣,可以查閱 Vue 3 的官方文檔或閱讀 Vue 3 的源碼來(lái)深入了解這一過(guò)程。
Vue 3 中虛擬 DOM 的編譯過(guò)程
1. 模板編譯
在 Vue 3 中,模板編譯主要由兩個(gè)階段組成:解析和優(yōu)化。
- 解析階段:Vue 3 的編譯器會(huì)將模板字符串解析成一個(gè)抽象語(yǔ)法樹(shù) (Abstract Syntax Tree, AST),這個(gè)樹(shù)結(jié)構(gòu)表示了模板的結(jié)構(gòu)和內(nèi)容。編譯器會(huì)識(shí)別出模板中的各種指令(如
v-if
,v-for
,v-bind
等)并將它們轉(zhuǎn)換成對(duì)應(yīng)的 AST 節(jié)點(diǎn)。 - 優(yōu)化階段:編譯器會(huì)對(duì) AST 進(jìn)行優(yōu)化,以減少不必要的計(jì)算和 DOM 操作。例如,它可以提前計(jì)算靜態(tài)節(jié)點(diǎn),并將其標(biāo)記為靜態(tài)的,這樣在渲染時(shí)就不需要重新生成這些節(jié)點(diǎn)。
2. 生成渲染函數(shù)
一旦 AST 被創(chuàng)建并優(yōu)化后,編譯器會(huì)生成一個(gè)渲染函數(shù),這個(gè)函數(shù)可以用來(lái)創(chuàng)建虛擬 DOM 節(jié)點(diǎn)(VNode)。渲染函數(shù)通常會(huì)利用 Vue 內(nèi)置的 h
函數(shù)(createVNode
的別名)來(lái)創(chuàng)建 VNode。
3. 創(chuàng)建虛擬 DOM (VNode)
在 Vue 3 中,h
函數(shù)被用來(lái)創(chuàng)建 VNode。一個(gè) VNode 是一個(gè) JavaScript 對(duì)象,它包含了關(guān)于 DOM 節(jié)點(diǎn)的信息,如標(biāo)簽名、屬性、子節(jié)點(diǎn)等。例如:
const vnode = h( 'div', // 標(biāo)簽名 { id: 'app' }, // 屬性對(duì)象 'Hello Vue 3!' // 子節(jié)點(diǎn) );
4. 渲染到真實(shí) DOM
當(dāng) VNode 被創(chuàng)建后,Vue 會(huì)使用高效的算法來(lái)比較新舊 VNode,并更新真實(shí)的 DOM。這個(gè)過(guò)程稱為 patching。Vue 3 的 diff 算法旨在最小化 DOM 操作,從而提高性能。
今天來(lái)簡(jiǎn)單介紹一下如何將一份虛擬dom轉(zhuǎn)成真實(shí)dom。
vdomToDom
虛擬dom結(jié)構(gòu)已有,掛載到root節(jié)點(diǎn)上,請(qǐng)問(wèn)如何實(shí)現(xiàn)render函數(shù)?
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="root"></div> <script> const vnode = { tag: 'div', attrs: { id: 'app', class:'box' }, children: [ { tag: 'span', children: [{ tag: 'a', children: [], }], }, { tag: 'span', children: [{ tag: 'a', children: [], }] } ] } render(vnode,document.getElementById('root')) function render(vnode, container) { } </script> </body> </html>
我們先來(lái)看一眼這份虛擬dom長(zhǎng)什么樣。
首先最外層有個(gè)id為app類名為box的div,里面有兩個(gè)子節(jié)點(diǎn)span,第一個(gè)子節(jié)點(diǎn)中又有一個(gè)a,第二個(gè)子節(jié)點(diǎn)中也有一個(gè)a
那么vue中編譯dom
的原理是什么,我們來(lái)一份簡(jiǎn)易版看看。
首先我們就想有一個(gè)方法只要給一個(gè)虛擬dom就能生成dom,然后將其掛載到root上去,接下來(lái)就是如何實(shí)現(xiàn)createDom
function render(vnode, container) { const newDom = createDom(vnode) container.appendChild(newDom) }
function createDom(vnode) { const { tag, attrs, children } = vnode const dom = document.createElement(tag) if (typeof attrs === 'object' && attrs !== null) { updateProps(dom, {}, attrs) // 為dom添加屬性 } if (children.length > 0) { reconcileChildren(children, dom) // 為dom添加子容器 } return dom }
然后思考,如何為子容器添加屬性以及如何為容器添加子容器?
function updateProps(dom, oldProps = {}, newProps = {}) { for (const key in newProps) { if (key === 'style') { let styleObj = newProps[key] for (let attr in styleObj) { dom.style[attr] = styleObj[attr] } } else { // id / class dom[key] = newProps[key] } } } function reconcileChildren(children, dom) { for (let child of children) { render(child, dom) } }
完整代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="root"></div> <script> const vnode = { tag: 'div', attrs: { id: 'app', className: 'box' }, children: [ { tag: 'span', children: [{ tag: 'a', children: [], }], }, { tag: 'span', children: [{ tag: 'a', children: [], }] } ] } render(vnode, document.getElementById('root')) function render(vnode, container) { const newDom = createDom(vnode) container.appendChild(newDom) } function createDom(vnode) { const { tag, attrs, children } = vnode const dom = document.createElement(tag) if (typeof attrs === 'object' && attrs !== null) { updateProps(dom, {}, attrs) // 為dom添加屬性 } if (children.length > 0) { reconcileChildren(children, dom) // 為dom添加子容器 } return dom } function updateProps(dom, oldProps = {}, newProps = {}) { for (const key in newProps) { if (key === 'style') { let styleObj = newProps[key] for (let attr in styleObj) { dom.style[attr] = styleObj[attr] } } else { // id / class dom[key] = newProps[key] } } } function reconcileChildren(children, dom) { for (let child of children) { render(child, dom) } } </script> </body> </html>
效果
以上就是Vue3中虛擬dom轉(zhuǎn)成真實(shí)dom的過(guò)程詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue3 虛擬dom轉(zhuǎn)成真實(shí)dom的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue中通過(guò)屬性綁定為元素綁定style行內(nèi)樣式的實(shí)例代碼
這篇文章主要介紹了Vue中通過(guò)屬性綁定為元素綁定style行內(nèi)樣式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04基于vue實(shí)現(xiàn)swipe分頁(yè)組件實(shí)例
本篇文章主要介紹了基于vue實(shí)現(xiàn)swipe分頁(yè)組件實(shí)例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05vue+springmvc導(dǎo)出excel數(shù)據(jù)的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue+springmvc導(dǎo)出excel數(shù)據(jù)的實(shí)現(xiàn)代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06vue中table表頭單元格合并(附單行、多級(jí)表頭代碼)
本文主要介紹了vue中table表頭單元格合并(附單行、多級(jí)表頭代碼),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04Vue3環(huán)境安裝以及項(xiàng)目搭建全過(guò)程
Vue工程化項(xiàng)目環(huán)境配置還是比較麻煩的,下面這篇文章主要給大家介紹了關(guān)于Vue3環(huán)境安裝以及項(xiàng)目搭建的相關(guān)資料,文中通過(guò)圖文以及代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12如何用vue3+Element?plus實(shí)現(xiàn)一個(gè)完整登錄功能
要實(shí)現(xiàn)用戶的登錄功能,可以使用Vue3和Element?Plus,下面這篇文章主要給大家介紹了關(guān)于如何基于Vue3和Element?Plus組件庫(kù)實(shí)現(xiàn)一個(gè)完整的登錄功能,文中提供了詳細(xì)的代碼示例,需要的朋友可以參考下2023-10-10Vue.js的兄弟組件傳值實(shí)現(xiàn)組件間互動(dòng)
在Vue.js中,組件是構(gòu)建用戶界面的基本單位,而兄弟組件傳值是組件間交互的重要組成部分,本文將探討兄弟組件傳值的方法和優(yōu)勢(shì),并通過(guò)有趣的示例展示其強(qiáng)大的功能,需要的朋友可以參考下2025-03-03vue 組件開(kāi)發(fā)原理與實(shí)現(xiàn)方法詳解
這篇文章主要介紹了vue 組件開(kāi)發(fā)原理與實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了vue.js組件開(kāi)發(fā)的原理與實(shí)現(xiàn)方法,需要的朋友可以參考下2019-11-11