谈:虚拟DOM与Vue.js

513 阅读6分钟

什么是虚拟DOM

以前的web,页面上不会有很多的交互,甚至很多都是简单的静态页面。发展到现在,我们在页面上会涉及到各种操作。那我们知道操作页面基本上就是在操作DOM,DOM操作越来越多,状态越来越多。用原生js或者说JQuery开发虽说简单易用,但代码中的逻辑会变得混乱,状态变得难以管理。

虚拟DOM算是时代的产物,它就是通过状态生成的一个虚拟节点树。

为什么要用虚拟DOM

从状态(js中的任意数据类型)到生成DOM渲染出页面。这个过程是很明了易懂的。那试想如果页面上进行了异步操作,比如点了一个按钮,那状态肯定发生了变化,随之页面需要重新渲染。DOM操作过多是非常影响性能以及用户体验的。

如果我们如果只更新改变的DOM的话,那不就可以最大程度上的解决这个问题了。 不可否认的是,这个问题的解决方案有很多。就目前主流的框架而言,Angular使用的是脏检查,React与Vue用的是虚拟DOM。

简单的说就是在状态发生改变的时候,用改变之后的状态生成与真实DOM节点所对应的虚拟节点树(vnode)与旧的节点(oldVnode)作比较,然后更新视图。这里的虚拟节点树可以缓存,最大可能的避免不必要的DOM操作。

vue.js可以做这种比较还是要归功于变化侦测。vue.js1.0中对比到每一个属性然后视图渲染,会产生一些内存开销,在2.0中,状态侦测只通知到组件级别,在组件内部由虚拟DOM渲染视图。

vue.js使用模板来映射状态与视图,将模板编译成渲染函数(render),然后生成虚拟节点再更新视图。

什么是Vnode

Vnode是Vue.js中的一个类,就是节点描述对象,它可以实例化出不同类型的vnode实例,用来表示不同的DOM元素。

vnode表示一个真实的DOM元素,它创建DOM节点,然后插入到页面中。

在平常的开发中,这也是极力推崇组件化的原因,只有尽可能的组件化,那一处的DOM节点新增、更新、删除,与整个页面的重新渲染比起来,毋庸置疑会节省出很多性能。也可以说是页面优化的一个点。

Vnode的类型

vnode有很多不同的类型,氛围以下几种:

  • 注释节点 一个注释节点只有两个有效属性--text和isComment。其它属性全是默认的undefined、false。
注释节点:
<!-- 注释节点 -->
vnode:
{
    text: '注释节点',
    isComment:true
}
  • 文本节点 文本节点只有一个text属性。
文本节点:
<p>文本节点</p>
vnode:
{
    text:'文本节点'
}
  • 元素节点 元素节点就是一段html的代码,它有4种有效属性:tag(节点名称,例如:div p等)。data(节点上的数据,例如class、style等)。children:当前节点的子节点。context:当前组件的vue实例。
元素节点:
<div><p>yuansu</p><p>jiedian</p></div>
vnode:
{
tag: 'div',
data: {...},
context: {...},
children: [VNode, VNode],
...
}
  • 组件节点 组件节点除了元素节点的属性,还有两个独有的属性。componentOptions(组件节点的选项参数,例如: tag、children、props等)。componentInstance(组件的实例)。
组件节点:
<component></component>
vnode:
{
tag: 'vue-component',
data: {...},
context: {...},
componentOptions: {...},
componentInstance: {...},
...
}
  • 函数式组件 函数式组件与组件节点类似,它也有两个独有的属性。functionalContext和functionalOptions。
vnode:
{
tag: 'vue-component',
data: {...},
context: {...},
functionalContext: {...},
functionalOptions: {...},
...
}
  • 克隆节点 克隆节点是把现有的节点复制到新的节点,主要是针对静态节点(组件通过虚拟DOM重新渲染视图,静态节点的特性就是内容不会改变 所以用到克隆节点直接克隆一份进行渲染。)与插槽节点。

patch

虚拟DOM的核心部分是patch,它将vnode渲染为真实的DOM。

patch也叫patching算法,它的意思实补丁。它渲染真实DOM的方式不是粗鲁的直接覆盖原有DOM,而是对比新旧两个vnode,找不同找到之后在现有的DOM基础上来更新视图。

这么做最根本的原因是想让js把计算的事情做好,然后更新有需要的DOM节点,而不是直接覆盖掉原先的DOM,尽管这样也能达到目的。但很消耗性能。js运算速度比DOM快的多,所以才有虚拟DOM这个东西。

patch的过程是创建节点、删除节点、更新节点的过程。

创建新增节点

新增节点有两个场景:

  • 当oldVnode不存在,但是vnode存在时,需要用vnode生成真是的DOM元素渲染视图。
  • 当vnode跟oldVnode不是同一个节点,需要用vnode生成真是的DOM元素渲染视图。这时候要做的操作就是删除oldVnode用vnode取代。

删除节点

删除节点就是当一个节点只在oldVnode中存在,上面的例子就是这个道理。

更新节点

前面的新增节点跟删除节点都是两个虚拟节点是完全不同的。如果新旧节点是同样的节点,再继续对节点内的细节进行对比,发现不同处。比如文本不同。那这个情况就需要对oldVnode中的内容进行更新。 更新节点涉及以下三种情况:

  • 静态节点: 就是哪些一单渲染到界面上,无论如何都不会发生改变的节点。(平常开发中就是写死的文字或者静态图片。)
  • 节点内有文本属性: 如果不是静态节点,要依照vnode为准则来更新节点,如果有text属性那需要调用setTextContent方法来改变视图中的文字。
  • 节点内无文本属性: 如果没有text属性,那就是一个元素节点,需要元素节点种是否有children属性。如果没有那就是一个空标签,直接把文本清空,把虚拟节点的元素节点插入视图的DOM中。如果没有children属性那就清空节点内的东西全部删除。

总结

本文介绍了虚拟DOM,与vue.js的关系以及Vnode与最关键的patch。

用patch我们对比新虚拟DOM与旧的虚拟DOM,针对发生改变的节点进行视图的更新。 说明了,节点从新建到删除,再到更新到视图的过程。旨在加深对框架的理解,以及技术的探讨。