問:
Virtual dom 是什么雳旅?為何會(huì)存在Virtual dom?
答:
用JS模擬DOM結(jié)構(gòu)墩弯。原因:
1.DOM操作是昂貴的,將DOM對(duì)比放在JS層苗分,JS運(yùn)行效率高
2.需要盡量減少DOM操作
我們通過個(gè)小例子來看什么是用JS模擬DOM結(jié)構(gòu):
<ul id=‘list’>
<li class=‘item’>Item 1 </li>
<li class=‘item’>Item 2 </li>
</ul>
====>通過某種方法將上面的html代碼轉(zhuǎn)為js對(duì)象
{
tag: ‘ul’,
attires: {id: ‘list’},
children: [
{
tag: ‘li’,
attires: {className: ‘item’},
children: [‘Item1']
},
{
tag: ‘li’,
attires: {className: ‘item’},
children: [‘Item2']
},
]
}
問:
vdom如何使用正驻,核心函數(shù)是什么弊攘?
答:
snabbdom是virtual dom的一種實(shí)現(xiàn)庫,附上github地址https://github.com/snabbdom/snabbdom
核心API
- h(‘<標(biāo)簽名>’, {…屬性…}, […子元素…])
- h(‘<標(biāo)簽名>’, {…屬性…}, '...')
- patch(container, vnode) =》 將vnode渲染在頁面上 初次渲染
- patch(vnode, newVnode) => rerender update
h函數(shù)可以生成vnode節(jié)點(diǎn)姑曙,這塊跟react解析jsx(React.createElement方法)很像襟交,都是生成vnode。h函數(shù)的傳參渣磷,第一個(gè)參數(shù)是標(biāo)簽名。第二個(gè)是此標(biāo)簽上的屬性授瘦,比如style, class, 事件等醋界。第三個(gè)是children,如果標(biāo)簽下面沒有子元素只有顯示文字的話只需傳字符串就可以了提完,但如果有子元素的話還需要第三個(gè)參數(shù)傳入子元素的vnode同樣使用h函數(shù)生成形纺。
而patch函數(shù)有兩種,一個(gè)是在初次渲染時(shí)調(diào)用徒欣,傳參分別是container元素和生成的vnode逐样。第二種是在頁面更新時(shí)re-render時(shí)調(diào)用,傳參分別是舊的vnode和生成的新的newVnode打肝。
具體看以下的代碼例子:
// 構(gòu)建一個(gè)虛擬節(jié)點(diǎn)脂新,h函數(shù)做的事情可以理解為上面所轉(zhuǎn)化的js對(duì)象
var vnode = h(‘ul#list’, {}, [
h(‘li.item’, {}, ‘Item1’),
h(‘li.item’, {}, ‘Item2’),
])
// 真實(shí)構(gòu)建一個(gè)container
var container = document.getElementById(‘container’)
// 把vnode渲染進(jìn)空的container容器中
patch(container, vnode)
// 模擬點(diǎn)擊button改變值
var btnChange = document.getElementById(‘btn-change’)
btnChange.addEventListener(‘click’, function(){
// 構(gòu)建新的虛擬節(jié)點(diǎn),數(shù)字不一樣
var newVnode = h(‘ul#list’, {}, [
h(‘li.item’, {}, ‘Item1’),
h(‘li.item’, {}, ‘ItemB’),
h(‘li.item’, {}, ‘Item3’),
])
// 將舊的節(jié)點(diǎn)和新的節(jié)點(diǎn)傳入patch函數(shù)粗梭,patch會(huì)diff算法計(jì)算實(shí)際改變的地方并只渲染dom改動(dòng)點(diǎn)
patch(vnode, newVnode)
})
問:
介紹一下diff算法
答:
- diff算法是linux的基礎(chǔ)命令
- vdom中應(yīng)用diff算法是為了找出需要更新的節(jié)點(diǎn)
- 實(shí)現(xiàn):patch(container, vnode)和patch(vnode, newVnode)
- 核心邏輯:createElement和updateChildren
我們來看patch(container, vnode) 的核心邏輯代碼(簡寫):
function createElement(vnode) {
var tag = vnode.tag
var attires = vnode.attrs || {}
var children = vnode.children || []
if (!tag) {
return null
}
// 創(chuàng)建元素
var elem = document.createElement(tag)
// 屬性
var attrName
for (attrName in attrs) {
if(attrs.hasOwnProperty(attrName)) {
elem.setAttribute(attrName, attrs[attrName])
}
}
//子元素
children.forEach(function(childVnode) {
// 遞歸調(diào)用 createElement 創(chuàng)建子元素
elem.appendChild(createElement(childVnode))
})
return elem
}
再來看patch(vnode, newVnode)的核心邏輯代碼争便,同樣是簡寫。断医。
function updateChildren(vnode, newVnode){
var children = vnode.children || []
var newChildren = newVnode.children || []
// 遍歷現(xiàn)有的children
children.forEach(function(child, index){
var newChild = newChildren[index]
if(newChild == null){
return
}
if(child.tag === newChild.tag){
//兩者tag一樣
updateChildren(child, newChild)
} else {
replaceNode(child, newChild)
}
})
}
PS: diff算法需要考慮的地方
- 節(jié)點(diǎn)新增和刪除
- 節(jié)點(diǎn)重新排序
- 節(jié)點(diǎn)屬性滞乙,樣式奏纪,事件綁定
- 如何極致壓榨性能