一种技术的出现一定是为了解决某一个问题。那么已经有DOM为什么又会出现虚拟DOM呢?
最早时期,我们看到的页面是完全静态的。这就意味着这就像一本已经出版的书,传达的内容就是当前所见的,无法更改。再后来随着js脚本的不断进化发展与ajax技术的出现,我们看见的页面不再是纯静态的,我们可以点击按钮,可以发送请求,然后随着这些操作来对页面的内容进行改变-也就是操作dom。随着互联网的不断发展与人们使用网络浏览内容的需求渐盛,页面上呈现出来的内容越发的丰富。我们所进行的某一个操作,可能会带来大量的dom变更。于是我们,为每一个dom取上不同的名字或是id或是class,我们要在这一次操作之后分别获取它们,然后把他们的内容变成需要变更的内容。如果一个变量在很多处都用到了,我们可能需要把这几处地方都找出来,然后一个一个的改正过来。在这种情况下,漏改,错改的情况会时常发生,状态维护变成了一件繁琐而艰难的事。于是为了解决控制DOM操作的这一个问题,我们衍生出来两种方案。一种是当状态变更的时候,重新生成一份dom替换掉原来的,这种方法简单粗暴出错少,缺点就是成本太高(操作DOM的成本远大于js脚本运行的成本)。另一种方案就是我们将变更的dom替换成原来的dom,这样大大减少了dom操作的成本,改了哪个变哪个,很明显这种方案是比较可取的,并且现在前端的各大框架都有自己的一套处理办法。
react与vue2.以后的版本中使用虚拟dom,angular中使用脏值检查
综上所述,虚拟dom只是为了解决操作dom的其中一种解决方案,并不是必须使用它
vue中所使用的虚拟dom
我们平时会写一些vue的文件,并通过组件之间的引入,把有联系的组件import进来,类似如此:
<template>
<div>
<div class="title">标题</div>
<!-- 子组件 -->
<sub-title></sub-title>
</div>
</template>
<script>
import SubTitle from './SubTile.vue'
export default {
name:'TestComp',
components:{
SubTitle
}
}
</script>
<style lang="scss">
</style>
vue在处理组件的时候,会将·<template>...</template>
这一部分进行编译,编译成代码字符串放入render
函数执行,从而生成当前组件的vnode
虚拟节点。当vue把所有组件处理完成后就形成了一个虚拟节点树
(每一个虚拟节点都是一个组件,也就是形成了一颗用vnode表达的组件树)首次渲染的时候根据虚拟节点来生成真实的dom,并把当前的vnode缓存起来,再次渲染的时候使用这一次生成的vnode与上一次生成的vnode进行对比,哪一个节点发生了变化就改变哪一个真实的dom节点。
vnode
虚拟节点:一个VNode实例(js对象),描述了当前节点的信息,如节点类型、节点属性及名称等。虚拟节点树
:由N个虚拟节点组成的一颗节点间的关系树。
由此可见,vue中的虚拟dom实际上只需要做好两件事:
- 提供与真实节点所对应的虚拟节点vnode
- 将新的vnode与旧的vnode进行对比,然后更新视图
vue的vnode
vnode的本质其实就是一个普通的js对象,它是VNode类的一个实例const vnode = new VNode(...)
,里面以属性值的方式存储了当前节点的描述信息,Vnode的模样如下:
export default class VNode {
tag: string | void; // 标签类型
data: VNodeData | void; // 存储当前节点的一些属性style、id ....
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
}
}
我们通过执行渲染函数而得到一份vnode
,处理完所有的模板从而得到一颗vnode树
,这棵树包含了所有我们对当前模板的描述信息。由于节点有不同的类型,那么生成的vnode也有很多类型:注释节点、文本节点、元素节点、组件节点、函数式节点、克隆节点。比如当我们遇到返回元素节点的渲染函数:
render(){
return h('div',{...},'我是div的内容')
}
执行完了之后我们可能得到这样的一个vnode对象
{
tag:'div',
data:{...},
children:[vnode], // 因为包含一个文本节点
// ...
}
同理,注释节点、文本节点、元素节点、组件节点、函数式节点、克隆节点等都会生成自己的描述对象,有一些节点有特定的属性,比如注释节点的isComment为true,静态节点(无响应式数据的节点)的isStatic为true。
总结: 虚拟dom是一种处理dom操作的解决方案,vue2.x后采用了这种方案。在vue中虚拟dom并不只是vnode,vnode的概念是虚拟节点。vue中的虚拟dom是指:生成能够生成真实节点的vnode,并通过vnode渲染成真实节点后,将当前这个vnode缓存起来,在下次状态更新导致dom更新的时候(会生成一份新的vnode)进行对比,那么只改变需要变更的地方从而最大化节省dom操作成本的一种解决方案。