Vue源码阅读(16):VNode

 我的开源库:

vnode 是虚拟DOM 中一个很重要的知识点,今天带大家了解一下。

1,什么是 VNode

首先说下什么是 VNode,VNode 的本质只是 JavaScript 中普通的对象字面量,只不过这个对象字面量中的属性可以很好的描述网页中的真实DOM,并且可以通过 VNode 创建出对应的真实的DOM 节点。

在源码中,有一个类专门用于实例化 vnode 对象。

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  functionalContext: Component | void; // real context vm for functional nodes
  functionalOptions: ?ComponentOptions; // for SSR caching
  functionalScopeId: ?string; // functioanl scope id support

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    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
  }

  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}

可以发现这个类的实现非常的简单,通过这个类可以实例化出不同类型真实DOM 所对应的 vnode 对象。

2,VNode 有什么作用

在 Vue 中,组件刚开始渲染的时候,首先需要通过组件的 render 函数结合当前的状态生成 vnode,然后通过 vnode 创建出对应的真实DOM 节点,最后把创建出来的DOM节点输出到页面中。

然后当组件中的状态发生了变化的时候,这个组件便会重新渲染,使用组件的 redner 函数结合最新的状态生成最新的 vnode,注意,这一次 Vue 不会直接使用新的 vnode 生成 DOM 节点输出到页面上,而是将本次生成的最新的 vnode 和上一次渲染使用的 vnode 进行比较(diff 算法),计算出哪些节点需要更新,然后到页面中去更新需要更新的节点,其他无需更新的节点则不需要做任何操作。通过这种方式,每次组件重新渲染的时候,都可以保证对真实 DOM 最小的操作量,以此来提高性能。

所以,至此,VNode 的作用也就很清晰了,它主要有以下两个作用:

  • 使用 VNode 可以很好的描述 DOM 节点,并且可以通过 VNode 生成对应的真实 DOM。
  • 每当组件渲染的时候,Vue 都会将当前的 vnode 保存起来,当下次组件重新渲染的时候,就可以将最新的 vnode 和上次渲染保存的 vnode 做比较(diff算法),然后只对有差异的DOM节点做更新操作。

3,VNode 有哪些类型

vnode一共有6种类型,如下所示:

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

1,注释节点

注释节点 vnode 用于描述真实 DOM 中的注释节点,例如有如下所示的注释:

<!-- 我是注释 -->

在 Vue 的源码中,有一个专门的函数,用于生成注释节点,如下所示:

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

可以看到,注释节点 vnode 有两个有效属性,分别是 text 和 isComment,text 就是注释DOM 中的字符,isComment 用于标识该 vnode 是不是注释节点。所以,上面例子中的注释对应的 vnode 如下所示:

{
  text: '我是注释',
  isComment: true
}

2,文本节点

文本节点用于描述真实 DOM 中的字符,在 Vue 源码中也有一个专门的函数,用于生成文本节点,如下所示:

export function createTextVNode (val: string | number) {
  return new VNode(undefined, undefined, undefined, String(val))
}

文本节点只有一个有效属性 text,例如,下面的文本节点。

{
  text: '我是文本'
}

3,元素节点

元素节点用于描述 web 中规范的 DOM 节点,例如:div,p,ul,li 等,通常有以下4种有效属性。

  • tag:DOM 节点的名称,例如:'div','p','ul','li'。
  • data:该属性是一个对象,内部包含了节点中的一些数据,例如:attrs,class,style 等。
  • children:当前节点的子节点列表。
  • context:当前元素节点所在的组件实例(Vue 实例)。

例如:有如下的真实 DOM 节点。

<h1>Hello World</h1>

对应的 vnode 如下所示:

{
  tag: 'h1',
  data: {...},
  context: {...},
  children: [vnode, vnode, ...]
}

4,克隆节点

我们在之前的文章中说过,在 Vue 中,节点存在静态节点的概念,静态节点每次渲染的时候,内容都不会变,所以静态节点对应的 vnode 除了首次渲染时会通过 render 函数生成,后续组件更新时并不会重新生成对应的 vnode。Vue 的内部在组件重新渲染的时候,会直接将静态 vnode 克隆一份,然后使用克隆的节点进行页面的渲染。

克隆的过程就是将被克隆节点的属性一一复制到克隆节点的过程,克隆节点的属性和被克隆节点的属性保持一致,唯一的区别是克隆节点中的 isCloned 属性为 true。

克隆节点也有对应的函数,如下所示:

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

5,组件节点

组件节点和元素节点的有效属性很像,只不过组件节点有以下两个独有属性。

  • componentOptions:是一个对象,内含组件节点的选项参数,例如 propsData。
  • componentInstance:指向该组件对应的实例,组件的实例也就是 Vue 的实例,在 Vue 的应用中,每使用一个组件,就会创建一个与之对应的 Vue 实例。

例如:

<hello></hello>

对应的 vnode 如下所示:

{
  tag: 'vue-component-1-hello',
  data: {...},
  context: {...},
  componentOptions: {...},
  componentInstance: {...},
  ...
}

6,函数式组件节点

函数式组件节点和组件节点很像,不过它也有两个独有的属性,分别是 functionalContext 和 functionalOptions。

一个函数式组件节点如下所示:

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

4,结语

好了,以上就是 VNode 的全部内容,接下来讲讲虚拟 DOM 中最为重要的 patch 方法(diff 算法)。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我来回答你的问题。 首先,我们需要了解一下 `Object.defineProperty` 这个方法。它是一个 JavaScript 的内置方法,用于在一个对象上定义一个新的属性或者修改一个已有的属性。这个方法接受三个参数,分别是要定义属性的对象、属性名和属性描述符。 其中,属性描述符是一个包含了四个属性的对象:`value`、`writable`、`enumerable` 和 `configurable`。`value` 属性指定了属性的值,`writable` 属性指定了属性是否可写,`enumerable` 属性指定了属性是否可枚举,`configurable` 属性指定了属性是否可配置。 在 Vue 组件中,我们可以使用 `Object.defineProperty` 来定义响应式数据。具体来说,我们可以在组件的 `data` 选项中定义一个对象,并使用 `Object.defineProperty` 将该对象的属性转换成响应式数据,这样当我们修改属性时,组件会自动重新渲染。 下面是一个简单的例子,展示了如何使用 `Object.defineProperty` 定义一个响应式数据: ``` const obj = {} Object.defineProperty(obj, 'name', { value: 'Vue', writable: true, enumerable: true, configurable: true }) ``` 这里我们定义了一个空对象 `obj`,然后使用 `Object.defineProperty` 将该对象的属性 `name` 定义为一个响应式数据,初始值为 `'Vue'`。由于 `writable` 属性被设置为 `true`,所以我们可以修改 `obj.name` 的值,而这个修改会触发组件的重新渲染。 希望我的回答能够解决你的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值