這篇文章主要介紹虛擬DOM如何實現(xiàn),文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

Virtual DOM中Diff算法得到的結(jié)果如何映射到真實DOM中,我們將在下一篇博客揭曉。
主要內(nèi)容為:
Virtual DOM的結(jié)構(gòu)Virtual DOM的Diff算法
注:這個Virtual DOM的實現(xiàn)并不是React Virtual DOM的源碼,而是基于virtual-dom)這個庫。兩者在原理上類似,并且這個庫更加簡單容易理解。相較于這個庫,React對Virtual DOM做了進一步的優(yōu)化和調(diào)整,我會在后續(xù)的博客中進行分析。
Virtual DOM的結(jié)構(gòu)
VirtualNode
作為Virtual DOM的元數(shù)據(jù)結(jié)構(gòu),VirtualNode位于vnode/vnode.js文件中。我們截取一部分聲明代碼來看下內(nèi)部結(jié)構(gòu):
function VirtualNode(tagName, properties, children, key, namespace) {
this.tagName = tagName
this.properties = properties || noProperties //props對象,Object類型
this.children = children || noChildren //子節(jié)點,Array類型
this.key = key != null ? String(key) : undefined
this.namespace = (typeof namespace === "string") ? namespace : null
...
this.count = count + descendants
this.hasWidgets = hasWidgets
this.hasThunks = hasThunks
this.hooks = hooks
this.descendantHooks = descendantHooks
}
VirtualNode.prototype.version = version //VirtualNode版本號,isVnode()檢測標(biāo)志
VirtualNode.prototype.type = "VirtualNode" // VirtualNode類型,isVnode()檢測標(biāo)志上面就是一個VirtualNode的完整結(jié)構(gòu),包含了特定的標(biāo)簽名、屬性、子節(jié)點等。
VText
VText是一個純文本的節(jié)點,對應(yīng)的是HTML中的純文本。因此,這個屬性也只有text這一個字段。
function VirtualText(text) {
this.text = String(text)
}
VirtualText.prototype.version = version
VirtualText.prototype.type = "VirtualText"VPatch
VPatch是表示需要對Virtual DOM執(zhí)行的操作記錄的數(shù)據(jù)結(jié)構(gòu)。它位于vnode/vpatch.js文件中。我們來看下里面的具體代碼:
// 定義了操作的常量,如Props變化,增加節(jié)點等
VirtualPatch.NONE = 0
VirtualPatch.VTEXT = 1
VirtualPatch.VNODE = 2
VirtualPatch.WIDGET = 3
VirtualPatch.PROPS = 4
VirtualPatch.ORDER = 5
VirtualPatch.INSERT = 6
VirtualPatch.REMOVE = 7
VirtualPatch.THUNK = 8
module.exports = VirtualPatch
function VirtualPatch(type, vNode, patch) {
this.type = Number(type) //操作類型
this.vNode = vNode //需要操作的節(jié)點
this.patch = patch //需要操作的內(nèi)容
}
VirtualPatch.prototype.version = version
VirtualPatch.prototype.type = "VirtualPatch"其中常量定義了對VNode節(jié)點的操作。例如:VTEXT就是增加一個VText節(jié)點,PROPS就是當(dāng)前節(jié)點有Props屬性改變。
Virtual DOM的Diff算法
了解了虛擬DOM中的三個結(jié)構(gòu),那我們下面來看下Virtual DOM的Diff算法。
這個Diff算法是Virtual DOM中最核心的一個算法。通過輸入初始狀態(tài)A(VNode)和最終狀態(tài)B(VNode),這個算法可以得到從A到B的變化步驟(VPatch),根據(jù)得到的這一連串步驟,我們就可以知道哪些節(jié)點需要新增,哪些節(jié)點需要刪除,哪些節(jié)點的屬性有了變化。在這個Diff算法中,又分成了三部分:
VNode的Diff算法Props的Diff算法Vnode children的Diff算法
下面,我們就來一個一個介紹這些Diff算法。
VNode的Diff算法
該算法是針對于單個VNode的比較算法。它是用于兩個樹中單個節(jié)點比較的場景。具體算法如下,如果不想直接閱讀源碼的同學(xué)也可以翻到下面,會有相關(guān)代碼流程說明供大家參考:
function walk(a, b, patch, index) {
if (a === b) {
return
}
var apply = patch[index]
var applyClear = false
if (isThunk(a) || isThunk(b)) {
thunks(a, b, patch, index)
} else if (b == null) {
// If a is a widget we will add a remove patch for it
// Otherwise any child widgets/hooks must be destroyed.
// This prevents adding two remove patches for a widget.
if (!isWidget(a)) {
clearState(a, patch, index)
apply = patch[index]
}
apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b))
} else if (isVNode(b)) {
if (isVNode(a)) {
if (a.tagName === b.tagName &&
a.namespace === b.namespace &&
a.key === b.key) {
var propsPatch = diffProps(a.properties, b.properties)
if (propsPatch) {
apply = appendPatch(apply,
new VPatch(VPatch.PROPS, a, propsPatch))
}
apply = diffChildren(a, b, patch, apply, index)
} else {
apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
applyClear = true
}
} else {
apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b))
applyClear = true
}
} else if (isVText(b)) {
if (!isVText(a)) {
apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
applyClear = true
} else if (a.text !== b.text) {
apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b))
}
} else if (isWidget(b)) {
if (!isWidget(a)) {
applyClear = true
}
apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b))
}
if (apply) {
patch[index] = apply
}
if (applyClear) {
clearState(a, patch, index)
}
}代碼具體邏輯如下:
如果a和b這兩個VNode全等,則認為沒有修改,直接返回。
如果其中有一個是thunk,則使用thunk的比較方法thunks。
如果a是widget且b為空,那么通過遞歸將a和它的子節(jié)點的remove操作添加到patch中。
如果b是VNode的話,
如果a也是VNode,那么比較tagName、namespace、key,如果相同則比較兩個VNode的Props(用下面提到的diffProps算法),同時比較兩個VNode的children(用下面提到的diffChildren算法);如果不同則直接將b節(jié)點的insert操作添加到patch中,同時將標(biāo)記位置為true。
如果a不是VNode,那么直接將b節(jié)點的insert操作添加到patch中,同時將標(biāo)記位置為true。
如果b是VText的話,看a的類型是否為VText,如果不是,則將VText操作添加到patch中,并且將標(biāo)志位設(shè)置為true;如果是且文本內(nèi)容不同,則將VText操作添加到patch中。
如果b是Widget的話,看a的類型是否為widget,如果是,將標(biāo)志位設(shè)置為true。不論a類型為什么,都將Widget操作添加到patch中。
檢查標(biāo)志位,如果標(biāo)識為為true,那么通過遞歸將a和它的子節(jié)點的remove操作添加到patch中。
這就是單個VNode節(jié)點的diff算法全過程。這個算法是整個diff算法的入口,兩棵樹的比較就是從這個算法開始的。
看完了單個VNode節(jié)點的diff算法,我們來看下上面提到的diffProps算法。
該算法是針對于兩個比較的VNode節(jié)點的Props比較算法。它是用于兩個場景中key值和標(biāo)簽名都相同的情況。具體算法如下,如果不想直接閱讀源碼的同學(xué)也可以翻到下面,會有相關(guān)代碼流程說明供大家參考:
function diffProps(a, b) {
var diff
for (var aKey in a) {
if (!(aKey in b)) {
diff = diff || {}
diff[aKey] = undefined
}
var aValue = a[aKey]
var bValue = b[aKey]
if (aValue === bValue) {
continue
} else if (isObject(aValue) && isObject(bValue)) {
if (getPrototype(bValue) !== getPrototype(aValue)) {
diff = diff || {}
diff[aKey] = bValue
} else if (isHook(bValue)) {
diff = diff || {}
diff[aKey] = bValue
} else {
var objectDiff = diffProps(aValue, bValue)
if (objectDiff) {
diff = diff || {}
diff[aKey] = objectDiff
}
}
} else {
diff = diff || {}
diff[aKey] = bValue
}
}
for (var bKey in b) {
if (!(bKey in a)) {
diff = diff || {}
diff[bKey] = b[bKey]
}
}
return diff
}代碼具體邏輯如下:
遍歷a對象。
b,則將此值存儲下來,value賦值為undefined。b對象的值;如果b對應(yīng)的value是hook的話,記錄b的值。b對應(yīng)的value進行記錄。b對象,將所有a對象中不存在的key值對應(yīng)的對象都記錄下來。整個算法的大致流程如下,因為比較簡單,就不畫相關(guān)流程圖了。如果邏輯有些繞的話,可以配合代碼食用,效果更佳。
下面讓我們來看下最后一個算法,就是關(guān)于兩個VNode節(jié)點的children屬性的diffChildren算法。這個個diff算法分為兩個部分,第一部分是將變化后的結(jié)果b的children進行順序調(diào)整的算法,保證能夠快速的和a的children進行比較;第二部分就是將a的children與重新排序調(diào)整后的b的children進行比較,得到相關(guān)的patch。下面,讓我們一個一個算法來看。
該算法的作用是將b節(jié)點的children數(shù)組進行調(diào)整重新排序,讓a和b兩個children之間的diff算法更加節(jié)約時間。具體代碼如下:
function reorder(aChildren, bChildren) {
// O(M) time, O(M) memory
var bChildIndex = keyIndex(bChildren)
var bKeys = bChildIndex.keys // have "key" prop,object
var bFree = bChildIndex.free //don't have "key" prop,array
// all children of b don't have "key"
if (bFree.length === bChildren.length) {
return {
children: bChildren,
moves: null
}
}
// O(N) time, O(N) memory
var aChildIndex = keyIndex(aChildren)
var aKeys = aChildIndex.keys
var aFree = aChildIndex.free
// all children of a don't have "key"
if (aFree.length === aChildren.length) {
return {
children: bChildren,
moves: null
}
}
// O(MAX(N, M)) memory
var newChildren = []
var freeIndex = 0
var freeCount = bFree.length
var deletedItems = 0
// Iterate through a and match a node in b
// O(N) time,
for (var i = 0 ; i < aChildren.length; i++) {
var aItem = aChildren[i]
var itemIndex
if (aItem.key) {
if (bKeys.hasOwnProperty(aItem.key)) {
// Match up the old keys
itemIndex = bKeys[aItem.key]
newChildren.push(bChildren[itemIndex])
} else {
// Remove old keyed items
itemIndex = i - deletedItems++
newChildren.push(null)
}
} else {
// Match the item in a with the next free item in b
if (freeIndex < freeCount) {
itemIndex = bFree[freeIndex++]
newChildren.push(bChildren[itemIndex])
} else {
// There are no free items in b to match with
// the free items in a, so the extra free nodes
// are deleted.
itemIndex = i - deletedItems++
newChildren.push(null)
}
}
}
var lastFreeIndex = freeIndex >= bFree.length ?
bChildren.length :
bFree[freeIndex]
// Iterate through b and append any new keys
// O(M) time
for (var j = 0; j < bChildren.length; j++) {
var newItem = bChildren[j]
if (newItem.key) {
if (!aKeys.hasOwnProperty(newItem.key)) {
// Add any new keyed items
// We are adding new items to the end and then sorting them
// in place. In future we should insert new items in place.
newChildren.push(newItem)
}
} else if (j >= lastFreeIndex) {
// Add any leftover non-keyed items
newChildren.push(newItem)
}
}
var simulate = newChildren.slice()
var simulateIndex = 0
var removes = []
var inserts = []
var simulateItem
for (var k = 0; k < bChildren.length;) {
var wantedItem = bChildren[k]
simulateItem = simulate[simulateIndex]
// remove items
while (simulateItem === null && simulate.length) {
removes.push(remove(simulate, simulateIndex, null))
simulateItem = simulate[simulateIndex]
}
if (!simulateItem || simulateItem.key !== wantedItem.key) {
// if we need a key in this position...
if (wantedItem.key) {
if (simulateItem && simulateItem.key) {
// if an insert doesn't put this key in place, it needs to move
if (bKeys[simulateItem.key] !== k + 1) {
removes.push(remove(simulate, simulateIndex, simulateItem.key))
simulateItem = simulate[simulateIndex]
// if the remove didn't put the wanted item in place, we need to insert it
if (!simulateItem || simulateItem.key !== wantedItem.key) {
inserts.push({key: wantedItem.key, to: k})
}
// items are matching, so skip ahead
else {
simulateIndex++
}
}
else {
inserts.push({key: wantedItem.key, to: k})
}
}
else {
inserts.push({key: wantedItem.key, to: k})
}
k++
}
// a key in simulate has no matching wanted key, remove it
else if (simulateItem && simulateItem.key) {
removes.push(remove(simulate, simulateIndex, simulateItem.key))
}
}
else {
simulateIndex++
k++
}
}
// remove all the remaining nodes from simulate
while(simulateIndex < simulate.length) {
simulateItem = simulate[simulateIndex]
removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key))
}
// If the only moves we have are deletes then we can just
// let the delete patch remove these items.
if (removes.length === deletedItems && !inserts.length) {
return {
children: newChildren,
moves: null
}
}
return {
children: newChildren,
moves: {
removes: removes,
inserts: inserts
}
}
}下面,我們來簡單介紹下這個排序算法:
a和b中的children是否擁有key字段,如果沒有,直接返回b的children數(shù)組。如果存在,初始化一個數(shù)組newChildren,遍歷a的children元素。
move操作patch(即remove+insert)。move操作列表。通過上面這個排序算法,我們可以得到一個新的b的children數(shù)組。在使用這個數(shù)組來進行比較厚,我們可以將兩個children數(shù)組之間比較的時間復(fù)雜度從o(n^2)轉(zhuǎn)換成o(n)。具體的方法和效果我們可以看下面的DiffChildren算法。
function diffChildren(a, b, patch, apply, index) {
var aChildren = a.children
var orderedSet = reorder(aChildren, b.children)
var bChildren = orderedSet.children
var aLen = aChildren.length
var bLen = bChildren.length
var len = aLen > bLen ? aLen : bLen
for (var i = 0; i < len; i++) {
var leftNode = aChildren[i]
var rightNode = bChildren[i]
index += 1
if (!leftNode) {
if (rightNode) {
// Excess nodes in b need to be added
apply = appendPatch(apply,
new VPatch(VPatch.INSERT, null, rightNode))
}
} else {
walk(leftNode, rightNode, patch, index)
}
if (isVNode(leftNode) && leftNode.count) {
index += leftNode.count
}
}
if (orderedSet.moves) {
// Reorder nodes last
apply = appendPatch(apply, new VPatch(
VPatch.ORDER,
a,
orderedSet.moves
))
}
return apply
}通過上面的重新排序算法整理了以后,兩個children比較就只需在相同下標(biāo)的情況下比較了,即aChildren的第N個元素和bChildren的第N個元素進行比較。然后較長的那個元素做insert操作(bChildren)或者remove操作(aChildren)即可。最后,我們將move操作再增加到patch中,就能夠抵消我們在reorder時對整個數(shù)組的操作。這樣只需要一次便利就得到了最終的patch值。
以上是虛擬DOM如何實現(xiàn)的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
新聞名稱:虛擬DOM如何實現(xiàn)-創(chuàng)新互聯(lián)
文章出自:http://www.chinadenli.net/article34/pdjse.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供域名注冊、網(wǎng)站維護、外貿(mào)建站、服務(wù)器托管、關(guān)鍵詞優(yōu)化、微信公眾號
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容