如何實現 virtual dom

2021-09-13 02:21:44 字數 4144 閱讀 9398

相信大部分前端同學之前早已無數次聽過或了解過vnode(虛擬節點),那麼什麼是vnode?vnode應該是什麼樣的?

如果不使用前端框架,我們可能會寫出這樣的頁面:

不難發現,整個文件樹的根節點只有乙個html,然後巢狀各種子標籤,如果使用某種資料結構來表示這棵樹,那麼它可能是這樣。

]},,

]}]}

但是實際開發中,整個文件樹中headscript標籤基本不會有太大的改動。頻繁互動可能改動的應當是body裡面的除script的部分,所以構建 虛擬節點樹 應當是整個 html 文件樹的乙個子樹,而這個子樹應當保持和 html 文件樹一致的資料結構。它可能是這樣。

這裡應當構建的 虛擬節點樹 應當是div#root這棵子樹:

,,,

]}

到這裡,vnode 的概念應當很清晰了,vnode 是用來表示實際 dom 節點的一種資料結構,其結構大概長這樣。

,

children:

}

一般,我們可能會這樣定義vnode

// vnode.js

export const vnode = function vnode() {}

使用react會經常寫jsx,那麼如何將jsx表示成vnode?這裡可以借助@babel/plugin-transform-react-jsx這個外掛程式來自定義轉換函式,

只需要在.babelrc中配置:

]

]}

然後在window物件上掛載乙個h函式:

// h.js

window.h = function h(tagname, attrs, ...children)

node.children = flattern(children)

return node

}

測試一下:

現在我們已經知道了如何構建vnode,接下來就是將其渲染成真正的 dom 節點並掛載。

// 將 vnode 建立為真正的 dom 節點

export function createelement(vnode)

const el = document.createelement(vnode.tagname)

setattributes(el, vnode.attrs)

return el

}// render.js

export default function render(vnode, parent)

這裡的邏輯主要為:

根據vnode.tagname建立元素

根據vnode.attrs設定元素的attributes第 2 步已經實現了vnodedom節點的轉換與掛載,那麼接下來某乙個時刻dom節點發生了變化,如何更新dom樹?顯然不能無腦解除安裝整棵樹,然後掛載新的樹,最好的辦法還是找出兩棵樹之間的差異,然後應用這些差異。

在寫diff之前,首先要定義好,要diff什麼,明確diff的返回值。比較上圖兩個 vnode,可以得出:

更換第 1、2、3 個li的內容

ul下建立兩個li,這兩個 li 為 第 4 個和 第 5 個子節點

那麼可能得返回值為:

],

"attrs":

},],

"attrs":

},],

"attrs": },,

"children": [3]

}},,"children": [4]

}}],"attrs":

}

diff的過程中,要保證節點的父節點正確,並要保證該節點在父節點 的子節點中的索引正確(保證節點內容正確,位置正確)。diff的核心流程:

/**

* diff 新舊節點差異

* @param oldvnode

* @param newvnode

*/export default function diff(oldvnode, newvnode)

} if (isnull(newvnode))

} if (isdiffrentvnode(oldvnode, newvnode))

} if (newvnode.tagname)

}}

知道了兩棵樹之前的差異,接下來如何應用這些更新?在文章開頭部分我們提到dom節點樹應當只有乙個根節點,同時diff演算法是保證了虛擬節點的位置和父節點是與dom樹保持一致的,那麼 patch 的入口也就很簡單了,從 虛擬節點的掛載點開始遞迴應用更新即可。

/**

* 根據 diff 結果更新 dom 樹

* 這裡為什麼從 index = 0 開始?

* 因為我們是使用樹去表示整個 dom 樹的,傳入的 parent 即為 dom 掛載點

* 從根節點的第乙個節點開始應用更新,這是與整個dom樹的結構保持一致的

* @param parent

* @param patches

* @param index

*/export default function patch(parent, patches, index = 0)

parent = typeof parent === 'string' ? document.queryselector(parent) : parent

const el = parent.childnodes[index]

/* eslint-disable indent */

switch (patches.type) = patches

const newel = createelement(newvnode)

break

}case replace: = patches

const newel = createelement(newvnode)

parent.replacechild(newel, el)

break

}case remove:

case update: = patches

patchattrs(el, attrs)

for (let i = 0, len = children.length; i < len; i++)

break}}}

至此,vdom的核心diffpatch都已基本實現。在測試 demo 中,不難發現diff其實已經很快了,但是patch速度會比較慢,所以這裡留下了乙個待優化的點就是patch

本文完整**均在這個倉庫。

8 1 4 Virtual DOM 的實現原理

本文為拉勾網大前端高薪訓練營第一期筆記 虛擬dom就是js物件描述dom物件,成本比真實dom低很多,因為真實dom的屬性特別多 以snabbdom為例,匯入時需要這樣寫 import from snabbdom md snabbdom demo cd snabbdom demo yarn init...

Virtual DOM原理詳解

背景 今天準備開始學習react,於是看到了react入門看這篇就夠了一文。而在看到虛擬dom的時候,讓我聯想到了vue中的虛擬dom,於是順著鏈結點進去看了一下virtual dom的實現原理。virtual dom 具體實現原理參考深度剖析 如何實現乙個 virtual dom 演算法一文,感覺...

運用diff演算法的Virtual DOM

百科解釋 把樹形結構按照層級分解,只比較同級元素。給列表結構的每個單元新增唯一的 key 屬性,方便比較。傳統的diff演算法根據大o推導法 具體演算法的問題不做細解 的時間複雜度為o n 3 在正常傳統diff演算法的大o推導法算出來的時間複雜度不適和現有的要求。在此時有乙個叫virtual do...