携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
虚拟DOM简介
虚拟DOM是把真实DOM节点用原生 js 代码抽象成一个个对象。
<div class="a" id="b">我是内容</div>
{
tag:'div', // 元素标签
attrs:{ // 属性
class:'a',
id:'b'
},
text:'我是内容', // 文本内容
children:[] // 子元素
}
此时这个 js 对象就可以用来描述这个 div 节点,把它称作这个真实DOM节点的虚拟DOM节点。
为什么要有虚拟DOM
数据刷新就不能避免操作DOM,而操作真实DOM节点是非常消耗性能的,因为一个DOM元素有特别多的属性。
const div = document.createElement("div")
let str = "";
for (key in div) {
str += key;
}
console.log(str);
打印一个空
div标签就有这么多属性,如果有复杂嵌套的DOM节点操作起来会更消耗性能。但是视图更新肯定就会操作DOM,所以只能尽可能减少DOM操作。用
JS对象模拟出DOM节点,称为虚拟DOM节点。当数据发生变化时,对比变化前后的虚拟DOM节点,通过DOM-Diff算法计算出需要更新的地方,然后去更新需要更新的视图,这是用JS的计算性能来换取操作DOM的性能。
Vue中的虚拟DOM
VNode类
在Vue中有一个 VNode 类,通过这个类把不同的节点类型实例化成虚拟 DOM 节点:
// 源码位置:src/core/vdom/vnode.js
export default class VNode {
constructor(
tag,
data,
children,
text,
elm,
context,
componentOptions,
asyncFactory
) {
this.tag = tag; /*当前节点的标签名*/
this.data =
data; /*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
this.children = children; /*当前节点的子节点,是一个数组*/
this.text = text; /*当前节点的文本*/
this.elm = elm; /*当前虚拟节点对应的真实dom节点*/
this.ns = undefined; /*当前节点的名字空间*/
this.context = context; /*当前组件节点对应的Vue实例*/
this.fnContext = undefined; /*函数式组件对应的Vue实例*/
this.fnOptions = undefined;
this.fnScopeId = undefined;
this.key = data && data.key; /*节点的key属性,被当作节点的标志,用以优化*/
this.componentOptions = componentOptions; /*组件的option选项*/
this.componentInstance = undefined; /*当前节点对应的组件的实例*/
this.parent = undefined; /*当前节点的父节点*/
this.raw = false; /*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
this.isStatic = false; /*静态节点标志*/
this.isRootInsert = true; /*是否作为跟节点插入*/
this.isComment = false; /*是否为注释节点*/
this.isCloned = false; /*是否为克隆节点*/
this.isOnce = false; /*是否有v-once指令*/
this.asyncFactory = asyncFactory;
this.asyncMeta = undefined;
this.isAsyncPlaceholder = false;
}
get child() {
return this.componentInstance;
}
}
VNode 类把一个真实 DOM 节点的必要属性保存起来,通过不同的属性值,就可以描述出各种类型的真实 DOM 节点。
VNode的类型
VNode 描述了下面几种节点类型:
注释节点
// 创建注释节点
export const createEmptyVNode = (text = "") => {
const node = new VNode();
node.text = text;
node.isComment = true;
return node;
};
描述一个注释节点只需两个属性,text表示注释的内容,isComment是一个标志,有这个属性说明该节点是注释节点。
文本节点
// 创建文本节点
export function createTextVNode(val) {
return new VNode(undefined, undefined, undefined, String(val));
}
文本节点只需要一个 text 属性,即文本内容。
克隆节点
// 创建克隆节点
export function cloneVNode(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.fnContext = vnode.fnContext;
cloned.fnOptions = vnode.fnOptions;
cloned.fnScopeId = vnode.fnScopeId;
cloned.asyncMeta = vnode.asyncMeta;
cloned.isCloned = true;
return cloned;
}
克隆节点是把已存在的节点的属性全部复制到新节点中,然后会新增一个属性 isCloned ,表示该节点克隆节点。
元素节点
// 真实DOM节点
<div id='a'><span>难凉热血</span></div>
// VNode节点
{
tag:'div',
data:{},
children:[
{
tag:'span',
text:'难凉热血'
}
]
}
元素节点和真实 DOM 节点很相似,有 tag 属性,描述标签属性的 data 属性,描述子节点信息的 children 属性等。
组件节点
组件节点除了元素节点具有的属性之外,还有两个特有的属性:
- componentOptions:组件的option选项,如组件的
props等 - componentInstance:当前组件对应的
Vue实例
函数式组件节点
函数式组件节点,比组件节点还多两个特有的属性:
- fnContext:函数时组件对应的
Vue实例 - fnOptions:组件的option选项
VNode的作用
在视图渲染之前,会把 template 模板先编译成 VNode 并缓存下来,数据发生变化后需要更新视图时,把数据变化后生成的 VNode 与之前缓存的 VNode 进行对比,然后找出对应的有差异的 DOM 节点就是我们需要更新的 DOM 节点,最后创建出真实 DOM 节点插入到视图中,完成视图更新。
总结
- 虚拟
DOM是把真实DOM抽象成一个JS对象。 - 操作真实
DOM非常消耗性能,为了减少性能消耗,需要尽可能的减少DOM操作,虚拟DOM使用JS的计算性能来换取操作DOM的性能。 VNode类,根据不同节点类型,生成不同类型的VNode实例。- 通过数据变化前后的
VNode实例对比找出差异,只更新有差异的视图,尽可能的减少操作真实DOM。