# 1. 虚拟DOM
# 1.1. 什么是 Vdom?
- 用JS模拟DOM结构;
- DOM变化的对比,放在JS层来做(图灵完备语言);
- 提高重绘性能。
# 1.2. 为什么使用 Vdom?
- DOM操作很昂贵,js运行效率高。
- 尽量减少DOM操作,不是推倒重来。项目越复杂,影响就越严重。
// 打印下简单的div,可见其很庞杂
const div = document.createElement('div')
let res = ''
for (let item in div) {
res += ' | ' + item
}
console.log(res)
# 1.3. 核心 API
以 snabbdom
为例,虚拟DOM框架的核心API包括h
函数和patch
函数
# 1.3.0.1. h
函数
h
函数=>vnode
的一个结点,JS的一个对象,模拟dom
结点h
函数第一个参数是结点,第二个参数是属性,第三个参数是子元素(数组或者字符串)
const vnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1')
h('li.item', {}, 'Item 2')
])
// 其表示的DOM结构如下:
{
tag: 'ul',
attrs: {
id: 'list'
},
children: [
{
tag: 'li',
attrs: { className: 'item' },
children: ['Item 1']
}, {
tag: 'li',
attrs: { className: 'item' },
children: ['Item 2']
}
]
}
# 1.3.0.2. patch
函数
- 把
vnode
全部加到空的容器中 - 可以对比新的
vnode
和旧的vnode
,找出所需的最小的改变
const vnode = h('ul#list', {}, [
h('li.item', {}, 'Item 1')
h('li.item', {}, 'Item 2')
])
const container = document.getElementById('container')
patch(container, vnode) // 把vnode加到空的容器中
// 模拟改变
const btnChange = document.getElementById('btnChange')
btnChange.addEventListener('click', function() {
const newVnode = h('ul#list', {}, [
h('li.item', {}, 'Item 11'),
h('li.item', {}, 'Item 22'),
h('li.item', {}, 'Item 33'),
])
patch(vnode, newVnode)
})
# 1.4. Vdom 为何使用 Diff 算法?
- DOM操作是昂贵的,因此尽量减少DOM操作。
- 找出本次DOM必须更新的节点来更新,其他的不更新。
- 这个“找出”的过程,就需要
diff
算法。
# 1.5. Vdom 的 Diff 算法
- 同层比对。第一层比较,如果第一层有差异,不管下面的DOM是否相同,删除下面的,全部替换。这样减少了DOM比对中的性能损耗,某些情况,比如第二层DOM相同时增加了性能损耗。
key
值极大了提升了Diff
算法性能。不要用index
做key
值,是因为index
值不稳定,比如删掉第一个,后面的index
都要减1。
# 1.6. Diff 算法和调和
# 1.6.1. Diff 算法的作用
计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面。
# 1.6.2. 传统 Diff 算法
通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3)
,n是树的节点数,这个有多可怕呢?——如果要展示1000个节点,得执行上亿次比较。。即便是CPU快能执行30亿条命令,也很难在一秒内计算出差异。
# 1.6.3. React 的 Diff 算法
(1)什么是调和?
将Virtual DOM
树转换成actual DOM
树的最少操作的过程 称为 调和 。
(2)什么是 React 的diff
算法?
diff
算法是调和的具体实现。
# 1.6.4. Diff 策略
React用 三大策略 将O(n^3)
复杂度 转化为 O(n)
复杂度
(1)策略一(tree diff
):
Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计。
(1)策略二(component diff
):
拥有相同类的两个组件 生成相似的树形结构,
拥有不同类的两个组件 生成不同的树形结构。
(1)策略三(element diff
):
对于同一层级的一组子节点,通过唯一id区分。
# 1.6.5. tree diff
(1)React通过updateDepth对Virtual DOM树进行层级控制。 (2)对树分层比较,两棵树 只对同一层次节点 进行比较。如果该节点不存在时,则该节点及其子节点会被完全删除,不会再进一步比较。 (3)只需遍历一次,就能完成整棵DOM树的比较。
那么问题来了,如果DOM节点出现了跨层级操作,diff会咋办呢? 答:diff只简单考虑同层级的节点位置变换,如果是跨层级的话,只有创建节点和删除节点的操作。
# 1.6.6. element diff
Key 应该具有稳定,可预测,以及列表内唯一的特质。不稳定的 key(比如通过 Math.random() 生成的)会导致许多组件实例和 DOM 节点被不必要地重新创建,这可能导致性能下降和子组件中的状态丢失。
可以使用元素在数组中的下标作为 key。这个策略在元素不进行重新排序时比较合适,如果有顺序修改,diff 就会变得慢。