综合开发

Vue源码探秘(六)(Virtual DOM)

微信扫一扫,分享到朋友圈

Vue源码探秘(六)(Virtual DOM)

引言

Virtual DOM (后文简称 vdom )的概念大规模的推广得益于 react 的出现, vdom 也是 react 框架比较重要的特性之一。相比较频繁的手动去操作 dom 而带来性能问题, vdom 很好的将 dom 做了一层映射关系,进而将在我们本需要直接进行 dom 的一系列操作,映射到了操作 vdom

让我们进入今天的文章。

VNode

VNodeVirtual DOMVue.js 中的数据结构定义,它被定义在 src/core/vdom/vnode.js 中:

// src/core/vdom/vnode.js
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;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtools
fnScopeId: ?string; // functional 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.fnContext = undefined;
this.fnOptions = undefined;
this.fnScopeId = 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;
}
}

可以看到 VNode 是一个类,有很多属性。每一个 vnode 都映射到一个真实的 dom 节点上。我们这里先了解几个重要的属性:

  • tag : 对应真实节点的标签名
  • data
    class
    attribute
    style
    VNodeData
    flow/vnode.js
    
// flow/vnode.js
declare interface VNodeData {
key?: string | number;
slot?: string;
ref?: string;
is?: string;
pre?: boolean;
tag?: string;
staticClass?: string;
class?: any;
staticStyle?: { [key: string]: any };
style?: string | Array<Object> | Object;
normalizedStyle?: Object;
props?: { [key: string]: any };
attrs?: { [key: string]: string };
domProps?: { [key: string]: any };
hook?: { [key: string]: Function };
on?: ?{ [key: string]: Function | Array<Function> };
nativeOn?: { [key: string]: Function | Array<Function> };
transition?: Object;
show?: boolean; // marker for v-show
inlineTemplate?: {
render: Function,
staticRenderFns: Array<Function>
};
directives?: Array<VNodeDirective>;
keepAlive?: boolean;
scopedSlots?: { [key: string]: Function };
model?: {
value: any,
callback: Function
};
}
  • children :
    vnode 的子节点
  • text :当前节点的文本
  • elm : 当前虚拟节点对应的真实节点
  • parent : 当前节点的父节点

看完 VNodeVue.js 中的数据结构定义,我想你已经大概知道 vdom 是什么了吧。

Virtual DOM 是什么?

本质上来说, vdom 只是一个简单的 js 对象,并且最少包含 tagpropschildren 三个属性。不同的框架对这三个属性的命名会有点差别,但表达的意思是一致的。它们分别是 标签名(tag)属性(props)子元素对象(children) 。下面是举一个经典的 vdom 例子:

<div>
Hello jack-cool
<ul>
<li id="1" class="li-1">
我是森林
</li>
</ul>
</div>

vdomdom 对象有着一一对应的关系,上面的 html 对应生成的 vdom 如下:

{
tag: "div",
props: {},
children: [
"Hello jack-cool",
{
tag: "ul",
props: {},
children: [{
tag: "li",
props: {
id: 1,
class: "li-1"
},
children: ["我是", "森林"]
}]
}
]
}

Virtual DOM 有什么作用?

vdom 的最终目标是将 vnode 渲染到视图上。但是如果直接使用新节点覆盖旧节点的话,会有很多不必要的 DOM 操作。

我们先来看下引入 vdom 前后,实现视图更新的不同流程:

引入 vdom 之前

  • 数据 + 模板生成真实
    DOM
  • 数据发生改变

  • 新的数据 + 模板生成新的
    DOM
  • 新的
    DOM 替换掉原来的
    DOM

这么做的缺点在于:即使模板中只有一个元素发生了变化,也会把整个模板替换掉。例如,一个 ul 标签下很多个 li 标签,其中只有一个 li 有变化,这种情况下如果使用新的 ul 去替代旧的 ul ,会有很多不必要的 DOM 操作而造成性能上的损失。

为了避免不必要的 DOM 操作, vdomvnode 映射到视图的过程中,将 vnode 与上一次渲染视图所使用的旧虚拟节点( oldVnode )做对比,找出真正需要更新的节点来进行 DOM 操作,从而避免操作其他无需改动的 DOM

引入 vdom 之后

  • 数据 + 模板生成虚拟
    DOM
  • 虚拟
    DOM 生成真实
    DOM
  • 数据发生改变

  • 新的数据 + 模板生成新的虚拟
    DOM 而不是真实
    DOM
  • DOM
    DOM
    diff
    up↑
    
  • 找出发生改变的元素

  • 直接修改原来的真实
    DOM 【性能
    up↑

总结

这一节我带大家大概了解了 Virtual DOM 的概念。 Vue.jsVNode 其实是借鉴了 snabbdom 的实现。

VNode 到真实 DOM 需要经过 creatediffpatch 等几个过程。本小节呢,我们只是大概了解一下 vdom 是什么以及它有什么作用。关于 vdom 的一些详细概念、流程和内部实现,我会在后面的章节中和大家分享(其实关于 Virtual DOM 单独出一个系列文章也不为过)。

回顾 Vue源码探秘(五)(_render 函数的实现) ,我们提到 vm._cvm.$createElement 都要调用 createELement 函数,其实这个函数就是 VNodecreate 过程,下一节我们就来分析 createELement 函数。

:heart:爱心三连击

1.看到这里了就点个在看支持下吧,你的 「在看」 是我创作的动力。

2.关注公众号 达达前端「每天为您分享原创或精选文章」

3.特殊阶段,带好口罩,做好个人防护。

4.添加微信【xiaoda0423】,拉你进 技术交流群 一起学习

扫码关注公众号,订阅更多精彩内容。

在看和转发是莫大鼓励

三年 Android 经验面经

上一篇

[多图]居家K歌利器:联想小新点歌机BK10图赏

下一篇

你也可能喜欢

评论已经被关闭。

插入图片

热门栏目

Vue源码探秘(六)(Virtual DOM)

长按储存图像,分享给朋友