虚拟DOM
什么是虚拟DOM
一个能代表DOM树的对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其它属性。
有关DOM的谣言
DOM操作慢,而虚拟DOM快?
这句话无异于说刘翔比姚明矮。DOM操作慢是相对于JS原生API,如数组操作。任何基于DOM的库(Vue/React)都不可能在操作DOM时比DOM快。但有些情况下虚拟DOM确实比真是DOM快。即【虚拟DOM优点】。
在一定规模操作下,虚拟DOM更快;而超出一定体量后,原生DOM操作更快。
虚拟DOM优点
1. 减少DOM操作
虚拟DOM可以将多次操作合并成一次操作【减少次数】。 如要给页面添加1000个节点,DOM操作只能逐个添加;而虚拟DOM(react)将1000个文本一次性添加到页面
虚拟DOM借助DOM diff将多余的操作省掉【减少范围】。 如现在有990个节点,要添加到1000个,只有10个是新增的,只需要更新这10个而不是全部更新
2. 跨平台
虚拟DOM不仅可以变成DOM,还可以变成小程序、IOS、安卓应用,因为虚拟DOM本质是一个JS对象
虚拟DOM示例
const vNode = {
key: null,
props: {
children: [ // 子元素
{ type: 'span', ...},
{ type: 'span', ...}
],
calssName: "red",
omClick: ()=>{} // 事件
},
ref: null,
type: "div", // 标签名 or 组件名
...
}
const vNode = {
tag: "div", // 标签上的属性
on: {
click: ()=<>{} // 事件
},
children: [ // 子元素
{ tag: 'span', ...},
{ tag: 'span', ...}
]
}
创建虚拟DOM
React.createElement
creatElement('div', {className: 'red', onClick: ()=>{}}, [
createElement('span', {}, 'span1'), // 标签,事件,内容
createElement('span', {}, 'span2'),
]
)
h【只能在render函数里得到h】
h('div', {
class: 'red',
on: {
click: ()=<>{}
},
}, [h('span', {}, 'span1), h('span', {}, 'span2'])
上面的写法是不是看起来和DOM操作一样繁琐?试试下面这种吧
用JSX简化创建虚拟DOM
React JSX:通过babel转为createElement形式
<div className:="red" onClick="{()=>{}}">
<span>span1</span>
<span>span2</span>
</div>
Vue Template:通过vue-loader转为h形式
<div class="red" @click="()=>{}">
<span>span1</span>
<span>span2</span>
</div>
DOM diff
什么是DOM diff
就是一个函数,我们称之为patch
patch= patch(oldVNode, newVNode)
patches就是要运行的DOM操作,可能长这样:
[
{type:'INSERT', vNode:...}, // ...表示插入的位置
{type:'TEXT', vNode:...},
{type:'PROPS', propsPatch:[...]},
]
DOM diff可能的逻辑
Tree diff
把虚拟DOM想象成树。将新旧两棵树逐层对比,找出哪些节点需要更新
如果节点是组件就看Component diff
如果节点是元素就看Element diff
Component diff
如果节点是组件,就先看组件类型
类型不同直接替换(删除旧的)
类型相同只更新属性
然后深入组件做Tree diff(递归)
Element diff
如果节点是原生标签,则看标签名
标签名不同直接替换,相同只更新属性
然后进入标签后代做Tree diff.(递归)
DOM diff缺点
同级节点对比存在bug:DOM diff 中的 key 问题
这个问题在 React 中也存在
关于DOM diff的更多博客
React入门学习(四)-- diffing 算法
React 虚拟 Dom 和 diff 算法
React 源码剖析系列 - 不可思议的 react diff