vlambda博客
学习文章列表

Vue理解虚拟DOM及key属性的作用

首先我们来回顾一下JQuery

早期有很多系统是由jquery来进行开发,它帮助了我们简化操作DOM的Api,我们可以很方便的通过Jquery绑定事件,然后通过事件来操作DOM,但是随着我们的系统越来越复杂,事件也会变的越来越多,不同的事件操作不同的DOM,或者是相同的DOM变的越来越乱(如下图),每次开发迭代的时候都会变的小心翼翼。可以说因为这个痛点,造就了react和Vue的诞生。

Vue理解虚拟DOM及key属性的作用

我们再看Vue

Vue理解虚拟DOM及key属性的作用

Virtual DOM

Vue理解虚拟DOM及key属性的作用

我们的数据不是直接反映到真实的DOM节点上,而是先通过数据和我们的模板生成类似DOM树一个结构,其实就是一个树结构,但是这个树结构不是真实的DOM树结构,而是通过我们的类似我们的json对象,来保留我们这样一个树形结构信息,那这个DOM树我们就称之为虚拟DOM,然后我们经过一定的算法机制,来计算出我们这个老的DOM树和我们即将要更新的DOM树,也就是我们新的DOM树,最终我们要改变哪些DOM,然后我们通过算法计算出来我们要改变真实DOM,通过算法尽可能的复用已有的DOM,减少我们因为更新DOM带来的性能消耗,这样就涉及到两个DOM树的比对。


Virtual Dom Diff

Vue理解虚拟DOM及key属性的作用

正常来说,按照我们正常的一个比对算法的话,那时间复杂度就是O(n^3),这个复杂度性能是比较低的,考虑到前端页面结构的特殊性,通常情况下我们不会出现跨层级节点,就像上图中画的一样,只是同一层级进行比较,那么我们的时间复杂度又降低到O(n),接下来我们来通过几个场景来加深理解。

场景1:移动

Vue理解虚拟DOM及key属性的作用

有上面这样2个DOM树,A的子节点B、C、D,变成了C、D、B。映射到我们的DOM树上呢,就是如下结构:


Vue理解虚拟DOM及key属性的作用

我们不需要关注的移动路径,需要关注的是是否是移动的还是新建的还是删除的就可以了。


场景二:删除新建

Vue理解虚拟DOM及key属性的作用

这里的代码结构都不贴了,B C D就是同层节点,没有相同类型的,简单来说原来的C节点,和b节点是同层节点,那现在我们更新完之后,我们c节点变成了B节点的子节点,对于这种情况我们并不是直接把我们的C节点直接移动到我们的B节点下面。而是原来的C节点直接删减掉。连带c节点的子节点直接删除掉,然后在B节点下新建我们的C E F节点,这个问题就是同层节点比较的结果。在我们比较我们第二层的时候,我么第一层A节点,第二层是BCD进行比较的时候发现我们的C节点不见了,那就直接删除,删除之后。直接到第三层,直接往下递归进行比较,B节点下面发现新增了c节点,那我们直接新增,持续新增我们的E和F.从这个可以看出算法不能达到最优解。那么说按照我们以前使用jquery,我们直接操作DOM,把C节点直接移到B节点下面。但算法它考虑的是一个时间算法复杂度的一个通用性,它并不能达到最优解,好在我们不需要在手动操作DOM.

场景三:删除重建

Vue理解虚拟DOM及key属性的作用

依然是删除C E F节点,新建G E F,主要是因为比对我们的第二层时候,发现我们的C节点不在了,原来可能是div,现在是span,在新的DOM树下,我不需要这个div了,  然后把C E F节点删除,然后新建G节点,同样新建E F节点,并不能达到合理的利用。


场景四:更新删除新建(无key)


Vue理解虚拟DOM及key属性的作用

对应的代码结构如下:

Vue理解虚拟DOM及key属性的作用

比对第二层节点的时候,直接将B2变成B1,将B1变成B2;当比对到第三层的时候,B2下的E F节点并没有得到合理的利用,当比对第三层节点的时候,直接新建E、F节点。那么如何让我们的E、F节点得到复用,要优化这个问题,关键是让算法知道,我们不是要更新节点,而是要移动节点,这就是我们key要做的。


场景五:移动(有key)

Vue理解虚拟DOM及key属性的作用


在我们节点加上Key之后,我们可以认为每一个节点都有唯一的标识符,这样就变成了我们节点的移动,这个场景就退化为或者说进化成我们场景1。


场景六:插入(有key)

这个场景在有key和没有key的情况是不一样的。

没有key的时候,将B2变成B4,B3变成B2,然后新建一个B3,如果是有key之后,直接插入一个B4.没有key的时候,算法不知道,就是知道要更新。有key的时候,每一个子节点都有唯一的标识符,现在是B1 B2 B3,现在需要插入B4,就可以复用原来的B1 B2 B3.

这个就回到我们前面几篇文章的案例中的todo-item中 key

这里的key对应的值是title,其实是不严谨的,很有可能是重复的key,不推荐这么做。

有的同学习惯使用index来作为key,其实想这种自定义组组件特别是有list这种动态的list,使用:key=“index”是有问题的。


小结:通过多个场景来了解了虚拟DOM如何去移动,去新建,删除的。

对于移动这个场景,不必纠结它的移动路径,我们只需要知道什么场景是移动的,什么场景是新建的,什么场景是删除的就ok了。还有我们key属性的作用。