我的开源库:
- fly-barrage 前端弹幕库,项目官网:https://fly-barrage.netlify.app/,可实现类似于 B 站的弹幕效果,并提供了完整的 DEMO,Gitee 推荐项目;
- fly-gesture-unlock 手势解锁库,项目官网:https://fly-gesture-unlock.netlify.app/,在线体验:https://fly-gesture-unlock-online.netlify.app/,可高度自定义锚点的数量、样式以及尺寸;
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 算法)。