DOM
- DOM代表了Document Object Model
- DOM是一種抽象化的結(jié)構(gòu)性文本
- DOM的作用只是需要能夠直接獲取東西
對(duì)于Web Developers來說,這種結(jié)構(gòu)性的文本就是HTML code,并且 DOM被簡(jiǎn)單的稱為HTML DOM。HTML code中的elements就變成了DOM中的nodes。
所以,既然HTML是一種文本,那么DOM就是HTML在內(nèi)存中的表現(xiàn)形式。
將它與程序的一個(gè)實(shí)例進(jìn)行比較。你可以在同一個(gè)程序的多個(gè)過程,就像你可以有多個(gè)相同的HTML DOM(例如同一頁(yè)面加載多標(biāo)簽)。
HTML DOM提供了能夠過去和修改Node的API,例如:getElementById和removeChild。
我們通常使用JavaScript來操作DOM對(duì)象。
下面舉個(gè)栗子:
var item = document.getElementById("hei");
item.parentNode.removeChild(item);
document對(duì)象是HTML DOM中root node的一個(gè)抽象體。
遇到的問題
由于HTML文檔的結(jié)構(gòu)允許,所以HTML DOM使用的是樹結(jié)構(gòu)。這就很厲害了,因?yàn)橥ㄟ^樹結(jié)構(gòu)我們可以很容易地遍歷樹。不幸的是,很容易操作并不意味著很快。
現(xiàn)在的WEB程序中,DOM樹?非常巨大。因?yàn)槲覀冊(cè)絹碓酵葡騽?dòng)態(tài)Web應(yīng)用程序(Single Page Applications - SPAs),我們需要大量的、不斷的修改DOM樹。?這真的非常痛苦。
順便說一下,假如我自己設(shè)法創(chuàng)造一個(gè)5GB +源網(wǎng)頁(yè)。它其實(shí)不是很難。但是考慮一下,我們將要在DOM使成千上萬的div。記住,我們是現(xiàn)代的程序猿,我們寫出的代碼應(yīng)該很容易管理!我們的方法,處理事件-點(diǎn)擊提交,很多,型INS…
比方說,典型的jQuery事件處理程序看起來像這一樣:
1、查找對(duì)事件感興趣的每一個(gè)節(jié)點(diǎn)
2、必要時(shí)更新
其中有兩個(gè)問題:
很難管理。
想象一下,你必須調(diào)整一個(gè)事件處理程序。如果你失去了背景,你必須潛水真正深入到代碼,甚至知道發(fā)生了什么事。耗時(shí)和錯(cuò)誤風(fēng)險(xiǎn)。
它的效率很低。我們真的需要手動(dòng)做所有這些調(diào)查結(jié)果嗎?也許我們可以更聰明,并提前告訴哪些節(jié)點(diǎn)是要更新?
那么Virtual DOM則是為了解決以上這些問題。
Virtual DOM
首先需要講明的一點(diǎn)
Virtual DOM技術(shù)并不是React發(fā)明的,但是React確實(shí)是使他發(fā)揚(yáng)光大的。
Virtual DOM是HTML DOM的抽象。它是輕量級(jí)和脫離瀏覽器的具體實(shí)施細(xì)節(jié)。因?yàn)镈OM本身已經(jīng)是一個(gè)抽象的、虛擬的DOM,事實(shí)上,Virtual DOM是一個(gè)抽象的抽象。
實(shí)際上,Virtual DOM包含:
- Javascript DOM模型樹(VTree),類似文檔節(jié)點(diǎn)樹(DOM)
- DOM模型樹轉(zhuǎn)節(jié)點(diǎn)樹方法(VTree -> DOM)
- 兩個(gè)DOM模型樹的差異算法(diff(VTree, VTree) -> PatchObject)
- 根據(jù)差異操作節(jié)點(diǎn)方法(patch(DOMNode, PatchObject) -> DOMNode)
VTree
VTree模型非常簡(jiǎn)單,基本結(jié)構(gòu)如下:
{
// tag的名字
tagName: 'p',
// 節(jié)點(diǎn)包含屬性
properties: { style: { color: '#fff' } },
// 子節(jié)點(diǎn)
children: [],
// 該節(jié)點(diǎn)的唯一表示,后面會(huì)講有啥用
key: 1
}
所以我們很容易寫一個(gè)方法來創(chuàng)建這種樹狀結(jié)構(gòu),例如React是這么創(chuàng)建的:
// 創(chuàng)建一個(gè)divreact.createElement('div', null, [
// 子節(jié)點(diǎn)img
react.createElement('img', { src: "avatar.png", class: "profile" }),
// 子節(jié)點(diǎn)h3
react.createElement('h3', null, [[user.firstName, user.lastName].join(' ')])
]);
VTree -> DOM
這方法也不太難,我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的:
function create(vds, parent) {
// 首先看看是不是數(shù)組,如果不是數(shù)組統(tǒng)一成數(shù)組
!Array.isArray(vds) && (vds = [vds]);
// 如果沒有父元素則創(chuàng)建個(gè)fragment來當(dāng)父元素
parent = parent || document.createDocumentFragment(); var node;
// 遍歷所有VNode
vds.forEach(function (vd) {
// 如果VNode是文字節(jié)點(diǎn)
if (isText(vd)) {
// 創(chuàng)建文字節(jié)點(diǎn)
node = document.createTextNode(vd.text);
// 否則是元素
} else {
// 創(chuàng)建元素
node = document.createElement(vd.tag);
}
// 將元素塞入父容器
parent.appendChild(node);
// 看看有沒有子VNode,有孩子則處理孩子
VNode vd.children && vd.children.length && create(vd.children, node);
// 看看有沒有屬性,有則處理屬性
vd.properties && setProps({ style: {} }, vd.properties, node); });
return parent;}
diff(VTree, VTree) -> PatchObject
差異算法是Virtual DOM的核心,實(shí)際上該差異算法是個(gè)取巧算法(當(dāng)然你不能指望用O(n^3)的復(fù)雜度來解決兩個(gè)樹的差異問題吧),不過能解決Web的大部分問題。
那么React是如何取巧的呢?
- 分層對(duì)比

如圖,React僅僅對(duì)同一層的節(jié)點(diǎn)嘗試匹配,因?yàn)閷?shí)際上,Web中不太可能把一個(gè)Component在不同層中移動(dòng)。
-
基于key來匹配
還記得之前在VTree中的屬性有一個(gè)叫key的東東么?這個(gè)是一個(gè)VNode的唯一識(shí)別,用于對(duì)兩個(gè)不同的VTree中的VNode做匹配的。
這也很好理解,因?yàn)槲覀兘?jīng)常會(huì)在Web遇到擁有唯一識(shí)別的Component(例如課程卡片、用戶卡片等等)的不同排列問題。
-
基于自定義元素做優(yōu)化
React提供自定義元素,所以匹配更加簡(jiǎn)單。
patch(DOMNode, PatchObject) -> DOMNode
由于diff操作已經(jīng)找出兩個(gè)VTree不同的地方,只要根據(jù)計(jì)算出來的結(jié)果,我們就可以對(duì)DOM的進(jìn)行差異渲染。
總結(jié):
- 給定一個(gè)可以代表DOM結(jié)構(gòu)的VTree結(jié)構(gòu)
- 通過
vdom/create-element函數(shù)來創(chuàng)建DOM中的節(jié)點(diǎn)- 通過將
vdom/patch函數(shù)將vtree/diff函數(shù)根據(jù)兩個(gè)不同的vtree結(jié)構(gòu)產(chǎn)生的patch包,更新的DOM中
引用
前沿技術(shù)揭秘
The difference between Virtual DOM and DOM

