淺談Vue?DIFF
不帶 key 的操作
對(duì)于不帶 key 的情況,我們的操作略顯原始,整個(gè)流程如下:
- 首先獲取 oldChildren 和 newChildren 的長(zhǎng)度
- 如果說(shuō)兩者一樣長(zhǎng)的話
就從 tag/children 逐個(gè)進(jìn)行比對(duì),如果相同,則進(jìn)行保留,如果不同,則進(jìn)行刪除,并且重新進(jìn)行掛載。
- 如果說(shuō)兩者不一樣長(zhǎng)的話
如果 newChildren 更長(zhǎng),就在進(jìn)行如上操作之后,再?gòu)?newChildren 多處的部分開始,逐個(gè)進(jìn)行添加。
如果 oldChildren 更長(zhǎng),就在進(jìn)行如上操作之后,再?gòu)?oldChildren 多出的部分開始,逐個(gè)進(jìn)行刪除。
代碼如下:
const patch = (n1, n2) => { if(n1.tag !== n2.tag) { const n1ElParent = n1.el.parentElement; n1ElParent.removeChild(n1.el); mount(n2, n1ElParent) } else { // 取出 element 對(duì)象,并且在 n2 中進(jìn)行保存 const el = n1.el = n2.el // 為了獲取 value const oldProps = n1.props || [] const newProps = n2.props || [] // 遍歷新對(duì)象 for(const key in newProps) { const oldValue = oldProps[key] const newValue = newProps[key] if(oldValue != newValue) { if(key.startsWith("on")) { el.addEventListener(key.slice(2).toLowerCase(), newValue); } else { el.setAttribute(key, newValue); } } } // 刪除舊的 props for(const key in oldProps) { if(key.startsWith("on")) { const value = oldProps[key]; // removeEventListener(type, listener) // type ==> 一個(gè)字符串,表示需要移除的事件類型 | listener ==> 需要從目標(biāo)事件中移除的事件處理函數(shù)。 el.removeEventListener(key.slice(2).toLowerCase(), value) } if(!(key in newProps)) { el.removeAttribute(key) } } // 3. 處理children const oldChildren = n1.children || [] const newChildren = n2.children || [] // 如果新的 node 的 children 只是一個(gè)字符串,那直接覆蓋就可以了 if (typeof newChildren === 'string') { // 如果舊的也是字符串,那就判斷一下是不是一樣的,然后覆蓋一下 if (typeof oldChildren === 'string') { if (newChildren != oldChildren) { el.textContent = newChildren } } else { // 如果新的是很多很多東西,我直接覆蓋就可以了 el.innerHTML = newChildren } } else { // 這種情況新的 node 并不是字符串,是數(shù)組啥的 // 然后判斷一下舊的的情況 // 如果是字符串 if(typeof oldChildren === 'string') { el.innerHTML = "" newChildren.forEach((item) => { mount(item, el) }) } else { // 如果都不是字符串的話 // 拿到共同長(zhǎng)度 const commonLength = Math.min(oldChildren.length, newChildren.length) for(let i=0;i<commonLength;i++) { patch(oldChildren[i], newChildren[i]) } // 如果新的節(jié)點(diǎn)的子節(jié)點(diǎn)多一些的話,那就全部掛載上去 if(newChildren.length > oldChildren.length) { newChildren.slice(commonLength).forEach((item) => { mount(item, el) }) } // 如果舊的節(jié)點(diǎn)的子節(jié)點(diǎn)多一些,那就一個(gè)個(gè)刪除! if(newChildren.length > oldChildren.length) { oldChildren.slice(commonLength).forEach((item) => { el.removeChild(item) }) } } } } }
帶 key 的操作
這個(gè)就比較靈性了。
簡(jiǎn)單 DIFF
首先我們還是正常進(jìn)行比對(duì)
我們開兩個(gè) for 循環(huán),外層是 newChildren ,內(nèi)層是 oldChildren,如果兩者的 key 相同,我們就可以進(jìn)行到下一步了 ==> 找到需要進(jìn)行移動(dòng)的元素。
Vue是如何找到需要進(jìn)行移動(dòng)的元素
我們會(huì)創(chuàng)建一個(gè) lastIndex 參數(shù),用來(lái)標(biāo)記 key 相同的情況的最大的 Index,大致邏輯是:
if (j < lastIndex) { // 進(jìn)行 patch 操作 } else { lastIndex = i }
所以在我們的 j 小于 lastIndex 的時(shí)候,我們就知道,該移動(dòng)了~
Vue是如何移動(dòng)元素的
我們知道 j(舊的vnode Index) < lastIndex 的 case 我們要進(jìn)行移動(dòng)
我們首先要拿到 newChildren[i - 1](新 vnode 的 i - 1 的值),當(dāng)前新節(jié)點(diǎn)的前一個(gè) vnode,然后進(jìn)行移動(dòng),最后直接 insert 就好了。
Vue是如何進(jìn)行新增元素的
我們會(huì)創(chuàng)建一個(gè) find 參數(shù),用來(lái)判斷這個(gè)節(jié)點(diǎn)是不是新節(jié)點(diǎn)
如果是舊節(jié)點(diǎn),我們就走移動(dòng)邏輯
如果是新節(jié)點(diǎn),我們就在后面新增,這里有一個(gè)問(wèn)題,就是我加在誰(shuí)的后面
如果 newChildren[i - 1] 為 null,我們就直接掛載到第一個(gè)節(jié)點(diǎn)上
如果 newChildren[i - 1] 不為 null,我們就把新節(jié)點(diǎn)掛載到 newChildren[i - 1] 上
Vue 是如何刪除多余的舊元素的
我們遍歷完新元素之后,還需要遍歷一遍舊元素,如果說(shuō)當(dāng)前舊元素的 key 在新元素中無(wú)對(duì)應(yīng),那就摘出來(lái),刪除它。
雙指針 DIFF
流程如下:
- 首先是 oldStart <- newStart
- 然后是 oldEnd <- newEnd
- 然后是 oldEnd <- newStart
- 最后是 newEnd <- newStart
這樣看起來(lái)是沒(méi)有問(wèn)題的,但是如果沒(méi)有一個(gè)匹配到呢,那是不是就有問(wèn)題啦?
在這種情況下,我們應(yīng)該用 newStart 的 key 和 oldChildren 中的所有 key 做比較,得到 idx ,如果 idx > 0 ,則我們就做移動(dòng),然后把這個(gè)位置設(shè)置為 undefined,后面如果碰到了這個(gè)位置就直接跳過(guò)去。
快速 DIFF
快速 DIFF 做了一個(gè)預(yù)處理,把相同的東西提前打補(bǔ)丁處理了。
分別從前往后開循環(huán)以及從后往前開循環(huán),把相同的處理掉,不同的留下來(lái)。
如何新增?
如圖所示,如果我們前后兩輪循環(huán)遍歷完之后,newEnd <= j,就說(shuō)明有新增,我們找到 oldEnd,while(j >= newEnd)在它的后面一個(gè)接一個(gè)的掛載進(jìn)去就可以了。
如何刪除?
如圖所示,如果我們前后兩輪循環(huán)遍歷完之后,oldEnd <= j,就說(shuō)明有新增,我們找到 oldEnd,while (j <= oldEnd) 一路刪除上去。
我們剛才看到的兩種都是理想情況,那如果預(yù)處理之后情況依舊復(fù)雜呢?
function patchKeyedChildren(n1, n2, container) { const newChildren = n2.children const oldChildren = n1.children // 更新相同的前置節(jié)點(diǎn) // 省略部分代碼 // 更新相同的后置節(jié)點(diǎn) // 省略部分代碼 if (j > oldEnd && j <= newEnd) { // 省略部分代碼 } else if (j > newEnd && j <= oldEnd) { // 省略部分代碼 } else { // 增加 else 分支來(lái)處理非理想情況 } }
這個(gè)時(shí)候我們?cè)匍_一個(gè) source
數(shù)組
source
數(shù)組將用來(lái)存儲(chǔ)新的一組子節(jié)點(diǎn)中的節(jié)點(diǎn)在舊的一組子節(jié)點(diǎn)中的位置索引,后面將會(huì)使用它計(jì)算出一個(gè)最長(zhǎng)遞增子序列,并用于輔助完成 DOM
移動(dòng)的操作
由于之前我們要尋找兩個(gè) key
相等的值需要使用兩個(gè) for
循環(huán),這樣就會(huì)導(dǎo)致時(shí)間復(fù)雜度相當(dāng)?shù)母?,所以我們?cè)谶@里使用了一個(gè) key-index[新數(shù)組的索引]的映射表,這樣我們只需要做一次便利就可以了,不需要去做嵌套,時(shí)間復(fù)雜度從 O(N^2) -> O(N)。
如何判斷是否移動(dòng)?
和之前簡(jiǎn)單 DIFF 的流程很像,之前我們是找最大索引值,如果索引小的都需要移動(dòng),索引更大則更新 lastIndex
我們?cè)谥v解簡(jiǎn)單 Diff 算法時(shí)曾提到,如果在遍歷過(guò)程中遇到的索引值呈現(xiàn)遞增趨勢(shì),則說(shuō)明不需要移動(dòng)節(jié)點(diǎn),反之則需要。
所以這里的 moved 為 true 我們就可以移動(dòng)了。
如何移動(dòng)元素通過(guò) source 找到最長(zhǎng)遞增子序列
通過(guò)最長(zhǎng)遞增子序列得到一個(gè)子序列索引的數(shù)組,最長(zhǎng)遞增子序列不需要移動(dòng)
然后做處理,對(duì)于新節(jié)點(diǎn),也就是 source 中是 -1 的情況,我們直接新增就好了,如果不是新節(jié)點(diǎn),我們就判斷兩者是否相同,相同則移動(dòng)最長(zhǎng)遞增子序列中的索引,不同則移動(dòng) source 中的索引。
以下是對(duì)于索引中兩者不相同的處理。
到此這篇關(guān)于淺談Vue DIFF的文章就介紹到這了,更多相關(guān)Vue DIFF內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue服務(wù)端渲染頁(yè)面緩存和組件緩存的實(shí)例詳解
本篇文章給大家?guī)?lái)的內(nèi)容是關(guān)于vue服務(wù)端渲染頁(yè)面緩存和組件緩存的介紹(代碼),有一定的參考價(jià)值,有需要的朋友可以參考一下2018-09-09vue2.x中monaco-editor的使用說(shuō)明
這篇文章主要介紹了vue2.x中monaco-editor的使用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08vue組件props屬性監(jiān)聽不到值變化問(wèn)題
這篇文章主要介紹了vue組件props屬性監(jiān)聽不到值變化問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04vue實(shí)現(xiàn)二級(jí)導(dǎo)航欄效果
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)二級(jí)導(dǎo)航欄效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10基于Vue3實(shí)現(xiàn)列表虛擬滾動(dòng)效果
這篇文章主要為大家介紹了如何利用Vue3實(shí)現(xiàn)列表虛擬滾動(dòng)效果,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定價(jià)值,需要的可以參考一下2022-04-04