VNode 是什么?

本文详细解读了Vue.js中的VNode概念,介绍了其作为节点描述对象的作用,包括创建过程、类型(元素节点、文本节点、注释节点等)以及在虚拟DOM中的缓存策略,以提高性能。重点强调了VNode在组件更新中的关键角色和不同类型的VNode属性区分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是 VNode

在 vue.js 中存在一个 VNode 类,使用它可以实例化不同类型的 vnode 实例,而不同类型的 vnode 实例各自表示不同类型的 DOM 元素。

例如,DOM 元素有元素节点,文本节点,注释节点等,vnode 实例也会对应着有元素节点和文本节点和注释节点。

VNode 类代码如下:

export default class VNode {
    constructor(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
       this.tag = tag
        this.data = data
        this.children = children
        this.text = text
        this.elm = elm
        this.ns = undefined
        this.context = context
        this.functionalContext = undefined
        this.functionalOptions = undefined
        this.functionalScopeId = undefined
        this.key = data && data.key
        this.componentOptions = componentOptions
        this.componentInstance = undefined
        this.parent = undefined
        this.raw = false
        this.isStatic = false
        this.isRootInsert = true
        this.isComment = false
        this.isCloned = false
        this.isOnce = false
        this.asyncFactory = asyncFactory
        this.asyncMeta = undefined
        this.isAsyncPlaceholder = false
    }
    get child() {
        return this.componentInstance
    }
 }

从上面的代码可以看出,vnode 只是一个名字,本质上来说就是一个普通的 JavaScript 对象,是从 VNode 类实例化的对象。我们用这个 JavaScript 对象来描述一个真实 DOM 元素的话,那么该 DOM 元素上的所有属性在 VNode 这个对象上都存在对应得属性。

简单来说,vnode 可以理解成节点描述对象,他描述了应该怎样去创建真实的 DOM 节点。
例如,tag 表示一个元素节点的名称,text 表示一个文本节点的文本,children 表示子节点等。vnode 表示一个真实的 DOM 元素,所有真实的 DOM 节点都是用 vnode 创建并插入到页面中。

VNode 创建 DOM 并插入到视图

图中展示了使用 vnode 创建真实的 DOM 并渲染到视图的过程。可以得知,vnode 和视图是一一对应的。我们可以把 vnode 理解成 JavaScript 对象版本的 DOM 元素。

渲染视图的过程是先创建 vnode,然后在使用 vnode 去生成真实的 DOM 元素,最后插入到页面渲染视图。

VNode 的作用

由于每次渲染视图时都是先创建 vnode,然后使用它创建的真实 DOM 插入到页面中,所以可以将上一次渲染视图时先所创建的 vnode 先缓存起来,之后每当需要重新渲染视图时,将新创建的 vnode 和上一次缓存的 vnode 对比,查看他们之间有哪些不一样的地方,找出不一样的地方并基于此去修改真实的 DOM。

Vue.js 目前对状态的侦测策略采用了中等粒度。当状态发生变化时,只通知到组件级别,然后组件内使用虚拟 DOM 来渲染视图。

如图下所示,当某个状态发生变化时,只通知使用了这个状态的组件。也就是说,只要组件使用的众多状态中有一个发生了变化,那么整个组件就要重新渲染。

如果组件只有一个节点发生了变化,那么重新渲染整个组件的所有节点,很明显会造成很大的性能浪费。因此,对 vnode 惊醒缓存,并将上一次的缓存和当前创建的 vnode 对比,只更新有差异的节点就变得很重要。这也是 vnode 最重要的一个作用。

VNode 的类型

vnode 有很多不同的类型,有以下几种:

注释节点
文本节点
元素节点
组件节点
函数式节点
克隆节点

前面介绍了 vnode 是一个 JavaScript 对象,不同类型的 vnode 之间其实属性不同,准确说是有效属性不同。因为当使用 VNode 类创建一个 vnode 时,通过参数为实例设置属性时,无效的属性会默认设置为 undefined 或者 false。对于 vnode 身上的无效属性,直接忽略就好。

  1. 注释节点

由于创建注释节点的过程非常简单,所以直接通过代码来介绍它有哪些属性:

export const createEmptyVNode = text => {
        const node = new VNode()
        node.text = text;
        node.isComment = true;
        return node
    }

一个注释节点只有两个有效属性 text 和 isComment。其余属性全是默认 undefined 或者 false。

例如一个真实的注释节点,所对应的 vnode 是下面的样子:

// <!-- 注释节点 -->
{
    text: "注释节点",
    isComment: true
}
  1. 文本节点

文本节点的创建过程也非常简单,代码如下:

export function createTextVNode(val) {
        return new VNode(undefined, undefined, undefined, String(val))
    }

当文本类型的 vnode 被创建时,它只有一个 text 属性:

{
    text: "文本节点"
}
  1. 克隆节点

克隆节点是将现有节点的属性赋值到新节点中,让新创建的节点和被克隆的节点的属性保持一致,从而实现克隆效果。它的作用是优化静态节点和插槽节点(slot node)。

以静态节点为例,当组件内某个状态发生变化后,当前组件会通过虚拟 DOM 重新渲染视图,静态节点因为它的内容不会改变,所以除了首次渲染需要执行渲染函数获取 vnode 之外,后续更新不需要执行渲染函数重新生成 vnode。

因此,这是就会使用创建克隆节点的方法将 vnode 克隆一份,使用克隆节点进行渲染。这样就不需要执行渲染函数生成新的静态节点的 vnode,从而提升一定的性能。

创建克隆节点的代码如下:

export function cloneVNode(vnode, deep) {
        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.isCloned = true
        if (deep && vnode.children) {
            cloned.children = cloneVNodes(vnode.children)
        }
        return cloned
    }

克隆现有节点,只需要将现有节点的属性全部赋值到新节点中。
克隆节点和被克隆节点位移的区别是 isCloned 属性,克隆节点为 true,被克隆的原始节点为 false。

  1. 元素节点

元素节点通常会存在以下 4 中有效属性。

tag:tag 就是一个节点的名称,例如 p、ul、li 和 div 等。
data:改属性包含了一些节点上的数据,比如 attrs、class 和 style 等。
children:当前节点的子节点列表。
context:它是当前组件的 Vue.js 实例

一个真实的元素节点,对应得 vnode 是下面这样:

// <p><span>Hello</span><span>World</span></p>
    {
        children: [VNode, VNode],
        context: {...},
        data: {...},
        tag: "p",
        ...
    }
  1. 组件节点

组件节点和元素节点类似,有以下两个独有的属性。

componentOptions:组件节点的选项参数,其中包含了 propsData、tag 和 children 等信息
componentInstance:组件的实例,也就是 Vue.js 的实例。事实上,在 Vue.js 中,每个组件都有一个 Vue.js 实例。

一个组件节点,对应得 vnode 是下面这样:

// <child></child>
    {
        componentInstance: {...},
        componentOptions: {...},
        context: {...},
        data: {...},
        tag: "vue-component-1-child",
        ...    
    }
  1. 函数式节点

函数式节点和组件节点类似,他有两个独有的属性 functionalContext 和 functionalOptions。
通常,一个函数式节点的 vnode 是下面这样:

{
        functionalContext: {...},
        functionalOptions: {...},
        context: {...},
        data: {...},
        tag: "div"
   	  }

总结

VNode是一个类,可以生产不同类型的vnode实例,不同类型的实例表示不同类型的真实DOM。

由于Vue.js对组件采用了虚拟DOM来更新视图,当属性发生变化时,整个组件都要进行重新渲染的操作,但组件内并不是所有的DOM节点都需要更新,所以将vnode缓存并将当前新生成的vnode和缓存的vnode作对比,只对需要更新的部分进行DOM操作可以提升很多的性能。

vnode有很多类型,它们本质上都是Vnode实例化出的对象,其唯一区别是属性不同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值