一、虚拟DOM
1. 虚拟DOM是个啥
简单来说虚拟DOM就是一个能代表 DOM 树的对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其他属性。它长这样:
//Vue
const vNode = {
tag: "div", // 标签名 or 组件名
data: {
class: "red", // 标签上的属性
on: {
click: () => {} // 事件
}
},
children: [ // 子元素们
{ tag: "span", ... },
{ tag: "span", ... }
],
...
}
//React
const vNode = {
key: null,
props: {
children: [ // 子元素们
{ type: 'span', ... },
{ type: 'span', ... }
],
className: "red" // 标签上的属性
onClick: () => {} // 事件
},
ref: null,
type: "div", // 标签名 or 组件名
...
}
2. 虚拟 DOM 的优点
- 减少 DOM 操作
减少DOM操作的次数,虚拟 DOM 可以将多次操作合并为一次操作,比如你添加 1000 个节点,却是一个接一个操作的。
减小DOM操作的范围,虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如你添加 1000 个节点,其实只有 10 个是新增的。
- 跨平台
虚拟 DOM 不仅可以变成 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚拟 DOM 本质上只是一个 JS 对象。
3. 虚拟 DOM 的缺点
需要额外的创建函数。如 createElement 或 h:
//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 来简化成 XML 写法:
//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 diff
1. DOM diff是个啥
DOM diff就是一个函数,我们称之为 patch,它会对比 oldNode 与 newNode 的区别,从而减少不必要的渲染:patches = patch(oldVNode, newVNode),patches 就是要运行的 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(递归)。
2. DOM diff 优缺点
DOM diff通过对比新旧虚拟DOM 这两个对象的差异,最终只把变化的部分重新渲染,提高渲染效率的过程。但DOM diff同级节点对比存在 bug,需要使用key来辅助diff的对比,从而消除bug。