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插入到视图中,完成一次视图更新。

相关推荐
咸鱼翻面儿4 分钟前
Javascript异步,这次我真弄懂了!!!
javascript
brrdg_sefg5 分钟前
Rust 在前端基建中的使用
前端·rust·状态模式
m0_7482309429 分钟前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
qq_5895681037 分钟前
Echarts的高级使用,动画,交互api
前端·javascript·echarts
黑客老陈2 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安2 小时前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
编程百晓君2 小时前
一文解释清楚OpenHarmony面向全场景的分布式操作系统
vue.js
暴富的Tdy2 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se2 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel