做網(wǎng)站視頻 上傳到哪兒百度手機(jī)版下載
虛擬DOM是最近非?;鸬募夹g(shù),兩大著名前端框架React和Vue都使用了虛擬DOM,所以我覺得非常有必要結(jié)合瀏覽器的工作機(jī)制對(duì)虛擬DOM進(jìn)行一次分析。當(dāng)然了,React和Vue框架本身所蘊(yùn)含的知識(shí)點(diǎn)非常多,而且也不是我們專欄的重點(diǎn),所以在這里我們還是把重心聚焦在虛擬DOM上。
在本文我們會(huì)先聊聊DOM的一些缺陷,然后在此基礎(chǔ)上介紹虛擬DOM是如何解決這些缺陷的,最后再站在雙緩存和MVC的視角來聊聊虛擬DOM。理解了這些會(huì)讓你對(duì)目前的前端框架有一個(gè)更加底層的認(rèn)識(shí),這也有助于你更好地理解這些前端框架。
DOM的缺陷
通過前面一系列文章的學(xué)習(xí),你對(duì)DOM的生成過程應(yīng)該已經(jīng)有了比較深刻的理解,并且也知道了通過JavaScript操縱DOM是會(huì)影響到整個(gè)渲染流水線的。另外,DOM還提供了一組JavaScript接口用來遍歷或者修改節(jié)點(diǎn),這套接口包含了getElementById、removeChild、appendChild等方法。
比如,我們可以調(diào)用document.body.appendChild(node)
虛擬DOM是最近非?;鸬募夹g(shù),兩大著名前端框架React和Vue都使用了虛擬DOM,所以我覺得非常有必要結(jié)合瀏覽器的工作機(jī)制對(duì)虛擬DOM進(jìn)行一次分析。當(dāng)然了,React和Vue框架本身所蘊(yùn)含的知識(shí)點(diǎn)非常多,而且也不是我們專欄的重點(diǎn),所以在這里我們還是把重心聚焦在虛擬DOM上。
在本文我們會(huì)先聊聊DOM的一些缺陷,然后在此基礎(chǔ)上介紹虛擬DOM是如何解決這些缺陷的,最后再站在雙緩存和MVC的視角來聊聊虛擬DOM。理解了這些會(huì)讓你對(duì)目前的前端框架有一個(gè)更加底層的認(rèn)識(shí),這也有助于你更好地理解這些前端框架。
DOM的缺陷
通過前面一系列文章的學(xué)習(xí),你對(duì)DOM的生成過程應(yīng)該已經(jīng)有了比較深刻的理解,并且也知道了通過JavaScript操縱DOM是會(huì)影響到整個(gè)渲染流水線的。另外,DOM還提供了一組JavaScript接口用來遍歷或者修改節(jié)點(diǎn),這套接口包含了getElementById、removeChild、appendChild等方法。
比如,我們可以調(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)慎。
當(dāng)然,對(duì)于簡(jiǎn)單的頁面來說,其DOM結(jié)構(gòu)還是比較簡(jiǎn)單的,所以以上這些操作DOM的問題并不會(huì)對(duì)用戶體驗(yàn)產(chǎn)生太多影響。但是對(duì)于一些復(fù)雜的頁面或者目前使用非常多的單頁應(yīng)用來說,其DOM結(jié)構(gòu)是非常復(fù)雜的,而且還需要不斷地去修改DOM樹,每次操作DOM渲染引擎都需要進(jìn)行重排、重繪或者合成等操作,因?yàn)镈OM結(jié)構(gòu)復(fù)雜,所生成的頁面結(jié)構(gòu)也會(huì)很復(fù)雜,對(duì)于這些復(fù)雜的頁面,執(zhí)行一次重排或者重繪操作都是非常耗時(shí)的,這就給我們帶來了真正的性能問題。
所以我們需要有一種方式來減少JavaScript對(duì)DOM的操作,這時(shí)候虛擬DOM就上場(chǎng)了。
什么是虛擬DOM
在談?wù)撌裁词翘摂MDOM之前,我們先來看看虛擬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è)賮砜纯词裁词翘摂MDOM。為了直觀理解,你可以參考下圖:
虛擬DOM執(zhí)行流程
該圖是我結(jié)合React流程畫的一張?zhí)摂MDOM執(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ī)制。通過上圖我們知道,當(dāng)有數(shù)據(jù)更新時(shí),React會(huì)生成一個(gè)新的虛擬DOM,然后拿新的虛擬DOM和之前的虛擬DOM進(jìn)行比較,這個(gè)過程會(huì)找出變化的節(jié)點(diǎn),然后再將變化的節(jié)點(diǎn)應(yīng)用到DOM上。
這里我們重點(diǎn)關(guān)注下比較過程,最開始的時(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,之前老的算法稱為Stack?reconciler。
在前面《async/await:使用同步的方式去寫異步代碼》那篇文章中我們介紹了協(xié)程,其實(shí)協(xié)程的另外一個(gè)稱呼就是Fiber,所以在這里我們可以把Fiber和協(xié)程關(guān)聯(lián)起來,那么所謂的Fiber?reconciler相信你也很清楚了,就是在執(zhí)行算法的過程中出讓主線程,這樣就解決了Stack?reconciler函數(shù)占用時(shí)間過久的問題。至于具體的實(shí)現(xiàn)過程在這里我就不詳細(xì)分析了,如果感興趣的話,你可以自行查閱相關(guān)資料進(jìn)行學(xué)習(xí)。
了解完虛擬DOM的大致執(zhí)行流程,你應(yīng)該也就知道為何需要虛擬DOM了。不過以上都從單純的技術(shù)視角來分析虛擬DOM的,那接下來我們?cè)購碾p緩存和MVC模型這兩個(gè)視角來聊聊虛擬DOM。
1.?雙緩存
在開發(fā)游戲或者處理其他圖像的過程中,屏幕從前緩沖區(qū)讀取數(shù)據(jù)然后顯示。但是很多圖形操作都很復(fù)雜且需要大量的運(yùn)算,比如一幅完整的畫面,可能需要計(jì)算多次才能完成,如果每次計(jì)算完一部分圖像,就將其寫入緩沖區(qū),那么就會(huì)造成一個(gè)后果,那就是在顯示一個(gè)稍微復(fù)雜點(diǎn)的圖像的過程中,你看到的頁面效果可能是一部分一部分地顯示出來,因此在刷新頁面的過程中,會(huì)讓用戶感受到界面的閃爍。
而使用雙緩存,可以讓你先將計(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)定輸出。
2.?MVC模式
到這里我們了解了虛擬DOM是一種類似雙緩存的實(shí)現(xiàn)。不過如果站在技術(shù)角度來理解虛擬緩存,依然不能全面理解其含義。那么接下來我們?cè)賮砜纯刺摂MDOM在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),你可以先參考下圖:
MVC基礎(chǔ)結(jié)構(gòu)
通過上圖你可以發(fā)現(xiàn),MVC的整體結(jié)構(gòu)比較簡(jiǎn)單,由模型、視圖和控制器組成,其核心思想就是將數(shù)據(jù)和視圖分離,也就是說視圖和模型之間是不允許直接通信的,它們之間的通信都是通過控制器來完成的。通常情況下的通信路徑是視圖發(fā)生了改變,然后通知控制器,控制器再根據(jù)情況判斷是否需要更新模型數(shù)據(jù)。當(dāng)然還可以根據(jù)不同的通信路徑和控制器不同的實(shí)現(xiàn)方式,基于MVC又能衍生出很多其他的模式,如MVP、MVVM等,不過萬變不離其宗,它們的基礎(chǔ)骨架都是基于MVC而來。
所以在分析基于React或者Vue這些前端框架時(shí),我們需要先重點(diǎn)把握大的MVC骨架結(jié)構(gòu),然后再重點(diǎn)查看通信方式和控制器的具體實(shí)現(xiàn)方式,這樣我們就能從架構(gòu)的視角來理解這些前端框架了。比如在分析React項(xiàng)目時(shí),我們可以把React的部分看成是一個(gè)MVC中的視圖,在項(xiàng)目中結(jié)合Redux就可以構(gòu)建一個(gè)MVC的模型結(jié)構(gòu),如下圖所示:
基于React和Redux構(gòu)建MVC模型
在該圖中,我們可以把虛擬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ì)于任何框架你都能輕松上手了。
總結(jié)
好了,今天就介紹到這里,下面我來總結(jié)下本文的主要內(nèi)容。
首先我們分析了直接操作DOM會(huì)觸發(fā)渲染流水線的一系列反應(yīng),如果對(duì)DOM操作不當(dāng)?shù)脑捝踔吝€會(huì)觸發(fā)強(qiáng)制同步布局和布局抖動(dòng)的問題,這也是我們?cè)诓僮鱀OM時(shí)需要非常小心謹(jǐn)慎的原因。
在此分析的基礎(chǔ)上,我們介紹了虛擬DOM是怎么解決直接操作DOM所帶來的問題以及React?Fiber更新機(jī)制。
要聊前端框架,就繞不開設(shè)計(jì)模式,所以接下來我們又從雙緩存和MVC角度分析了虛擬DOM。雙緩存是一種經(jīng)典的思路,應(yīng)用在很多場(chǎng)合,能解決頁面無效刷新和閃屏的問題,虛擬DOM就是雙緩存思想的一種體現(xiàn)。而基于MVC的設(shè)計(jì)思想也廣泛地滲透到各種場(chǎng)合,并且基于MVC又衍生出了很多其他模式(如MVP、MVVM等),不過萬變不離其宗,它們的基礎(chǔ)骨架都是基于MVC而來。站在MVC視角來理解虛擬DOM能讓你看到更為“廣闊的世界”。