文章目錄
- 虛擬DOM的作用
- 簡單實作虛擬DOM
- diff演算法
虛擬DOM的作用
首先我們要知道虛擬dom的出現是為了解決什么問題的,他解決我們平時頻繁的直接操作DOM效率低下的問題,那么為什么我們直接操作DOM效率會低下呢?
比如我們創建一個div,我們可以在控制臺查看一下這個div上自帶或者繼承了很多屬性,尤其是我們使用js操作DOM的時候,我們的DOM本身就很復雜,js的操作也會占用很多時間,但是我們控制不了DOM元素本身,因此虛擬DOM解決的是js操作DOM這一層面,其實解決的是減少了操作dom的次數
簡單實作虛擬DOM
-
虛擬DOM,見名知意,就是假的DOM,我們真實的DOM掛載在頁面上的,而我們的虛擬DOM則是在記憶體中的,這個就需要我們把真實的DOM抽象成一個物件放在記憶體中,這個物件就可以是如下型別:
var element = { tagName: 'div', props: { class: 'box' }, children: { { tagName: 'p', props: { class: 'p1' }, children: ['我是p1'] }, { tagName: 'p', props: { class: 'p2' }, children: ['我是p2'] }, { tagName: 'p', props: { class: 'p3' }, children: ['我是p3'] }, } } -
我們想要構造出這樣的物件可以自己封裝一個建構式如下:
function Element(tagName, props, children) { this.tagName = tagName this.props = props this.children = children } -
有了這個物件,我們需要把這個虛擬DOM渲染到真實DOM上,可以寫出如下方法:
Element.prototype.render = function () { const { tagName, props, children } = this var el = document.createElement(tagName) for (key in props) { el.setAttribute(key, props[key]) } children.forEach((item) => { const childEl = (item instanceof Element) ? item.render() : document.createTextNode(item) el.appendChild(childEl) }) return el } -
最后我們可以new出這個物件呼叫render()方法然后appendChild到body中就好了:
let virtualDom = new Element('div', { class: 'box' }, [ new Element('p', { class: 'p1' }, ['我是p1']), new Element('p', { class: 'p2' }, ['我是p2']), new Element('p', { class: 'p3' }, ['我是p3']), ]) let a = virtualDom.render() document.body.appendChild(a)
diff演算法
首先我們先了解一下diff演算法的作用
如果我們的虛擬dom發生了變化,我們的記憶體中又會產生新的虛擬DOM,如果我們直接用這個新的虛擬DOM結構的話,又會導致很多重復的渲染,因此 這個時候diff演算法的作用就體現了出來,diff通過比較新舊兩個虛擬DOM樹,找出差異,并且記錄下來,然后把記錄的差異應用到真實的DOM樹上,
原理:
diff演算法通過對新舊兩顆樹進行深度優先遍歷,每一個節點都加一個唯一的標識,
這個程序分為2步
- 找出兩個樹的差異,并記錄在一個偽陣列里,
- 把這些不同應用到真實的DOM樹上
對于dom的操作基本可化為4種型別
- 對節點的洗掉,移動,添加子節點
- 更換節點標簽
- 對于文本節點,修改節點文本
- 修改節點props
下面會用偽代碼的形式大致過一下這個流程
// diff 函式,對比兩棵樹
function diff(oldTree, newTree) {
var patchs = {}; // 偽陣列,記錄差異
// 對4種節點做錯判斷
dfWork(oldTree, newTree, patchs, index)
return patchs
}
function dfWork(oldTree, newTree, patchs, index) {
let currentPatch = []
if (1) { // 對節點的洗掉
currentPatch.push()
} else if (3) { // 對節點的文本的更換
currentPatch.push()
} else { // 修改節點的props 對children的檢查
// 對props作diff演算法,把變化記錄到patchs中,
currentPatch.push({ type: patch.PROPS, props: propsPatches })
// 然后需要對子節點作diff演算法
diffChildren(oldNode.children, newNode.children, index, patches, currentPatch)
}
}
function diffChildren(oldChildren, newChildren, index, patches, currentPatch) {
// 對子節點作diff演算法,遍歷子節點,遞回呼叫dfWork,做差異得到patchs
}
// 把變化應用在真實的DOM樹上
function patch(node, patchs) {
// node為老的DOM樹,patchs變化,
// 我們會遍歷這個patchs,并且把node和patch對應上,
}
function applyPatch(node, patchs) {
// 應為每個節點可能有多個變化,所以也需要遍歷
switch (patchs.type) {
case REPLACE: // 節點替換
// node.render()
break;
case REORDER: // 節點的移動洗掉新增子節點,
break;
case PROPS:
// setProps
break;
case TEXT: // 對節點文本的修改
// node.nodeValue
break;
default:
break;
}
}
參考檔案:深度剖析:如何實作一個 Virtual DOM 演算法 作者:livoras,內置原始碼,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/277341.html
標籤:其他
下一篇:2021-04-17
