自從react流行起來(lái)后床蜘,前端開(kāi)發(fā)者們對(duì)虛擬DOM的研究就從未停止過(guò)悠垛,現(xiàn)在開(kāi)源世界里已經(jīng)有了非常多優(yōu)秀的虛擬DOM庫(kù)供大家使用蕾额。虛擬DOM這項(xiàng)技術(shù)能流行起來(lái)大概有三個(gè)原因但荤。
- 通過(guò)減少真實(shí)DOM操作提高系統(tǒng)運(yùn)行效率
- 降低維護(hù)狀態(tài) -> 視圖的復(fù)雜程度
- 幫助開(kāi)發(fā)者輕松實(shí)現(xiàn)前端組件化
一罗岖、虛擬DOM的工作過(guò)程
虛擬DOM以virtual node為基礎(chǔ)單元構(gòu)造虛擬DOM樹(shù),然后隨系統(tǒng)狀態(tài)的變化來(lái)操作真實(shí)DOM腹躁。大致的工作過(guò)程如下:
- 創(chuàng)建virtual node桑包,構(gòu)建起虛擬DOM樹(shù)
- 根據(jù)虛擬DOM樹(shù)創(chuàng)建真實(shí)DOM樹(shù)
- 系統(tǒng)狀態(tài)變化,產(chǎn)生新的虛擬DOM樹(shù)
- 拿新樹(shù)與舊樹(shù)做diff操作纺非,分析出兩棵樹(shù)的差異
- 針對(duì)差異對(duì)真實(shí)DOM做修改
二哑了、虛擬DOM節(jié)點(diǎn)
虛擬DOM節(jié)點(diǎn)(virtual node)對(duì)應(yīng)真實(shí)DOM中的HTML Element,它的構(gòu)造非常簡(jiǎn)單烧颖,只需要提供標(biāo)簽名弱左,屬性,子節(jié)點(diǎn)和一個(gè)用于區(qū)分兄弟節(jié)點(diǎn)的key炕淮。它的結(jié)構(gòu)和真實(shí)DOM里的元素非常類似拆火。
function VirtualNode(tagName, properties, children, key) {
this.tagName = tagName
this.properties = properties || null
this.children = children || []
this.key = key != null ? String(key) : undefined
...
}
一層一層的virtual node組成了一棵虛擬DOM樹(shù)
三、diff
每棵樹(shù)都有一個(gè)根節(jié)點(diǎn)涂圆,從根節(jié)點(diǎn)做遍歷是分析樹(shù)最快的方式们镜。遍歷的方式可以選擇深度優(yōu)先遍歷或者廣度優(yōu)先遍歷。為了降低diff的復(fù)雜度润歉,一般的虛擬DOM庫(kù)都會(huì)設(shè)置兩個(gè)前提:
- 類型不同(tagName不同)的兩個(gè)virtual node被認(rèn)為是完全不一樣的模狭,即使它們的所有屬性、子節(jié)點(diǎn)和key都一樣
- 對(duì)于同一層級(jí)的一組子節(jié)點(diǎn)踩衩,它們可以通過(guò)唯一的 key值進(jìn)行區(qū)分
接下來(lái)是具體的diff過(guò)程
1.不同類型節(jié)點(diǎn)的對(duì)比
對(duì)于不同類型的兩個(gè)節(jié)點(diǎn)胞皱,反映到真實(shí)DOM上的操作是邪意,直接刪除舊的節(jié)點(diǎn),然后插入新的節(jié)點(diǎn)
2.相同類型節(jié)點(diǎn)的對(duì)比
根據(jù)key值分兩種情況反砌, key值相同的兩個(gè)節(jié)點(diǎn)會(huì)接著比較屬性和子節(jié)點(diǎn)雾鬼,key值不同的情況下,和不同類型節(jié)點(diǎn)一樣宴树,刪除舊的節(jié)點(diǎn)策菜,插入新的節(jié)點(diǎn)。
3.virtual node節(jié)點(diǎn)列表對(duì)比
這里需要強(qiáng)調(diào)的是酒贬,節(jié)點(diǎn)列表里的每一項(xiàng)都沒(méi)有key值的情況下又憨,舊列表中的所有項(xiàng)都不會(huì)被重新利用,反映到真實(shí)DOM節(jié)點(diǎn)就是直接用新的元素列表代替舊的元素列表锭吨,這樣情況沒(méi)有任何可以優(yōu)化的地方蠢莺。所以這里說(shuō)的是每一項(xiàng)帶有唯一key值的virutal node節(jié)點(diǎn)列表之間的對(duì)比。
節(jié)點(diǎn)列表之間的對(duì)比類似于比較兩個(gè)字符串
A: [a b c d e f g]
B: [a c b h f e g]
首先分析出可重用的節(jié)點(diǎn)零如,如a
躏将,b
,c
考蕾,e
祸憋,f
,g
肖卧,通過(guò)對(duì)比兩個(gè)列表的key值得出一個(gè)新的臨時(shí)列表C蚯窥,在B列表中沒(méi)有的用-1表示,把B列表中存在但A列表不存在的放到最后塞帐。
C: [a b c -1 e f g h]
然后做一下處理拦赠,記錄下值是-1的節(jié)點(diǎn)的索引,這些節(jié)點(diǎn)對(duì)應(yīng)的DOM元素會(huì)被刪除葵姥,在C列表中也刪除-1的值
C: [a b c e f g h]
接下來(lái)同時(shí)遍歷B荷鼠、C兩個(gè)列表,對(duì)比相同位置下的節(jié)點(diǎn)牌里,根據(jù)下面的規(guī)則來(lái)制定出移動(dòng)真實(shí)DOM元素次數(shù)最少的步驟颊咬。
- 值相同务甥,對(duì)應(yīng)的真實(shí)DOM不移動(dòng)牡辽,如
a
節(jié)點(diǎn) - 值不同,記錄下當(dāng)前索引敞临,把C列表中的節(jié)點(diǎn)也記錄下來(lái)态辛,它對(duì)應(yīng)的DOM元素會(huì)被插入到剛才記錄下的索引處,如
c
會(huì)被插入到列表第二的位置
最后挺尿,得到兩個(gè)列表對(duì)比的分析結(jié)果
四奏黑、把差異應(yīng)用到真正的DOM樹(shù)上
和diff過(guò)程采用的遍歷方式一樣炊邦,遍歷一次整個(gè)真實(shí)DOM樹(shù),最后根據(jù)記錄的差異來(lái)操作DOM元素熟史。
五馁害、小結(jié)
本文大致講解了虛擬DOM的工作過(guò)程和Diff原理,參考的是virtual-dom這個(gè)虛擬DOM庫(kù)蹂匹,同時(shí)我自己用這個(gè)庫(kù)實(shí)現(xiàn)了一個(gè)todo-list應(yīng)用 todolist-virtualdom
碘菜,幫助大家理解虛擬DOM的使用方法和工作過(guò)程。
參考資料
深度剖析:如何實(shí)現(xiàn)一個(gè) Virtual DOM 算法
深入淺出 React(四):虛擬 DOM Diff 算法解析
Reconciliation