浅析虚拟DOM和DOM diff

391 阅读3分钟

一.虚拟DOM

1.虚拟DOM是和真实DOM对应的概念

什么是虚拟DOM,就是一个能代表DOM树的对象,通常含有标签名,标签上的属性,事件监听和子元素们,已经其他属性

2.为什么说虚拟DOM比DOM更快:

  • DOM操作慢是对比于JS原生API的,如数组操作
  • 任何基于DOM的库(像vue和react)都不可能在操作DOM时比DOM快

3.虚拟DOM比DOM快的原因(或者说虚拟DOM的优点)

a.可以减少DOM操作

  • 虚拟DOM可以将多次操作合并为一次操作,比如添加了1000个div节点,确实一个接一个操作的
  • 虚拟DOM借助DOM diff可以把多余的操作省掉,比如添加1000个div节点,其实只有10个div是新增的

b.跨平台

  • 虚拟DOM不仅可以变成DOM,还可以变成小程序,IOS应用,安卓应用,因为虚拟DOM本质上只是一个JS对象

4.代码示例:

  • 虚拟DOM(react)代码:

这个对象表示了一个标签为div,子元素为两个span,className为red,点击事件调用一个函数的虚拟DOM

const vNode = {
    key:null,
    props:{
        children:[
           {type:'span',...},
           {type:'span',...},
        ],
        className:"red" //标签上的属性
        onClick:()=>{}   //事件
    },
    ref:null,
    type:"div",  //标签名or组件名
    ...
}
  • 虚拟DOM(vue)代码:

这个对象表示了一个标签为div,子元素为两个span,data中class为red,点击事件on调用一个click函数的虚拟DOM

const vNode = {
    tag:"div",  //标签名or组件名
    data:{
        class:"red", //标签上的属性
        on:{
            click: ()=>{} //事件
        }
    },
    children:[  //子元素们
       {type:'span',...},
       {type:'span',...},
    ],
    ...
}

5.如何创建虚拟DOM

(1)React中

React.createElement

createElement('div',{className:'red',onclick:()=> {}},[        createElement('span',{},'span1'),        createElement('span',{},'span2')    ]
)

简化代码(React),通过babel转为createElement形式:

    <div className = "red" onClick={fn}>
        <span>span1</span>
        <span>span2</span>
    </div>

(2)vue中 Vue只能在render函数里面得到h

h('div',{
    class:'red',
    on:{
        click:()=>{}
    },
  },[h('span',{},'span1'),h('span',{},'span2'])
)

简化代码,Vue Template,通过vue-loader转为h形式

    <div class="red" @click = "fn">
        <span>span1</span>
        <span>span2</span>
    </div>

5.虚拟DOM的缺点:

需要额外的创建函数,如createElement或h,但可以通过JSX来简化成XML写法

而简化后的方法的缺点:严重依赖打包工具,否则js不认识

二.真实DOM和虚拟DOM谁更慢

1.规模较小的时候虚拟DOM是较快的,而规模较大的时候虚拟DOM是很慢的,原生DOM,或者说真实DOM是可以保证稳定性的

2.当规模只有数千以及以下时候,虚拟DOM会很好的优化一些操作

三.DOM diff(虚拟DOM的对比算法)

image.png

什么是DOM diff

就是一个函数,我们称之为patch 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(递归)