Vue源码探究虚拟DOM

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

ts 复制代码
<div class="a" id="b">我是内容</div>

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

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

那么为什么需要这个虚拟DOM节点?我们知道VUE是数据驱动视图的,数据变化视图随之变化,在更新视图时需要操作DOM,而操作真实DOM节点是非常耗费性能的,这是因为浏览器把DOM节点设计的非常复杂,一个真实DOM节点是非常庞大的。

所以对于需要时常更新的视图我们不能每次都去操作真实DOM,这是不合理的。所以VUE提出了解决办法,就是用JS的计算性能换取操作DOM消耗的性能。

既然我们逃不掉操作DOM,那么我们可以尽可能的减少操作DOM。那如何在更新视图时尽可能少的操作DOM呢?最直观的思路就是我们不要盲目的去更新DOM,而通过对比更新前后的状态,计算哪些地方需要更新而哪些地方没有变化,以此去操作只需要更新的地方,其他地方原封不动,这样便可以尽可能少的操作DOM。也就是用JS性能换取DOM消耗性能。

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

VUE的虚拟DOM

vue中存在一个VNode类,通过这个类,可以实例化出不同类型的虚拟DOM节点,源码如下:

TS 复制代码
// 源码位置: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节点。

1.VNode的类型

VNode可以描述出不同类型的真实DOM节点,那么有哪些类型呢?

  • 注释节点
  • 文本节点
  • 元素节点
  • 组件节点
  • 函数式组件节点
  • 克隆节点
1.1 注释节点
ts 复制代码
// 创建注释节点
export const createEmptyVNode = (text: string = '') => {
  const node = new VNode()
  node.text = text
  node.isComment = true
  return node
}

描述一个注释节点只需要两个属性:text和isComment。其中text属性表示具体的注释信息,isComment是一个标志,用来标识一个节点是否是注释节点。

1.2文本节点
TS 复制代码
// 创建文本节点
export function createTextVNode (val: string | number) {
  return new VNode(undefined, undefined, undefined, String(val))
}

只有一个text属性

1.3克隆节点
TS 复制代码
// 创建克隆节点
export function cloneVNode (vnode: VNode): VNode {
  const cloned = new VNode(
    vnode.tag,
    vnode.data,
    vnode.children,
    vnode.text,
    vnode.elm,
    vnode.context,
    vnode.componentOptions,
    vnode.asyncFactory
  )
  cloned.ns = vnode.ns
  cloned.isStatic = vnode.isStatic
  cloned.key = vnode.key
  cloned.isComment = vnode.isComment
  cloned.fnContext = vnode.fnContext
  cloned.fnOptions = vnode.fnOptions
  cloned.fnScopeId = vnode.fnScopeId
  cloned.asyncMeta = vnode.asyncMeta
  cloned.isCloned = true
  return cloned
}

就是把一个已经存在的节点复制一份出来,主要为了做模版编译优化使用

1.4元素节点
TS 复制代码
// 真实DOM节点
<div id='a'><span>ww</span></div>

// VNode节点
{
  tag:'div',
  data:{},
  children:[
    {
      tag:'span',
      text:'ww'
    }
  ]
}

元素节点更贴近于我们看到的真实dom节点,它有描述标签的tag属性,描述节点属性如class,attributes等的data属性,有描述子节点信息的chilrend属性。

1.5组件节点

除了有元素节点具有的属性外,还有两个特有属性:

componentOptions:组件的option选项,如组件的props等

componentInstance:当前组件节点对应的Vue实例

1.6函数式组件节点

同上

fnContext:函数式组件对应的vue实例

fnOptions:组件的option选项

2.VNode的作用

我们在视图渲染之前,把写好的template模版编译成VNode并缓存下来,等到数据更新时页面需要重新渲染的时候,我们把数据变化后的VNode节点与前一次缓存的进行对比,找出差异,最后有差异的VNode对应的真实DOM就是需要重新渲染的节点,根据有差异的VNode创建出真实DOM插入到视图中,完成一次视图更新。

相关推荐
Devil枫16 分钟前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦1 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子1 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山2 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享2 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
程序媛小果2 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
从兄3 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
凉辰4 小时前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式
清灵xmf4 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨4 小时前
VUE+Vite之环境文件配置及使用环境变量
前端