浅谈虚拟DOM、diff算法

React首先在内存中对整个结构进行组装,然后把这个结构转换成实际的DOM节点插入到浏览器DOM中。render执行的结果并不是真正的DOM节点,而是轻量的JS对象。React的虚拟对象使两个DOM树对比起来更快,具有批处理和高效Diff算法,找到他们的变化,计算更新DOM所需要做的最小的变更

虚拟DOM

  1. 用JS对象模拟DOM
  2. 把这个虚拟DOM对象转为真实的DOM插入到页面中
  3. 如果有事件修改了虚拟DOM,比较两个虚拟DOM树的差异,得到差异对象
  4. 把差异对象应用到真正的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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 节点类
class Element{
constructor(type,props,children){
this.type= type;
this.props= props;
this.children= children;
}
}
// 生成节点类实例
function createElement(type,props,children){
return new Element(type,props,children)
}

// 创建一个虚拟DOM对象
let virtualDOM= createElement('ul',{class: 'list'},[
createElement('li',{class: 'item'},['a']),
createElement('li',{class: 'item'},['b']),
createElement('li',{class: 'item'},['c'])
])
console.log(virtualDOM)

// 设置属性的方法
function setAttr(node,key,value){
switch(key){
case 'value': // node是input或者textarea
if(node.tagName.toUpperCase()=== 'INPUT' || node.tagName.toUpperCase()=== 'TEXTAREA'){
node.value= value
}else{
node.setAttribute(key,value)
}
break;
case 'style':
node.style.cssText= value;
break;
default:
node.setAttribute(key,value)
break;
}
}

// render方法将vnode 转化成真实的dom
function render(eleObj){
let el= document.createElement(eleObj.type); //生成父节点
// 遍历父节点的props添加属性
for(let key in eleObj.props){
// 设置属性的方法
setAttr(el,key,eleObj.props[key])
}

// 遍历父节点的children
eleObj.children.forEach(item => {
item= (item instanceof Element)? render(item) : document.createTextNode(item) //创建文本节点
el.appendChild(item)
});
return el
}

let el= render(virtualDOM)
console.log(el)

// 将元素插入到页面
function renderDOM(el,target){
target.appendChild(el)
}
renderDOM(el,document.body)

diff算法

  • Diff 比较两个虚拟DOM的区别(比较两个对象的区别)
  • 根据两个虚拟对象的区别,创建出补丁(patch),描述改变的内容,将这个补丁用来更新页面
  • 差异计算: 先序深度优先遍历

diff三种优化策略

  1. 比较同级的节点(同一父节点的子节点)

    当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个DOM树的比较。对于不同层的节点,只有简单的创建和删除。

  2. 相同类型的节点比较:对属性进行重设实现节点的转换
1
2
3
renderA: <div style={{color: 'red'}} />
renderB: <div style={{fontWeight: 'bold'}} />
=> [removeStyle color], [addStyle font-weight 'bold']
  1. 列表节点的比较(key存在的意义)
  • 添加、删除、排序
  • 顺序的调整类似插入/删除

diff算法的三个维度

  1. Tree diff
  2. Component diff
  3. Element fiff
    image

虚拟DOM的优缺点

  1. 优点
  • 易用性强,使用DOM时,将把所有的XML文档信息都存于内存中,并且遍历简单,支持XPath,增强了易用性。
  1. 缺点
  • 效率低,解析速度慢,内存占用量过高,对于大文件来说几乎不可能使用。另外效率低还表现在大量的消耗时间,因为使用DOM进行解析时,将为文档的每个element、attribute、processing-instrUCtion和comment都创建一个对象,这样在DOM机制中所运用的大量对象的创建和销毁无疑会影响其效率。