猿创征文 |《深入浅出Vue.js》打卡Day3

本文深入探讨了虚拟DOM的概念及其在Vue.js中的应用,包括为何引入虚拟DOM以提高性能,以及Vue.js中的VNode结构和作用。VNode作为JavaScript对象形式的DOM,用于对比和更新视图。此外,文章还详细阐述了patch算法,它是虚拟DOM的核心,负责智能地更新DOM,减少不必要的操作。通过对新旧VNode的比较,patch能精确地定位并更新变化的节点,提高了DOM操作的效率。
摘要由CSDN通过智能技术生成

第5章 虚拟DOM简介

5.1 什么是虚拟DOM

虚拟DOM是随着时代发展而诞生的产物。

在Web早期,页面的交互效果比现在简单得多,没有很复杂得状态需要管理,也不太需要频繁地操作DOM,使用jQuery来开发也可以满足我们的需求。

随着时代的发展页面上的功能越来越多,我们需要实现的需求也越来越复杂,程序中需要维护的状态也越来越多,DOM的操作也越来越频繁。

现在,我们使用三大主流框架Vue.jsAngularReact都是声明式操作DOM。我们通过描述状态和DOM之间的映射关系式怎么样的,就可以将状态渲染成视图。关于状态到视图的转换过程,框架会帮我们做,不需要我们手动去操作DOM。那这些框架是如何帮我们进行操作DOM的呢?

Angular中就是脏检查的流程,React中使用虚拟DOMVue.js 1.0通过颗粒度的绑定。因此虚拟DOM本质上只是众多解决方案中的一种,可以用但不一定必须用。

虚拟DOM的解决方法是通过状体生成一个虚拟节点数,然后使用虚拟节点数进行渲染,在渲染之前,会使用新生成的虚拟节点数和上一次生成的虚拟节点数进行对比,只渲染不同的部分。

5.2 为什么要引入虚拟DOM

事实上,AngularReact的变化侦测有一个共同点,那就是它们都不知道那些状态变量,因此,就需要进行暴力的比较。React是通过虚拟DOM的比较,Angular是使用脏检查的流程。

Vue.js 的变化侦测跟他们都不一样,它在一定程度上知道具体哪些状态发生的变化,这样就可以通过更细粒度的绑定来更新视图。也就是说,在Vue.js当中,当状态发生变化时,它在一定程度上知道哪个节点使用的这个状态,从而对这些节点进行更新操作,根本不需要对比。事实上在Vue.js 1.0的时候就是这样实现的。

但是这样做其实也有一定的代价,因为粒度太小了,每一个绑定都会有一个对应的watcher来观察状态的变化,这样就会有一些内存开销以及一些依赖追踪的开销,当状态被越多的节点使用时,开销越大。对于一个大型项目来说,这个开销是非常大的。

因此,在Vue.js 2.0开始选择了一个中等粒度的解决方案,那就是引入虚拟DOM。组件级别是一个 watcher实例,也就是说即便一个组件内有10个节点使用的某个状态,但其实也只有一个watcher来观察这个状态的变化,所以当这个状态发生变化是只能通知到组件,然后组件内部通知虚拟DOM去进行对比与渲染,这是一个比较折中的方案。

5.3 Vue.js中的虚拟DOM

在Vue.js中,我们使用模板来描述状态与DOM之间的映射关系。Vue.js通过编译将模板转换成渲染函数,执行渲染函数就可以得到一个虚拟节点树,在虚拟节点映射到视图的过程中,将虚拟节点与上次渲染视图所使用的旧虚拟节点(oldVnode)做对比,找出真正需要更新的节点来进行DOM操作,从而避免操作其他无任何改动的DOM。
在这里插入图片描述
可以看出,虚拟DOM在Vue.js中所做的事情其实并没有想象中那么复杂,它主要做了两件事。

  • 提供与真实DOM节点所对应的虚拟节点vnode
  • 将虚拟节点vnode和旧虚拟节点oldVnode进行比较更新视图

对两个虚拟节点进行比较时虚拟DOM中最核心的算法(即patch),它可以判断出哪些节点发生了变化,从而只对发生了变化的节点进行更新操作。

第6章 VNode

6.1 什么是VNode

Vue.js中存在一个vnode类,可以理解JavaScript对象版本的DOM元素,使用它可以实例化不同类型的vnode实例,而不同类型的vnode实例各自表示不同类型的DOM元素。

6.2 VNode的作用

每次渲染视图都是先创建vnode,然后使用它创建真实DOM插入到页面中,所以可以将上一次渲染视图时所创建的vnode缓存起来,之后每当需要重新渲染视图时,将新创建的vnode和上次缓存的vnode进行对比,查看它们之间有哪些不一样的地方,找出这些不一样的地方并基于此去修改真实的DOM。

Vue.js 目前对状态的侦测策略采用了中等粒度。当状态发生变化时,只通知到组件级别,然后组件内使用虚拟DOM来渲染视图。

6.3 VNode的类型

vnode的类型有以下几种:

  • 注释节点
    一个注释节点只有两个有效属性——textisComment,其余属性全是默认undefined或者false
  • 文本节点
    只有一个text属性
  • 元素节点
    元素节点通常会存在4种有效属性:
    tag:一个节点的名称;
    data:该属性包含了一些节点上的数据;
    children:当前节点的子节点列表;
    context:它是当前组件的Vue.js 实例
  • 组件节点
    组件节点和元素节点类似,有以下两个独有的属性:
    componentOptions:组件节点的选项参数,其中包含propsDate、tag和children等信息;
    componentInstance:组件的实例,也是Vue.js的实例。事实上,在Vue.js中,每个组件都是一个Vue.js实例。
  • 函数组件
    函数式组件和组件节点类似,它有两个独有属性:functionalContext functionalOptions
  • 克隆节点
    克隆节点是将现有节点的属性复制到新的节点中,让新创建的节点被克隆节点属性保持一致,从而实现克隆效果。它的作用是优化静态节点和插槽节点

以静态节点为例,当组件内的某个状态发生变化后,当前组件会通过虚拟DOM重新渲染视图,静态节点因为它的内容不会改变,所以除了首次渲染需要执行渲染函数获取vnode之外,后续更新不需要执行渲染函数重新生成vnode。因此,这时就会使用创建克隆节点的方法将vnode克隆一份,使用克隆节点进行渲染。这样就不需要重新执行渲染函数生成新的静态节点的vnode,从而提升一定程度的性能。

克隆节点和被克隆节点之间的唯一区别是isCloned属性,克隆节点的isCloned为true,被克隆的原始节点的isCloned为false

第7章 patch

虚拟DOM最核心的部分是patch,它可以将vnode渲染成真实的DOM。

7.1 patch介绍

patch也可以叫作patching算法,通过它渲染真实DOM时,并不是暴力覆盖原有DOM,而是比对新旧两个vnode之间有哪些不同,然后根据对比结果找出需要更新的节点进行更新,从名字就可以看出,patch本身就有补丁、修补等意思,其实际作用是在现有DOM上进行修改来实现更新视图的目的。patch对现在DOM进行修改需要做三件事:

1、创建新增的节点
(1)当oldVnode不存在该节点,而Vnode存在,就需要使用Vnode生成真实的DOM元素并将其插入到视图当中去。在首次渲染时,DOM中不存在任何节点,所以oldVnode是不存在,直接使用Vnode创建元素并渲染视图。
在这里插入图片描述
(2)oldVnodeVnode都有节点,但是两个不是相同节点的时候,可以得知Vnode是一个全新的节点,而oldVnode就是一个被废弃的节点,此时我们使用Vnode创建一个新的DOM节点,用它去替换oldVnode所对应的真实DOM节点。
在这里插入图片描述

2、删除已经废弃的节点
(1)当一个节点只在oldVnode中存在,我们需要把它从DOM中删除。因为渲染视图时,需要以Vnode为标准,所以Vnode中不存在的节点都属于被废弃的节点,而被废弃的节点需要从DOM中删除。
(2)节点替换过程,也有删除节点。当oldVnodeVnode不是同一个节点时,在DOM中需要使用Vnode创建的新节点替换oldVnode所对于的旧节点,而替换过程是将新创建的DOM节点插入到旧节点的旁边,然后再将旧节点删除,从而完成替换过程。

3、修改需要更新的节点
新旧两个节点是同一个节点,我们需要多者两个节点进行比较细致的比对。比如说,新旧两个节点是同一个文本节点,但是两个文本节点的文本不一样,我们需要重新设置oldVnode在视图中所对应的真实DOM节点的文本。
在这里插入图片描述

小结

在这里插入图片描述
接下来是对 ’patch对现在DOM进行修改需要做三件事‘ 的一个详细描述

7.2 创建节点

vnode只有三种类型的节点会被创建并插入到DOM中:元素节点(判断是否具有tag属性即可)、注释节点(是否有唯一的标识isComment:true)和文本节点(前面两种节点都不是,那剩下的就是文本节点)。

如果判断好节点类型之后,则需要调用当前环境下的createElement方法(在浏览器环境下就是调用document.createElement )来创建真实的元素节点,接下来将元素插人到指定的父节点中。如果,如果说元素节点还有子节点的话,我们也需要把子节点创建出来插入到指定的元素节点,一起跟着元素节点插入到元素节点的父节点。

如果是文本节点,则调用当前环境下的createTextNode方法(浏览器环境下调用document.createTextNode)来创建真实的文本节点并将其插入到指定的父节点中;

如果是注释节点,则调用当前环境下的createComment方法(浏览器环境下调用document.createComment方法)来创建真实的注释节点并将其插入到指定的父节点中。

将元素渲染到视图的过程非常简单。只需要调用当前环境下的 appendchild方法(在浏览器环境下就是调用parentNode . appendChild ),就可以将一个元素插入到指定的父节点中。如果这个指定的父节点已经被渲染到视图,那么把元素插人到它的下面将会自动将元素渲染到视图。

小结

在这里插入图片描述

7.3 删除节点

removeVnodes(vnodes,startIdx,endIdx)方法用于删除vnodes数组中从startIdx指定的位置到endIdx指定位置的内容;
removeNode方法用于删除视图中的单个节点,而nodeOps是对节点操作的封装;

为什么要封装?直接删除单个节点不行蛮?
这里涉及到跨平台的知识,跨平台渲染的本质是在设计框架的时候,要让框架的渲染机制和DOM解耦。只要把框架更新DOM时的节点操作进行封装,就可以实现跨平台渲染,在不同平台下调用节点的操作。这也是使用nodeOps封装的原因。

7.4 更新节点

直接上图清楚明了
在这里插入图片描述

7.5 更新子节点

对比两个子节点列表( children ),首先需要做的事情是循环。循环newChildren(新子节点列表),每循环到一个新子节点,就去 oldChildren(旧子节点列表)中找到和当前节点相同的那个旧子节点。如果在 oldchildren中找不到,说明当前子节点是由于状态变化而新增的节点,我们要进行创建节点并插入视图的操作;如果找到了,就做更新操作;如果找到的旧子节点的位置和新子节点不同,则需要移动节点等。

这里说下移动优化策略:
通常情况下,并不是所有子节点的位置都会发生移动,一个列表中总有几个节点的位置是不变的。针对这些位置不变的或者说位置可以预测的节点,我们不需要循环来查找,因为我们有一个更快捷的查找方式。我们只需要尝试使用相同位置的两个节点来比较是否是同一个节点:如果恰巧是同一个节点,直接就可以进入更新节点的操作;如果尝试失败了,再用循环的方式来查找节点,我们把这种很快速的查找节点的方式称为快捷查找,那么它共有4中查找方法,分别是:
新前与旧前;
新后与旧后;
新后与旧前;
新前与旧后;

如果这4种方式对比之后都没有找到相同的节点,这是再通过循环的方式去oldChildren中详细找一圈,看看能否找到。循环的条件就是 while(oldStartIdx < = oldEndIdx && newStartIdx <= newEndIndx){}

这里 ’更新子节点‘ 写的比较简单。小编能力有限,太累了。我之前也写过一篇关于diff算法文章,可以两者结合看,可能会比较清楚。叫人头疼得diff算法原理

在这里插入图片描述

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

*neverGiveUp*

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值