snabbdom 是乙個虛擬 dom 庫,專注提供簡單、模組性的體驗,以及強大的功能和效能,據說vue的diff演算法就是參考它的。這裡記錄一下我這幾天學習snabbdom diff演算法的心得和感悟。
我是這樣理解的虛擬dom的,虛擬dom就是將真實dom抽象成乙個物件。當我們要多次操
作dom的時候,我們可以將真實dom轉換為虛擬dom,在虛擬dom中完成相應內容的更改再批量同步到真實dom上去。這樣子可以減小操作dom引起的重排次數。其中,虛擬dom的更新會遵循最小量更新,這就是diff演算法。
diff演算法就是比較兩個虛擬dom的演算法,他是廣度優先的,時間複雜度是o(n)。
在比較兩棵樹時,如果兩顆樹的頂級節點的標籤名或者key值不一樣,diff演算法就會認為這是兩顆不一樣的樹,因此他會銷毀舊的dom樹,再重新建立一棵樹。
對於同一層次的節點,會以key值作為標識來進行,同一節點只移動,不會銷毀後再建立新節點
我們可以看一下vnde的源**
export
default
function
(sel,data,children,text,elm)
;}
可以看到他是將一些引數包裝成乙個虛擬dom這,主要的屬性有:
屬性作用
sel選擇器
data
資料,包含props,class等內容
children
子節點text
文字節點
elm真實dom物件
key標識
import
from
'./vnode'
import
*as is from
'./is'
export type vnodes = vnode[
]export type vnodechildelement = vnode | string | number | undefined |
null
export type arrayorelement<
t>=t
|texport type vnodechildren = arrayorelement
function
addns
(data: any, children: vnodes | undefined, sel: string | undefined)
:void}}
}export
function
h(sel: string)
: vnode
export
function
h(sel: string, data: vnodedata |
null
): vnode
export
function
h(sel: string, children: vnodechildren)
: vnode
export
function
h(sel: string, data: vnodedata |
null
, children: vnodechildren)
: vnode
export
function
h(sel: any, b?
: any, c?
: any)
: vnode
var children: any
var text: any
var i: number
if(c !== undefined)
if(is.
array
(c))
else
if(is.
primitive
(c))
else
if(c && c.sel)
}else
if(b !== undefined && b !==
null
)else
if(is.
primitive
(b))
else
if(b && b.sel)
else}if
(children !== undefined)}if
( sel[0]
==='s'
&& sel[1]
==='v'
&& sel[2]
==='g'
&&(sel.length ===
3|| sel[3]
==='.'
|| sel[3]
==='#'))
return
vnode
(sel, data, children, text, undefined)
};
雖然h函式比較多,但是我們看到它無非做了一件事情·,將傳進來的引數構建成乙個虛擬dom
return
function
patch
(oldvnode: vnode | element, vnode: vnode)
: vnode
// 判斷是不是同乙個虛擬節點,這裡只是判斷了是不是sel和key相同if(
samevnode
(oldvnode, vnode)
)else
}for
(i =
0; i < insertedvnodequeue.length;
++i)
for(i =
0; i < cbs.post.length;
++i) cbs.post[i]()
return vnode
}}
unction createelm
(vnode: vnode, insertedvnodequeue: vnodequeue)
: node
}const children = vnode.children
const sel = vnode.sel
if(sel ===
'!')
vnode.elm = api.
createcomment
(vnode.text!)}
else
if(sel !== undefined)}}
else
if(is.
primitive
(vnode.text)
)const hook = vnode.data!
.hook
if(isdef
(hook))}
}else
return vnode.elm
}
可以看到最後將建立的dom新增到了虛擬dom的lelm屬性上
function
patchvnode
(oldvnode: vnode, vnode: vnode, insertedvnodequeue: vnodequeue)if(
isundef
(vnode.text)
)elseif(
isdef
(ch)
)elseif(
isdef
(oldch)
)elseif(
isdef
(oldvnode.text))}
else
if(oldvnode.text !== vnode.text)
// 新節點文字替換老節點文字
api.
settextcontent
(elm, vnode.text!)}
hook?
.postpatch?
.(oldvnode, vnode)
}
function
updatechildren
(parentelm: node,
oldch: vnode,
// 舊子節點
newch: vnode,
// 新子節點
insertedvnodequeue: vnodequeue)
else
if(oldendvnode ==
null
)else
if(newstartvnode ==
null
)else
if(newendvnode ==
null
)elseif(
samevnode
(oldstartvnode, newstartvnode)
)elseif(
samevnode
(oldendvnode, newendvnode)
)elseif(
samevnode
(oldstartvnode, newendvnode)
)elseif(
samevnode
(oldendvnode, newstartvnode)
)else
idxinold = oldkeytoidx[newstartvnode.key as string]
// 新前節點在舊節點中對應的下標if(
isundef
(idxinold)
)else
else
}// 新前指標移動
newstartvnode = newch[
++newstartidx]}}
if(oldstartidx <= oldendidx || newstartidx <= newendidx)
else
}}
可以看到,更新子節點是比較麻煩的,利用了四個指標,依次按照舊前結點和新前結點,舊后結點與新後結點,舊前結點與新後結點,舊后結點與新前結點來進行比較。這就是sanbbdom的核心diff演算法的大概流程了 diff演算法 diff演算法介紹
diff演算法的作用 計算出virtual dom中真正變化的部分,並只針對該部分進行原生dom操作,而非重新渲染整個頁面。傳統diff演算法 通過迴圈遞迴對節點進行依次對比,演算法複雜度達到 o n 3 n是樹的節點數,這個有多可怕呢?如果要展示1000個節點,得執行上億次比較。即便是cpu快能執...
Diff演算法研究
在unix linux的世界裡面,如果我們需要比較兩個檔案,就會用乙個比較的命令 diff。而這個diff的原理是什麼呢?在diff裡面,我們比較的兩個檔案叫做old和new,而一般是按行來比較。這裡我們可以抽象成乙個字串的比較,比如 old abcdefger new abdefereger 那麼...
diff程式的演算法
diff程式很重要,linux中的源 補丁都是diff作出來的,diff在比較兩個文字檔案的不同方面很高效,它是基於行的,diff會將兩個檔案都按照行分成若干部分,然後計算這些行每一行的校驗碼,之後的問題就是比較這兩個檔案的所有行的校驗碼序列的不同了,這就把問題歸結為了序列比對,diff用的是動態規...