虚拟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,计算机是遍历数组或对象,需要手动赋值!
深入了解可参考饥人谷 —- 方应杭在知乎的回答
