您好,登錄后才能下訂單哦!
本篇內容介紹了“Vue中Virtual DOM和Diff原理及實現方法是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
vdom即虛擬DOM,將DOM映射為JS對象,結合diff算法更新DOM
以下為DOM
<div id="app"> <div class="home">home</div> </div>
映射成VDOM
{ tag: 'div', attrs: { id: 'app' }, children: [ { tag: 'div', attrs: { class: 'home' }, children: [ { tag: undefined, attrs: undefined, text: 'home', children: undefined } ] } ] }
通過這個vdom實現簡單的render
函數,可以通過js操作修改dom
<template> <div id="app"> <div v-for="item in arr">{{ item.name }} : {{ item.id }}</div> </div> <button id="btn">reRender</button> </template>
let app = document.getElementById('app') let data = { arr: [ { name: 'a', id: 1 }, { name: 'b', id: 2 }, { name: 'c', id: 3 }, ] } function render(data) { app.innerHtml = '' let children = [] data.forEach(item => { let el = document.createElement("div") el.innerHtml = `${ item.name } : ${item.id}` app.appendChild(el) }) } // test render(data.arr) // 首次渲染 let btn = document.getElementById('btn') btn.onClick = () => { data.arr[2].id++ // 修改關聯數據 render(data.arr) // 重新渲染:暴力刷新DOM,沒有diff,實際上只用更新最后一個div就行 }
使用snabbdom實現VDOM
snabbldom是簡易實現vdom功能的庫,有兩個核心api:h函數和patch函數
h(tag, attrs, children) // 創建vnode patch(vnode, newVnode) // 對vnode進行diff后掛載到真實dom上
結合h
和patch
實現render
渲染函數
let app = document.getElementById('app') let vnode; function render(data) { let newVnode = h('div', { class: 'wrap' }, data.forEach(item => { return h('div', {}, `${item.name} : ${item.id}`) }) ) patch(vnode, newVnode) vnode = newVnode } render(data.arr) // 首次渲染 let btn = document.getElementById('btn') btn.onClick = () => { data.arr[2].id++ // 修改關聯數據 render(data.arr) // 重新渲染:在patch函數里經過vdom的diff后再掛載到真實dom,這里只更新最后一個div }
為了盡量減少DOM操作,需要通過diff對比新舊vnode,針對更改的地方進行更新DOM,而非替換整個DOM
大體思路為:
對新舊兩個節點調用patch
函數
進來先判斷兩個節點是否為同一類型,具體是對比key
、tag
、data
等屬性
若不為同一類型,那么基于新節點創建dom之后作替換
若為同一類型,那么調用patchVnode
函數
進來先判斷兩個節點是文本節點的話,那么就作文本內容替換
否則判斷是否都有子節點,都有的話調用updateChildren
函數,通過首尾四個指針
對子節點數組進行diff更新;若舊節點有子節點,新節點沒有,這時就刪除子節點;若舊節點無子節點,新節點有,這時基于新節點創建dom作替換即可
通過createElment
函數,將VDOM轉為真實DOM
function createElement(vnode) { if(vnode.text) return document.createTextNode(vnode) // 文本節點 let { tag, attrs, children } = vnode let el = document.createElement(tag) // tag for(let key of attrs){ // attrs el.setAttribute(key, attrs[key]) } children.forEach(childVnode => { // children el.appendChild(createElement(childVnode)) }) vnode.el = el return el }
通過patch
函數,執行diff更新操作
判斷vnode
和newVnode
是否為同一類型節點,是則繼續遞歸對比子節點,否則直接替換
function patch(vnode, newVnode) { if (isSameNode(vnode, newVnode)) patchVnode(vnode, newVnode) else replaceVnode(vnode, newVnode) } function replaceVnode(vnode, newVnode) { let el = vnode.el // 舊節點 let parentEl = api.getParentNode(el) // 獲取父節點 api.insertBefore(parentEl, createElement(newVnode), api.getNextSibling(el)) // 插入新節點 api.removeChild(parentEl, el) // 刪除舊節點 } function isSameNode(vnode, newVnode) { return ( vnode.key == newVnode.key && // key是否相同 vnode.tag == newVnode.tag && // tag是否相同 isDef(vnode.data) == isDef(newVnode.data) // 是否都定義了data // &&... 其他條件 ) } function patchVnode(vnode, newVnode) { let el = newVnode.el = vnode.el // 獲取當前舊節點對應的dom,并賦值給新節點的el // 1.都為文本節點,且文本不一樣 if (vnode.text && newVnode.text && vnode.text != newVnode.text) return api.setElText(el, newVnode.text) // 替換文本 let ch = vnode.children let newCh = newVnode.children if (ch && newCh) return updateChildren(el, ch, newCh) // 2.都有子節點,遞歸對比 if (ch) return api.removeChild(el) // 3.vnode有子節點,newVnode無,刪除子節點 return replaceVnode(vnode, newVnode) // 4. newNode有子節點,vnode無,替換即可 }
updateChildren
實現比較復雜,使用首尾四指針
進行vnode
和newVnode
的對比
function updateChildren(el, ch, newCh) { // 子節點下標 let l = 0 let r = ch.length - 1 let newL = 0 let newR = newCh.length - 1 // 子節點 let lNode = ch[l] let rNode = ch[r] let newLNode = newCh[newL] let newRNode = newCh[newR] while (l <= r && newL <= newR) { if (!lNode || !rNode || !newLNode || !newRNode) { // 邊界處理 if (!lNode) lNode = ch[++l] if (!rNode) rNode = ch[--r] if (!newLNode) newLNode = newCh[++newL] if (!newRNode) newRNode = newCh[--newR] continue } // 新舊子節點首尾指針對比 l*newL、r*newR、l*newR、r*newL if (isSameNode(lNode, newLNode)) { patchVnode(lNode, newLNode) lNode = ch[++l] newLNode = newCh[++newL] continue } if (isSameNode(rNode, newRNode)) { patchVnode(rNode, newRNode) rNode = ch[--r] newRNode = newCh[--newR] continue } if (isSameNode(lNode, newRNode)) { patchVnode(lNode, newRNode) api.insertBefore(el, lNode.el, api.nextSibling(rNode.el)) lNode = ch[++l] newRNode = newCh[--newR] continue } if (isSameNode(rNode, newLNode)) { patchVnode(rNode, newLNode) api.insertBefore(el, rNode.el, lNode.el) rNode = ch[--r] newLNode = newCh[++newL] continue } // 在vnode未知序列區間[l,r]生成key-idx的map表,用newLNode的key在未知序列中找到可復用的位置 if (!keyIdxMap) keyIdxMap = getKeyIdxMap(ch, l, r) // map keyIdx = keyIdxMap.get(newLNode.key) if (!keyIdx) { api.insertBefore(el, createElement(newLNode), lNode.el) } else { let nodeToMove = ch[keyIdx] patchVnode(nodeToMove, newLNode) api.insertBefore(el, nodeToMove.el, lNode.el) } newLNode = newCh[++newL] } } function getKeyIdxMap(ch, l, r) { let map = new Map() while (l <= r) map.set(ch[l].key, l++) return map }
“Vue中Virtual DOM和Diff原理及實現方法是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。