在Vue.js
中存在一个VNode
类,使用它可以实例化不同类型的vnode
实例,而不同类型的vnode
实例各自表示不同类型的DOM元素。
vnode
只是一个名字,本质上其实是JavaScript
中的一个普通对象,是从VNode
类实例化的对象。我们用这个JavaScript
对象来描述一个真实的DOM元素,那么该DOM元素上的所有属性在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.context = context;
this.componentOptions = componentOptions;
this.asyncFactory = asyncFactory;
this.ns = undefined;
this.functionalContext = undefined;
this.funtionalOptions = undefined;
this.functionalScopeId = undefined;
this.key = data && data.key;
this.parent = undefined;
this.componentInstance = undefined;
this.raw = false;
this.isStatic = false;
this.isRootInsert = true;
this.isComment = false;
this.isCloned = false;
this.isOnce = false;
this.asyncMeta = undefined;
this.isAsyncPlaceholder = false;
}
get child (){
return this.componentInstance;
}
}
vnode
可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点。我们可以把vnode
理解成JavaScript
对象版本的DOM元素。
渲染视图的过程是先创建vnode
,然后使用vnode
去生成真实的DOM元素,最后插入到页面渲染视图。
只要组件的众多状态中有一个发生了变化,那么整个组件就要重新渲染。因此,对vnode
进行缓存,并将上一次缓存的vnode
和当前新创建的vnode
进行对比,只更新发生变化的节点就尤为重要。
注释节点只有两个有效属性:text
和isComment
,其余属性全是默认的undefined
或false
:
export const createEmptyVNode = text =>{
const node = new VNode();
node.text = text;
node.isComment = true;
console.log(node);
}
一个真实的注释节点:
<!--注释节点-->
对应的vnode
是这样的:
{
text:"注释节点",
isComment:true
}
文本节点类型的vnode
被创建时,它只有一个text
属性:
export function createTextVNode(val){
return new VNode(undefined,undefined,undefined,String(val));
}
文本类型的vnode
:
{
text:"hello word"
}
克隆节点是将现有节点的属性复制到新节点中,让新创建的节点和被克隆的节点属性保持一致,从而实现克隆效果。作用是优化静态节点和插槽节点(slot node
)。
克隆现有节点时,只需要将现有节点的属性全部复制到新节点中即可。
/* 克隆节点 */
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 = cloneVNode(vnode.children);
}
return cloned;
}
克隆节点和被克隆节点之间唯一的区别是isCloned
属性,克隆节点的isCloned
为true
,被克隆的原始节点的isCloned
为false
。
元素节点通常会存在一下4种有效属性:
tag
:就是一个节点的名称,例如:p、ul、li和div等。data
:该属性包含了一些节点上的数据,比如attrs
、class
、style
。children
:当前节点的子节点列表。context
:当前组件的Vue.js
实例。<p>
<span>Hellospan><span>Wordspan>
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
。
组件的vnode
是这个样子的:
{
functionalContext:{...},
functionalOptions:{...},
context:{...},
data:{...},
tag:"div",
}
VNode
是一个类,可以生成不同类型的vnode
实例,而不同类型的vnode
表示不同类型的真实DOM元素。
由于Vue.js
对组件采用了虚拟DOM来更新视图,当属性发生变化时,真个整个组件都要进行重新渲染的操作,但组件内并不是所有的DOM节点都需要更新,所以将vnode
缓存并将当前新生成的vnode
和上一个缓存的oldVNode
进行对比,只需要更新的部分进行DOM操作可以提升很多性能。
vnode
有多种类型,它们本质上都是从VNode
类实例化出的对象,其唯一区别只是属性不同。