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

下面,我们看下整体代码


相关文章
|
1月前
|
JavaScript
vue监听dom元素的宽高变化和自定义指令监听dom元素的宽高变化
vue监听dom元素的宽高变化和自定义指令监听dom元素的宽高变化
31 0
|
6天前
|
JavaScript 算法 开发者
vue diff算法介绍
vue diff算法介绍
19 2
|
3天前
|
JavaScript 前端开发 算法
React中的DOM diff算法是如何工作的
React的DOM diff算法通过对比新旧虚拟DOM树找到最小更新策略,提高组件更新效率。它生成并比较虚拟DOM,按类型、属性和"key"逐节点检查。不同类型节点直接替换,属性不同则更新属性,相同则递归比较子节点。确定DOM操作后批量执行,减少对真实DOM的访问,优化性能。然而,在复杂场景下可能有性能问题,可借助shouldComponentUpdate、memo或PureComponent等进行优化。
|
13天前
|
JavaScript 算法 前端开发
【专栏】前端开发中的slot算法和shadow DOM,两者提供更灵活、高效和模块化的开发方式
【4月更文挑战第29天】本文探讨了前端开发中的slot算法和shadow DOM,两者提供更灵活、高效和模块化的开发方式。slot算法允许在组件中定义插槽位置,实现内容的灵活插入和复用,提高代码可读性和维护性。shadow DOM则通过封装DOM子树,实现样式和事件的隔离,增强组件独立性和安全性。这两种技术常应用于组件开发、页面布局和主题定制,但也面临兼容性、学习曲线和性能优化等挑战。理解并掌握它们能提升开发效率和用户体验。
|
13天前
|
JavaScript 算法 前端开发
基于抽象语法树+diff算法实现Markdown编译器
基于抽象语法树+diff算法实现Markdown编译器
|
13天前
|
JavaScript 算法 前端开发
虚拟Dom
虚拟Dom
|
16天前
|
JavaScript 前端开发 API
Vue生命周期:在虚拟世界的牵绊与自由
Vue生命周期:在虚拟世界的牵绊与自由
18 1
|
18天前
|
JavaScript 开发者
Teleport传送:使用Vue的Teleport进行跨DOM结构渲染
【4月更文挑战第24天】Vue.js的`<teleport>`组件用于跨DOM结构渲染,解决组件视觉呈现跨越父组件DOM的问题。它允许子组件内容传送到DOM的任意位置,如示例中将模态框移到`modal-container`元素。通过`target`属性指定目标元素,结合`v-if`控制显示,实现灵活的UI布局和交互。在适当场景下使用`<teleport>`能优化复杂应用的结构。
|
18天前
|
JavaScript 算法 对象存储
Vue是如何diff算法的
【4月更文挑战第24天】Vue 的 diff 算法核心是对比新旧虚拟 DOM 树,通过比较节点类型、属性及子节点,采用双指针策略和 key 判断,实现高效更新。当节点类型或属性变化时,Vue 更新或替换节点。子节点比较则尝试最小化 DOM 操作,通过 key 优化列表变更。算法递归处理组件和子节点,最终生成补丁对象来更新真实 DOM,提升性能。开发中,合理使用 key 和优化状态变化可进一步提升性能。
|
18天前
|
JavaScript 算法 前端开发
Vue的虚拟DOM:Vue虚拟DOM的工作原理
【4月更文挑战第24天】Vue的虚拟DOM提升渲染性能,通过创建JavaScript对象树(虚拟DOM树)来跟踪DOM变化。当状态改变,Vue用新的虚拟DOM树与旧树对比(diff算法),找到最小DOM操作集合来更新真实DOM。优化策略包括减少状态变化、使用key属性和简化组件结构。理解虚拟DOM工作原理有助于Vue的性能优化。