目錄
一、虛擬 DOM 的核心概念
二、虛擬 DOM 到真實 DOM 的流程
三、手寫虛擬 DOM 到真實 DOM 的實現
1. 定義虛擬 DOM 的結構(VNode)?
?2. 創建虛擬 DOM 轉真實 DOM 的函數
?3. 掛載虛擬 DOM 到頁面
?4. 更新虛擬 DOM 的過程(Diff 算法簡化版)
四、完整示例:虛擬 DOM 到真實 DOM 的生命周期
五、總結
一、虛擬 DOM 的核心概念
虛擬 DOM 是用 JavaScript 對象(VNode)模擬真實 DOM 結構的輕量級抽象。它的核心作用是:
- 減少直接操作真實 DOM 的次數:通過對比新舊虛擬 DOM 樹的差異(Diff 算法),僅更新變化的部分。
- 聲明式編程:開發者只需關注數據邏輯,無需手動操作 DOM。
- 跨平臺能力:虛擬 DOM 可用于 Web、移動端(如 Weex)或服務端渲染(SSR)
?
二、虛擬 DOM 到真實 DOM 的流程
Vue 的渲染流程可分為以下步驟:
- 模板編譯:將模板(
<template>
)編譯為渲染函數(render function
)。 - 生成虛擬 DOM 樹:通過?
render
?函數返回一個虛擬 DOM 樹(由 VNode 節點組成)。 - 首次掛載:將虛擬 DOM 樹轉化為真實 DOM 并渲染到頁面。
- 數據更新:生成新的虛擬 DOM 樹,與舊樹進行 Diff 算法比較。
- 局部更新:將差異部分應用到真實 DOM 上(Patch 過程)。
三、手寫虛擬 DOM 到真實 DOM 的實現
以下是一個簡化版的實現示例,涵蓋虛擬 DOM 的創建、掛載和更新過程。
1. 定義虛擬 DOM 的結構(VNode)?
// VNode 結構
const vnode = {tag: 'div', // 標簽名props: { id: 'app' }, // 屬性children: [ // 子節點{tag: 'p',props: { class: 'text' },children: ['Hello, Vue!']}]
};
?2. 創建虛擬 DOM 轉真實 DOM 的函數
// 將虛擬 DOM 轉換為真實 DOM
function createDom(vnode) {const { tag, props, children } = vnode;const dom = document.createElement(tag); // 創建真實 DOM 元素// 設置屬性if (props && typeof props === 'object') {updateProps(dom, props);}// 處理子節點if (Array.isArray(children)) {reconcileChildren(children, dom);}return dom;
}// 設置屬性
function updateProps(dom, props) {for (const key in props) {if (key === 'style') {// 處理 style 屬性for (const styleKey in props.style) {dom.style[styleKey] = props.style[styleKey];}} else {// 處理其他屬性(id、class 等)dom[key] = props[key];}}
}// 遞歸處理子節點
function reconcileChildren(children, dom) {for (const child of children) {const childDom = createDom(child); // 遞歸創建子節點dom.appendChild(childDom);}
}
?
?3. 掛載虛擬 DOM 到頁面
// 將虛擬 DOM 掛載到容器
function render(vnode, container) {const dom = createDom(vnode); // 生成真實 DOMcontainer.appendChild(dom); // 掛載到頁面
}// 示例:將虛擬 DOM 掛載到 #root 容器
const root = document.getElementById('root');
render(vnode, root);
?4. 更新虛擬 DOM 的過程(Diff 算法簡化版)
// 比較新舊虛擬 DOM 樹并更新真實 DOM
function patch(oldVnode, newVnode, parentDom) {// 如果節點類型不同,直接替換if (oldVnode.tag !== newVnode.tag) {parentDom.replaceChild(createDom(newVnode), oldVnode.elm);return;}// 獲取真實 DOMconst elm = (newVnode.elm = oldVnode.elm);// 更新屬性updateProps(elm, oldVnode.props, newVnode.props);// 遞歸比較子節點patchChildren(oldVnode.children, newVnode.children, elm);
}// 更新屬性
function updateProps(dom, oldProps, newProps) {// 移除舊屬性for (const key in oldProps) {if (!newProps[key]) {dom[key] = null;}}// 更新或新增屬性for (const key in newProps) {if (key === 'style') {for (const styleKey in newProps.style) {dom.style[styleKey] = newProps.style[styleKey];}} else {dom[key] = newProps[key];}}
}// 遞歸比較子節點
function patchChildren(oldChildren, newChildren, parentDom) {// 簡化版:逐個比較子節點const maxLength = Math.max(oldChildren.length, newChildren.length);for (let i = 0; i < maxLength; i++) {const oldChild = oldChildren[i];const newChild = newChildren[i];if (oldChild && newChild) {patch(oldChild, newChild, parentDom);} else if (newChild) {// 新增節點parentDom.appendChild(createDom(newChild));} else if (oldChild) {// 刪除節點parentDom.removeChild(oldChild.elm);}}
}
四、完整示例:虛擬 DOM 到真實 DOM 的生命周期
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>手寫 Vue 虛擬 DOM</title>
</head>
<body><div id="root"></div><script>// 1. 初始虛擬 DOMconst initialVnode = {tag: 'div',props: { id: 'app' },children: [{tag: 'p',props: { class: 'text' },children: ['Hello, Vue!']}]};// 2. 掛載初始虛擬 DOMconst root = document.getElementById('root');const initialDom = createDom(initialVnode);root.appendChild(initialDom);// 3. 模擬數據更新后的新虛擬 DOMconst newVnode = {tag: 'div',props: { id: 'app' },children: [{tag: 'p',props: { class: 'text updated' },children: ['Hello, Vue! Updated!']}]};// 4. 更新虛擬 DOM 到真實 DOMpatch(initialVnode, newVnode, root);</script>
</body>
</html>
五、總結
通過上述實現,我們可以看到 Vue 虛擬 DOM 的核心原理:
- 虛擬 DOM 是 JavaScript 對象:通過?
createDom
?函數將虛擬 DOM 轉化為真實 DOM。 - Diff 算法是性能優化的關鍵:通過比較新舊虛擬 DOM 樹的差異,僅更新變化的部分。
- 局部更新減少重排/重繪成本:避免了直接操作真實 DOM 的高昂代價。
在實際開發中,Vue 的虛擬 DOM 實現更為復雜(如處理組件、事件綁定等),但核心思想一致。掌握虛擬 DOM 的原理,不僅能幫助我們更好地理解 Vue 的運行機制,還能在性能優化和跨平臺開發中游刃有余。
?