面试官:虚拟DOM与Diff算法

71 阅读3分钟

虚拟DOM与Diff算法

虚拟DOM

1、什么是虚拟DOM

虚拟DOM的本质就是一个JS对象,他描述了我们希望在页面中看到的元素。比如:我们在template标签中的元素,就可以抽象为一个虚拟DOM,即一个JS对象。

template 里标签结构:

<template>
  <div id="box">
    <p class="my_p">123</p>
  </div>
</template>

对应的虚拟 DOM 结构:

const dom = {
  type: 'div',
  attributes: [{ id: 'box' }],
  children: {
    type: 'p',
    attributes: [{ class: 'my_p' }],
    text: '123'
  }
}

2、虚拟DOM的作用

虚拟DOM真正的作用在于抽象了渲染过程,提升跨平台编程的能力。体现在Vue中的表现就是提升DOM的渲染效率,高效更新。为什么这么说?因为我们知道直接操作真实DOM的性能代价很大,会引起页面的重绘重排,并且多次、频繁的操作真实DOM,会引起页面的卡顿。而我们通过虚拟DOM树+Diff算法,则可以有效地提高渲染效率。

例如:我们在初次渲染页面的时候,会通过数据与模板,创建一个虚拟DOM树。

//数据
data: {
  list: [1, 2, 3, 4]
}


//模板
<ul>
  <li v-for="item in list">{{ item }}</li>
</ul>

//虚拟DOM
const dom = {
  type: 'ul',
  attributes: [],
  children: [
    { type: 'li', attributes: [], text: '1' },
    { type: 'li', attributes: [], text: '2' },
    { type: 'li', attributes: [], text: '3' },
    { type: 'li', attributes: [], text: '4' }
  ]
}

然后将这个虚拟DOM挂载到指定结点,形成真实DOM。

在数据发生变化,需要更改页面的数据时,会创建一个新的虚拟DOM树。

//数据
data: {
  list: [1, 2, 3, 4, 5]
}

//模板
<ul>
  <li v-for="item in list">{{ item }}</li>
</ul>

//虚拟DOM树
const dom = { 
    type: 'ul', 
    attributes: [], 
    children: [
      { type: 'li',attributes: [], text: '1'}, 
      { type: 'li', attributes: [], text:'2'}, 
      { type: 'li', attributes: [], text: '3'}, 
      { type: 'li',attributes: [], text: '4'},
      { type: 'li', attributes: [], text:'随机数'} 
    ] 
}

此时有两份虚拟DOM树,我们可以利用 diff 算法进行两份虚拟DOM进行对比,进而提升页面渲染效率。

image-20210914183148171.3760d1f4.png

Diff算法

Diff算法决定了我们的浏览器在更新数据的时候,是复用现有DOM还是创建新的DOM。当我们的数据发生变化之后,Diff算法会对两个虚拟DOM树进行逐层比较,在这个比较的过程中可以分为3中情况:

998023-20180519212338609-1617459354.png

1、结点的元素模板发生变化

当一个元素的模板,即template标签发生变化时,Diff算法会删除现有的页面结点,直接创建新的节点进行替代。

//旧的虚拟DOM模板
<div id="box">
    <p class="my_p">123</p>
</div>

//新的虚拟DOM模板
<ul id="box">
    <li class="my_p">123</li>
</ul>

2、结点的模板没有发生变化,数据发生变化

当一个元素的模板没有发生变化,但是数据发生了变化。此时Diff算法会优先复用现有的结点,只将数据进行更新。

998023-20180519212357826-1474719173.png

3、v-for配合key属性

当我们通过v-for来循环渲染一个标签的时候,我们会给这个标签设置一个key属性。Diff算法根据key的属性值来判断是否新建结点。如果虚拟DOM树上两个结点的key值相同,则复用现有的结点,只更新结点的数据;如果key值不同,则删除现有的真实DOM,创建新的DOM结点。