相信大部分前端同學之前早已無數次聽過或了解過vnode
(虛擬節點),那麼什麼是vnode
?vnode
應該是什麼樣的?
如果不使用前端框架,我們可能會寫出這樣的頁面:
不難發現,整個文件樹的根節點只有乙個html
,然後巢狀各種子標籤,如果使用某種資料結構來表示這棵樹,那麼它可能是這樣。
]},,
]}]}
但是實際開發中,整個文件樹中head
和script
標籤基本不會有太大的改動。頻繁互動可能改動的應當是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 步已經實現了vnode
到dom
節點的轉換與掛載,那麼接下來某乙個時刻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
的核心diff
與patch
都已基本實現。在測試 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...