# 什么是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是从VNode类实例化的对象,可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点,也可以理解成JavaScript对象版本的DOM元素。

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

# VNode的作用

Vue.js目前对状态的侦测策略采用了中等粒度。当状态发生变化时,只通知到组件级别,也就是组件中使用的状态只有一个发生变化,整个组件都要重新渲染,这样明显浪费性能。

我们可以将vnode进行缓存,用上一次缓存的vnode和当前新创建的vnode进行对比,只更新发生变化的节点。

# VNode的类型

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

# 注释节点

注释节点只有两个有效属性——text和isComment

<!-- 注释节点 -->

将这个注释节点表示成vnode

{
  text: "注释节点",
  isComment: true
}

创建注释节点的代码:

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

# 文本节点

文本节点只有一个text属性

hello world

上面这个文本节点对应的vnode

{
  text: 'hello world'
}

创建文本节点的代码:

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

# 克隆节点

克隆节点的代码:

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
  // 克隆节点的isCloned属性为true,被克隆节点的isCloned属性为false
  cloned.isCloned = true
  if (deep && vnode.children) {
    cloned.children = cloneVNodes(vnode.children)
  }
  return cloned
}

从代码中可以看出克隆节点时就是将现有节点的属性全部复制到新节点。

什么情况下会使用克隆节点呢?

因为静态节点的内容不会变,只需要在首次渲染的时候获取vnode,后续更新不需要重新生成vnode,将vnode克隆一份,使用克隆节点进行渲染,减少了重新生成vnode的消耗,有利于提升性能。

# 元素节点

元素节点存在四个属性

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

表示成vnode

{
  children: [VNode, VNode],
  context: {...},
  data: {...}
  tag: "p",
  ……
}

# 组件节点

组件接地在元素节点上新增了两个特有的属性

  • componentOptions:顾名思义,就是组件节点的选项参数,其中包含propsData、tag和children等信息。
  • componentInstance:组件的实例,也是Vue.js的实例。事实上,在Vue.js中,每个组件都是一个Vue.js实例。
<child></child>

对应的vnode

{
  componentInstance: {...},
  componentOptions: {...},
  context: {...},
  data: {...}
  tag: "vue-component-1-child",
  ……
}

# 函数式组件

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

从代码可以看出函数式组件和组件节点类似,有两个特有的属性

  • functionalContext
  • functionalOptions

# 总结

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

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

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