虚拟 DOM
DOM 操作慢?虚拟 DOM 快 ?
- DOM 操作慢是对比于 JS 原生 API,如数组操作
- 任何基于 DOM 的库(Vue/React)都不可能在操作 DOM 时比 DOM 快
- 但在某些情况下,虚拟 DOM 快
虚拟 DOM 优点
1.减少 DOM 操作
- 虚拟 DOM 可以将多次操作合并为一次操作,比如你添加 1000 个节点,却是一个接一个操作的(减少频率)
- 虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如你添加 1000 个节点,其实只有 10 个是新增的(减少范围)
2.跨平台
- 虚拟 DOM 不仅可以变成 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚拟 DOM 本质上只是一个 JS 对象
虚拟 DOM 长什么样子
- React
const vNode = {
key: null,
props: {
children: [ // 子元素们
{ type: 'span', ... },
{ type: 'span', ... }
],
className: "red" // 标签上的属性
onClick: () => {} // 事件
},
ref: null,
type: "div", // 标签名 or 组件名
...
}
- Vue
const vNode = {
tag: "div", // 标签名 or 组件名
data: {
class: "red", // 标签上的属性
on: {
click: () => {} // 事件
}
},
children: [ // 子元素们
{ tag: "span", ... },
{ tag: "span", ... }
],
...
}
如何创建虚拟 DOM
- React.createElement
createElement('div',{className:'red',onClick:()=> {}},[ createElement('span', {}, 'span1'), createElement('span', {}, 'span2') ]
)
- Vue(只能在 render 函数里得到 h)
h('div', {
class: 'red',
on: {
click: () => { }
},
}, [h('span',{},'span1'), h('span', {}, 'span2'])
用 JSX 简化创建虚拟 DOM
- React JSX
<div className="red" onClick="{()=> {}}">
<span>span1</span>
<span>span2</span>
</div>
通过 babel 转为 createElement 形式
- Vue Template
<div class="red" @click="fn">
<span>span1</span>
<span>span2</span>
</div>
通过 vue-loader 转为 h 形式
总结
- 虚拟 DOM 是什么 一个能代表 DOM 树的对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其他属性
- 虚拟 DOM 有什么优点 能减少不必要的 DOM 操作(两个例子背下来) 能跨平台渲染
- 虚拟 DOM 有什么缺点 需要额外的创建函数,如 createElement 或 h,但可以通过 JSX 来简化成 XML 写法
DOM diff(虚拟 DOM 的对比算法)
把虚拟 DOM 想象成树形
<div :class="x">
<span v-if="y">{string1}</span>
<span>{string2}</span>
</div>
当数据变化时
x 从 red 变成 green
<div :class="x">
<span v-if="y">{string1}</span>
<span>{string2}</span>
</div>
- DOM diff 发现 div 标签类型没变,只需要更新 div 对应的 DOM 的属性 子元素没变,不更新
y 从 true 变成 false
- DOM diff 发现 div 没变,不用更新 子元素1标签没变,但是children变了,更新 DOM 内容 子元素2不见了,删除对应的 DOM
什么是 DOM diff?
就是一个函数,我们称之为 patch patches = patch(oldVNode, newVNode) //对比新老node patches 就是要运行的 DOM 操作,可能长这样://DOM更新操作 [ {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 的缺点
- 同级节点对比存在 bug
假设你有三个子组件,每个子组件里面有一个「有状态的」孙子组件。
当用户把第二个自组件删掉了,结果却是第三个消失了。因为data里的数据[1,2,3]变成了[1,3],删除的是下标,不信的话点击验证。
解决办法是每个组件属性都加上:key = id (注意不要用index下标作为key)