前言
本文我們會(huì)先聊聊 DOM 的一些缺陷娱颊,然后在此基礎(chǔ)上介紹虛擬 DOM 是如何解決這些缺陷的傲诵,最后再站在雙緩存和 MVC 的視角來聊聊虛擬 DOM。理解了這些會(huì)讓你對(duì)目前的前端框架有一個(gè)更加底層的認(rèn)識(shí)箱硕,這也有助于你更好地理解這些前端框架拴竹。
DOM 的缺陷
比如,我們可以調(diào)用 document.body.appendChild(node)
往 body
節(jié)點(diǎn)上添加一個(gè)元素剧罩,調(diào)用該 API 之后會(huì)引發(fā)一系列的連鎖反應(yīng)栓拜。首先渲染引擎會(huì)將 node
節(jié)點(diǎn)添加到 body
節(jié)點(diǎn)之上,然后觸發(fā)樣式計(jì)算惠昔、布局幕与、繪制、柵格化镇防、合成等任務(wù)啦鸣,我們把這一過程稱為 重排。除了重排之外来氧,還有可能引起重繪或者合成操作诫给,形象地理解就是“牽一發(fā)而動(dòng)全身”。另外啦扬,對(duì)于 DOM 的不當(dāng)操作還有可能引發(fā)強(qiáng)制同步布局和布局抖動(dòng)的問題中狂,這些操作都會(huì)大大降低渲染效率。因此扑毡,對(duì)于 DOM 的操作時(shí)我們需要非常謹(jǐn)慎胃榕。
對(duì)于一些復(fù)雜的頁面或者目前使用非常多的單頁應(yīng)用來說,其 DOM 結(jié)構(gòu)是非常復(fù)雜的瞄摊,而且還需要不斷地去修改 DOM 樹勋又,每次操作 DOM 渲染引擎都需要進(jìn)行重排、重繪或者合成等操作换帜,因?yàn)?DOM 結(jié)構(gòu)復(fù)雜赐写,所生成的頁面結(jié)構(gòu)也會(huì)很復(fù)雜,對(duì)于這些復(fù)雜的頁面膜赃,執(zhí)行一次重排或者重繪操作都是非常耗時(shí)的,這就給我們帶來了真正的性能問題揉忘。所以我們需要有一種方式來減少 JavaScript 對(duì) DOM 的操作跳座,這時(shí)候虛擬 DOM 就上場(chǎng)了端铛。
什么是虛擬 DOM?
在談?wù)撌裁词翘摂M DOM 之前疲眷,我們先來看看虛擬 DOM 到底要解決哪些事情禾蚕。
- 將頁面改變的內(nèi)容應(yīng)用到虛擬 DOM 上,而不是直接應(yīng)用到 DOM 上狂丝。
- 變化被應(yīng)用到虛擬 DOM 上時(shí)换淆,虛擬 DOM 并不急著去渲染頁面,而僅僅是調(diào)整虛擬 DOM 的內(nèi)部狀態(tài)几颜,這樣操作虛擬 DOM 的代價(jià)就變得非常輕了倍试。
- 在虛擬 DOM 收集到足夠的改變時(shí),再把這些變化一次性應(yīng)用到真實(shí)的 DOM 上蛋哭。
基于以上三點(diǎn)县习,我們?cè)賮砜纯词裁词翘摂M DOM。為了直觀理解谆趾,你可以參考下圖:
該圖是結(jié)合 React 流程畫的一張?zhí)摂M DOM 執(zhí)行流程圖躁愿,下面我們就結(jié)合這張圖來分析下虛擬 DOM 到底怎么運(yùn)行的。
- 創(chuàng)建階段沪蓬。首先依據(jù) JSX 和基礎(chǔ)數(shù)據(jù)創(chuàng)建出來虛擬 DOM彤钟,它反映了真實(shí)的 DOM 樹的結(jié)構(gòu)。然后由虛擬 DOM 樹創(chuàng)建出真實(shí) DOM 樹跷叉,真實(shí)的 DOM 樹生成完后逸雹,再觸發(fā)渲染流水線往屏幕輸出頁面。
- 更新階段性芬。如果數(shù)據(jù)發(fā)生了改變峡眶,那么就需要根據(jù)新的數(shù)據(jù)創(chuàng)建一個(gè)新的虛擬 DOM 樹;然后 React 比較兩個(gè)樹植锉,找出變化的地方辫樱,并把變化的地方一次性更新到真實(shí)的 DOM 樹上;最后渲染引擎更新渲染流水線俊庇,并生成新的頁面狮暑。
既然聊到虛擬 DOM 的更新,那我們就不得不聊聊最新的 React Fiber 更新機(jī)制了辉饱。最開始的時(shí)候搬男,比較兩個(gè)虛擬 DOM 的過程是在一個(gè)遞歸函數(shù)里執(zhí)行的伦意,其核心算法是 reconciliation案腺。通常情況下裆熙,這個(gè)比較過程執(zhí)行得很快,不過當(dāng)虛擬 DOM 比較復(fù)雜的時(shí)候慧域,執(zhí)行比較函數(shù)就有可能占據(jù)主線程比較久的時(shí)間,這樣就會(huì)導(dǎo)致其他任務(wù)的等待锯仪,造成頁面卡頓溉卓。
為了解決這個(gè)問題,React 團(tuán)隊(duì)重寫了 reconciliation 算法敦冬,新的算法稱為 Fiber reconciler辅搬,所謂的 Fiber reconciler,就是在執(zhí)行算法的過程中出讓主線程脖旱,這樣就解決了之前執(zhí)行函數(shù)占用時(shí)間過久的問題堪遂。至于具體的實(shí)現(xiàn)過程在這里就不詳細(xì)分析了,如果感興趣的話萌庆,你可以自行查閱相關(guān)資料進(jìn)行學(xué)習(xí)溶褪。
了解完虛擬 DOM 的大致執(zhí)行流程,你應(yīng)該也就知道為何需要虛擬 DOM 了踊兜。不過以上都從單純的技術(shù)視角來分析虛擬 DOM 的竿滨,那接下來我們?cè)購碾p緩存和 MVC 模型這兩個(gè)視角來聊聊虛擬 DOM。
雙緩存
使用雙緩存捏境,可以讓你先將計(jì)算的中間結(jié)果存放在另一個(gè)緩沖區(qū)中于游,等全部的計(jì)算結(jié)束,該緩沖區(qū)已經(jīng)存儲(chǔ)了完整的圖形之后垫言,再將該緩沖區(qū)的圖形數(shù)據(jù)一次性復(fù)制到顯示緩沖區(qū)贰剥,這樣就使得整個(gè)圖像的輸出非常穩(wěn)定。
在這里筷频,你可以把虛擬 DOM 看成是 DOM 的一個(gè) buffer蚌成,和圖形顯示一樣,它會(huì)在完成一次完整的操作之后凛捏,再把結(jié)果應(yīng)用到 DOM 上担忧,這樣就能減少一些不必要的更新,同時(shí)還能保證 DOM 的穩(wěn)定輸出坯癣。
MVC 模式
接下來我們?cè)賮砜纯刺摂M DOM 在 MVC 模式中所扮演的角色瓶盛。
在各大設(shè)計(jì)模式當(dāng)中,MVC 是一個(gè)非常重要且應(yīng)用廣泛的模式示罗,因?yàn)樗軐?shù)據(jù)和視圖進(jìn)行分離惩猫,在涉及到一些復(fù)雜的項(xiàng)目時(shí),能夠大大減輕項(xiàng)目的耦合度蚜点,使得程序易于維護(hù)轧房。關(guān)于 MVC 的基礎(chǔ)結(jié)構(gòu),你可以先參考下圖:
通過上圖可以發(fā)現(xiàn)绍绘,MVC 的整體結(jié)構(gòu)比較簡(jiǎn)單奶镶,由模型迟赃、視圖和控制器組成,其核心思想就是將數(shù)據(jù)和視圖分離厂镇,也就是說視圖和模型之間是不允許直接通信的捺氢,它們之間的通信是通過控制器來完成的。
比如在分析 React 項(xiàng)目時(shí)剪撬,我們可以把 React 的部分看成是一個(gè) MVC 中的視圖,在項(xiàng)目中結(jié)合 Redux 就可以構(gòu)建一個(gè) MVC 的模型結(jié)構(gòu)悠反,如下圖所示:
在該圖中残黑,我們可以把虛擬 DOM 看成是 MVC 的視圖部分,其控制器和模型都是由 Redux 提供的斋否。其具體實(shí)現(xiàn)過程如下:
- 圖中的控制器是用來監(jiān)控 DOM 的變化梨水,一旦 DOM 發(fā)生變化,控制器便會(huì)通知模型茵臭,讓其更新數(shù)據(jù)疫诽。
- 模型數(shù)據(jù)更新好之后,控制器會(huì)通知視圖旦委,告訴它模型的數(shù)據(jù)發(fā)生了變化奇徒。
- 視圖接收到更新消息之后,會(huì)根據(jù)模型所提供的數(shù)據(jù)來生成新的虛擬 DOM缨硝。
- 新的虛擬 DOM 生成好之后摩钙,就需要與之前的虛擬 DOM 進(jìn)行比較,找出變化的節(jié)點(diǎn)查辩。
- 比較出變化的節(jié)點(diǎn)之后胖笛,React 將變化的虛擬節(jié)點(diǎn)應(yīng)用到 DOM 上,這樣就會(huì)觸發(fā) DOM 節(jié)點(diǎn)的更新宜岛。
- DOM 節(jié)點(diǎn)的變化又會(huì)觸發(fā)后續(xù)一系列渲染流水線的變化长踊,從而實(shí)現(xiàn)頁面的更新。
在實(shí)際工程項(xiàng)目中萍倡,你需要學(xué)會(huì)分析出各個(gè)模塊身弊,并梳理出它們之間的通信關(guān)系,這樣對(duì)于任何框架你都能輕松上手了遣铝。