虚拟DOM是什么?
一个能代表DOM树的对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其他属性。
一般说法是虚拟DOM比真实的DOM要快很多,但这要看什么情景下比较:
- DOM操作慢:最早的说法来自一本书《高性能JavaScript》。说DOM操作很慢,但实际上这本书把DOM操作与JS原生API做对比,就好比刘翔和姚明比高,就说刘翔矮?但实际上刘翔并不矮!(该书现已过时)
- DOM操作快:任何基于DOM的库(Vue/React)都不可能在操作DOM时比DOM快。
虚拟DOM优点
- 可以减少DOM操作
- 虚拟DOM操作可以将多次操作合并为一次操作,比如要添加1000个节点,却是一个接一个操作的(减少频率)
- 虚拟DOM借助DOM diff可以把多余的操作省掉,比如要添加1000个节点,其实只有10个节点是新增的(减少范围)
- 跨平台
- 虚拟DOM不仅可以变成DOM,还可以变成小程序、IOS应用、安卓应用,因为虚拟DOM本质上只是一个JS对象。
虚拟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 组件名
.........
}
如何创建虚拟DOM
旧:
Vue(只能在render函数里得到h)
h('div',{
class: 'red',
on: {
click: () => {}
},
},[h('span',{},'span1'), h('span',{},'span2'])
Reach
createElement('div',{className:'red',onClick:()=>{}},[
createElement('span',{},'span1'),
createElement('span',{},'span2')
])
但是上面的创建方式太麻烦了,现在有更好的创建方式:
新:
Vue Template
<div class="red" @click="fn">
<span>span1</span>
<span>span2</span>
</div>
前提:通过vue-loader转为h形式。
React
<div className="red" onClick={fn}>
<span>span1</span>
<span>span2</span>
</div>
前提:通过babel转为 createElement形式
所以DOM这里就不太好了
虚拟DOM的缺点
需要新建额外的创建函数,如createElement或h,但可以通过JSX简化成XML写法。
DOM diff 是什么?
diff 英文意思是 比较!即 DOM对比算法,就是在 旧的DOM 与 新改的DOM 这两者之间做对比,在改变的地方就渲染到页面中,没有改变的地方就不再渲染到真实的DOM树上,节约了性能!
其中,大概的逻辑:
- Tree diff
- 将新旧两棵树逐层对比,找出哪些节点需要更新。
- 如果节点是组件,就看组件类型。
- 如果节点是标签就看Element diff
- Component diff
- 如果节点是组件,就先看组件类型。
- 类型不同直接替换(删除旧的)
- 类型相同则只更新属性
- 然后深入组件做Tree diff(递归)
- Element diff
- 如果节点是原生标签,则看标签名。
- 标签名不同直接替换,相同则只更新属性。
- 然后进入标签后代做Tree diff (递归)
DOM diff存在BUG
根据DOM diff的渲染规则,如果没有相应的key作为标识,以一个同级节点(A,B,C)的DOM为例,如果对前一个节点B做删除处理,DOM diff就会:
- 把B变成C
- 把C删除
结果为(A,B)没有C。
解决办法为:给每个节点添加一个独一无二的key,这样就不会出现这样的BUG!
注:永远不要把index作为key,计算机是遍历数组或对象,需要手动赋值!
深入了解可参考饥人谷 —- 方应杭在知乎的回答