在本文我們會先聊聊 DOM 的一些缺陷,然后在此基礎上介紹虛擬 DOM 是如何解決這些缺陷的,最后再站在雙緩存和 MVC 的視角來聊聊虛擬 DOM。理解了這些會讓你對目前的前端框架有一個更加底層的認識,這也有助于你更好地理解這些前端框架。
## DOM的缺陷
JavaScript 操縱 DOM 是會影響到整個渲染流水線的,另外,DOM 還提供了一組 JavaScript 接口用來遍歷或者修改節點,這套接口包含了 getElementById、removeChild、appendChild 等方法。
比如,我們可以調用`document.body.appendChild(node)`往 body 節點上添加一個元素,調用該 API 之后會引發一系列的連鎖反應。首先渲染引擎會將 node 節點添加到 body 節點之上,然后觸發樣式計算、布局、繪制、柵格化、合成等任務,我們把這一過程稱為**重排**。除了重排之外,還有可能引起**重繪**或者**合成**操作,形象地理解就是“**牽一發而動全身**”。另外,對于 DOM 的不當操作還有可能引發**強制同步布局**和**布局抖動**的問題,這些操作都會大大降低渲染效率。因此,對于 DOM 的操作我們時刻都需要非常小心謹慎。
當然,對于簡單的頁面來說,其 DOM 結構還是比較簡單的,所以以上這些操作 DOM 的問題并不會對用戶體驗產生太多影響。但是對于一些復雜的頁面或者目前使用非常多的單頁應用來說,其 DOM 結構是非常復雜的,而且還需要不斷地去修改 DOM 樹,每次操作 DOM 渲染引擎都需要進行重排、重繪或者合成等操作,因為 DOM 結構復雜,所生成的頁面結構也會很復雜,對于這些復雜的頁面,執行一次重排或者重繪操作都是非常耗時的,這就給我們帶來了真正的性能問題。
所以我們需要有一種方式來減少 JavaScript 對 DOM 的操作,這時候虛擬 DOM 就上場了。
## 什么是虛擬 DOM
在談論什么是虛擬 DOM 之前,我們先來看看虛擬 DOM 到底要解決哪些事情。
* 將頁面改變的內容應用到虛擬 DOM 上,而不是直接應用到 DOM 上。
* 變化被應用到虛擬 DOM 上時,虛擬 DOM 并不急著去渲染頁面,而僅僅是調整虛擬 DOM 的內部狀態,這樣操作虛擬 DOM 的代價就變得非常輕了。
* 在虛擬 DOM 收集到足夠的改變時,再把這些變化一次性應用到真實的 DOM 上。
基于以上三點,我們再來看看什么是虛擬 DOM。為了直觀理解,你可以參考下圖:

該圖是我結合 React 流程畫的一張虛擬 DOM 執行流程圖,下面我們就結合這張圖來分析下虛擬 DOM 到底怎么運行的。
* **創建階段**。首先依據 JSX 和基礎數據創建出來虛擬 DOM,它反映了真實的 DOM 樹的結構。然后由虛擬 DOM 樹創建出真實 DOM 樹,真實的 DOM 樹生成完后,再觸發渲染流水線往屏幕輸出頁面。
* **更新階段**。如果數據發生了改變,那么就需要根據新的數據創建一個新的虛擬 DOM 樹;然后 React 比較兩個樹,找出變化的地方,并把變化的地方一次性更新到真實的 DOM 樹上;最后渲染引擎更新渲染流水線,并生成新的頁面。
既然聊到虛擬 DOM 的更新,那我們就不得不聊聊最新的**React Fiber 更新機制**。通過上圖我們知道,當有數據更新時,React 會生成一個新的虛擬 DOM,然后拿新的虛擬 DOM 和之前的虛擬 DOM 進行比較,這個過程會找出變化的節點,然后再將變化的節點應用到 DOM 上。
這里我們重點關注下比較過程,最開始的時候,比較兩個虛擬 DOM 的過程是在一個遞歸函數里執行的,其**核心算法是 reconciliation**。通常情況下,這個比較過程執行得很快,不過當虛擬 DOM 比較復雜的時候,執行比較函數就有可能占據主線程比較久的時間,這樣就會導致其他任務的等待,造成頁面卡頓。為了解決這個問題,React 團隊重寫了 reconciliation 算法,新的算法稱為 Fiber reconciler,之前老的算法稱為 Stack reconciler。
其實協程的另外一個稱呼就是 Fiber,所以在這里我們可以把 Fiber 和協程關聯起來,那么所謂的 Fiber reconciler 相信你也很清楚了,就是在執行算法的過程中出讓主線程,這樣就解決了 Stack reconciler 函數占用時間過久的問題。
了解完虛擬 DOM 的大致執行流程,你應該也就知道為何需要虛擬 DOM 了。不過以上都從單純的技術視角來分析虛擬 DOM 的,那接下來我們再從雙緩存和 MVC 模型這兩個視角來聊聊虛擬 DOM。
## 雙緩
在開發游戲或者處理其他圖像的過程中,屏幕從前緩沖區讀取數據然后顯示。但是很多圖形操作都很復雜且需要大量的運算,比如一幅完整的畫面,可能需要計算多次才能完成,如果每次計算完一部分圖像,就將其寫入緩沖區,那么就會造成一個后果,那就是在顯示一個稍微復雜點的圖像的過程中,你看到的頁面效果可能是一部分一部分地顯示出來,因此在刷新頁面的過程中,會讓用戶感受到界面的閃爍。
而使用雙緩存,可以讓你先將計算的中間結果存放在另一個緩沖區中,等全部的計算結束,該緩沖區已經存儲了完整的圖形之后,再將該緩沖區的圖形數據一次性復制到顯示緩沖區,這樣就使得整個圖像的輸出非常穩定。
在這里,你可以把虛擬 DOM 看成是 DOM 的一個 buffer,和圖形顯示一樣,它會在完成一次完整的操作之后,再把結果應用到 DOM 上,這樣就能減少一些不必要的更新,同時還能保證 DOM 的穩定輸出。
## MVC 模式
到這里我們了解了虛擬 DOM 是一種類似雙緩存的實現。不過如果站在技術角度來理解虛擬緩存,依然不能全面理解其含義。那么接下來我們再來看看虛擬 DOM 在 MVC 模式中所扮演的角色。
在各大設計模式當中,MVC 是一個非常重要且應用廣泛的模式,因為它能將數據和視圖進行分離,在涉及到一些復雜的項目時,能夠大大減輕項目的耦合度,使得程序易于維護。
關于 MVC 的基礎結構,你可以先參考下圖:

通過上圖你可以發現,MVC 的整體結構比較簡單,由模型、視圖和控制器組成,其**核心思想就是將數據和視圖分離**,也就是說**視圖和模型之間是不允許直接通信**的,它們之間的通信都是通過控制器來完成的。通常情況下的通信路徑是視圖發生了改變,然后通知控制器,控制器再根據情況判斷是否需要更新模型數據。當然還可以根據不同的通信路徑和控制器不同的實現方式,基于 MVC 又能衍生出很多其他的模式,如 MVP、MVVM 等,不過萬變不離其宗,它們的基礎骨架都是基于 MVC 而來。
所以在分析基于 React 或者 Vue 這些前端框架時,我們需要先重點把握大的 MVC 骨架結構,然后再重點查看通信方式和控制器的具體實現方式,這樣我們就能從架構的視角來理解這些前端框架了。比如在分析 React 項目時,我們可以把 React 的部分看成是一個 MVC 中的視圖,在項目中結合 Redux 就可以構建一個 MVC 的模型結構,如下圖所示:

在該圖中,我們可以把虛擬 DOM 看成是 MVC 的視圖部分,其控制器和模型都是由 Redux 提供的。其具體實現過程如下:
* 圖中的控制器是用來監控 DOM 的變化,一旦 DOM 發生變化,控制器便會通知模型,讓其更新數據;
* 模型數據更新好之后,控制器會通知視圖,告訴它模型的數據發生了變化;
* 視圖接收到更新消息之后,會根據模型所提供的數據來生成新的虛擬 DOM;
* 新的虛擬 DOM 生成好之后,就需要與之前的虛擬 DOM 進行比較,找出變化的節點;
* 比較出變化的節點之后,React 將變化的虛擬節點應用到 DOM 上,這樣就會觸發 DOM 節點的更新;
* DOM 節點的變化又會觸發后續一系列渲染流水線的變化,從而實現頁面的更新。
**在實際工程項目中,你需要學會分析出這各個模塊,并梳理出它們之間的通信關系,這樣對于任何框架你都能輕松上手了。**
- 說明
- CSS與HTML
- BFC的特性及其常見應用
- CSS深入理解之margin
- CSS深入理解之line-height
- CSS盒模型相關知識
- CSS知識總結
- HTML知識總結
- 三欄布局五種方式
- JavaScript內置對象
- 1.循環
- 2.數組方法對比
- 3.字符串實用常操紀要
- JavaScript核心
- var、let、const定義變量
- this 的指向問題詳解
- 箭頭函數
- ES6部分知識歸納
- ES6的Class
- Promise和Async/await
- 面向對象的概念及JS中的表現
- 創建對象的九種方式
- JS的繼承
- 閉包總結
- 構造函數與作用域
- 原型與原型鏈
- 函數的四種調用模式
- apply、call、bind詳解
- JavaScript應用
- 1.JavaScript實現深拷貝與淺拷貝
- 2.函數防抖與節流
- 3.無阻塞腳本加載技術
- DOM
- 如何寫出高性能DOM?
- 事件探秘
- 事件委托
- 操作DOM常用API詳解
- 重排和重繪
- 運行機制與V8
- 瀏覽器的線程和進程
- Vue.js
- Vue.js知識點總結
- Vue-Router知識點總結
- 父子組件之間通信的十種方式
- 優化首屏加載
- 關于Vuex
- 前端路由原理及實現
- 在Vue.js編寫更好的v-for循環的6種技巧
- 12個Vue.js開發技巧和竅門
- 網絡協議
- HTTP緩存機制
- UDP協議
- TCP協議
- HTTPS協議
- HTTPS的背景知識、協議的需求、設計的難點
- HTTPS與HTTP的區別
- 框架與架構
- MVC、MVP、MVVM
- Gulp與Webpack的區別
- Angular React 和 Vue的比較
- 虛擬DOM和實際的DOM有何不同?
- 架構問題
- 工程化
- npm link命令
- npm scripts 使用指南
- 前端工程簡史
- 常見的構建工具及其對比
- Webpack基本配置與概念
- 設計模式
- 工廠設計模式
- 單例設計模式
- 適配器模式
- 裝飾器模式