Vue虚拟DOM和diff算法

虚拟dom是当前前端最流行的两个框架(vue和react)都用到的一种技术,
都说他能帮助vue和react提升渲染性能,,提升用户体验.

js执行 —>虚拟DOM( 计算变更) — > 操作真是的DOM —> 视图更新

一.虚拟dom的定义与作用

1.1什么是虚拟dom

虚拟DOM:用JavaScript对象描述DOM的层次结构。虚拟dom就是一个普通的js对象
DOM 中的一切属性都在虚拟DOM中有对应的属性。

1.2 虚拟dom的结构

Vue虚拟DOM和diff算法_第1张图片

1.3 虚拟dom的作用 (为什么要使用虚拟DOM?)

当dom发生更改时候,先用虚拟dom进行diff,算出最小差异,然后再修改真实dom,当用传统的方式操作DOM的时候,浏览器会从构建DOM树开始从头到尾执行一遍流程,效率很低。而虚拟DOM是用javascript对象表 示的,而操作javascript是很简便高效的。虚拟DOM和真正的DOM有一层映射关系,很多需要操作DOM的地方都会去操作虚拟DOM,最后统一一次更新DOM。因而可以提高性能.

二.Vue中的虚拟dom

目前虚拟dom的类库有多种,常见的有snabbdom和virtual-dom, vue以前用的是virtual-dom,从2.x版本后都是使用的snabbdom。(snabbdom源码下载) 今天,我们就通过snabbdom源码来解析vue的虚拟dom.
要搞清楚vue虚拟dom,我们就需要搞清楚几个核心的方法:

1.h函数 (与vnode一起生成虚拟dom)
2.patch函数 diff的入口
3.patchVnode函数 相同节点的比较更新
4.updateChildren函数 (有子节点children调用)

2.1 h函数

h函数,看着是不是很眼熟? 他是在vue的什么阶段去调用的?
Vue虚拟DOM和diff算法_第2张图片
眼熟吧,是不是在这地方看过啊。没错,h函数就是在render函数内运行的。
我们在前面vue生命周期的文章中就提过,vue在created–>beforeMount之间的时候会将模板编译成render函数,其实就是将模板编译成某种格式放在render函数内,然后当render函数运行得时候,就会生成虚拟dom。
那么编译成什么格式呢。就是编译成h函数所认可的格式。那么我们来看看h函数需要什么格式

const vdom = h('div', { class: 'vdom'}, [
  h('p', { class: 'text'}, ['hello word']),
  h('input', { class: 'ipt', value: '今天星期二' })
]) // 模板就是会编译成这种格式
console.log(vdom)

而h函数内最主要得就是执行了 vnode函数,vnode函数得主要作用就是将h函数传进来得参数转行为了js对象(即虚拟dom)
Vue虚拟DOM和diff算法_第3张图片
而vnode函数,我就不多说了,没几句代码,也很简单,反正就是执行了生成js对象(虚拟节点)的代码。
Vue虚拟DOM和diff算法_第4张图片
下面我们总结下虚拟dom生成的过程。

1.首先,代码初次运行,会走生命周期,当生命周期走到created到beforeMount之间的时候,会编译template模板成render函数。然后当render函数运行时,h函数被调用,而h函数内调用了vnode函数生成虚拟dom,并返回生成结果。故虚拟dom首次生成。
2.之后,当数据发生变化时会重新编译生成一个新vdom。再后面就等待新 旧两个vdom进行对比吧。我们后面就继续说对比的事情。

三.diff算法

3.1 什么是diff算法

diff算法就是用于比较新旧两个虚拟dom之间差异的一种算法;
diff算法是虚拟dom的核心

1.只比较同一层级,不跨级比较
2.标签名不同,直接删除,不继续深度比较
3.标签名相同,key相同,就认为是相同的节点,不继续深度比较
Vue虚拟DOM和diff算法_第5张图片

四.patch 函数

patch是比较的开始,相当于是diff的入口,diff就是从这一步开始的。那么既然是开始,说明patch函数比较的肯定就是两个新旧vdom的根节点了。所以,两个vdom直接的比较,patch是只会触发一次的。
作用:比较两个虚拟dom根节点是否相同。下面我们看下主要的核心代码
Vue虚拟DOM和diff算法_第6张图片

五.patchVnode 函数

patchVnode 是用于比较两个相同节点的子级(文本,或子节点)的一个函数。故它的调用总是在sameVnode判断之后。只有判断当前比较的两个vnode相同时(这里我最后再解释一次,两个vnode相同仅仅代表key相同且sel选择器相同),才会被执行。
作用:对比新旧两个节点,更新dom的子级(子级包含文本或者是子节点)

五.updateChildren函数

作用:用于比较新旧两个vnode的子节点
涉及diff算法优化策略:
四种命中查找: ① 新前与旧前
② 新后与旧后
③ 新后与旧前
④ 新前与旧后

总结:
我们在最后整理一下步骤。
1、比较两个虚拟dom树,对根节点root进行执行patch(oldVnode,newVnode)函数,比较两个根节点是否是相同节点。如果不同,直接替换(新增新的,删除旧的)

2、如果相同,对两个节点执行patchVnode(oldVnode, newVnode),比较属性,文本,已经子节点。此时,要么新增,要么删除。要么直接修改文本内容。只有当都存在子节点时,并且oldVnode === newVnode 为false时。会执行updateChildren函数,去进一步比较他们的子节点。

3、比较分3大类。
第一类:oldStart === newStart, oldStart === newEnd,oldEnd === newStart,oldEnd === newEnd 这4种情况的比较。如果这4种情况中任何一种匹配。那么会执行patchVnode进一步比较。同时指针往中间移

第二类:oldStart > oldEnd 或者 newStart > newEnd时。表示匹配结束。此时,多余的元素删除,新增的元素新增。

第三类:上面几种情况都不匹配。那么这个时候key是否存在。就起到关键性作用了。存在key时,可以直接通过key去找到节点的原来的位置。如果没有找到,就新增节点。找到了,就移动节点位置。查找效率非常高
而如果没有key呢,那么压根就不会去原来的节点中查找了。而是直接新增这个节点。这就导致这个节点下的所有子节点都会被重新新增。会出现明显的性能损耗。所以,合理的应用key,也是一种性能上的优化。

总之一句话。diff的过程,就是一个 patch —> patchVnode —> updateChildren —> patchVnode —> updateChildren —> patchVnode…
这样的一个循环递归的过程

你可能感兴趣的:(vue,vue.js,javascript,算法)