
一、虚拟DOM简介
什么是虚拟DOM
虚拟DOM是一种优化性能的解决方案,当某个状态发生变化时,只更新与这个状态有关的DOM节点。其本质是一个JavaScript对象,内部保存了与虚拟DOM对应的真实DOM,子节点,选择器等属性。当然,虚拟DOM只是其中一个解决方案,如在Angular中就是通过脏检查的流程。
为什么引入虚拟DOM
1.ES和 DOM是两种东西,每次连接都需要消耗性能(由于浏览器通常将DOM和ECMAScript独立实现,因此JS每次去访问DOM都会消耗大量的性能)
2.操作DOM会导致重排和重绘,重排会占用、消耗CPU; 重绘会占用、消耗GPU
(重排:当DOM的变化影响了元素的几何属性(宽和高),浏览器需要重新计算元素的几何属性,同样其他相邻元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程称为“重排”。
重绘:完成重排后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘.)
因此,我们需要尽可能减少访问DOM,虚拟DOM可以帮助我们减少对DOM的访问
二、diff算法
diff算法本质就是通过对比新旧的虚拟DOM来检查哪些节点需要更新/删除/增加等操作并访问DOM更新视图。
1.vnode
你可以将vnode理解为一个节点描述对象,他描述了怎样创建一个真实DOM节点 你可以将vnode理解成JavaScript对象版本的DOM元素
1 2 3 4 5
| function vnode(sel, data, children, text, elm) {
let key = data.key return { sel, data, children, text, elm, key } }
|
2.patch
patch函数用来将vnode渲染成真正的DOM 通过对比新旧虚拟节点进而更新视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function patch(oldVnode, newVnode) { if (oldVnode.sel == '' || oldVnode.sel == undefined) { oldVnode = vnode(oldVnode.tagName.toLocaleUpperCase(), {}, undefined, undefined, oldVnode) } if (oldVnode.sel == newVnode.sel && oldVnode.key == newVnode.key) { patchVnode(oldVnode,newVnode) } else { let nodeElm = creatElement(newVnode) oldVnode.elm.parentNode.insertBefore(nodeElm, oldVnode.elm); oldVnode.elm.parentNode.removeChild(oldVnode.elm) } }
|
3.patchVnode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function patchVnode(oldVnode, newVnode) { if (newVnode !== oldVnode) { if (newVnode.text) { if (newVnode.text !== oldVnode.text) { oldVnode.elm.innerText = newVnode.text } } if (newVnode.children) { if (oldVnode.text) { oldVnode.elm.innerHTML = '' for (let i = 0; i < newVnode.children.length; i++) { let dom = creatElement(newVnode.children[i]) oldVnode.elm.appendChild(dom) } } if (oldVnode.children) { newVnode.elm = creatElement(newVnode) console.log(newVnode); updateChildren(oldVnode.elm, oldVnode.children, newVnode.children) } } } }
|
4.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function h(sel, data, c) { if (typeof c == 'string' || typeof c == 'number') { return vnode(sel, data, undefined, c, undefined) } else if (Array.isArray(c)) { let children = [] for (let i = 0; i < c.length; i++) { if (!c[i].hasOwnProperty('sel')) { throw new Error('第' + i + '个数据中应存在sel属性') } else { children.push(c[i]) } } return vnode(sel, data, children, undefined, undefined) } else if (typeof c == 'object' && c.hasOwnProperty('sel')) { let children = [c] return vnode(sel, data, children, undefined, undefined) } }
|
5.creatElement
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function creatElement(v_node) { let domNode = document.createElement(v_node.sel) if(v_node.text !== '' && (v_node.children == undefined || v_node.children.length == 0)){ domNode.innerText = v_node.text }else if(v_node.children.length != 0){ let arr = v_node.children for(let i = 0;i<arr.length;i++){ let nodeElm = creatElement(arr[i]) domNode.appendChild(nodeElm) } } v_node.elm = domNode return v_node.elm }
|
6.updateChildren
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| function checkSameNode(a, b) { if (a.sel == b.sel && a.key == b.key) { } return a.sel == b.sel && a.key == b.key } function updateChildren(parentElm, oldC, newC) {
let newStartIdx = 0
let oldStartIdx = 0
let newEndIdx = newC.length - 1
let oldEndIdx = oldC.length - 1
let newStartVnode = newC[newStartIdx]
let oldStartVnode = oldC[oldStartIdx]
let newEndVnode = newC[newEndIdx]
let oldEndVnode = oldC[oldEndIdx]
let keyMap = null;
while (newStartIdx <= newEndIdx && oldStartIdx <= oldEndIdx) { console.log(newStartIdx, newEndIdx, oldStartIdx, oldEndIdx); if (checkSameNode(oldStartVnode, newStartVnode)) { console.log('新前 旧前') patchVnode(oldStartVnode, newStartVnode) oldStartVnode = oldC[++oldStartIdx] newStartVnode = newC[++newStartIdx] } else if (checkSameNode(oldEndVnode, newEndVnode)) { console.log('新后 旧后'); patchVnode(oldEndVnode, newEndVnode) oldEndVnode = oldC[--oldEndIdx] newEndVnode = newC[--newEndIdx] } else if (checkSameNode(oldStartVnode, newEndVnode)) { console.log('新后 旧前'); parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm); patchVnode(oldStartVnode, newEndVnode) oldStartVnode = oldC[++oldStartIdx] newEndVnode = newC[--newEndIdx]
} else if (checkSameNode(oldEndVnode, newStartVnode)) { console.log('新前 旧后'); parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm) patchVnode(oldEndVnode, newStartVnode) oldEndVnode = oldC[--oldEndIdx] newStartVnode = newC[++newStartIdx] } else { if (!keyMap) { keyMap = {} for (let i = oldStartIdx; i < oldEndIdx; i++) { key = oldC[i].key keyMap[key] = i } } let keyOldIdx = keyMap[newStartVnode.key] if(keyOldIdx == undefined){ console.log('全新的项'); parentElm.insertBefore(newStartVnode.elm,oldStartVnode.elm); }else{ patchVnode(oldC[keyOldIdx],newStartVnode) parentElm.insertBefore(oldC[keyOldIdx].elm,oldStartVnode.elm); console.log('在旧节点中存在的项 需要移动到新的位置'); } newStartVnode = newC[++newStartIdx] } } if (newStartIdx <= newEndIdx) { console.log('仍有新节点未处理'); while (newStartIdx <= newEndIdx) { parentElm.insertBefore(newEndVnode.elm,oldStartVnode.elm) newEndVnode = newC[--newEndIdx] } } if (oldStartIdx <= oldEndIdx) { console.log('仍有旧节点未处理'); while (oldStartIdx <= oldEndIdx) { console.log(oldEndVnode); parentElm.removeChild(oldEndVnode.elm) oldEndVnode = oldC[--oldEndIdx] } } }
|