虛擬 DOM 的作用
- 維護視圖和狀態(tài)的關系
- 復雜視圖情況下提升渲染性能
- 跨平臺
? 瀏覽器平臺渲染DOM
? 服務端渲染 SSR(Nuxt.js/Next.js)
? 原生應用(Weex/React Native)
? 小程序(mpvue/uni-app)等
Snabbdom
- Vue.js 2.x 內(nèi)部使用的虛擬 DOM 就是改造的 Snabbdom ?
- 大約 200 SLOC (single line of code) 3
- 通過模塊可擴展
- 源碼使用 TypeScript 開發(fā) ?
- 最快的 Virtual DOM 之一
導入 Snabbdom
- 安裝 Snabbdom
? npm intall snabbdom@2.1.0 - 導入 Snabbdom
Snabbdom 的兩個核心函數(shù) init 和 h()
? init() 是一個高階函數(shù)腔剂,返回 patch()
? h() 返回虛擬節(jié)點 VNode射沟,這個函數(shù)我們在使用 Vue.js 的時候見過 -
文檔中導入的方式
-
實際導入的方式
parcel/webpack 4 不支持 package.json 中的 exports 字段
模塊
模塊的作用
- Snabbdom 的核心庫并不能處理 DOM 元素的屬性/樣式/事件等操骡, 可以通過注冊 Snabbdom 默認提供的模塊來實現(xiàn)
- Snabbdom 中的模塊可以用來擴展 Snabbdom的功能
- Snabbdom 中的模塊的實現(xiàn)是通過注冊全局的鉤子函數(shù)來實現(xiàn)的
官方提供的模塊
?attributes
? props
? dataset
? class
? style
? eventlisteners
- 導入需要的模塊
- init() 中注冊模塊
- h() 函數(shù)的第二個參數(shù)處使用模塊
Snabbdom 的核心
- init() 設置模塊钉稍,創(chuàng)建 patch() 函數(shù)
- 使用 h() 函數(shù)創(chuàng)建 JavaScript 對象(VNode)描述真實 DOM
- patch() 比較新舊兩個 Vnode
- 把變化的內(nèi)容更新到真實 DOM 樹
patch 整體過程分析
? patch(oldVnode, newVnode)
? 把新節(jié)點中變化的內(nèi)容渲染到真實 DOM,最后返回新節(jié)點作為下一次 處理的舊節(jié)點
? 對比新舊 VNode 是否相同節(jié)點(節(jié)點的 key 和 sel 相同)
? 如果不是相同節(jié)點四敞,刪除之前的內(nèi)容啥纸,重新渲染
? 如果是相同節(jié)點究珊,再判斷新的 VNode 是否有 text,如果有并且和 oldVnode 的 text 不同竿裂,直接更新文本內(nèi)容
? 如果新的 VNode 有 children玉吁,判斷子節(jié)點是否有變化
Diff 算法
Snbbdom 根據(jù) DOM 的特點對傳統(tǒng)的diff算法做了優(yōu)化
? DOM 操作時候很少會跨級別操作節(jié)點
? 只比較同級別的節(jié)點
執(zhí)行過程
在對開始和結束節(jié)點比較的時候,總共有四種情況
? oldStartVnode / newStartVnode (舊開始節(jié)點 / 新開始節(jié)點)
? oldEndVnode / newEndVnode (舊結束節(jié)點 / 新結束節(jié)點)
? oldStartVnode / newEndVnode (舊開始節(jié)點 / 新結束節(jié)點)
? oldEndVnode / newStartVnode (舊結束節(jié)點 / 新開始節(jié)點)
開始和結束節(jié)點
如果新舊開始節(jié)點是 sameVnode (key 和 sel 相同)
? 調(diào)用 patchVnode() 對比和更新節(jié)點
? 把舊開始和新開始索引往后移動 oldStartIdx++ / newStartIdx++
舊開始節(jié)點 / 新結束節(jié)點
? 調(diào)用 patchVnode() 對比和更新節(jié)點
? 把 oldStartVnode 對應的 DOM 元素腻异,移動到右邊进副,更新索引
為什么要移動到右邊?
舊結束節(jié)點 / 新開始節(jié)點
? 調(diào)用 patchVnode() 對比和更新節(jié)點
? 把 oldEndVnode 對應的 DOM 元素悔常,移動到左邊影斑,更新索引
為什么要移動到左邊?
非上述四種情況
- 遍歷新節(jié)點机打,使用 newStartNode 的 key 在老節(jié)點數(shù)組中找相同節(jié)點
- 如果沒有找到矫户,說明 newStartNode 是新節(jié)點
? 創(chuàng)建新節(jié)點對應的 DOM 元素,插入到 DOM 樹中 -
如果找到了
? 判斷新節(jié)點和找到的老節(jié)點的 sel 選擇器是否相同
? 如果不相同残邀,說明節(jié)點被修改了
重新創(chuàng)建對應的 DOM 元素皆辽,插入到 DOM 樹中
? 如果相同柑蛇,把 elmToMove 對應的 DOM 元素,移動到左邊
循環(huán)結束
? 當老節(jié)點的所有子節(jié)點先遍歷完 (oldStartIdx > oldEndIdx)驱闷,循環(huán)結束
? 新節(jié)點的所有子節(jié)點先遍歷完 (newStartIdx > newEndIdx)耻台,循環(huán)結束
-
oldStartIdx > oldEndIdx
如果老節(jié)點的數(shù)組先遍歷完(oldStartIdx > oldEndIdx)
? 說明新節(jié)點有剩余,把剩余節(jié)點批量插入到右邊
-
newStartIdx > newEndIdx
如果新節(jié)點的數(shù)組先遍歷完(newStartIdx > newEndIdx)
? 說明老節(jié)點有剩余空另,把剩余節(jié)點批量刪除