這是我參與更文挑戰的第6天,活動詳情查看: 更文挑戰javascript
標題靈感來源評論區:html
什麼是虛擬 DOM?簡單來講,虛擬 DOM 就是一個模擬真實 DOM 的樹形結構,這個樹結構包含了整個 DOM 結構的信息。java
正常咱們看到的真實 DOM 是這樣的:node
而虛擬 DOM 則是這樣的,包含了標籤名稱、標籤屬性、子節點等真實 DOM 信息:react
虛擬 DOM 既然是模擬真實 DOM 的樹形結構,那麼爲何要用虛擬 DOM 呢?直接操做 DOM 有什麼缺點嗎?git
直接操做 DOM 沒有缺點,可是頻繁的操做 DOM 就缺點很大,由於操做 DOM 會引發重排,頻繁操做 DOM 時,瀏覽器會頻繁重排,致使頁面卡頓。github
瀏覽器渲染的大體流程以下:web
重排(也叫回流、reflow)就是當涉及到 DOM 節點的佈局屬性發生變化時,就會從新計算該屬性,瀏覽器會從新描繪相應的元素(上述第 4 步)。算法
DOM Tree 裏的每一個節點都會有 reflow 方法,一個節點的 reflow 頗有可能致使子節點,甚至父點以及同級節點的 reflow。編程
所以,爲了提高性能,咱們應該儘可能減小 DOM 操做。
當有一個表格須要作排序功能時,有出生年月、性別等排序方式可選,當選擇某排序方式時,表格將按該方式從新排序。
從上可知,虛擬 DOM 經過 diff 算法,幫助咱們大量的減小 DOM 操做。
從另外一個角度看,虛擬 DOM 爲咱們提供了函數式的編程方式,使代碼可讀性和可維護性更高。
注:該章節的虛擬 DOM 實現原理並非參比 React 源碼,而是參比 simple-virtual-dom,可經過該章節簡單瞭解虛擬 DOM 實現原理,React 中的虛擬 DOM 實現可查看 React 官網 Virtual DOM 及內核。
虛擬 DOM 經過如下步驟實現:
模擬真實 DOM 樹,構建虛擬 DOM 樹結構,包含標籤名 tagName、屬性對象 props、子節點 children、子節點數 count 等屬性。
function Element (tagName, props = {}, children = []) {
// 標籤名
this.tagName = tagName
// 屬性對象
this.props = props
// 子節點
this.children = children
// key標誌
const { key = void 666 } = this.props
this.key = key
// 子節點數量
let count = 0
this.children.forEach((child, index) => {
if (child instanceof Element) {
count += child.count
}
count++
})
this.count = count
}
複製代碼
建立虛擬 DOM 對象:
console.log(el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom'])
]))
複製代碼
生成的虛擬 DOM 對象如圖:
將虛擬 DOM 轉換爲真實 DOM:
Element.prototype.render = function () {
const el = document.createElement(this.tagName)
const props = this.props
for (const propName in props) {
const propValue = props[propName]
_.setAttr(el, propName, propValue)
}
this.children.forEach((child) => {
let childEl
if (child instanceof Element) {
childEl = child.render()
} else {
childEl = document.createTextNode(child)
}
el.appendChild(childEl)
})
return el
}
複製代碼
填充進頁面:
document.body.appendChild(el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom'])
]).render())
複製代碼
效果如圖:
當數據更新時,須要對新舊虛擬 DOM 樹進行對比。
if (_.isString(oldNode) && _.isString(newNode)) {
if (newNode !== oldNode) {
currentPatch.push({ type: patch.TEXT, content: newNode })
}
// Nodes are the same, diff old node's props and children
}
複製代碼
if (
oldNode.tagName === newNode.tagName &&
oldNode.key === newNode.key
) {
// Diff props
var propsPatches = diffProps(oldNode, newNode)
if (propsPatches) {
currentPatch.push({ type: patch.PROPS, props: propsPatches })
}
// Diff children. If the node has a `ignore` property, do not diff children
if (!isIgnoreChildren(newNode)) {
diffChildren(
oldNode.children,
newNode.children,
index,
patches,
currentPatch
)
}
}
複製代碼
currentPatch.push({
type: PATCH_KEY.REPLACE,
node: newNode
})
複製代碼
總結一下,虛擬 DOM 只在同層級間 Diff,若是標籤不一樣則直接替換該節點及其子節點。
嘗試對比虛擬 DOM 以下:
function renderTree () {
return el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['the count is :' + Math.random()])
])
}
let tree = renderTree()
setTimeout(() => {
const newTree = renderTree()
const patches = diff(tree, newTree)
console.log(patches)
}, 2000)
複製代碼
對比差別爲 p 標籤的文本節點發生改變,輸出結果如圖:
最後一步是根據 diff 結果,對真實 DOM 進行修改。
遍歷真實 DOM 樹,若是該 DOM 節點有 diff,則根據 diff 類型,處理 DOM 節點,若是該 DOM 節點無 diff,則遍歷其子節點,直至遍歷完成。
注:React 實現更優,具體請見 React fiber。
function patch (node, patches) {
var walker = {index: 0}
dfsWalk(node, walker, patches)
}
function dfsWalk (node, walker, patches) {
var currentPatches = patches[walker.index]
var len = node.childNodes
? node.childNodes.length
: 0
for (var i = 0; i < len; i++) {
var child = node.childNodes[i]
walker.index++
dfsWalk(child, walker, patches)
}
if (currentPatches) {
applyPatches(node, currentPatches)
}
}
複製代碼
嘗試更新真實 DOM,代碼以下:
function renderTree () {
return el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['the count is :' + Math.random()])
])
}
let tree = renderTree()
const root = tree.render()
document.body.appendChild(root)
setTimeout(() => {
const newTree = renderTree()
const patches = diff(tree, newTree)
patch(root, patches)
tree = newTree
}, 2000)
複製代碼
效果如圖:
上圖可見,成功更新真實 DOM。
本文從什麼是虛擬 DOM、爲何使用虛擬 DOM、虛擬 DOM 的實現原理等 3 個角度對虛擬 DOM 進行講述。
虛擬 DOM 經過模擬真實 DOM 的樹結構,收集大量 DOM 操做,經過 diff 算法對真實 DOM 進行最小化修改,減小瀏覽器重排,提高加載速度,達到優化網站性能的做用。
虛擬 DOM 採用函數式編程,讓咱們碼得更好看更快樂。
可經過 github源碼 進行實操練習。
但願能對你有所幫助,感謝閱讀~
別忘了點個贊鼓勵一下我哦,筆芯❤️