虚拟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

  1. const vNode = {
  2. tag: "div", //标签名 or 组件名
  3. data: {
  4. class: "red", //标签上的属性
  5. on:{
  6. click: () = {} //事件
  7. }
  8. },
  9. children: [
  10. {tag: "span", ...},
  11. {tag: "span", ...}
  12. ],
  13. ..........
  14. }

React

  1. const vNode = {
  2. key: null,
  3. props: {
  4. children:[
  5. {type: 'span',...},
  6. {type: 'span',...}
  7. ],
  8. className: "red" //标签上的属性
  9. onClick:() => {} //事件
  10. },
  11. ref: null,
  12. type: "div", //标签名 or 组件名
  13. .........
  14. }

如何创建虚拟DOM

旧:
Vue(只能在render函数里得到h)

  1. h('div',{
  2. class: 'red',
  3. on: {
  4. click: () => {}
  5. },
  6. },[h('span',{},'span1'), h('span',{},'span2'])

Reach

  1. createElement('div',{className:'red',onClick:()=>{}},[
  2. createElement('span',{},'span1'),
  3. createElement('span',{},'span2')
  4. ])

但是上面的创建方式太麻烦了,现在有更好的创建方式:

新:
Vue Template

  1. <div class="red" @click="fn">
  2. <span>span1</span>
  3. <span>span2</span>
  4. </div>

前提:通过vue-loader转为h形式。

React

  1. <div className="red" onClick={fn}>
  2. <span>span1</span>
  3. <span>span2</span>
  4. </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,计算机是遍历数组或对象,需要手动赋值!

深入了解可参考饥人谷 —- 方应杭在知乎的回答