一. 什么是虚拟dom
本质上就是一个 JS 对象,作用:本质是保存节点信息, 属性和内容的一个JS对象。
比如 template 里标签结构
对应的虚拟 DOM 结构
两个虚拟dom 之间 的对比 与 两个真实的dom 的对比 要简单多
虚拟dom:和真实的dom 相比 只会抽出个别属性
二. vue 是如何利用虚拟dom来更新
- 初次渲染时,会根据 model 数据创建一个虚拟 DOM 对象(树)
- 根据虚拟 DOM 生成真正的 DOM,渲染到页面
- 当数据变化后,会重新根据新的数据,创建新的虚拟 DOM 对象(树)
此时有两份虚拟 dom,利用 diff 算法进行两份虚拟 dom 进行对比
Vue 渲染真实dom的原则 一般都是采用就地复用,只更新 改变了的真实dom
为什么使用虚拟dom
虚拟dom可以进行高效更新(体现在批量处理dom等),同时也可以使用虚拟dom进行跨平台: 开发的代码只是模板代码 => 虚拟dom => web(编译为web的dom) => (小程序的节点)
三. Diff的流程
在Vue
中,主要是patch()
、patchVnode()
和updateChildren()
这三个主要方法来实现Diff
的。
- 当我们
Vue
中的响应式数据变化的时候,就会触发页面更新函数updateComponent()
- 此时
updateComponet()
就会调用patch()
方法,在该方法中进行比较是否为相同节点,是的话执行patchVnode()
方法,开始比较节点差异;而如果不是相同节点的话,则进行替换操作,具体后面会讲到; - 在
patchVnode()
中,首先是更新节点属性,然后会判断有没有孩子节点,有的话则执行updateChildren()
方法,对孩子节点进行比较;如果没有孩子节点的话,则进行节点文本内容判断更新;(文本节点是不会有孩子节点的) updateChildren()
中,会对传入的两个孩子节点数组进行一一比较,当找到相同节点的情况下,调用patchVnode()
继续节点差异比较。
3.1 SameVnode
在源码中会用sameVnode()
方法去判断两个节点是否相同,实质上是通过去判断key
值,tag
标签等静态属性从而去判断两个节点是否为相同节点。
注意的是,这里的相同节点不意味着为相等节点,比如<div>HelloWorld</div>
和<div>HiWorld</div>
为相同节点,但是它们并不相等。在源码中是通过vnode1 === vnode2
去判断是不是为相等节点。
3.2 Patch
patch()
方法,该方法接收新旧虚拟Dom,即oldVnode
,vnode
,这个函数其实是对新旧虚拟Dom
做一个简单的判断,而还没有进入详细的比较阶段。
- 首先判断
vnode
是否存在,如果不存在的话,则代表这个旧节点要整个删除; - 如果
vnode
存在的话,再判断oldVnode
是否存在,如果不存在的话,则代表只需要新增整个vnode
节点就可以; - 如果两者不是相同节点的话,
vnode
直接替换oldVnode
- 如果
vnode
和oldVnode
都存在的话(sameVnode),判断两者是不是相同节点,如果是的话,这调用patchVnode
方法,对两个节点进行详细比较判断;
3.3 PatchVnode
在patchVnode()
中,同样是接收新旧虚拟Dom,即oldVnode
,vnode
;在该函数中,即开始对两个虚拟Dom
进行比较更新了。
- 首先判断两个虚拟
Dom
是不是全等,即没有任何变动;是的话直接结束函数,否则继续执行; - 其次更新节点的属性;
- 接着判断
vnode.text
是否存在,即vnode
是不是文本节点。是的话,只需要更新节点文本既可,否则的话,这继续比较; - 判断
vnode
和oldVnode
是否有孩子节点:
-
- 如果两者都有孩子节点的话,执行
updateChildren()
方法,进行比较更新孩子节点; - 如果
vnode
有孩子节点而oldVnode
没有的话,则直接新增所有孩子节点,并将该节点文本属性设为空; - 如果
oldVnode
有孩子节点而vnode
没有的话,则直接删除所有孩子节点; - 如果两者都没有孩子节点,就判断
oldVnode.text
是否有内容,有的话清空内容既可
- 如果两者都有孩子节点的话,执行
3.4 UpdateChildren
- 这个方法传入三个比较重要的参数,即
parentElm
父级真实节点,便于直接节点操作;oldCh
为oldVnode
的孩子节点;newCh
为Vnode
的孩子节点。
OldCh
和newCh
都是一个数组。 这个方法的作用,就是对这两个数组一一比较,找到相同的节点,执行patchVnode
再次进行比较更新,剩下的少退多补。
我们想到最简单的方法,就是两个数组进行遍历匹配,但是这样子的复杂度是很大的,而且我们真实项目中,页面结构是非常庞大和复杂的,所以这个方案是非常耗性能的。
diff 算法 其实就是为了提高新旧虚拟dom节点的 对比效率的, 采取一种算法
Vue2 里采用的是 双针比较法
- 首先是
oldStartVnode
和newStartVnode
进行比较,如果比较相同的话,移动oldStartIdx
和newStartIdx
。
- 如果
oldStartVnode
和newStartVnode
匹配不上的话,接下来就是oldEndVnode
和newEndVnode
做比较了。 - 但如果两头比较和两尾比较都不是相同节点的话,这时候就开始交叉比较了。首先是
oldStartVnode
和newEndVnode
做比较。 - 如果
oldStartVnode
和newEndVnode
匹配不上的话,就oldEndVnode
和newStartVnode
进行比较。 -
此时,如果四种比较方法都匹配不到相同节点的话,我们就只能使用暴力解法去实现了,也就是针对于
newStartVnode
这个节点,我们去遍历oldCh
中剩余的节点,一一匹配。可以先根据key 取匹配。对比算法 双针比较法
-
四.key的作用
key的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点,提高diff 对比效率。
SameVnode 方法里对比是否是同一个的节点 会根据 节点身上的key属性进阶对比,key相同 ==> 可以相同节点 ===> 给列表项添加的唯一标识, 为了高效的更新虚拟DOM。
4.1 无key
最大限度尝试就地修改/复用相同类型元素
4.2 有key, 值为索引
-
插入新来的小li
4.3 有key值,唯一不重复的字符串或数字
-
时间复杂度
算法两个指标 空间复杂度和时间复杂度
空间复杂度:对一个算法在运行过程中临时占用存储空间大小的量度
时间复杂度:指执行当前算法所消耗的时间
-
diff 算法的时间 复杂度
为你这个 其实就是让你讲一下 diff 算法
两个树的完全的diff算法是一个时间复杂度为 O(n3) ,Vue进行了优化·O(n3) 复杂度的问题转换成 O(n) 复杂度。
传统的diff算法
两棵树做 diff,复杂度是 O(n^3) 的,因为每个节点都要去和另一棵树的全部节点对比一次,这就是 n 了,如果找到有变化的节点,执行插入、删除、修改也是 n 的复杂度。所有的节点都是这样,再乘以 n,所以是 O(n * n * n) 的复杂度
这样复杂度对于前端框架来说是不可接受的,这意味着 1000 个节点,渲染一次就要处理 1000 * 1000 * 1000,一共 10 亿次
-
Vue2 diff
●只比较同一层级,不跨级比较
●tag不相同,则会直接删除重建,不再深度比较
●tag和key都相同,则认为其为相同节点。
这样只要遍历一遍,对比一下 tag 就行了,是 O(n) 的复杂度。而且 tag 变了就不再对比子节点,能省下一大片节点的遍历。另外,因为 vdom 中记录了关联的 dom 节点,执行 dom 的增删改也不需要遍历,是 O(1)的,整体的 diff 算法复杂度就是 O(n) 的复杂度DIV
DIV
DIV
DIV
DIV
SPAN
SPAN
'DONG*
'GUANG'
五.总结
什么是虚拟dom?
虚拟dom本质就是一个js对象,用来描述真正dom是什么样的,这个对象就称为虚拟dom
为什么用虚拟dom?
虚拟dom可以进行高效更新,同时也可以使用虚拟dom进行跨平台: 开发的代码只是模板代码 => 虚拟dom => web(编译为web的dom) => (小程序的节点)
如何实现高效更新?
初始化渲染的时候,会根据数据和模板生成一份虚拟dom树,当数据发生变化,会根据新的数据和模板生成新的虚拟dom树,将两份虚拟dom树进行对比,对比的算法采用的是diff算法
diff算法?
同级比较.深度优先,比较核心的就是采用了双指针算法,四个指针,遍历旧的虚拟dom有两个指针,指向开始的位置和结束的位置,同理新的虚拟dom也有这两个指针,循环的时候开始的指针对比完后,指针向后推,后面的指针对比后向前推,从而达到效率提升
diff对比之后的情况?
元素不同: 删除重建
元素相同,属性不同: 元素复用,属性更新
v-for:
key 的作用: key的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点,提高diff 对比效率。
有key:不建议使用索引,索引会变化,建议使用唯一值,对比的使用key进行对比
无key, 看数据的变化是否影响到顺序,如果影响到顺序,影响到性能
无key, 看数据的变化是否影响到顺序,如果没有影响到顺序,性能没有影响