導讀:
- 當用原生 JS / jQuery 操作 DOM 時,瀏覽器會從構建 DOM 樹開始從頭到尾渲染一遍 DOM 節點,而大量的 DOM 操作會頻繁的更新 DOM( 即再次從頭到尾渲染 ),這無疑是非常消耗瀏覽器性能的,因此由 FaceBook 團隊提出的虛擬 DOM 橫空出世,并運用在了 React 之上( 目前 Vue 2.x 也引入了這個概念 ),接下來我們就分析一下虛擬 DOM 的原理。
虛擬 DOM 原理:
-
無虛擬 DOM 時,React 中 DOM 的渲染流程:
- state 數據
- JSX 模板
- 數據 + 模板 結合,生成真實的 DOM,來顯示
- state 發生改變
- 數據 + 模板 結合,生成真實的 DOM,替換原始的 DOM
缺陷:
第一次生成了一個完整的 DOM 片段
第二次生成了一個完整的 DOM 片段
第二次的 DOM 替換了第一次的 DOM,非常耗性能
-
React 優化后,React 中 DOM 的渲染流程:
- state 數據
- JSX 模板
- 數據 + 模板 結合,生成真實的 DOM,來顯示
- state 發生改變
- 數據 + 模板 結合,生成真實的 DOM,并不直接替換原始的 DOM
- 新的 DOM( DocumentFragment )和原來的 DOM 樹做對比,找差異( 耗性能 )
- 找出發生變化的節點
- 只能新的 DOM 中發生變化的節點替換掉老的 DOM 中的相應節點
缺陷:
性能提升并不明顯
-
引入虛擬 DOM 后,React 中 DOM 的渲染流程:
- state 數據
- JSX 模板
- 數據 + 模板,生成虛擬 DOM ( 虛擬 DOM 就是一個 JS 對象,用它來描述真實的 DOM )( 損耗了性能 )
[ 'div', { id: "root" }, [ 'span', { }, 'hello world' ] ] - 用虛擬 DOM 的結構生成真實的 DOM,來顯示
<div id="root"><span>hello world</span></div> - state 發生變化
- 數據 + 模板 結合,生成新的虛擬 DOM( 極大提升了性能 )
[ 'div', { id: "root" }, [ 'span', { }, 'bye bye' ] ] - 比較新的虛擬 DOM 和原始虛擬 DOM 的區別,找到區別是 span 中的內容( 極大提升了性能 )
- 直接操作 DOM,改變 span 中的內容
總結:JSX -> React.createElement -> 虛擬 DOM (JS對象)-> 真實的DOM
優點:
性能提升
它使得跨端應用得以實現。React Native
DOM Diff 算法:
-
首先當改變 state 數據時,由于 setState 是一個異步函數( 官方文檔原話:不保證它是同步的 ),這里關于異步的問題我們暫時這么理解,如果深究可以自行去其他博客學習,當你一次調用三次 setState 時,React 并不會生成三次虛擬 DOM 而是將三次 setState 合并,然后生成一次虛擬 DOM 進行比對,接下來:
-
當新的虛擬 DOM 與原來的虛擬 DOM 進行比對時,它會進行同層比較,即相同的節點層進行比較,如果不同則直接將原始虛擬 DOM 中該節點層及以下的節點全部刪除,重新生成新的虛擬 DOM 節點,而不會繼續向下比對。
-
這里順便提一點,當你在 JSX 模板中遍歷 state 中某個數據時,為什么不加 key 值瀏覽器會報錯,這是因為你不再遍歷的每條數據加上 key 值,更改 state 中那條數據的值,生成虛擬 DOM 后,React 就不知道原始遍歷的數據和這次更新后遍歷的數據一一對應的關系,就會再次重新渲染,而加上 key 值,它則能迅速比對出有差異的部分進行部分的更新,但是為什么不建議用 index 作為 key 值呢?因為當你插入 / 刪除中間的數據時,從改變的那個數據開始,后續每個數據的 index 值就會變,從而就導致了每個數據的 key 值相應變化了,這樣依舊會引起大規模渲染,這就是其中的原因啦。
總結:
- 這篇文章真的干貨滿滿,上面的內容都是博主精心總結的,建議各位讀者細讀,必有大收獲!
- Vue 2.x 的虛擬 DOM 原理和 DOM Diff算法與 React 的基本大同小異,一篇懂,全都懂!