一、什么是虛擬 DOM?
在前端開發中,“虛擬 DOM” 是一個高頻出現的術語,尤其在 React 生態中被廣泛討論。但很多開發者對它的理解往往停留在 “JS 對象” 這個表層認知上。
實際上,虛擬 DOM 是一種編程概念—— 在這個概念里,UI 以一種理想化的、“虛擬的” 表現形式被保存于內存中。它本質上是對真實 DOM 的一種描述,而不是具體的實現。
React 官方文檔對虛擬 DOM 的定義是:“一種編程概念,UI 以一種理想化的,或者說 ’ 虛擬的 ’ 表現形式被保存于內存中”。這意味著,只要能描述真實 DOM 的層次結構,無論采用何種形式(JSON、XML 等),都可以稱為虛擬 DOM。而 React 選擇了JS 對象作為這種思想的具體實現。
二、為什么需要虛擬 DOM?
在深入了解虛擬 DOM 的工作原理前,我們先思考一個問題:為什么需要虛擬 DOM?它解決了什么痛點?
2.1 性能優化:減少 DOM 操作成本
瀏覽器操作 DOM 的成本非常高,主要原因有兩點:
- DOM 對象包含大量屬性和方法(遠多于普通 JS 對象)
- DOM 操作會觸發瀏覽器的重排(Reflow)和重繪(Repaint)
相比之下,JS 層面的計算成本要低得多。通過先在 JS 層面對虛擬 DOM 進行計算和比對,再映射到真實 DOM,可以大幅減少不必要的 DOM 操作。
虛擬 DOM 的優勢在更新階段尤為明顯 —— 它避免了全量銷毀重建 DOM 的昂貴操作,只做必要的修改。
2.2 跨平臺渲染能力
虛擬 DOM 最大的價值之一是提供了平臺無關性。它將 UI 描述與具體渲染邏輯分離,使得同一套代碼可以在不同平臺渲染:
- 瀏覽器環境:通過 ReactDOM 渲染為 DOM
- 移動設備:通過 React Native 渲染為原生組件
- 服務端:通過 ReactDOMServer 渲染為字符串
- 測試環境:直接渲染為 JS 對象進行斷言
這種抽象能力完美契合了 “一次編寫,多端運行” 的現代開發需求。
三、React 中的虛擬 DOM 實現
在 React 中,虛擬 DOM 以 “React 元素” 的形式存在。讓我們從 JSX 開始,一步步揭開它的面紗。
3.1 JSX 與 createElement 的關系
我們編寫的 JSX 代碼:
<h1 className="title" id="header">Hello, Virtual DOM!<span>React</span>
</h1>
會被 Babel 編譯為 createElement
方法的調用:
React.createElement('h1',{ className: 'title', id: 'header' },'Hello, Virtual DOM!',React.createElement('span', null, 'React')
)
這個方法最終會返回一個描述 DOM 結構的 JS 對象 —— 這就是 React 中的虛擬 DOM:
{$$typeof: Symbol(react.element),type: 'h1',key: null,ref: null,props: {className: 'title',id: 'header',children: ['Hello, Virtual DOM!',{$$typeof: Symbol(react.element),type: 'span',// ... 其他屬性}]},_owner: null
}
3.2 createElement 核心邏輯解析
React 的 createElement
函數主要做了四件事:
- 處理屬性(過濾保留字、提取 key 和 ref)
- 處理子元素(支持多個子節點,轉為數組)
- 合并默認屬性(defaultProps)
- 創建并返回 React 元素對象
簡化版實現如下:
function createElement(type, config, children) {const props = {};let key = null;let ref = null;// 處理配置屬性if (config) {key = config.key || null;ref = config.ref || null;// 復制非保留字屬性到 propsfor (const prop in config) {if (config.hasOwnProperty(prop) && !RESERVED_PROPS.hasOwnProperty(prop)) {props[prop] = config[prop];}}}// 處理子元素const childrenLength = arguments.length - 2;if (childrenLength === 1) {props.children = children;} else if (childrenLength > 1) {props.children = Array.from(arguments).slice(2);}// 合并默認屬性if (type && type.defaultProps) {for (const prop in type.defaultProps) {if (props[prop] === undefined) {props[prop] = type.defaultProps[prop];}}}return {$$typeof: REACT_ELEMENT_TYPE,type,key,ref,props,_owner: null};
}
這個對象包含了渲染真實 DOM 所需的全部信息:元素類型(type)、屬性(props)、子元素(children)等。
四、虛擬 DOM 的工作流程
虛擬 DOM 不是孤立存在的,它是 React 渲染流程的核心環節。完整流程包括:
4.1 初始渲染
- 從 JSX 生成虛擬 DOM
- 將虛擬 DOM 轉換為真實 DOM 并插入頁面
4.2 狀態更新
- 狀態變化觸發重新渲染,生成新的虛擬 DOM
- 通過 Diff 算法對比新舊虛擬 DOM,找出差異
- 只將差異部分應用到真實 DOM(Patch 過程)
4.3 Diff 算法:高效比對的核心
React 的 Diff 算法是虛擬 DOM 實現高性能的關鍵,它基于三個假設:
- 不同類型的元素會產生不同的樹
- 可以通過 key 屬性標識同一層級的子元素,保持穩定性
- 只進行同層級比對(時間復雜度 O (n))
這種策略大幅簡化了比對過程,同時保證了大多數場景下的高效性。
五、虛擬 DOM 的局限性
盡管虛擬 DOM 帶來了諸多好處,但也存在一定問題:
- 初始渲染開銷:首次渲染時,虛擬 DOM 因為多了一層 JS 計算,可能比直接操作 DOM 稍慢
- 過度優化:對于簡單場景,手動優化的 DOM 操作可能比虛擬 DOM 更高效
- 內存占用:大量虛擬 DOM 對象可能占用較多內存
React 團隊也在不斷優化這些問題,例如通過 Fiber 架構實現增量渲染,進一步提升性能。
六、總結
虛擬 DOM 是 React 架構的基石,它通過 “描述式編程” 的思想,讓開發者專注于 UI 應該是什么樣子,而不是如何操作 DOM。
核心要點:
- 虛擬 DOM 是一種思想,JS 對象是 React 中的實現方式
- 主要優勢是減少 DOM 操作和提供跨平臺能力
- 工作流程包括:創建虛擬 DOM → 比對差異 → 應用差異
- React 通過
createElement
方法將 JSX 轉換為虛擬 DOM 對象