在浏览器中我们可以通过js来操作DOM,但是这样的操作性能很差,因为真正的DOM元素非常庞大,于是Virtual Dom应运而生。可以这样理解,Virtual Dom就是在js中模拟DOM对象树来优化DOM操作的一种技术或思路。
下面先来看看Vue中的Virtual Dom,源码位置在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;
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void;
parent: VNode | void;
//...
constructor (
tag?: string, // vNode的标签,例如div、p等标签
data?: VNodeData, // vNode上的data值,包括其所有的class、attribute属性、style属性
children?: ?Array<VNode>, // vNode上的子节点
text?: string, // 文本
elm?: Node, // vNode上对应的真实dom元素
context?: Component, // vdom的上下文
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
//...
}
//...
}
从上面源码可以看出,Virtual Dom相对浏览器中的真实Dom要简单得多。其实Vue中的Virtual Dom就是把真实Dom中一些常用的属性类型,再加上Vue里面的一些配置项组成的。
Vue中的Virtual Dom不仅对真实浏览器Dom进行简化外,还对不同场景进行了Virtual Dom的分类。下面是EmptyVNode、TextVNode、CloneVNode的创建,源码位置在src/core/vdom/vnode.js
// EmptyVNode
export const createEmptyVNode = (text: string = '') => {
const node = new VNode()
node.text = text
node.isComment = true
return node
}
// TextVNode
export function createTextVNode (val: string | number) {
return new VNode(undefined, undefined, undefined, String(val))
}
// CloneVNode
export function cloneVNode (vnode: VNode): VNode {
const cloned = new VNode(
vnode.tag,
vnode.data,
vnode.children && vnode.children.slice(),
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
}
关于Virtual Dom的分类,除了上面三种外, 还有ElementVnode,ComponentVnode等。
关于VNode的创建这里就不多说,在"Vue模板编译-AST生成Render字符串"那一节内容中我们就说了生成Render字符串中_c就是createElement创建VNode,还不清楚的可以回头再看一下。
比较两棵DOM树的差异是 Virtual DOM
最核心的部分,这也是所谓的 Virtual DOM
的diff算法。 它的作用就是根据两个虚拟对象创建出描述改变内容的补丁,将这个补丁用来更新DOM。
两个Dom树的 diff 算法是一个时间复杂度为 O(n^3) 的问题,但实际上在前端当中,很少会跨越层级地移动DOM元素。即Virtual DOM 只会对同一个层级的元素进行对比,这样算法复杂度就可以达到 O(n)。
最后,在这里总结一下Vue中Virtual Dom执行过程:
1.用JS对象模拟DOM树,即调用createElement生成VNode
2.比较两棵虚拟DOM树的差异
3.把差异应用到真正的DOM树上