dom-diff簡易版實現
一、建立虛擬dom
利用 create-react-app
快速建立一個專案模板;
刪掉src下的原始檔,替換成 index.js
首先我們先要用一個物件定義一個虛擬DOM的資料結構:
Element {
type: 'ul',
props: {
class: 'list'
},
children: [
Element{
type: 'li',
props: {
class: 'item'
},
children: ['a']
}
]
}
複製程式碼
開始碼程式碼實現虛擬dom的方法實現。
瀏覽器上檢視列印的日誌資訊,如下:既然虛擬DOM方法已經寫好,下一步就要將這個虛擬dom插入到頁面中,那我們可以專門寫一個渲染真實節點的方法render
先遍歷最外層ul
的type
和props
兩個屬性
注意:input
標籤的value
屬性 還有所有標籤的style
屬性
好了,接下來就是繼續遍歷children
屬性,此時children
會有兩種情況
- 如果是文字 直接插入;
- 如果是子元素,遞迴遍歷直到最終的結果是文字;
下一步我們將這個實際的DOM元素結構插入到頁面中
完成第一部分。
二、實現dom-diff演算法
dom-diff
演算法就是在兩棵抽象語法樹的同一位置採用先序的深度遍歷演算法做比較,同時用補丁的形式記錄需要更新的節點位置。
若type
不一致直接替換當前節點以及當前節點下的子節點;
如果兩個父節點一致,則從左往後遍歷子節點,若子節點一致,遍歷子節點下的子節點,依次遞迴。
補丁包的定義規則如下:
1. 屬性不同(type: 'ATTRS', attrs)
2. 新的節點被刪除了 (type: 'REMOVE', index: xxxx)
3. 節點型別不同/新增 (type: 'REPLACE', newNode)
4. 僅僅是文字變化(type: 'TEXT', text)
複製程式碼
新建一個dom-diff.js
,專門處理diff
演算法
手動呼叫diff
方法(react中呼叫diff
演算法是在觸發setState
之後)
兩個虛擬dom結構如下:
先處理type
相同,屬性不同的情況。
發現控制檯已經列印到屬性變化的補丁包,最後我們把屬性的小補丁包存放到最外層的大補丁包中
// 補丁包 存放兩個虛擬dom的差異部分
let patchs = {}
// 放到最外層的大補丁包中
if (currentPatchs.length > 0) {
patchs[index] = currentPatchs
}
複製程式碼
好了 相同型別的父節點一樣,在屬性比較完成之後,就需要比較children
的屬性是否有變化
比較children
屬性內部元素是否變化,利用遞迴去遍歷
let globalIndex = 0
function diffChildren (oldChildrens, newChildrens) {
oldChildrens.forEach((child, idx) => {
walk(child, newChildrens[idx], ++globalIndex)
})
}
複製程式碼
如果一開始type
型別不相同不需要再去比較,直接用新節點替換老節點即可;
// type不一致
currentPatchs.push({
type: TYPES.REPLACE,
newNode: newTree
})
複製程式碼
相容並處理好各種情況,比如:新節點不存在的情況,新節點增加,新節點型別改變,新節點文字改變以及新節點的屬性變化等情況;
最終拿到所有與舊節點有差異的物件放入patchs這樣的一個補丁物件中。
補丁包的key
就是對應新節點有變化的資料位置。
三、 打補丁更新檢視
最後一步將補丁的差異物件與現有虛擬DOM節點遍歷進行一一比較與替換。
根據之前定義的不同補丁物件結構依次處理
大功告成!
這只是diff演算法的一個簡易實現,還存在一些複雜情況處理的情況以及還有很多演算法上面優化的方案,不過已經讓我們大概瞭解了diff
演算法的原理。
如有筆誤或者其他實現不對的地方,還望大家指出,謝謝!
具體程式碼可以參考github連結檢視:dom-diff-demo