diff之React:Virtual DOM

一垃瞧、讀懂diff

diff是Unix/Linux系統(tǒng)的一個(gè)很重要的工具程序预鬓。
它用來比較兩個(gè)文本文件的差異,是代碼版本管理的基石之一鞭盟。

diff <文件1> <文件2>

diff就會(huì)告訴你圾结,這兩個(gè)文件有何差異。

1. diff的三種格式
  • 正常格式
  • 上下文格式
  • 合并格式
2. 創(chuàng)建示例文件
touch test1
vim test1 //寫入12345
cat test1
cp test1 test2
vim test2//修改5位6  12346
cat test2
3. 正常格式
diff test1 test2 
對(duì)比

解析:

  • 5c5:用來說明改變的位置懊缺。前面的5表示第一個(gè)文件第5行改變疫稿,a(add),c(change),d(delete)。后面的5表示第二個(gè)文件變動(dòng)行鹃两。
  • < 5:前面的小于號(hào)遗座,表示要從test1當(dāng)中刪除的內(nèi)容。
  • ---:test1和test2的分割線俊扳。
  • > 6:前面的大于號(hào)途蒋,表示要從test2當(dāng)中增加該內(nèi)容。
4. 上下文格式

上面的結(jié)果顯示太簡(jiǎn)單不易理解馋记,所以加入上下文的方式号坡。它的使用方法是加入c參數(shù)(代表context)

為了更明顯的顯示上下文的效果 我們把文件改成如下內(nèi)容:


test1

test2
diff -c test1 test2
對(duì)比結(jié)果

解析:

  • ***:表示修改前的文件懊烤。
  • ---:表示修改后的文件。
  • 6,13:表示顯示的行6-13行宽堆。這時(shí)不僅顯示發(fā)生變化的行內(nèi)容腌紧,變化內(nèi)容前面三行和后面三行。
  • 另外畜隶,文件內(nèi)容的每一行最前面壁肋,還有一個(gè)標(biāo)記位。
    如果為空籽慢,表示該行無變化浸遗;
    如果是感嘆號(hào)(!),表示該行有改動(dòng)箱亿;
    如果是減號(hào)(-)跛锌,表示該行被刪除;
    如果是加號(hào)(+)届惋,表示該行為新增髓帽。
5. 合并格式

如果兩個(gè)文件相似度很高,那么上下文格式的diff盼樟,將顯示大量重復(fù)的內(nèi)容氢卡,比較浪費(fèi)空間。
使用方式加一個(gè)參數(shù)u

diff -u test1 test2
合并結(jié)果
6. git格式的diff

版本管理系統(tǒng)git晨缴,使用的是合并格式diff的變體译秦。

git diff
結(jié)果

解析:

  • 進(jìn)行比較的是,a版本的test1(即變動(dòng)前)和b版本的test2(即變動(dòng)后)击碗。
  • 第二行表示兩個(gè)版本的git哈希值(index區(qū)域的fcb0de4對(duì)象筑悴,與工作目錄區(qū)域的1e628d3對(duì)象進(jìn)行比較),最后的六位數(shù)字是對(duì)象的模式(普通文件稍途,644權(quán)限 讀寫-讀-讀 =>屬主-組成員-其他用戶)阁吝。
7. diff程序原理

在diff里面,我們比較的兩個(gè)文件叫做old和new械拍,而一般是按行來比較突勇。這里我們可以抽象成一個(gè)字符串的比較,比如:
old: abc
new: abcd
那么其中的每一個(gè)字符都可以表示文件里面的一行坷虑。那么diff里面用到的比較思想是從old和new里面找出最長(zhǎng)的subsequence樟插。這是基于LCS(Longest Common Subsequnece)算法柠座。

子序列的概念是......舉例說明吧:

a b c d e f g h i
a b c h d f g ij

那么最長(zhǎng)公共子序列就是:abcdfgi(不需要連續(xù))戳吝。

 lcs = (A, B) ->
    result = 0
    if A.length is 0 or B.length is 0
        result
    else if A[0] is B[0]
        result = 1 + lcs(A[1..], B[1..])
    else
        result = Math.max(lcs(A, B[1..]), lcs(A[1..], B))
使用遞歸來計(jì)算最長(zhǎng)的相似元素的長(zhǎng)度澜掩。

矩陣的列是old,橫坐標(biāo)是new

MJAU
GC||GA

詳情閱讀LCS算法

二、首先了解一下虛擬DOM
1. 對(duì)前端狀態(tài)管理的小總結(jié)

在最開始我們對(duì)于狀態(tài)改變更新對(duì)應(yīng)的視圖,都是操作dom痊远。但是對(duì)于復(fù)雜的應(yīng)用程序來說垮抗,無疑這是很低效率,不便于維護(hù)的碧聪。

于是冒版,出現(xiàn)了MVC等架構(gòu)模式,希望能從代碼組織方式來降低維護(hù)這種復(fù)雜應(yīng)用程序的難度矾削。但是 MVC 架構(gòu)沒辦法減少你所維護(hù)的狀態(tài)壤玫,也沒有降低狀態(tài)更新你需要對(duì)頁面的更新操作(前端來說就是DOM操作),你需要操作的DOM還是需要操作哼凯,只是換了個(gè)地方。

于是楚里,后來人們想出了 MVVM 模式断部,只要在模版中聲明視圖組件是和什么狀態(tài)進(jìn)行綁定的,雙向綁定引擎就會(huì)在狀態(tài)更新的時(shí)候自動(dòng)更新視圖班缎。

MVVM 可以很好的降低我們維護(hù)狀態(tài) -> 視圖的復(fù)雜程度蝴光。但是這不是唯一的辦法,還有一個(gè)非常直觀的方法达址,可以大大降低視圖更新的操作:一旦狀態(tài)發(fā)生了變化蔑祟,就用模版引擎重新渲染整個(gè)視圖,然后用新的視圖更換掉舊的視圖沉唠。最大的問題就是這樣做會(huì)很慢疆虚,因?yàn)榧词挂粋€(gè)小小的狀態(tài)變更都要重新構(gòu)造整棵 DOM,性價(jià)比太低满葛。Virtual DOM 就是這么做的径簿,只是加了一些特別的步驟來避免了整棵 DOM 樹變更。

2. Virtual DOM

DOM很慢嘀韧,輕微的操作都可能導(dǎo)致頁面重新排版篇亭,非常耗性能。

相對(duì)于DOM對(duì)象锄贷,js對(duì)象處理起來更快译蒂,而且更簡(jiǎn)單。

Virtual DOM算法原理就是:用 JavaScript 對(duì)象表示 DOM 信息和結(jié)構(gòu)谊却,當(dāng)狀態(tài)變更的時(shí)候柔昼,重新渲染這個(gè) JavaScript 的對(duì)象結(jié)構(gòu)。 我們用新的對(duì)象樹去比對(duì)舊的對(duì)象樹因惭,記錄差異岳锁,然后應(yīng)用到真正的DOM樹上面去。這樣我們就不需要重新構(gòu)造整個(gè)DOM樹了蹦魔。本質(zhì)就是:在js和DOM之間做了一個(gè)緩存激率。

3. 對(duì)React Virtual DOM 的誤解

React 從來沒有說過 “React 比原生操作 DOM 快”咳燕。React 的基本思維模式是每次有變動(dòng)就整個(gè)重新渲染整個(gè)應(yīng)用。如果沒有 Virtual DOM乒躺,簡(jiǎn)單來想就是直接重置 innerHTML招盲。很多人都沒有意識(shí)到,在一個(gè)大型列表所有數(shù)據(jù)都變了的情況下嘉冒,重置 innerHTML 其實(shí)是一個(gè)還算合理的操作... 真正的問題是在 “全部重新渲染” 的思維模式下曹货,即使只有一行數(shù)據(jù)變了,它也需要重置整個(gè) innerHTML讳推,這時(shí)候顯然就有大量的浪費(fèi)顶籽。

  • 框架的意義在于為你掩蓋底層的 DOM 操作,讓你用更聲明式的方式來描述你的目的银觅,從而讓你的代碼更容易維護(hù)礼饱。

  • 沒有任何框架可以比純手動(dòng)的優(yōu)化 DOM 操作更快,因?yàn)榭蚣艿?DOM 操作層需要應(yīng)對(duì)任何上層 API 可能產(chǎn)生的操作究驴,它的實(shí)現(xiàn)必須是普適的镊绪。在構(gòu)建一個(gè)實(shí)際應(yīng)用的時(shí)候,我們不可能為每一個(gè)地方都去做手動(dòng)優(yōu)化洒忧,出于可維護(hù)性的考慮蝴韭,這顯然不可能。

  • 框架給你的保證是熙侍,你在不需要手動(dòng)優(yōu)化的情況下榄鉴,我依然可以給你提供過得去的性能。

4. React:虛擬DOM Diff算法基礎(chǔ)學(xué)習(xí)

步驟1:用js對(duì)象模擬DOM樹

代碼

代碼運(yùn)行效果

步驟2:DOM Diff算法
即給定任意兩棵樹核行,找到最少的轉(zhuǎn)換步驟牢硅。但是標(biāo)準(zhǔn)的的Diff算法復(fù)雜度需要O(n^3),這里n表示的是樹的節(jié)點(diǎn)的總數(shù)芝雪。這顯然無法滿足性能要求减余。要達(dá)到每次界面都可以整體刷新界面的目的,勢(shì)必需要對(duì)算法進(jìn)行優(yōu)化惩系。這看上去非常有難度位岔,然而Facebook工程師卻做到了,他們結(jié)合Web界面的特點(diǎn)做出了兩個(gè)簡(jiǎn)單的假設(shè)堡牡,使得Diff算法復(fù)雜度直接降低到O(n)抒抬,這相當(dāng)于一個(gè)一層的for循環(huán)。

  • Web 中DOM節(jié)點(diǎn)跨層次的移動(dòng)操作很少晤柄,可以忽略不計(jì)
  • 擁有相同類的兩個(gè)組件將會(huì)生成相似的樹形結(jié)構(gòu)擦剑,擁有不同類的兩個(gè)組件將會(huì)生成不同的樹形結(jié)構(gòu)。
  • 對(duì)于同一層次的一組子節(jié)點(diǎn),它們可以通過唯一的id進(jìn)行區(qū)分惠勒。

基于以上三個(gè)前提策略赚抡,React對(duì)diff算法進(jìn)行了優(yōu)化。使得算法的復(fù)制度降到了O(n)纠屋。

tree diff
DOM的diff算法實(shí)際上只會(huì)對(duì)樹進(jìn)行逐層的比較涂臣。

比較圖
【示例1】

【示例1】React diff 的執(zhí)行情況:create A -> create B -> create C -> delete A。

當(dāng)出現(xiàn)節(jié)點(diǎn)跨層級(jí)移動(dòng)時(shí)售担,并不會(huì)出現(xiàn)想象中的移動(dòng)操作赁遗,而是以 A 為根節(jié)點(diǎn)的樹被整個(gè)重新創(chuàng)建,這是一種影響 React 性能的操作族铆,因此 React 官方建議不要進(jìn)行 DOM 節(jié)點(diǎn)跨層級(jí)的操作岩四。所以保持穩(wěn)定的DOM結(jié)構(gòu)會(huì)有助于性能的提升。

component diff

  • 對(duì)于同一類型的組件骑素,會(huì)按照原策略繼續(xù)比較 virtual DOM tree炫乓。
  • 對(duì)于不同類型的組件,直接整個(gè)替換献丑,即使結(jié)構(gòu)非常相似。

element diff
當(dāng)節(jié)點(diǎn)處于同一層級(jí)時(shí)侠姑,React diff 提供了三種節(jié)點(diǎn)操作创橄,分別為:INSERT_MARKUP(插入)、MOVE_EXISTING(移動(dòng))和 REMOVE_NODE(刪除)莽红。

ABCD--->BADC 這樣我們需要做的就很多了妥畏,我們需要?jiǎng)h除ABCD 然后插入DCBA。

React 發(fā)現(xiàn)這類操作繁瑣冗余安吁,因?yàn)檫@些都是相同的節(jié)點(diǎn)醉蚁,但由于位置發(fā)生變化,導(dǎo)致需要進(jìn)行繁雜低效的刪除鬼店、創(chuàng)建操作网棍,其實(shí)只要對(duì)這些節(jié)點(diǎn)進(jìn)行位置移動(dòng)即可。

針對(duì)這一現(xiàn)象妇智,React 提出優(yōu)化策略:允許開發(fā)者對(duì)同一層級(jí)的同組子節(jié)點(diǎn)滥玷,添加唯一 key 進(jìn)行區(qū)分,雖然只是小小的改動(dòng)巍棱,性能上卻發(fā)生了翻天覆地的變化惑畴!

那么對(duì)于上面的例子,我們就可以只移動(dòng)AC,BD不做任何操作航徙。

移動(dòng)

那么如贷,如此高效的 diff 到底是如何運(yùn)作的呢?

首先對(duì)新集合的節(jié)點(diǎn)進(jìn)行循環(huán)遍歷,for (name in nextChildren)杠袱,通過唯一 key 可以判斷新老集合中是否存在相同的節(jié)點(diǎn)尚猿,if (prevChild === nextChild),如果存在相同節(jié)點(diǎn)霞掺,則進(jìn)行移動(dòng)操作谊路,但在移動(dòng)前需要將當(dāng)前節(jié)點(diǎn)在老集合中的位置與 lastIndex 進(jìn)行比較
if (在老集合中的節(jié)點(diǎn)位置 < lastIndex)菩彬,則進(jìn)行節(jié)點(diǎn)移動(dòng)操作缠劝,否則不執(zhí)行該操作。這是一種順序優(yōu)化手段骗灶,lastIndex 一直在更新惨恭,表示訪問過的節(jié)點(diǎn)在老集合中最右的位置(即最大的位置),如果新集合中當(dāng)前訪問的節(jié)點(diǎn)比 lastIndex 大耙旦,說明當(dāng)前訪問節(jié)點(diǎn)在老集合中就比上一個(gè)節(jié)點(diǎn)位置靠后脱羡,則該節(jié)點(diǎn)不會(huì)影響其他節(jié)點(diǎn)的位置,因此不用添加到差異隊(duì)列中免都,即不執(zhí)行移動(dòng)操作锉罐,只有當(dāng)訪問的節(jié)點(diǎn)比 lastIndex 小時(shí),才需要進(jìn)行移動(dòng)操作绕娘。

待補(bǔ)充




最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末脓规,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子险领,更是在濱河造成了極大的恐慌侨舆,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绢陌,死亡現(xiàn)場(chǎng)離奇詭異挨下,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)脐湾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門臭笆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沥割,你說我怎么就攤上這事耗啦。” “怎么了机杜?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵帜讲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我椒拗,道長(zhǎng)似将,這世上最難降的妖魔是什么获黔? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮在验,結(jié)果婚禮上玷氏,老公的妹妹穿的比我還像新娘。我一直安慰自己腋舌,他們只是感情好盏触,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著块饺,像睡著了一般赞辩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上授艰,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天辨嗽,我揣著相機(jī)與錄音,去河邊找鬼淮腾。 笑死糟需,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谷朝。 我是一名探鬼主播洲押,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼圆凰!你這毒婦竟也來了诅诱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤送朱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后干旁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驶沼,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年争群,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了回怜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡换薄,死狀恐怖玉雾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情轻要,我是刑警寧澤复旬,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站冲泥,受9級(jí)特大地震影響驹碍,放射性物質(zhì)發(fā)生泄漏壁涎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一志秃、第九天 我趴在偏房一處隱蔽的房頂上張望怔球。 院中可真熱鬧,春花似錦浮还、人聲如沸竟坛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽担汤。三九已至,卻和暖如春延刘,著一層夾襖步出監(jiān)牢的瞬間漫试,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工碘赖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驾荣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓普泡,卻偏偏與公主長(zhǎng)得像播掷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撼班,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 參考文章:深度剖析:如何實(shí)現(xiàn)一個(gè)Virtual DOM 算法 作者:戴嘉華React中一個(gè)沒人能解釋清楚的問題——...
    waka閱讀 5,965評(píng)論 0 21
  • 了解過react的都必定會(huì)知道 virtual DOM 的存在歧匈,不夸張的說,virtual DOM 就是 reac...
    沐童Hankle閱讀 1,817評(píng)論 3 4
  • 前言 “步入前端兩年半砰嘁,自覺菜雞懶又爛件炉。” 近來想著寫寫一些前端學(xué)習(xí)的心得矮湘,左思右想斟冕。還是從 React 入筆。為...
    唐紫依閱讀 4,790評(píng)論 2 12
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,104評(píng)論 25 707
  • 大學(xué)第一課是軍訓(xùn),在軍訓(xùn)中我學(xué)會(huì)了很多道理十办,班上的同學(xué)是來自全國(guó)各地秀撇,每個(gè)人性格都會(huì)不一樣所以難免會(huì)有性格上的差異...
    明洙的七七閱讀 1,933評(píng)論 0 0