最近研究 virtual dom 死了大概十几亿脑细胞…
前几天在juejin上看这篇文章,按照作者的思路实现了一遍,写的很好,主要是 virtual dom 思路。这里我就不再详述了,原文很详细。
这里我讲一下我碰到的问题,删除节点会有问题,且没有实现增加节点的功能,特别是删除节点的逻辑,下面是代码:
childNodes.forEach(child => walk(child));
if (current) {
doPatch(node, current); // 打上补丁
}
如果删除节点,不管是先执行子元素的递归还是先操作元素本身,都会出现问题,循环的会出现index不匹配的问题,我怎么改都有问题,感觉完全没有思路。
后面有个想法,有没有可能不通过index的方式去匹配,通过对象去绑定。正好最近在看 WeakMap,它的 key值是对象而且是弱引用,而且它不会阻扰垃圾回收机制,换句话说如果key值被删了,那key值所关联的数据也会被释放掉。如果是key值是节点,如果元素被删除了,WeakMap key值会被回收,关键的数据也同样被释放
// html
<div>
<p id='p'>p</p>
</div>
//javascript
const wm = new WeakMap()
wm.set(document.querySelector('#p'), '111')
console.log(wm.get(document.querySelector('#p'))); // 11
document.querySelector('#p').parentNode.removeChild(document.querySelector('#p')); // 元素被清除
console.log(wm.get(document.querySelector('#p'))); // undefined
上面的代码中,元素被移除后,无法通过key值去获取数据
WeakMap key值适合可能会消失的对象,因为不会影响垃圾回收
回到diff算法,如果把dom元素设置为key值,元素对应的对象为value。在创建元素的时候将元素与对象进行关联,在进行diff的时候将diff类型绑定到元素上,patches的时候通过WeakMap获取对应的diff类型,最后执行补丁修改
// element.js
function render(dom, wm) {
let el = document.createElement(dom.type)
wm.set(el, dom)
for (let key in dom.props) {
setAttrs(el, key, dom.props[key])
}
dom.children.forEach(child => {
child = child instanceof Element ? render(child, wm) : createTextNode(child, wm)
el.appendChild(child)
})
return el
}
// diff.js
function walk(oldNode, newNode, parent) {
if (!newNode) {
oldNode.diff = [{ type: 'REMOVE' }]
} else if (!oldNode) {
if (!parent.diff) {
parent.diff = []
}
parent.diff.push({ type: 'ADD', newNode })
} else if (isStringOrNumber(oldNode) && isStringOrNumber(newNode)) {
if (oldNode !== newNode) {
if (!parent.diff) {
parent.diff = []
}
parent.diff.push({ type: 'TEXT', text: newNode })
}
} else if (oldNode.type === newNode.type) {
let attr = diffAttr(oldNode.props, newNode.props)
if (Object.keys(attr).length > 0) {
oldNode.diff = [{ type: 'ATTR', attr }]
}
diffChildren(oldNode.children, newNode.children, oldNode)
} else {
oldNode.diff = [{ type: 'REPLACE', newNode }]
}
}
// patches.js
function(node, wm) {
const virtualDom = wm.get(node) || {}
const { diff } = virtualDom
if (diff) {
doPatch(node, diff, wm)
}
if (!isInPage(node)) {
return
}
...
}