Vue的Virtual DOM是其實現(xiàn)響應式和高效渲染的關(guān)鍵。而Virtual DOM的中心是VNode,這是能夠描述DOM節(jié)點和其屬性的JavaScript對象。Vue在進行渲染時會通過內(nèi)部算法創(chuàng)建整個組件樹父子關(guān)系的虛擬節(jié)點樹,其實就是VNode樹,再從這個虛擬節(jié)點樹入手開始渲染,根據(jù)差異更新DOM。但是如果不清理無用的VNode,仍然會造成內(nèi)存泄漏和性能影響。
那么VNode如何回收哪些VNode將會被回收是Vue面對的問題。Vue采用的是標記清除的算法,其流程如下:
function markVNode(node) { node.isMarked = true node.children && node.children.forEach(child =>markVNode(child)) } function sweepVNode(node) { if (!node.isMarked) { // 將需要被回收的節(jié)點的真實DOM節(jié)點從DOM樹上移除 node.parent.removeChild(node.elm) } else { // 如果標記了,則不回收并將標記去除 node.isMarked = false // 對于需要重復使用的節(jié)點,會調(diào)用這里對應的鉤子函數(shù),讓其得到重復利用 if (node.tag && !node.isStatic) { invokeHooks(node.componentInstance, 'deactivated') } } // 遞歸操作 node.children && node.children.forEach(child =>sweepVNode(child)) } function invokeHooks(vm, hook) { if (!vm) { return } vm.$children.forEach(child =>{ invokeHooks(child, hook) }) vm._inactive = true vm.$emit(`hook:${hook}`) } // 標記整棵vnode樹中的組件及其子組件所用到的vnode markVNode(vnode) // 根據(jù)標記進行清理 sweepVNode(vnode)
以上代碼演示了清理VNode的過程,首先遍歷整棵VNode樹,嘗試標記所有被使用的VNode,遍歷完成后未被標記的VNode即視為無用VNode,需要進行回收。如果一個被標記的VNode之前其實在真實DOM上渲染過,且其現(xiàn)在還需要被渲染,那么這個VNode會被重用,并且鉤子函數(shù)deactivated會被調(diào)用。在這個鉤子函數(shù)中,重用的VNode會被重新初始化。最后需要從整棵VNode樹中刪除不再使用的無用VNode。
總結(jié)下來,Vue使用標記清除的算法來清理無用VNode,包括了遍歷整棵VNode樹,標記所有被使用的VNode,最后清理未被標記的無用VNode,同時將被重用的VNode重新初始化。這樣處理過后可以減少內(nèi)存泄漏和性能影響,使得Vue的Virtual DOM能夠持續(xù)高效地運行。