【Vue2深度学习】虚拟DOM篇-Vue中的虚拟DOM

1,106 阅读4分钟
虚拟DOM简介

所谓虚拟DOM,就是用一个JS对象来描述一个DOM节点,像如下示例:

<div class="a" id="b">我是内容</div>
​
{
  tag:'div',        // 元素标签
  attrs:{           // 属性
    class:'a',
    id:'b'
  },
  text:'我是内容',  // 文本内容
  children:[]       // 子元素
}

我们把组成一个DOM节点的必要东西通过一个JS对象表示出来,那么这个JS对象就可以用来描述这个DOM节点,我们把这个JS对象就称为是这个真实DOM节点的虚拟DOM节点。

Vue中为什么要引入虚拟DOM

我们知道,Vue是数据驱动视图的,数据发生变化视图就要随之更新,在更新视图的时候难免要操作DOM,而操作真实DOM又是非常耗费性能的,这是因为浏览器的标准就把 DOM 设计的非常复杂,所以一个真正的 DOM 元素是非常庞大的,如下所示:

let div = document.createElement('div')
let str = ''
for (const key in div) {
  str += key + ''
}
console.log(str)

上图中我们打印一个简单的空div标签,就打印出这么多东西,更不用说复杂的、深嵌套的DOM节点了。由此可见,直接操作真实DOM是非常消耗性能的。

那么有没有什么解决方案呢?当然是有的。我们可以用JS的计算性能来换取操作DOM所消耗的性能。

既然我们逃不掉操作DOM这道坎,但是我们可以尽可能少的操作DOM。那如何在更新视图的时候尽可能少的操作DOM呢?最直观的思路就是我们不要盲目的去更新视图,而是通过对比数据变化前后的状态,计算出视图中哪些地方需要更新,只更新需要更新的地方,而不需要更新的地方则不需关心,这样我们就可以尽可能少的操作DOM了。这也就是上面所说的用JS的计算性能来换取操作DOM的性能。

我们可以用JS模拟出一个DOM节点,称之为虚拟DOM节点。当数据发生变化时,我们对比变化前后的虚拟DOM节点,通过DOM-Diff算法计算出需要更新的地方,然后去更新需要更新的视图。

这就是虚拟DOM产生的原因以及最大的用途。

Vue中如何实现虚拟DOM

因为虚拟DOM就是用JS来描述一个真实的DOM节点,所以Vue中就创建了一个VNode类,通过这个类,我们就可以实例化出不同类型的虚拟DOM节点,源码如下:

// 源码位置:src/core/vdom/vnode.js
​
export default class VNode {
  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag                                /*当前节点的标签名*/
    this.data = data        /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
    this.children = children  /*当前节点的子节点,是一个数组*/
    this.text = text     /*当前节点的文本*/
    this.elm = elm       /*当前虚拟节点对应的真实dom节点*/
    this.ns = undefined            /*当前节点的名字空间*/
    this.context = context          /*当前组件节点对应的Vue实例*/
    this.fnContext = undefined       /*函数式组件对应的Vue实例*/
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key           /*节点的key属性,被当作节点的标志,用以优化*/
    this.componentOptions = componentOptions   /*组件的option选项*/
    this.componentInstance = undefined       /*当前节点对应的组件的实例*/
    this.parent = undefined           /*当前节点的父节点*/
    this.raw = false         /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
    this.isStatic = false         /*静态节点标志*/
    this.isRootInsert = true      /*是否作为跟节点插入*/
    this.isComment = false             /*是否为注释节点*/
    this.isCloned = false           /*是否为克隆节点*/
    this.isOnce = false                /*是否有v-once指令*/
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }
​
  get child (): Component | void {
    return this.componentInstance
  }
}

从上面的代码中可以看出:VNode类中包含了描述一个真实DOM节点所需要的一系列属性,如tag表示节点的标签名,text表示节点中包含的文本,children表示该节点包含的子节点等。通过属性之间不同的搭配,就可以描述出各种类型的真实DOM节点。

通过本篇,我们了解了什么是虚拟DOM以及虚拟DOM的作用,并知道了Vue中是通过创建VNode类实现虚拟DOM的。下一篇,我们将详细介绍VNode类是如何运作的。