了解Vue的虚拟dom及diff算法

vue框架里面的两大核心,虚拟dom和数据双向绑定原理,数据双向绑定原理已经在我的另外一篇博客中详细介绍了,本文来了解一下虚拟dom以及与虚拟dom难离难舍的diff算法。

一、Vue的虚拟dom

Virtual dom,也就是我们常说的虚拟节点,它是通过JS的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM的节点。

真实DOM的代码:

<div>
	<p>test</p>
</div>

虚拟DOM的伪代码:

var Vnode = {
    tag: 'div',
    children: [
        { tag: 'p', text: 'test' }
    ]

为什么要引入虚拟dom?

概括:解决浏览器性能问题,减少重排,提高性能。

再回顾一下浏览器渲染页面的流程:

(1)HTML被HTML解析器解析成DOM Tree, css则被css解析器解析成CSSOM Tree(并行解析)。

(2)DOM Tree和CSSOM Tree解析完成后,被合并到一起,形成渲染树(render Tree)。

(3)重排:节点信息计算,即根据渲染树计算每个节点的几何信息(大小及位置)。

(4)重绘:渲染绘制,即根据计算好的信息绘制整个页面,渲染出最终的页面。

总结:每次真实 dom 发生改变引起重排都会将上面的流程跑一遍,而重排过程特别是计算节点信息是非常消耗性能的,于是我们引入vdom,在vdom上进行的操作不会引起重排,然后再通过diff算法比较新vdom(修改之后的)和旧 vdom(修改前的)的不同从而去更新真实dom(patch方法)。

注意:特别要提一下 Vue 的 patch 是即时的,并不是打包所有修改最后一起操作DOM(React则是将更新放入队列后集中处理),这样岂不是相当于没有优化?实际上现代浏览器对这样的DOM操作做了优化,所以表现出来的结果是一样的,即减少的操作真实dom的次数,达到减少重排,提高性能的目的。
转自参考文章

二、Vue的diff算法

用于高效更新 dom 的一套算法

原理解析
图出自《React’s diff algorithm》
经典核心原理图:递归地使用diff算法来比较同一层级的 node
图片转自第一篇参考文章
(图片转自第一篇参考文章)

diff 算法在执行时有三个维度,分别是Tree diff、Component diff 和Element diff,执行时按顺序依次执行,它们的差异仅仅因为 diff 粒度不同、执行先后顺序不同

vue中 diff 算法的大致思路

在这里插入图片描述
(为了显得不那么抽象,贴一张移动了一次首指针之后的图,来自参考文章)

核心就是循环进行头尾节点比较,终止条件就是新旧vdom有一方首尾指针相遇。
新旧vdom分别有两个首尾指针一开始时指向首尾节点,这里我用:新头、新尾、旧头、旧尾表示。然后对其指向的节点进行四次比较:新头—旧头、新尾—旧尾、新尾—旧头、新头—旧尾。

循环过程:

  • 第一步 头头比较。若相似(指tag一致,比如都是div),旧头新头指针后移,真实dom不变,进入下一次循环;不相似,进入第二步。

  • 第二步 尾尾比较。若相似,旧尾新尾指针前移,真实dom不变,进入下一次循环;不相似,进入第三步。

  • 第三步 新尾旧头比较。若相似,旧头指针后移,新尾指针前移,真实dom序列中的头移到尾(此时的头尾指向节点),进入下一次循环;不相似,进入第四步。

  • 第四步 新头旧尾比较。若相似,旧尾指针前移,新头指针后移,真实dom序列中的尾移到头,进入下一次循环;不相似,进入第五步。

  • 第五步 若新头节点有 key 且在旧子节点数组中找到sameVnode(tag和key都一致),则将其dom移动到当前真实dom序列的头部,新头指针后移;否则,新头对应的节点插入当前真实dom序列的头部,新头指针后移。

:终止循环后如果只有一方的首尾指针相遇,就要进行增删操作,例如是新 vdom 首尾指针未相遇,就要在真实dom上增加新节点,反之为删。

经常与v-for搭配使用的 key 在这里充当什么角色呢?

key的作用主要是为了更加高效地更新虚拟dom

由于v-for大部分情况下生成的都是相同tag的标签,如果没有key标识,那么相当于每次头头比较都能成功。也就不会有第五步里有key时的插入操作。
转自参考文章
根据上图的例子理解:
原序列是:a,b,c,d,e
新序列:a,b,c,z,d,e

我们当然知道直接在c和d之间直接插入z就可以了但是没有设置key时,diff算法它是不知道的,于是执行的过程就会是将d换成z,将e换成d,再添加一个e;当数据量较大时,明显多了很多不必要的操作,效率较低。

总结:有key的情况,其实就是多了一步匹配查找的过程。也就是上面循环流程中的第五步,会尝试去旧子节点数组中找到与当前新子节点相似的节点,减少dom的操作!

用index作为key会发生什么?(划重点,字节面试题)

用 index 作为 key,和没写基本上没区别,因为不管你数组的顺序怎么颠倒,index 都是 0, 1, 2 这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作(类名、样式、指令,那么都会被全量的更新)。

:用组件唯一的 id(一般由后端返回)作为它的 key,实在没有的情况下,可以在获取到列表的时候通过某种规则为它们创建一个 key,并保证这个 key 在组件整个命周期中都保持稳定。

没有设置key时就地复用问题

“就地复用”官方的解释是:

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出

实在是太抽象了,这里我就用表单输入值的例子简单说明一下,例如有一个可以切换输入语言格式的输入框,即设置一个按钮可以切换输入中文或英文,当我们在中文状态下在输入框输入了一定的中文字体时,此时点击切换到英文输入状态,之前输入框中的中文字体会被带过去并展示出来,这就是就地复用原则导致的。

总之,要灵活使用!

关于diff算法这里我只总结一下大致的思路和需要注意的地方,具体的图示和细节推荐大家看 深入Vue2.x的虚拟DOM diff原理 这篇博客是我目前看到的关于diff算法讲得最简单易懂的一篇,自觉无法超越,极力推荐!

参考文章:
Vue 虚拟DOM和Diff算法
vue虚拟dom 以及diff 算法
深入Vue2.x的虚拟DOM diff原理

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值