文章包含以下内容:
- 虚拟 DOM 是什么
- 虚拟 DOM 的优点
- 虚拟 DOM 的缺点
- DOM diff 是什么
- DOM diff 的优点
- DOM diff 的问题
什么是虚拟 DOM
一个能代表DOM树的对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其他属性
虚拟 DOM 举例 👇
//React虚拟DOM,由此可以看出虚拟DOM本质是对象
const vNode = {
key: null,
props: {
children: [ // 子元素们
{ type: 'span', ... },
{ type: 'span', ... }
],
className: "red" // 标签上的属性
onClick: () => {} // 事件
},
ref: null,
type: "div", // 标签名 or 组件名
...
}
//Vue虚拟DOM
const vNode = {
tag: "div", // 标签名 or 组件名
data: {
class: "red", // 标签上的属性
on: {
click: () => {} // 事件
}
},
children: [ // 子元素们
{ tag: "span", ... },
{ tag: "span", ... }
],
...
}
虚拟 DOM 的优点
减少DOM操作
虚拟DOM可以将多次操作合并成一次操作,比如添加1000个节点,用DOM只能一次一次添加,虚拟DOM只需要操作一次。
虚拟DOM可以借助DOM diff,把多余的操作省掉,比如添加1000个节点,其实只有10个新增的,那么DOM diff可以根据情况减少操作
跨平台
虚拟DOM本质是一个对象,所以他不仅可以变成DOM,还可以变成小程序,变成iOS应用,安卓应用。
虚拟 DOM 的缺点
需要有额外的创建函数,比如createElement 或 h,但是可以通过JSX 来简化成XML写法,即便如此,还需要打包工具,进行额外的构建,JS才能识别
简化举例👇
//React JSX
<div className="red" onClick="{()=> {}}">
<span>span1</span>
<span>span2</span>
</div>
//Vue Template
h('div', {
class: 'red',
on: {
click: () => { }
},
}, [h('span',{},'span1'), h('span', {}, 'span2'])
什么是DOM diff
虚拟DOM的对比算法,其实是一个patch函数,该函数会对比新老节点,将差别转化为需要运行的DOM操作
//伪代码举例
patches = patch(oldVNode, newVNode)
[
{type: 'INSERT', vNode: ... },
{type: 'TEXT', vNode: ... },
{type: 'PROPS', propsPatch: [...]}
]
DOM diff 的大概逻辑
Tree diff 将新旧两棵树逐层对比,找出哪些节点需要更新 如果节点是组件就看 Component diff 如果节点是标签就看 Element diff
Component diff 如果节点是组件,就先看组件类型 类型不同直接替换(删除旧的) 类型相同则只更新属性 然后深入组件做 Tree diff(递归)
Element diff 如果节点是原生标签,则看标签名 标签名不同直接替换,相同则只更新属性 然后进入标签后代做 Tree diff(递归)
具体例子参考最后的问题部分
DOM diff 的优点
DOM diff 实际上就是虚拟DOM的实现方式,所以DOM diff的优点其实就是虚拟DOM的优点,见上文。
DOM diff 的问题
当我们删除某个节点时,可能出现删错的情况,解决方法就是加key,具体例子参考 这里