虚拟DOM的基本思想
- 用js对象模拟DOM树
- 比较两颗虚拟DOM树的差异
- 把差异应用到真正的DOM树上
虚拟dom
好处
- 不用手动操作dom树
- 尽可能小面积的重绘视图 节省性能
算法实现 - 用js对象模拟dom树
- 比较两棵虚拟dom树的差异
- 差异化更新dom树
用 JavaScript 对象结构表示 DOM 树的结构;
然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树;
然后用新的树和旧的树进行比较,记录两棵树差异把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了
1 | //一段dom对象 |
用js实现dom对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function Element(tagname,props,children) {
this.tagname=tagname
this.props=props
this.children=children
}
// es6 class
class Element {
constructor(tagname, props = {}, children = []) {
this.tagname = tagname
this.props = props
this.children = children
}
}
module.exports = function (tagName, props, children) {
return new Element(tagName, props, children)
}将虚拟dom应用到真正的dom树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Element.prototype.render=function() {
let el=document.createElement(this.tagname)
let props=this.props
for(let propname in props){
let propvalue=props[propname]
el.setAttribute(propname,propvalue)
}
let children=this.children||[]
children.forEach(function (child){
let childEl=(child instanceof Element)?child.render():document.createTextNode(child)
el.appendChild(childEl)
})
return el
}比较两棵虚拟dom树的差异(diff算法) 🌟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70function diff(oldTree,newTree) {
let index=0
let patches={}//记录节点差异的对象
dfs(oldTree,newTree,index,patches)
return patches
}
let initialIndex = 0
function dfs(oldNode,newNode,index,patches){
let diffResult=[]
if(!newNode) {
diffResult.push({
type:'REMOVE',
index
})
}
else if(typeof oldNode === 'string && typeof newVirtualDom === 'string') {
if(oldNode !=== newNode) {
diffResult.push({
type: 'MODIFY_TEXT',
data: newVirtualDom,
index
})
}
}else if(oldVirtualDom.tagName === newVirtualDom.tagName) {
let diffAttributeResult={}
for(let key in oldNode) {
if(oldNode[key]!==newNode[key]) {
diffAttributeResult[key]=newNode[key]
}
}
for(let key in newNode) {
if(!oldNode.hasOwnProperty(key)) {
diffAttributeResult[key]=newNode[key]
}
}
if (Object.keys(diffAttributeResult).length > 0) {
diffResult.push({
type: 'MODIFY_ATTRIBUTES',
diffAttributeResult
})
}
oldNode.children,forEach((child,index)=>{
diff(child,newNode.children[index],++initialIndex,patches)
})
}
else {
diffResult.push({
type: 'REPLACE',
newNode
})
}
if (!oldNode) {
diffResult.push({
type: 'REPLACE',
newNode
})
}
if (diffResult.length) {
patches[index] = diffResult
}
}将差异应用到dom上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46const walk = (node, walker, patches) => {
let currentPatch = patches[walker.index]
let childNodes = node.childNodes
childNodes.forEach(child => {
walker.index++
walk(child, walker, patches)
})
if (currentPatch) {
doPatch(node, currentPatch)
}
}
const doPatch = (node, patches) => {
patches.forEach(patch => {
switch (patch.type) {
case 'MODIFY_ATTRIBUTES':
const attributes = patch.diffAttributeResult.attributes
for (let key in attributes) {
if (node.nodeType !== 1) return
const value = attributes[key]
if (value) {
setAttribute(node, key, value)
} else {
node.removeAttribute(key)
}
}
break
case 'MODIFY_TEXT':
node.textContent = patch.data
break
case 'REPLACE':
let newNode = (patch.newNode instanceof Element) ? render(patch.newNode) : document.createTextNode(patch.newNode)
node.parentNode.replaceChild(newNode, node)
break
case 'REMOVE':
node.parentNode.removeChild(node)
break
default:
break
}
})
}