vue 虚拟dom和diff算法详解1

简介: vue 虚拟dom和diff算法详解

虚拟dom是当前前端最流行的两个框架(vue和react)都用到的一种技术,都说他能帮助vue和react提升渲染性能,提升用户体验。那么今天我们来详细看看虚拟dom到底是个什么鬼

虚拟dom的定义与作用

  • 什么是虚拟dom

大家一定要记住的一点就是,虚拟dom就是一个普通的js对象。是一个用来描述真实dom结构的js对象,因为他不是真实dom,所以才叫虚拟dom。

  • 虚拟dom的结构

从下图中,我们来看一看虚拟dom结构到底是怎样的

如上图,这就是虚拟dom的结构,他是一个对象,下面有6个属性,sel表示当前节点标签名,data内是节点的属性,elm表示当前虚拟节点对应的真实节点(这里暂时没有),text表示当前节点下的文本,children表示当前节点下的其他标签

  • 虚拟dom的作用

1、我们都知道,传统dom数据发送变化的时候,我们都需要不断的去操作dom,才能更新dom的数据,虽然后面出现了模板引擎这种东西,可以让我们一次性去更新多个dom。但模板引擎依旧没有一种可以追踪状态的机制,当引擎内某个数据发生变化时,他依然要操作dom去重新渲染整个引擎。

而虚拟dom可以很好的跟踪当前dom状态,因为他会根据当前数据生成一个描述当前dom结构的虚拟dom,然后数据发送变化时,又会生成一个新的虚拟dom,而这两个虚拟dom恰恰保存了变化前后的状态。然后通过diff算法,计算出两个前后两个虚拟dom之间的差异,得出一个更新的最优方法(哪些发生改变,就更新哪些)。可以很明显的提升渲染效率以及用户体验

2、因为虚拟dom是一个普通的javascript对象,故他不单单只能允许在浏览器端,渲染出来的虚拟dom可同时在node环境下或者weex的app环境下允许。有很好的跨端性

什么是diff算法

diff算法就是用于比较新旧两个虚拟dom之间差异的一种算法。具体详情,后面我们会说

vue中的虚拟dom

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

首先我们看下snabbdom源码结构。

要搞清楚vue虚拟dom,我们就需要搞清楚几个核心的方法

  • h函数
  • patch函数
  • patchVnode函数
  • updateChildren函数

这几个核心函数的源码,看着可能会比较累,我就不一一对源码做详细的介绍,我主要会介绍每个函数主要做了什么事情,然后后面再附上源码,会加点注释,看的懂得可以详细看看

h函数

h函数,看着是不是很眼熟? 他是在vue的什么阶段去调用的?

眼熟吧,是不是在这地方看过啊。没错,h函数就是在render函数内运行的。我们在前面vue生命周期的文章中就提过,vue在created–>beforeMount之间的时候会将模板编译成render函数,其实就是将模板编译成某种格式放在render函数内,

然后当render函数运行得时候,就会生成虚拟dom。那么编译成什么格式呢。就是编译成h函数所认可的格式。那么我们来看看h函数需要什么格式

有的人可能会说,唉,这个h函数怎么定义了多个啊。没错,h函数是使用函数重载的方式定义的,那么什么是函数重载

函数重载

函数重载就是定义多个重名函数,利用函数的参数个数以及参数类型来区分。当参数个数不同,参数类型不同时,函数内执行的代码也会相应不同。

下面,我们就来看下最典型得一种,也就是图中得第四种。

  • 第一个参数sel 表示dom选择器,如: div#app.wrap ==》
  • 第二个参数表示dom属性,是个对象如:{ class: ‘ipt’, value: ‘今天天气很好’ }
  • 第三个参数表示子节点,子节点也可以是一个子虚拟节点,也可以是文本节点

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)

而vnode函数,我就不多说了,没几句代码,也很简单,反正就是执行了生成js对象(虚拟dom)的代码。直接上图

看到现在,我们心里应该要清楚虚拟dom是怎么生成的,什么时候生成的。如果不清楚,那么请往上滑,再看一遍,哈哈。下面我们总结下虚拟dom生成的过程。

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

diff 比较规则

1、diff 比较两个虚拟dom只会在同层级之间进行比较,不会跨层级进行比较。而用来判断是否是同层级的标准就是

  1. 是否在同一层
  2. 是否有相同的父级
    下面,我们来一张图,就很好理解了(盗用网上一张很经典的图)

2、diff是采用先序深度优先遍历得方式进行节点比较的,即,当比较某个节点时,如果该节点存在子节点,那么会优先比较他的子节点,直到所有子节点全部比较完成,才会开始去比较改节点的下一个同层级节点。不好理解吗?没关系,我们画个图看一下,就很清晰了

  1. 当比较新旧两个dom时,会按照图中1-9的顺序去进行比较。

不过,既然话都说到他的比较顺序了,我就想干脆,先整体将他每一步是如何比较的,让大家心里有一个总体的比较思路后,我们再去一步一步看patch函数,patchVnode函数和updateChildren函数

diff比较整体思路

首先开始比较两个vdom时,这两个vdom肯定是都有各自的根节点的,且根节点必定是一个元素,不可能存在多个。我们首先要比较的肯定是根节点,那我们都知道根节点只有一个,就可以直接比较了。而一个节点的比较,通常分为3个部分

声明,下面所说的sel选择器相同,指的是标签名,id,class都相同。

例如

’这样一个dom,他的sel是"div#app.abc"

比较两个节点是否是相同节点,判断是否是相同节点的条件是,key和sel(选择器)必须都相同(那有的人可能会说了,那我标签没有key怎么办啊,没有key那就是undefined,undefined === undefined 始终为true,所以没有key只需要保证sel相同就行)。如果不相同,那么执行替换操作(即新增新vnode上的元素,删除旧vnode上的元素 例如,原来是div,新vnode变成了p,那么就是新增p元素,再删除div元素。相当于就是p替换了div),这一步,只有比较根节点时,是在patch函数中进行的。非根节点都是在updateChildren函数中执行的,因为根节点只会有一个,可以直接比较,而其他节点会存在多个,需要通过一些算法来判断,具体详情后面会说

如果节点相同,那么进去第二部分,即比较两个节点的属性是否相同,节点是否存在文本,文本是否相同。是否存在子节点,子节点是否相同。这部分主要在patchVnode中执行

那么,在第二部分,会做哪些事情呢。

1、如果存在文本时,更新文本

2、如果存在属性时,更新属性

3、如果存在子节点时,更新子节点

那么,如何更新呢,逻辑也很简单,遵循以下规则:

1、如果旧vnode上存在,而新vnode上不存在,那么执行删除操作

2、如果旧vnode上不存在,而新vnode上存在,那么执行新增操作

3、如果新旧vnode上都存在,那么执行替换操作(即,新增新的,删除旧的),文本,和属性的替换是在这部分完成。而对于子节点,如果新vnode和旧vnode上都存在子节点时,那么会进入第三部分比较。比较子节点的差异。

第三部分,主要在updateChildren函数中执行,主要用于比较某个节点下的子节点差异。而在这里,就要用到diff的一个算法了。具体怎么算。我们后面详细说updateChildren时再说。

可能大家看的有点懵,没关系,看完心里有个大概的步骤就好,下面我们再来详细讲每一步对应的函数

patch 函数

上面我们说了,patch是比较的开始,相当于是diff的入口,diff就是从这一步开始的。那么既然是开始,说明patch函数比较的肯定就是两个新旧vdom的根节点了。所以,两个vdom直接的比较,patch是只会触发一次的。

作用:比较两个虚拟dom根节点是否相同。下面我们看下主要的核心代码

patchVnode

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

但,在比对之前,会先判断下oldVnode === vNode ,因为如果全等,代表子级肯定也完全相等,那么就没必要对比了,直接return;


作用:对比新旧两个节点,更新dom的子级(子级包含文本或者是子节点)

对比过程:

1、如果新vnode有text属性

  • 旧vnode是否有子节点,如果有,代表原来是子节点,现在变成文本了,那么删除子节点,并且设置vnode对象的真实dom的text值(使用setTextContent函数)
  • 其他情况不用管,直接设置vnode对象的真实dom的text值


2、如果新vnode没有text属性

  • 如果新vnode和旧vnode都存在子节点时。是不是要深度对比两个vnode的子节点啊。这个时候会进入第三步,比较子节点(执行updateChildren)

如果只有新vnode有子节点,老vnode没有,那么很简单,执行添加节点的操作

如果只有旧vnode有子节点,新vnode没有子节点,很明显,要执行删除旧vnode子节点的操作

如果两个vnode上都没有子节点。但旧节点有text,那么很简单,说明原来有文本,现在没有了,清空vnode对应dom的text

下面,我们看下整体代码


相关文章
|
2月前
|
算法 JavaScript UED
Diff 算法的实现原理
【10月更文挑战第18天】Diff 算法是 Vue.js 中实现高效 DOM 更新的核心机制,通过合理的比较和优化策略,能够在保证界面正确性的同时,最大程度地减少 DOM 操作,提高应用的性能和用户体验。
42 2
|
2月前
|
算法 JavaScript
Vue 中的 Diff 算法
【10月更文挑战第18天】需要注意的是,Diff 算法虽然能够提高性能,但在某些复杂的场景下,可能仍然会存在一些性能瓶颈。因此,在实际开发中,我们需要根据具体情况合理地使用 Diff 算法,并结合其他优化手段来提高应用的性能。
21 1
|
2月前
|
JavaScript 算法 前端开发
vue 中diff算法
【10月更文挑战第10天】
43 1
|
2月前
|
JavaScript
在Vue中获取DOM元素的实际宽高
【10月更文挑战第2天】
368 1
|
2月前
|
JavaScript 算法 前端开发
【VUE】Vue的diff算法和React的diff算法
【VUE】Vue的diff算法和React的diff算法
|
3月前
|
XML JavaScript 前端开发
学习react基础(1)_虚拟dom、diff算法、函数和class创建组件
本文介绍了React的核心概念,包括虚拟DOM、Diff算法以及如何通过函数和类创建React组件。
40 3
|
3月前
|
JavaScript 前端开发
react字符串转为dom标签,类似于Vue中的v-html
本文介绍了在React中将字符串转换为DOM标签的方法,类似于Vue中的`v-html`指令,通过使用`dangerouslySetInnerHTML`属性实现。
111 0
react字符串转为dom标签,类似于Vue中的v-html
|
3月前
|
机器学习/深度学习 JavaScript 算法
面试中的网红虚拟DOM,你知多少呢?深入解读diff算法
该文章深入探讨了虚拟DOM的概念及其diff算法,解释了虚拟DOM如何最小化实际DOM的更新,以此提升web应用的性能,并详细分析了diff算法的实现机制。
|
2月前
|
JavaScript
DOM 节点列表长度(Node List Length)
DOM 节点列表长度(Node List Length)
|
2月前
|
JavaScript
HTML DOM 节点树
HTML DOM 节点是指在 HTML 文档对象模型中,文档中的所有内容都被视为节点。整个文档是一个文档节点,每个 HTML 元素是元素节点,元素内的文本是文本节点,属性是属性节点,注释是注释节点。DOM 将文档表示为节点树,节点之间有父子和同胞关系。

热门文章

最新文章