返回顶部
首页 > 资讯 > 精选 >Vue源码分析之虚拟DOM的示例分析
  • 136
分享到

Vue源码分析之虚拟DOM的示例分析

2023-06-15 07:06:20 136人浏览 泡泡鱼
摘要

小编给大家分享一下Vue源码分析之虚拟DOM的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!为什么需要虚拟dom?虚拟DOM就是为了解决浏览器性能问题而被

小编给大家分享一下Vue源码分析之虚拟DOM的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

为什么需要虚拟dom?

虚拟DOM就是为了解决浏览器性能问题而被设计出来的。例如,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个js对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量。简单来说,可以把Virtual DOM 理解为一个简单的JS对象,并且最少包含标签名( tag)、属性(attrs)和子元素对象( children)三个属性。

  • ----- 元素节点: 元素节点更贴近于我们通常所看到的真实DOM节点,他有描述节点标签名词的tag属性,描述节点属性如class,attributes等的data属性,有描述包含的子节点信息的children属性等,由于元素节点所包含的情况相对而言比较复杂,源码中没有像前三种节点一样直接写死。

  • Vnode的作用: 用js的计算性能来换取操作真实DOM所消耗的性能,

  • ----- VNode在Vue的整个虚拟DOM过程起到了什么作用呢。 其实VNode的作用是相当大的,我们在视图渲染之前,把写好的template模板先编译成VNode并缓存下来,等到数据变化页面需要重新渲染的时候,我们把数据发生变化后的生成的VNode与前一次缓存下来的VNode进行对比,找出差异。然后有差异的VNode对应的真实的DOM节点就是需要重新渲染的节点,最后根据有差异的创建出来的DOM节点再插入到视图中,最终完成一次视图更新。就是再数据变化前后生成真实的DOM对应的虚拟DOM节点

为什么要有虚拟DOM:

----- 就是以JS的计算性能来换取操作真实DOM所消耗的性能,Vue是通过VNode类来实例化不同类型的虚拟DOM节点,并且学习了不同类型节点生成的属性的不同,所谓不同类型的节点其本质还是一样的,都属VNode类的实例,只是实例化的时候传入的参数不同罢了。
有了数据变化前后的VNode,我们才能进行后续的DOM-Diff找出差异,最终做到只更新有差异的视图,从而达到尽可能少的操作真实DOM的目的,以节省性能

----- 而找出更新有差异的DOM节点,已达到最少操作真实DOM更新视图的目的。而对比新旧两份VNode并找出差异的过程就是所谓的DOM-Diff过程,DOM-Diff算法是整个虚拟DOM的核心所在。

Patch

在Vue中,把DOM-Diff过程就叫做patch过程,patch意思为补丁,一个思想:所谓旧的VNode(odlNode)就是数据变化之前属于所对应的虚拟DOM节点,而新的NVode是数据变化之后将要渲染的视图所对应的虚拟DOM节点,所以我们要以生成的新的VNode为基准,对比旧的oldVNode,如果新的VNode上有的节点而旧的oldVNode没有,那么就在旧的oldVNode上加上去,如果新的VNode上没有的节点而旧的oldVNode上有,那么就在旧的oldVnode上去掉。如果新旧Vnode节点都有,则以新的VNode为准,更新旧的oldVNode,从而让新旧VNode相同。

整个patch:就是在创建节点:新的VNode有,旧的没有。就在旧的oldVNode中创建

删除节点:新的VNode中没有,而旧的oldVNode有,就从旧的oldVNode中删除

更新节点:新的旧的都有,就以新的VNode为准,更新旧的oldVNode

更新子节点

for (let i = 0; i < newChildred.length; i++) {    const newChild = newChildren[i]    for (let j = 0; j < oldChildren.length; j++) {        const oldChild = oldChildren[i]        if (newChild === oldChild) {            // ...        }    }}

那么以上这个过程将会存在一下四种情况

  1. 创建子节点,如果newChildren里面的某个子节点在oldChildren里找不到与之相同的子节点,那么说明newChildren里面的这个子节点是之前没有的,是需要此次新增的节点,那就创建子节点

  2. 删除子节点,如果把newChildren里面的每一个子节点都循环完毕后,oldChildren还有未处理的子节点,那就说明未处理的子节点式需要被废弃的,那就把这些节点删除

  3. 移动子节点,如果newChildren里面的某个子节点在oldChildren里找到了与之相同的子节点,但是所处的位置不同,这说明此次变化的需要调整该子节点的位置,那以newChildren里的子节点1的位置为基准,调整oldChildren里该节点的位置,使之与在newChildren里的位置相同

  4. 更新节点:如果newChildren里面的某个子节点在oldCHildren里找到了与之相同的子节点,并且所处的位置也相同,那么就更新oldChildren里该节点,使之与newChildren里的该节点相同

我们一再强调更新节点要以新Vnode为基准,然后操作旧的oldVnode,使之最后旧的oldVNode与新的VNode相同。

更新的时候分为三个部分:

如果VNode和oldVNode均为静态节点,

我们说了,静态节点无论数据发生任何变化都与它无关,所以都为静态节点的话则直接跳过,无需处理

如果VNode是文本节点

如果VNode是我文本节点即表示这个节点内只包含纯文本,那么只需要看oldVNode是否也是文本节点,如果是那就比较两个文本是否不同,如果不用则把oldVNode里的文本改成跟VNode的文本一样,如果oldVNode不是文本节点,那么不论它是什么,直接调用setTextNode方法把他改成文本节点,并且文本内容跟VNode相同

如果VNode是元素节点,则又细分以下两种情况

  1. 该节点包含子节点,那么此时要看旧的节点是否包含子节点,如果旧的节点里包含了子节点,那就需要递归对比更新子节点

  2. 如果旧的节点里不包含子节点,那么这个旧节点可能是空节点或者文本节点

  3. 如果旧的节点是空节点就把新的节点里的子节点创建一份然后插入到旧的节点里面,

  4. 如果旧的节点是文本节点,则把文本清空,然后把新的节点里的子节点创建一份然后插入到旧的节点里面

  5. 该节点不包含子节点,如果该节点不包含子节点,同时他又不是文本节点,那就说明该节点是个空节点,那就好办了,不管旧的节点之前里面有啥,直接清空即可

// 更新节点function patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) {    // vnode 与 oldVnode 是否完全一样,如果是,退出程序    if (oldVnode === vnode) {        return    }    const elm = vnode.elm = oldVnode.elm    // vnode 与 oldVnode是否都是静态节点,如果是退出程序    if (isTrue(vnode.isStatic) && isTrue(vnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {        return    }    const oldCh = oldVnode.children    const ch = vnode.children    // vnode 有 text属性,若没有    if (isUndef(vnode.text)) {        if (isDef(oldCh) && isDef(ch)) {            // 若都存在,判断子节点是否相同,不同则更新子节点            if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)        }        // 若只有vnode的子节点的存在        else if (isDef(ch)) {                        if (isDef(oldVnode.text)) nodeOps.setTextContext(elm, '')            addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)        }        // 如果只有oldnode的子节点存在        else if (isDef(oldCh)) {            // 清空DOM中的所有子节点            removeVnodes(elm, oldCh, 0, oldCh.length - 1)        }        // 若vnode和oldnode都没有子节点,但是oldnode中有文本        else if (isDef(oldVnode.text)) {            nodeOps.setTextContext(elm, '')        }        // 上面两个判断一句话概括就是,如果vnode中既没有text,也没有子节点,那么对应的oldnode中有什么清空什么    } else if (oldVnode.text !== vnode.text) {        nodeOps.setTextContext(elm, vnode.text)    }}

上面的我们了解了Vue的patch也就是DOM-DIFF算法,并且知道了在patch过程之中基本会干三件事,分别是创建节点,删除节点和更新节点。 创建节点和删除节点比较简单,而更新节点因为要处理各种可能出现的情况逻辑就比较复杂一些。 更新过程中九点Vnode可能都包含子节点,对于子系欸但的对比更新会有额外的一些逻辑,那么本篇文章就来学习Vue中是如何对比子节点的

更新子节点

当新的Vnode与旧的oldVnode都是元素节点并且都包含子节点的时候,那么这连个节点VNode实例上的chidlren属性就是所包含的子节点数组,对比两个子节点的通过循环,外层循环newChildren数组,内层循环oldChildren数组,每循环外层newChildren数组里的一个子节点,,就去内层oldChiildren数组里找看有没有与之相同的子节点

. 创建子节点

创建子节点的位置应该是在所有未处理节点之前,而并非所有已处理节点之后。 因为如果把子节点插入到已处理后面,如果后续还要插入新节点,那么新增子节点就乱了

. 移动子节点

所有未处理结点之前就是我们要移动的目的的位置

优化更新子节点:

前面我们介绍了当新的VNode与旧的oldVNode都是元素节点并且都包含了子节点的时候,vue对子节点是先外层循环newChildren数组,再内层循环oldChildren数组,每循环外层newChildren数组里的一个子节点,就去内层oldChildren数组里找看有没有与之相同的子节点,最后根据不同的情况做出不同的操作。这种还存在可优化的地方,比如当包含子节点数量较多的时候,这样循环算法的时间复杂度就会变得很大,不利于性能提升。

方法:

  1. 先把newChildren数组里的所有未处理子节点的第一个子节点和oldChildren数组里所有未处理子节点的第一个子节点做对,如果相同,那就直接进入更新节点的操作;

  2. 如果不同,再把newChildren数组里所有未处理子节点的最后一个节点和oldChildren数组里所有未处理子节点的最后一个子节点做比对,如果相同,那就直接进入更新节点的操作;

  3. 如果不同,再把newChildren数组里所有未处理子节点的最后一个子节点和oldChildren数组里所有未处理子节点的第一个子节点做比对,如果相同,那就直接进入更新节点的操作,更新完后再将oldChildren数组里的该节点移动到newChildren数组里节点相同的位置;如果不同,

  4. 再把newChildren数组里所有未处理子节点的第一个子节点和oldChildren数组里所有未处理子节点的最后一个子节点做比对,如果相同,那就直接进入更新节点的操作,更新后再将oldChildren数组里的该节点移动到与newChildren数组里节点相同的位置;

  5. 最后四种情况都试完如果还不同,那就按照之前循环的方式来查找节点。
    Vue为了避免双重循环数据量大时间复杂度升高带来的性能问题,而选择了从子节点数组中的四个特殊位置互相对比,分别是:新前和旧前,新后和旧后,新后和旧前,新前和旧后

在前面几篇文章中,介绍了Vue中的虚拟DOM以及虚拟DOM的patch(DOM-Diff)过程,而虚拟DOM存在的必要条件是的现有VNode,那么VNode又是从哪里来的。 把用户写的模板进行编译,就会产生VNode

模板编译:

什么是模板编译:把用户template标签里面的写的类似于原生html的内容进行编译,把原生HTML的内容找出来,再把非原生的HTML找出来,经过一系列的逻辑处理生成渲染函数,也就是render函数的这一段过程称之为模板编译过程。 render函数会将模板内容生成VNode

整体渲染流程,所谓渲染流程,就是把用户写的类似于原生HTML的模板经过一系列的过程最终反映到视图中称之为整个渲染流程,这个流程在上文中已经说到了。

抽象语法树AST:

  • 用户在template标签中写的模板对Vue来说就是一堆字符串,那么如何解析这一堆字符串并且从中提取出来元素的标签,属性,变量插值 等有效信息呢,这就需要借助一个叫做抽象语法树的东西。
    抽象语法树简称语法树,是源代码语法结构的一种抽象表示,他以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构,之所以说语法是抽象的,是因为这里的语法并不会表示出真实语法中出现的每个析姐,比如,嵌套括号被隐含在树的结构中,并没有以节点的i形式呈现,

具体流程:

  • 将一堆字符串模板解析成抽象语法树AST后,我们就可以对其进行各种操作处理了,处理完毕之后的AST来生成render函数,其具体三个流程可以分为以下三个阶段

模板解析阶段:将一堆模板字符串用正则表达式解析成抽象语法树AST

优化阶段:编译AST,找出其中的静态节点,并打上标记

代码生成阶段: 将AST转换成渲染函数

有了模板编译,才有了虚拟DOM,才有了后续的视图更新

以上是“Vue源码分析之虚拟DOM的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网精选频道!

--结束END--

本文标题: Vue源码分析之虚拟DOM的示例分析

本文链接: https://lsjlt.com/news/278588.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Vue源码分析之虚拟DOM的示例分析
    小编给大家分享一下Vue源码分析之虚拟DOM的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!为什么需要虚拟dom?虚拟DOM就是为了解决浏览器性能问题而被...
    99+
    2023-06-15
  • Vue源码分析之虚拟DOM详解
    为什么需要虚拟dom? 虚拟DOM就是为了解决浏览器性能问题而被设计出来的。例如,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内...
    99+
    2024-04-02
  • vue中虚拟DOM的示例分析
    这篇文章主要介绍了vue中虚拟DOM的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。虚拟 DOM 优点:保证性能下限:&n...
    99+
    2024-04-02
  • vue虚拟Dom-render的源码解析
    这篇文章主要介绍“vue虚拟Dom-render的源码解析”,在日常操作中,相信很多人在vue虚拟Dom-render的源码解析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”...
    99+
    2024-04-02
  • vue2.0中虚拟DOM渲染的示例分析
    这篇文章主要为大家展示了“vue2.0中虚拟DOM渲染的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“vue2.0中虚拟DOM渲染的示例分析”这篇文章吧...
    99+
    2024-04-02
  • Vue中的虚拟DOM和Diff算法实例分析
    这篇文章主要介绍了Vue中的虚拟DOM和Diff算法实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue中的虚拟DOM和Diff算法实例分析文章都会有所收获,下面我们一起来看看吧。简单介绍一下 虚拟DO...
    99+
    2023-06-29
  • Vue中AST源码解析的示例分析
    这篇文章主要介绍Vue中AST源码解析的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!从这个函数开始的:// Line-3924  Vue.prototy...
    99+
    2024-04-02
  • vue源码架构的示例分析
    这篇文章将为大家详细讲解有关vue源码架构的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。下载去github上下载Vue https://github.com/v...
    99+
    2024-04-02
  • Vue2.x中虚拟DOM diff原理的示例分析
    这篇文章主要介绍Vue2.x中虚拟DOM diff原理的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!前言经常看到讲解Vue2的虚拟Dom diff原理的,但很多都是在原代码...
    99+
    2024-04-02
  • Java源码解析之ConcurrentHashMap的示例分析
    小编给大家分享一下Java源码解析之ConcurrentHashMap的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!早期 ConcurrentHashMap,其实现是基于:分离锁,也就是将内部进行分段(Segme...
    99+
    2023-06-15
  • python源码剖析之PyObject的示例分析
    这篇文章主要介绍python源码剖析之PyObject的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、Python中的对象Python中一切皆是对象。————Guido van Rossum(1989)这...
    99+
    2023-06-15
  • DOM的示例分析
    这篇文章将为大家详细讲解有关DOM的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。DOM(Document Object Modle) 操作文档的编程接口DOM定...
    99+
    2024-04-02
  • Vue源码解析之数据响应系统的示例分析
    这篇文章主要介绍Vue源码解析之数据响应系统的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!数据双向绑定的思路1. 对象先来看元素是对象的情况。假设我们有一个对象和一个监测方...
    99+
    2024-04-02
  • Vue.js源码分析之自定义指令的示例分析
    这篇文章给大家分享的是有关Vue.js源码分析之自定义指令的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。前言除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令...
    99+
    2023-06-14
  • Vue源码之数据代理访问的示例分析
    小编给大家分享一下Vue源码之数据代理访问的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!概念解析:1) 数据代理: 通...
    99+
    2024-04-02
  • Vue.use源码的示例分析
    这篇文章将为大家详细讲解有关Vue.use源码的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。先上vue.use源码// javascript的方法是可...
    99+
    2024-04-02
  • mysql源码示例分析
    这篇文章主要介绍了mysql源码示例分析,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获。下面让小编带着大家一起了解一下。mysql的内存管理庞大而先进,这在mem0pool.c文...
    99+
    2024-04-02
  • Java源码解析之接口Collection的示例分析
    小编给大家分享一下Java源码解析之接口Collection的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、图示二、方法定义我们先想一想,公司如果要我...
    99+
    2023-06-15
  • Java虚拟机之类加载的示例分析
    小编给大家分享一下Java虚拟机之类加载的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Java是什么Java是一门面向对象编程语言,可以编写桌面应用程序...
    99+
    2023-06-15
  • webpack源码之loader机制的示例分析
    这篇文章主要介绍webpack源码之loader机制的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!loader概念loader是用来加载处理各种形式的资源,本质上是一个函数...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作