vue知识点整理 - 01

64 阅读12分钟

1. 为什么Vue被称为“渐进框架”?

什么是渐进式框架

渐进式框架是一种软件开发框架,它允许开发者根据项目的规模、复杂度和需求,逐步引入框架的功能,而不是一开始就采用框架的全部特性和复杂架构。

为什么vue是渐进式框架

Vue 被称为渐进式框架,主要原因在于它允许开发者根据项目的实际需求,逐步、灵活地引入框架功能,在不同阶段以不同方式使用 Vue,具体表现如下:

  1. 使用方式灵活,易于集成

Vue 能以非常轻量的方式嵌入到现有项目中。只需在 HTML 页面引入 Vue 的脚本文件,就可以通过简单的指令实现数据绑定和基本交互

  1. 功能逐步集成

随着项目的发展,开发者可以按需逐步集成 Vue 的更多高级功能.比如基础组件逐步组装为复杂组件

  1. 适配不同规模项目

小型项目友好,大型项目可扩展性强,这也是依赖于vue的简单灵活和生态的完备

  1. 学习成本低且可渐进提升

Vue 的 API 设计简洁明了,对于初学者而言,容易理解和上手。随着对 Vue 基础功能的熟悉,开发者可以逐步深入学习更高级的特性

2. SPA首屏为什么加载慢?

2.1. 原因

资源体积过大

资源请求过多

js执行阻塞

样式渲染阻塞

框架初始化过慢

2.2. 优化

资源加载优化:代码拆分、懒加载、资源预加载、预渲染

减少资源体积:代码压缩、图片优化

渲染优化:避免阻塞渲染(css放头部、js异步加载)、优化js执行(减少首屏js的执行量)

网络优化:cdn加速、合并请求、缓存数据

3. 请说说Vue.use方法的作用及原理

3.1. Vue 插件的规范

一个 Vue 插件通常是一个对象,这个对象需要包含一个 install 方法。该 install 方法会在调用 Vue.use() 时被执行。此外,插件也可以直接是一个函数,在这种情况下,这个函数会被当作 install 方法来执行。

3.2. Vue.use() 的执行流程

检查插件是否已安装

Vue.use() 首先会检查要安装的插件是否已经在 Vue._installedPlugins 数组中。Vue._installedPlugins 是 Vue 内部用于记录已安装插件的数组。如果插件已在该数组中,说明插件已经安装过,Vue.use() 直接返回,不再重复安装。

处理参数

  1. Vue.use() 可以接受一个插件(对象或函数)作为参数,也可以接受多个参数,第一个参数为插件,其余参数会被传递给插件的 install 方法。
  2. 如果传递给 Vue.use() 的第一个参数不是对象且不是函数,Vue.use() 会抛出一个错误,提示插件必须是一个对象****

调用 install 方法

  1. 如果插件是一个对象,Vue.use() 会调用该对象的 install 方法,并将 Vue 构造函数作为第一个参数传递进去,如果 Vue.use() 有额外的参数,这些参数会紧接着 Vue 之后传递给 install 方法。
  2. 如果插件本身就是一个函数,Vue.use() 会直接调用这个函数,并同样将 Vue 构造函数作为第一个参数传递,额外参数依次往后传递。

4. 你觉得虚拟DOM比真实DOM性能好吗?为什么?

在大多数情况下,虚拟 DOM 比真实 DOM 性能更好,但并非绝对,具体还得根据场景来区分:

4.1. 虚拟DOM性能更好的原因

减少直接操作真实 DOM 的频率

  • 真实 DOM 操作代价高:真实 DOM 是浏览器渲染页面的基础,对其进行修改(如添加、删除、移动元素)时,浏览器需要重新计算元素的布局、样式,并重新绘制页面,这是非常昂贵的操作,会消耗大量的性能。例如,当在页面中添加一个新的 DOM 元素时,浏览器需要重新计算该元素及其周围元素的位置和样式,可能还需要重新绘制整个或部分页面区域。
  • 虚拟 DOM 的缓冲作用:虚拟 DOM 是在 JavaScript 中以对象形式存在的轻量级数据结构,它对真实 DOM 进行了抽象。当数据发生变化时,先在虚拟 DOM 层面进行修改,然后通过比较新旧虚拟 DOM 树,计算出最小的变化集,最后将这些变化一次性应用到真实 DOM 上。这样就大大减少了直接操作真实 DOM 的次数,降低了浏览器重排和重绘的频率。比如,在一个包含 100 个列表项的列表中,如果只更新其中一项的数据,虚拟 DOM 可以精准地计算出只有这一项需要更新,而不是重新渲染整个列表。

高效的 Diff 算法

  • 快速对比差异:虚拟 DOM 在更新时,使用 Diff 算法来比较新旧虚拟 DOM 树,找出它们之间的差异。这个算法采用了一些启发式的策略,例如只对比同一层级的节点,避免跨层级的比较,从而大大减少了比较的复杂度。在比较过程中,它能够快速确定哪些节点是新增的、哪些是需要删除的、哪些是属性发生变化的。
  • 最小化 DOM 操作:通过 Diff 算法计算出的差异,会生成一个最小的 DOM 操作补丁,只对需要变化的真实 DOM 部分进行更新,而不是重新构建整个 DOM 结构。例如,在一个复杂的表单中,用户只修改了一个输入框的值,Diff 算法可以精确地定位到这个输入框对应的虚拟 DOM 节点,计算出其属性或文本内容的变化,然后只更新真实 DOM 中的这个输入框元素,而不影响其他部分。

4.2. 虚拟DOM性能不是最佳的场景

简单场景下可能优势不明显

  • 操作简单直接:在一些非常简单的页面或操作中,直接操作真实 DOM 可能更加高效。例如,页面上只有一个按钮,点击按钮时只是简单地修改按钮的文本内容,这种情况下直接操作真实 DOM(如button.textContent = '新文本')比通过虚拟 DOM 来处理要简单直接,因为引入虚拟 DOM 会带来额外的抽象和计算开销,包括创建虚拟 DOM 对象、进行 Diff 算法计算等。

初始渲染性能

  • 首次渲染开销:在首次渲染时,虚拟 DOM 需要将数据转化为虚拟 DOM 树结构,然后再根据虚拟 DOM 生成真实 DOM 并插入页面。这个过程相对于直接通过模板生成真实 DOM 会有一些额外的计算开销。尤其是在页面结构简单、数据量小的情况下,这种额外开销可能会比较明显。但在大型应用中,由于后续的更新操作频繁,虚拟 DOM 在整体性能上的优势会逐渐体现出来,弥补首次渲染的微小劣势。

总体而言,虚拟 DOM 在大多数复杂的前端应用场景中,通过减少真实 DOM 操作频率和利用高效的 Diff 算法,展现出比直接操作真实 DOM 更好的性能,但在简单场景或对首次渲染性能要求极高的特定情况下,其优势可能不显著甚至可能稍逊一筹。

5. 请描述下虚拟DOM的解析过程

5.1. 虚拟 DOM 的创建

5.1.1. 模板编译

Vue 应用通常从模板开始。模板是一种接近 HTML 的语法,用于描述视图结构。Vue 的编译器(在构建过程中或运行时,取决于配置)会将模板编译成渲染函数。这个过程会解析模板中的指令(如v-for)、插值表达式(如{{ message }})等,将其转化为 JavaScript 代码。

5.1.2. 渲染函数生成虚拟 DOM

渲染函数被调用时,会创建虚拟 DOM。虚拟 DOM 本质上是普通的 JavaScript 对象,它描述了真实 DOM 的结构、属性和子节点等信息。每个虚拟 DOM 节点包含标签名(tag)、属性(attrs)、文本内容(text)以及子节点数组(children)等信息。

5.2. 虚拟 DOM 的更新

5.2.1. 数据变化检测

Vue 通过响应式系统检测数据的变化。当响应式数据发生改变时,会触发重新渲染。

5.2.2. 生成新的虚拟 DOM

Vue 会再次调用渲染函数,基于新的数据生成新的虚拟 DOM 树。

5.2.3. Diff 算法比较新旧虚拟 DOM

Vue 使用 Diff 算法来高效地比较新旧虚拟 DOM 树,找出它们之间的差异

5.2.4. 生成最小更新补丁

通过 Diff 算法比较后,会生成一个描述变化的最小更新补丁。

5.3. 虚拟 DOM 更新应用到真实 DOM

5.3.1. DOM 更新操作

根据生成的更新补丁,Vue 会将这些变化应用到真实 DOM 上。

5.3.2. 触发重排与重绘

对真实 DOM 的修改会触发浏览器的重排(重新计算布局)和重绘(重新绘制页面)。但由于 Vue 通过虚拟 DOM 和 Diff 算法尽量减少了真实 DOM 的变化,从而降低了重排和重绘的范围和频率,提高了性能。

6. diff算法的原理

6.1. 核心策略

分层比较:Diff 算法只会对同一层级的节点进行比较,不会跨层级比较。这大大减少了比较的复杂度。

使用唯一key:在使用v - for指令渲染列表时,Vue 要求开发者提供唯一的key值。key用于帮助 Diff 算法准确识别每个节点的身份,从而在节点顺序变化时能正确地复用和移动节点。

6.2. 比较过程

6.2.1. 初始化比较

Diff 算法开始时,分别从新旧虚拟 DOM 树的根节点开始,按照层级顺序进行比较。例如,假设有新旧两棵虚拟 DOM 树,新树为newTree,旧树为oldTree,从它们的根节点newTree.rootoldTree.root开始比较。

6.2.2. 节点类型比较

首先比较两个节点的类型(标签名),如果节点类型不同,那么直接认为这两个节点及其子树完全不同,会将旧节点移除,在相应位置插入新节点。例如,旧节点是<div>,新节点是<p>,则直接删除旧的<div>节点,插入新的<p>节点。

6.2.3. 节点类型相同时

如果节点类型相同,接着比较节点的key值(如果有)和属性以及子节点(如果有)。

6.2.3.1. key值不同

key值不同,即使标签名和属性都相同,也会认为是不同的节点,同样会删除旧节点,插入新节点。例如,两个<li>标签,标签名相同,属性也相同,但key值不同,Diff 算法会将它们视为不同节点处理。

6.2.3.2. key值相同

key值相同且标签名相同时,比较节点的属性。对于属性的变化,会更新真实 DOM 上相应的属性。例如,旧节点<div class="old - class"></div>,新节点<div class="new - class"></div>,会更新真实 DOM 中<div>元素的class属性。

6.2.3.3. 比较子节点
  • 新节点有子节点,旧节点没有:直接将新节点的子节点插入到真实 DOM 中对应位置。
  • 旧节点有子节点,新节点没有:直接从真实 DOM 中移除旧节点的子节点。
  • 新旧节点都有子节点:此时会采用双端比较法。分别从新旧子节点数组的两端开始比较,即开始位置(索引为 0)和结束位置(索引为 length - 1)。假设有新旧两个子节点数组oldChildrennewChildren
  • 旧前与新前比较:比较oldChildren[0]newChildren[0],如果相同(标签和key都相同),则将这两个节点视为相同节点,继续比较下一组(oldChildren[1]newChildren[1]),同时旧节点和新节点的起始索引都向后移动一位。
    • 旧后与新后比较:比较oldChildren[oldChildren.length - 1]newChildren[newChildren.length - 1],如果相同,则视为相同节点,继续比较下一组,同时旧节点和新节点的结束索引都向前移动一位。
    • 旧前与新后比较:比较oldChildren[0]newChildren[newChildren.length - 1],如果相同,将旧节点数组起始位置的节点移动到旧节点数组的结束位置,同时旧节点起始索引向后移动一位,新节点结束索引向前移动一位。
    • 旧后与新前比较:比较oldChildren[oldChildren.length - 1]newChildren[0],如果相同,将旧节点数组结束位置的节点移动到旧节点数组的起始位置,同时旧节点结束索引向前移动一位,新节点起始索引向后移动一位。
    • 遍历查找:如果以上四种情况都不满足,会在旧子节点数组中查找与newChildren[0](新节点起始位置的节点)key相同的节点。如果找到,将其移动到旧子节点数组的起始位置,并更新相关索引;如果没找到,则认为这是一个新节点,插入到真实 DOM 对应位置。

6.3. 生成更新补丁

在比较过程中,Diff 算法会记录下所有的变化,生成一个更新补丁(patch)。这个补丁描述了如何将旧的真实 DOM 转换为新的真实 DOM,包含了节点的新增、删除、移动和属性更新等操作。最后,Vue 会根据这个更新补丁,将变化应用到真实 DOM 上,完成视图的更新。