Think
在 react 之前我只接觸過 mvvm 結(jié)構(gòu)的框架屑迂。
mvvm 采用這樣一種機(jī)制:只要在模版中聲明視圖組件是和什么狀態(tài)進(jìn)行綁定的蛉签,雙向綁定引擎就會在狀態(tài)更新的時候自動更新視圖装盯。
那對于更新視圖有沒有其他的實(shí)現(xiàn)呢县钥?
我們還可以考慮當(dāng)組件狀態(tài)發(fā)生變化時,就使用模版引擎去渲染整個視圖往声。用舊的視圖去替換掉新的視圖擂找。但是這樣做,就會出現(xiàn)大量的 DOM 操作烁挟。
而DOM操作是非秤ね荩可怕的??
var div = document.createElement("div");
var str = "";
for( var key in div ) {
str = str + key + " ";
}
console.log(str);
因此,我們考慮到當(dāng)狀態(tài)變化時撼嗓,只更新發(fā)生變化的部分柬采,避免更新整顆 DOM 樹。
Virtual DOM
react 的核心概念便是 Virtual DOM且警。
相較于上面的原生 DOM 事件粉捻,使用原生 Javascript 來實(shí)現(xiàn)就更快,更簡單斑芜。
var element = {
tagName: 'ul', // 節(jié)點(diǎn)標(biāo)簽名
props: { // DOM的屬性肩刃,用一個對象存儲鍵值對
id: 'list'
},
children: [ // 該節(jié)點(diǎn)的子節(jié)點(diǎn)
{tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
]
}
對應(yīng)的 HTML 結(jié)構(gòu)為:
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
<li class='item'>Item 3</li>
</ul>
既然 javascript 可以用來表示 dom ,那就可以通過 javascript 的樹結(jié)構(gòu)來構(gòu)成一顆 dom 樹杏头。
這樣盈包,在發(fā)生狀態(tài)變更時,我們可以通過對比舊的樹和新的樹醇王,來記錄差異呢燥。只針對差異部分進(jìn)行 dom 操作。這樣寓娩,頁面更新了叛氨,而 dom 操作也只變更了不同的地方。
Virtual DOM 本質(zhì)上就是在 JS 和 DOM 之間做了一個緩存棘伴∧海可以類比 CPU 和硬盤,既然硬盤這么慢焊夸,我們就在它們之間加個緩存:既然 DOM 這么慢仁连,我們就在它們 JS 和 DOM 之間加個緩存。CPU(JS)只操作內(nèi)存(Virtual DOM)阱穗,最后的時候再把變更寫入硬盤(DOM)怖糊。
算法實(shí)現(xiàn)
-
構(gòu)建虛擬DOM ( element )
用 JavaScript 來表示一個 DOM 節(jié)點(diǎn)時帅容,只需要記錄它的節(jié)點(diǎn)類型、屬性伍伤,還有子節(jié)點(diǎn)。
-
render
方法會根據(jù)tagName
構(gòu)建一個真正的DOM節(jié)點(diǎn)遣钳,然后設(shè)置這個節(jié)點(diǎn)的屬性扰魂,最后遞歸地把自己的子節(jié)點(diǎn)也構(gòu)建起來。?
-
找出新舊 DOM 樹的區(qū)別 ( diff )
比較兩棵DOM樹的差異是 Virtual DOM 算法最核心的部分蕴茴。兩個樹的完全的 diff 算法是一個時間復(fù)雜度為 O(n^3) 的問題劝评。但是在前端當(dāng)中,你很少會跨越層級地移動DOM元素倦淀。所以 Virtual DOM 只會對同一個層級的元素進(jìn)行對比:
及第一層的 div 只會與第一層的 div 對比蒋畜,第二層的 div 只會與第二層的 div 對比。這樣算法復(fù)雜度就可以降到 O(n)撞叽。
深度優(yōu)先遍歷姻成,記錄差異
差異類型
-
列表對比算法
?
-
將差異運(yùn)用到真正的 DOM 樹上 ( patch )
我們可以對 DOM 樹進(jìn)行深度優(yōu)先的遍歷,遍歷的時候從生成的
patches
對象中找出當(dāng)前遍歷的節(jié)點(diǎn)差異愿棋,然后進(jìn)行 DOM 操作科展。
// 1. 構(gòu)建虛擬DOM
var tree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: blue'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li')])
])
// 2. 通過虛擬DOM構(gòu)建真正的DOM
var root = tree.render()
document.body.appendChild(root)
// 3. 生成新的虛擬DOM
var newTree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li'), el('li')])
])
// 4. 比較兩棵虛擬DOM樹的不同
var patches = diff(tree, newTree)
// 5. 在真正的DOM元素上應(yīng)用變更
patch(root, patches)
Mark:
- 如何實(shí)現(xiàn)一個 Virtual DOM 算法https://github.com/livoras/blog/issues/13
- Virtual DOM dom&&diff 算法實(shí)現(xiàn)http://f2e.souche.com/blog/react-vitural-dom-diffsuan-fa-wei-dai-ma-shi-xian/
- 一個比較深刻的react.js源碼分析http://purplebamboo.github.io/2015/09/15/reactjs_source_analyze_part_one/