Advertisement

【Vue】面试必备知识点总结

阅读量:

Vue、Vuex等总结

  • 一、Vue基础

    • 1、Vue的基本原理
    • 2、双向数据绑定原理
    • 3、使用 Object.defineProperty() 来进行数据劫持有什么缺点?
    • 4、MVVM、MVC、MVP的区别
    • 5、computed和watch的区别
    • 6、Comuted和methods区别
    • 7、slot是什么?作用?原理?
    • 8、如何保存页面的当前状态
    • 9、常见的事件修饰符及其作用
    • 10、v-if、v-show、v-html的原理
    • 11、v-if和v-show的区别
    • 12、v-model如何实现的,语法糖实际是什么?
    • 13、v-model可以被用在自定义组件上吗?如果可以如何使用?
    • 14、data为什么是一个函数而不是对象
    • 15、对keep-alive的理解,它是如何实现的,具体缓存的是什么?
    • 16、$nextTick原理及作用
    • 17、Vue 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决?
    • 18、vue中封装数组的方法有哪些?其如何实现页面更新
    • 19、 Vue 单页应用与多页应用的区别
    • 20、Vue template 到 render 的过程
    • 21、Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?
    • 22、mixin、extends的覆盖逻辑
    • 23、描述下Vue自定义指令
    • 24、子组件可以直接改变父组件的数据吗?
    • 25、Vue是如何收集依赖的?
    • 26、对React和Vue的理解,他们的异同
    • 27、assets和static的区别
    • 28、delete和Vue.delete删除数组的区别
    • 29、对SSR的理解
    • 30、vue初始化页面闪动的问题
    • 31、MVVM的优缺点?
    • 32、v-if和v-for哪个优先级更高?如果同时出现,应如何优化?
    • 33、对Vue组件化的理解
    • 34、v-model的实现原理
    • 35、对vue设计原则的理解
    • 36、vue中常见的性能优化的方法
  • 二、vue的生命周期

    • 1、概述vue的生命周期
    • 2、子组件与父组件的工作流程
    • 3、created与mounted之间的区别
    • 4、通常在哪个生命周期阶段进行异步数据请求
    • 5、keep-alive阶段涉及哪些生命周期事件

    • 三、组件通信

      • 1、props/$emit
      • 2、eventBus事件总线(emit/on)
  • 四、路由

    1. 如何实现Vue Router的懒加载功能?
    1. 请阐述Vue Router中hash模式与history模式之间的区别
    1. 如何获取页面hash值的变化情况?
    1. route与router在.vue router中的区别是什么?
    1. 请详细说明如何在.vue router中定义动态路由,并解释如何获取传入的动态参数?
    1. 请描述vue router路由钩子在其生命周期中的具体表现及其作用
    1. 请分析在vue router中跳转操作与location.href属性有何不同之处?
    1. 解析一下$params'与'query'之间的区别
    1. 请列举并解释vue router中的导航守卫类型及其作用
  • 对前端路由的理解:请分享你对前端路由管理的理解及其应用实践

  • 第5章 Vuex 原理

    • 动作(Action)与更新(Mutation)在VUE中的主要区别是什么?
      • VUE与localStorage之间的主要区别是什么?
      • VUE相较于纯全局对象有哪些独特之处?
      • 为何VUE中的更新(Mutation)无法执行异步操作?
      • 如何在一个组件内批量获取VUE的getter属性?
  • 六、Vue 3.0

  • 1、Vue 3.0带来了哪些主要更新

  • 2、定义属性与代理之间的区别是什么?这有助于理解其功能差异

  • 3、为什么在某些情况下优先选择使用Proxy组件?

  • 七、虚拟dom

    • 认识虚拟DOM的本质
    • 解析虚拟DOM的工作原理
    • 采用虚拟DOM的主要原因是什么?
    • 相较于真实的DOM,在性能上是否具备优势?
    • DIFF算法的基本原理是什么?
    • Vue中的key属性有何作用?
    • 为何不推荐使用index作为键值对的唯一标识?

原文地址:https://www.yuque.com/cuggz/interview/hswu8g

一、Vue基础

1、Vue的基本原理

在Vue实例创建时(也就是Vue生成一个实例的时候),Vue会遍历其数据中的属性,并使用Object.defineProperty方法将其转换为getter和setter功能。这些操作会实时跟踪相关的依赖项,在属性被读取或修改时触发更新。每个组件都会有一个对应的watcher程序(也就是所谓的事件监听器),它会在组件渲染的过程中将相关属性标记为依赖,并在依赖项的setter被调用时通知watcher进行重新计算。

2、双向数据绑定原理

Vue.js 是基于数据劫持与发布者-订阅者模式的技术框架实现的。该框架通过调用 Object.defineProperty() 函数实现对各属性 setter 和 getter 的拦截功能,在数据发生变化时会向注册的监听器推送消息,并启动相应的监听回调机制。该框架主要包括以下几个方面:架构设计、组件管理以及事件处理机制。

  • 对于需要observe的数据对象进行递归遍历(recursive traversal),涵盖所有子属性对象(sub-attribute objects)的所有属性(attributes),系统会自动添加对应的目标设置器(setters)和获取器(getters)。当对某个属性值进行赋值时(assigning a value to),该操作会触发相应设置器的方法(method),从而实现对数据变化的实时监听(listener)。
  • 系统编译解析模板指令的过程如下:首先将模板中的变量替换成实际数据;然后初始化渲染页面视图;接着为每个指令节点绑定更新函数;最后建立订阅者与数据变化的连接机制;当数据发生变化时(change in data values),订阅者会通过其notice()方法通知相关组件,并触发相应的更新响应。
  • Watcher订阅者组件是Observer与Compile之间的信息传递媒介(intermediary)。其主要功能包括:在实例化时加入订阅器列表(dep)中;自身必须实现一个名为update()的方法;当dep.notice()被调用时(triggered by notice() method),该组件会调用自身的update()方法,并驱动Compile模块中的回调机制运行完毕。
  • MVVM模式作为数据绑定的核心入口,在整合Observer、Compile与Watcher组件的基础上构建起完整的通信架构:通过Observer监听模型数据的变化;通过Compile完成模板指令的解析与编译;利用Watchers搭建起两个组件之间的信息传输通道;最终实现了双向绑定效果——即视图更新受数据变化驱动、而视图交互的变化又反向影响到模型数据的状态更新。

3、使用 Object.defineProperty() 来进行数据劫持有什么缺点?

当我们对某些属性执行操作时

在Vue3.0中不再采用之前的方法,在线实现对目标对象的数据控制。该方法通过引入Proxy技术来实现数据劫持功能。其优点在于能够完美地监控所有数据变更情况,并能有效阻止不必要的操作影响系统稳定性。然而其局限性在于兼容性方面的限制,并非适用于所有开发环境。由于Proxy基于ES6语法设计,在实际应用中需要注意相关版本的支持情况

4、MVVM、MVC、MVP的区别

MVC、MVP 和 MVVM 是三种典型的软件架构设计模式 ,主要以集中功能模块的方式来优化代码架构,并提高开发效率

MVC采用将Model、View及Controller分离以优化代码架构的方法。其中View层负责页面显示功能的实现,在此过程中与Model层保持紧密关联,并通过观察者模式实现双向通信机制。当Model层发生变化时会自动通知其关联的View层进行相应更新;而Controller层则充当了系统操作的核心枢纽,在接收来自User的各种交互信号后会依次触发相应的事件处理流程:首先调用相关的Model组件完成必要的业务逻辑处理,并根据返回结果动态地驱动其对应的数据呈现界面的变化过程。

MVVM由Model、View和ViewModel组成:
其中Data Model负责存储数据及业务逻辑;
User Interface(UI)界面用于展示数据;
ViewModel则响应数据变化并协调视图更新以处理交互操作;
详细来说:

  • Data model层存储所有数据及其业务逻辑;
  • View层负责将数据呈现给用户;
  • ViewModel不仅监控模型层的变化还负责触发相应视图更新以实现交互功能;

Model与View之间没有直接关联,并非通过直接的方式建立联系。相反地,则是通过ViewModel来实现相互之间的连接关系。同时,在该系统架构中存在一个明确的数据绑定机制:一方面,在模型层的变化会引起视图层相应的内容更新;另一方面,在视图层因用户交互操作导致的数据变化也会在模型层得到同步。

这种模式实现了Model和View之间的数据自动同步。
因此开发者只需专注于数据的维护操作即可。
无需自行处理DOM

MVP

5、computed和watch的区别

computed:
● 它采用缓存机制,在相关数据发生变化时才会重新计算
● 具备响应式机制并不具备异步功能;若Computed中存在异步操作,则无法监控数据变化情况
● computed默认采用缓存策略,并基于其响应式依赖进行优化;即依据data声明或来自父组件传递过来的所有props中的相关内容来执行计算
● 若某个属性由其他 attribute决定,则通常会选择使用 computed 来实现这种关系
● 若 computed 属性的目标是 function,则通常会使用 get 方法获取返回值;而 set 方法则用于更新该 attribute 的 value

● 它不具备缓存机制,在数据发生变化时会触发相应的操作
● 具备异步监听功能
● 监听的函数接收两个参数
● 当属性发生变化时会执行相应的操作
● 监听数据仅限于data中声明的数据或来自父组件传递的props
● 深度监听仅能检测到复杂数据类型(如数组或对象)的整体变化
当需要在组件加载后或响应数据变化时执行异步或耗时的操作时使用watch

总结:
● 计算出的属性 : 基于其它属性值计算得出,并具有缓存特性。只有当其依赖的属性值发生变化时(下次读取),才会重新计算该计算出的属性值。
● 观察者模式中的监听器 : 主要负责观察作用,在实现过程中不具有缓存特性(类似于某些数据的监听回调)。每当监听的数据发生变化时(执行回调函数),就会触发后续操作。

运用场景:
● 当涉及数学运算且依赖外部数据时,则应采用 computed机制(因为computed具有缓存特性),从而避免重复计算带来的性能开销。
● 当面临动态更新需求或需执行耗时操作(如调用API)且希望控制频率时,则应选择watch机制(watch选项可触发相应动作),确保在获得最终结果前有中间状态标识(而这是 mere computation无法实现的)。

6、Comuted和methods区别

可以将同一函数定义为一个 approach 或者一个 computing property. 两种方式的结果是相同的.

不同点:
● computed-based: 计算属性基于其依赖项缓存,在其相关依赖变化时会重新计算;
● method 调用总是被调用该函数。

7、slot是什么?作用?原理?

也可称为插槽,在Vue中负责内容分发的核心机制。通过 slot 元素作为内容分发的出口端端口,
插槽 slot 属于子组件中的一个模板标签类型,
它的存在与否以及展示方式完全由父组件决定。
slot 分为三种类型:
默认插槽、具名插槽和作用域插槽。

-默认插槽

  • 具名插槽
  • 作用域插槽

8、如何保存页面的当前状态

旨在维持页面状态的稳定(这与组件状态的维护具有同等重要性)。这将导致下面这两种情形:

  1. 在当前情况下, 前组件将被卸载.
  2. 相反地, 在这种情况下, 前组件不会被卸载.
    根据以上两种情形的具体表现形式, 则可相应地制定出相应的解决方案.

组件会被卸载:
(1)将状态存储在LocalStorage / SessionStorage

仅需在组件即将被销毁的过程中,在 LocalStorage 和 SessionStorage 中将当前组件的状态以 JSON.stringify() 的形式存储即可。
需要注意的是,在此过程中需要考虑组件更新状态的最佳时机。

当从 B 组件转向 A 组件时,A 组件应更新其自身的状态.然而,当有其他组件转向 B 组件时,实际上希望 B 组件重新渲染而非从 Storage 中获取信息.因此,应在 Storage 中的状态字段添加一个 flag 属性,以控制 A 组件是否应当读取 Storage 中的状态.

(2)路由传值
React Router 的 Link 组件的 to 属性实现了支持路由间传递参数的作用。

在这里需要用到 state 参数,在 B 组件中可以通过 history.location.state 获取 state 值,并将其存储下来。当返回给 A 组件时携带该 state 值可实现路由状态保持的效果。

9、常见的事件修饰符及其作用

● .stop:与JavaScript中event.stopPropagation()的作用相同(阻止事件冒泡);
● .prevent:当事件可被取消时,则会取消该特定事件但不会阻止后续传播;
● .capture:捕获方向与冒泡相反(捕获事件由外到内);
● .self:只会触发自身范围内的相关操作而不会影响子元素;
● .once:仅会在首次触发时执行指定操作(后续不再响应)。

10、v-if、v-show、v-html的原理

11、v-if和v-show的区别

● 功能:本方案采用动态的方式增减DOM树中的节点数量(新增或删除),而展示效果则通过CSS样式属性进行控制。
● 编译流程:当状态发生变化时会触发本地化编译与卸载操作(仅在条件首次由假变为真时启动本地化处理流程)。展示效果则基于CSS样式表进行快速切换(该过程无需触发本地化操作)。
● 编译特性:本方案具有惰性特征(即初始状态为假时不触发任何操作),仅在条件首次由假变为真时启动本地化处理流程。相比之下,在所有状态下都会被编译并保持静态缓存状态(包括初始状态)。
● 性能特点:相比本方案存在较高的切换频率导致额外开销较大(主要体现在响应式更新能力方面),而展示效果由于频繁渲染导致初始阶段存在较高的计算负担。
● 应用场景建议:对于无需频繁更新的情况可优先选择本方案(适用于运营期间业务逻辑不会发生重大变更的情况),而如果需要频繁展示不同版本的内容则更适合采用展示效果机制

12、v-model如何实现的,语法糖实际是什么?

该代码实现了 input 元素的 value 设置为 message 变量,并在输入事件触发时动态地将 message 设置为目标值:绑定于表单元素

在自定义组件中,默认会应用 v-model 于组件中:它会自动利用以 value 命名的属性和以 input 命名的事件。

本质上是一个父子组件之间的通信机制作为语法糖层结构,在该机制中通过prop属性以及.emit方法实现了父组件与子组件之间的数据交互能力。由此可见,在父组件中基于v-model视图绑定语法糖层结构可以被重新定义为:

13、v-model可以被用在自定义组件上吗?如果可以如何使用?

v-model是一个语法糖;

<input
v-bind:value=“searchText”
v-on:input=“searchText = $event.target.value”

14、data为什么是一个函数而不是对象

Vue组件可能存在多个实例的情况可能导致数据共享问题。具体而言,在以对象形式定义$data字段时,默认情况下所有实例都会共享同一个数据源$data。这种设计导致的状态变更会影响所有组件实例的操作行为,并引发数据同步问题。这是不可取的;而通过采用函数式初始化方式,在initData方法中会生成独立的新数据源(通常是新对象)。因此在单个组件开发中这种方法更为安全可靠;而当使用Vue根组件(Root Component)时不会出现这一问题这是因为当仅允许创建一个主应用逻辑主体的时候根本无法实现多线程处理从而避免了潜在的数据冲突风险

15、对keep-alive的理解,它是如何实现的,具体缓存的是什么?

如果在组件切换过程中需要保存一些组件的状态以避免多次渲染,则可以采用 keep-alive 组件来包裹需保存的组件状态信息。

16、$nextTick原理及作用

Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。

nextTick的核心是基于本机JavaScript功能,并采用MutationObserver等内置JavaScript机制来模拟相应的微/宏任务执行过程**其核心在于依靠JavaScript自身的异步回调机制来构建框架级的异步处理体系

nextTick 既是 Vue 内部异步队列被调用的方法之一,在实际开发中也支持开发者利用该方法来处理其项目中 DOM 数据更新的具体时机安排。

Vue采用的是基于数据驱动的视图模式,在某些情况下仍需进行DOM操作。有时会遇到如DOM1发生变更导致DOM2依赖数据未及时同步的情况。此时就需要调用nextTick来处理更新问题。

因为Vue的DOM操作是异步进行的,在处理上述情况时,则必须将其DOM2获取数据的操作放置到$nextTick处。

应用场景:

  • 当数据发生更新或调整时触发的一个动作,在这种情况下需要使用与当前数据相关的DOM结构时,则必须将其放置在nextTick()的回调函数中。
    • 同样地,在Vue生命周期管理过程中,在created()钩子执行相关的DOM操作时,则也必须将其放置在nextTick()的回调函数中。

由于在created()钩子函数运行时页面的DOM尚未完成渲染流程,在此阶段无法直接对DOM进行操作;因此,在这种情况下如果希望对DOM进行操作,则必须将执行该操作相关的代码逻辑安排在nextTick()回调函数内部处理

17、Vue 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决?

单击按钮后会发现(obj.b)已经成功地将该属性添加到实例中,但视图并未刷新。这是因为当Vue实例被创建时,默认情况下不会将所有属性声明为响应式,因此只有那些在实例创建时已经声明过的属性才会被转换为响应式属性并触发视图更新;而此时由于obj.b并未被声明,所以它不会触发视图重绘,这就需要调用Vue提供的全局设置方法$set()来重新渲染视图

18、vue中封装数组的方法有哪些?其如何实现页面更新

Vue采用Object.defineProperty机制来实现数据拦截功能。但该方法无法实时监控数组内部的变化情况(包括数组长度的变动和元素的增删操作)。因此必须针对这些场景采取特殊处理措施。

在Vue框架中,默认情况下响应式数据处理主要依赖于Object.defineProperty机制来进行数据拦截功能实现。然而该默认的方法不具备动态监控能力(包括但不限于数组内部状态的变化),具体表现为无法及时感知到数组长度的变化以及元素的增删操作等关键场景的变化情况。因此必须针对这些特定的操作场景采取相应的技术手段(即所谓的"hack")来确保系统能够持续关注并反映这些动态变化信息。

换句话说,在替换该数组中的所有固有方法之前,请注意程序会获取其对应的Observer对象。
当程序处理该数组时会获取其对应的Observer对象。
当新值出现时会启动对该变化的追踪(这等价于通过特定属性判断实现了特定行为)。
随后触发通知机制以通知渲染watcher进行更新操作。

19、 Vue 单页应用与多页应用的区别

● 单页应用(SinglePage Application)即指只有一个主页面的应用程序,在初次访问时只需加载一次相关的脚本和样式表文件即可运行。所有的功能都被整合到同一个界面中,并将各个功能模块独立成组件进行处理。单页应用切换时会重新渲染相关的区域而不影响其他部分。
● 多页应用(MultiPage Application)则是由多个独立的功能页面组成的应用程序系统,在每次访问不同的页面时都需要重新加载相应的脚本和样式表文件。多页应用切换时会整体刷新整页的内容以确保显示效果的一致性和完整性。

20、Vue template 到 render 的过程

vue的模版编译过程主要如下:template -> ast -> render函数

在模版构建过程中,Vue 会运行 compileToFunctions 函数将 template 转换为 render 函数。

compileToFunctions中的主要逻辑如下:

1)调用parse方法将template转化为ast(抽象语法树)

parse的主要任务:将模板转化为AST树结构,并采用JavaScript对象形式来表示整个模板

解析过程:借助正则表达式按顺序解析模板,在遇到起始标签、结束标签以及文本内容时都会触发相应的回调函数操作,最终构建出AST树结构。
其中type=1对应普通元素节点、type=2代表运算符节点以及type=3是纯文本节点。

该过程旨在识别并筛选出所有静态节点,并对其进行标记处理。这样一来,在后续更新渲染时会无需参与动态更新即可完成相应工作

深入分析AST结构中的各个子树元素是否属于静态节点或其父级静态节点。若这些节点均为静态,则其对应的DOM结构将保持不变,在动态模板更新过程中将带来显著优化效果。

generate负责将ast抽象语法树转换为 render字符串,并将其中的静态部分存储在 staticRenderFns 中。最终通过构造 new Function( render) 来实现 render 函数的创建。

当Vue数据中的某个属性发生变化时,是否会立即触发并导致视图重绘?

不会立即同步地执行重绘操作。Vue实现响应式并非仅在数据发生变化后才进行DOM更新。Vue在更新DOM时采用的是异步方式,在这种机制下,一旦检测到数据变化就会启动一个事件队列,并将所有在同一事件循环中发生的数据显示变更进行缓冲。

如果同一个watcher被触发多次,则只会加入队列一次。清除缓冲区中的重复数据对于防止无意义的计算和DOM操作的发生至关重要。接着,在下一个事件循环tick之后会重新加载队列并执行已完成的工作。

22、mixin、extends的覆盖逻辑

23、描述下Vue自定义指令

在 Vue 2.0 中实现代码复用与抽象的主要手段是组件。然而,在某些情况下,则需要直接操作普通的 DOM 元素。这时候就需要调用自定义指令。通常,在需要对 DOM 元素执行底层操作时,请尽量仅用于展示效果,并不修改其内部属性值。当直接通过自定义指令修改 value 属性时需要注意的是,在 Vue 组件中绑定 v-model 的属性不会实时同步更新。如必须修改可以在自定义指令中使用 keydown 事件,在 Vue 组件中使用 change 事件,并在回调函数内对 Vue 数据结构进行相应修改以完成状态更新。

24、子组件可以直接改变父组件的数据吗?

子组件不能直接修改父组件的数据。其目的是为了维护父子之间的单向数据流动。因此当父级组件更新时 子级的所有props都会被更新为最新的值。如果进行了操作(即直接修改了parent的状态) 该做法会导致在浏览器控制台出现提示信息。

Vue推荐采用单向数据传输模式,在这种架构中,默认情况下父级组件的属性更新会传递到子组件中。然而这种双向传递的行为却被严格禁止避免潜在的问题出现其主要目的是为了避免由于意外操作而导致父组件状态发生突变从而使得整个应用程序的数据流动路径变得复杂难以理清。一旦违反了这一原则特别是在处理复杂的应用程序时调试的成本将大幅上升

只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改

25、Vue是如何收集依赖的?

在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将

26、对React和Vue的理解,他们的异同

27、assets和static的区别

相同点:其中 assets 和 static 两个变量都用于存储静态资源文件。在项目中所需的图片、字体图标以及样式文件等都可以存放在上述这两个变量中

28、delete和Vue.delete删除数组的区别

● 删除操作仅将被删除的元素替换为空或undefined状态,并未影响其他元素的数据。
● 该方法导致数组被彻底删除,并对剩余数据产生影响。

29、对SSR的理解

SSR即为服务端渲染技术,在该方法中我们通过将Vue在客户端进行标签渲染生成HTML的过程将其转移至服务端执行并由其完成后再将生成的HTML内容直接传递回客户端

SSR的优势:
● 更好的SEO
● 首屏加载速度更快

SSR 的缺点:
● 开发环境可能会受到一定的限制;由于服务器端渲染仅支持 beforeCreate 和 created 两个钩子作为可用 hook;
● 在引入一些外部扩展库时可能需要采取特殊措施;而服务端渲染的应用程序默认情况下要求运行于 Node.js 环境中;
● 更多的服务端负载可能导致性能压力。

SSR的缺点主要体现在以下几个方面:
● 从应用开发环境来看,在服务器端渲染场景下仅支持beforeCreate和created两个钩子函数;
● 如果需要引入外部扩展库进行功能扩展,则需做额外处理;同时,在服务端渲染的应用程序部署时必须确保运行在Node.js环境中;
● 服务端层面的负载压力会相对更大。

30、vue初始化页面闪动的问题

在使用Vue进行开发时,在启动Vue之前(即进行初始化)时因为div并未绑定到Vue管理系统而导致编写的代码在此阶段尚未被解析就会出现闪退现象遇到类似{{message}}这样的提示信息尽管这种情况通常时间非常短暂但也值得采取措施彻底解决问题

31、MVVM的优缺点?

优点:
● 将视图(View)与模型(Model)解耦以降低代码之间的依赖关系从而提升代码复用性: 比如将视图(View)独立于模型的变化与修改之外一个ViewModel可以绑定不同的"View"实例当视图发生变化时模型也随之变化而当模型发生变化时原有的视图依然保持原有状态你可以将一些固定的视图逻辑封装到一个ViewModel中实现多个视图能够复用相同的逻辑片段
● 通过ViewModel的设计使得测试变得更加高效
● 利用双向绑定机制实现数据更新后的自动呈现效果提升开发者的编码效率

缺点:
● 由于采用双向绑定机制,在界面出现异常时难以判断问题根源。具体来说,在界面上出现异常可能是你所编写的View代码存在缺陷(即你所编写的View代码有问题),也可能是你编写的Model代码存在缺陷(即Model代码有问题)。通过数据绑定会将一个位置出现的问题迅速传递到其他地方(即另一个位置),从而使得找出最初出现问题的地方变得更加困难(即变得不容易)。此外
● 在一个较大的模块中模型也会变得很大,在操作起来非常方便的同时也能很好地保持数据的一致性(即能够保证数据一致性)。然而长期持有内存资源会导致内存占用过高
● 对于大型图形应用程序而言,在界面显示的状态较多的情况下会导致ViewModel构建和维护的成本显著提高。

32、v-if和v-for哪个优先级更高?如果同时出现,应如何优化?

在V-Sphere环境中,默认情况下会优先使用v-arc来解析磁盘管理项,并且当磁盘管理项同时存在VMDK文件和虚拟磁盘时,在每次渲染过程中都会首先执行循环操作后再进行条件判断。无论怎样都会不可避免地导致这种现象发生吗?

为了解决这个问题,在代码结构中需要采取以下措施:首先,在外层逻辑中嵌入一个template组件,并在其主体部分执行v-if判断逻辑;接着,在这个template内设置一个循环体(使用v-for),以处理具体的遍历操作。特别地,当条件位于循环体内时(即当某个特定条件满足时),可以通过计算属性的方式提前筛选并排除不需要呈现的数据项。

33、对Vue组件化的理解

  1. 组件是自成一体且共用的代码单元体。作为Vue的核心特性之一,在该框架下开发者能够方便地采用小型独立且通常具备共用性的组件来构建复杂的大型应用。
  2. 组件化开发明显提升了应用开发效率、效能以及可重用性的水平。
  3. 组件按照功能分类主要包括页面类组件(如按钮或链接)、业务类组件(如数据处理逻辑)以及通用类组件(如模板支持)。
  4. 在Vue框架中,默认提供的基础功能是以配置形式存在的,默认情况下编写的应用程序都是基于VueCore构建起来的。
  5. 在实际开发中常用的几种核心功能模块包括:
    • 属性prop:用于实现对象级别的属性绑定;
    • 自定义事件:支持创建自定义事件机制;
    • 插槽机制:用于实现对象间数据交互;
      这些功能模块主要用于实现模块间的通信与扩展功能。
  6. 合理规划与划分合理的功能边界有助于提升系统的整体性能表现;
  7. 遵循"高内聚低耦合"的设计原则能够有效降低系统的复杂度与维护难度。

34、v-model的实现原理

在Vue中使用v-model可以达成数据的双向绑定功能;然而,在这种情况下为何这个指令能够实现这一功能呢?因为v-model本质上是一个语法糖层;具体而言,在绑定完成后不仅锁定了数据本身的数据源属性,并且还新增了一个input事件监听机制供后续操作使用。

实现原理:
● v-bind绑定响应数据
● 触发input事件并传递数据

35、对vue设计原则的理解

  • 分层式的JavaScript框架:与其他大型框架不同的是,Vue旨在通过自顶向下的分层架构实现灵活的应用开发。其核心组件专注于视图层的设计,并且在易用性方面表现出众——不仅操作简便还支持与第三方库或现有项目无缝对接。
  • 当将其与现代化的工具链以及各类支持类库进行集成后,则能够充分发挥其功能优势——不仅简化了开发流程还显著提升了应用性能。
  • 该框架的最大优点在于其灵活性,在应用规模较小的情况下我们甚至可以仅依赖核心特性即可实现基本功能;随着项目的不断扩展规模和技术需求的变化则会逐步引入路由管理、状态控制等高级功能模块——无论是在学习曲线还是应用复杂度上都呈现出平滑递增的趋势。
  • 由于采用高效的虚拟DOM技术和智能DI算法,在性能优化方面也取得了显著进展——特别是在数据响应式的优化方面引入了Proxy组件并优化了静态内容处理效率都会带来明显提升。

36、vue中常见的性能优化的方法

该组件配置了Lazy Router功能。

keep-alive缓存页面

复制代码
    <template>
      <div id="app">
    <keep-alive>
      <router-view/>
    </keep-alive>
      </div>
    </template>
  • 采用v-show指令进行DOM操作

  • 通过v-for指令遍历组件时,默认不会绑定到组件内自身定义的事件

  • 针对长列表场景的技术优化

  • 组件销毁时会自动解绑其所有事件监听器

  • 图片懒加载技术

  • 采用按需加载策略引入第三方插件
    例如Element UI等第三方组件库可采用按需加载策略以减少整体体积。

    • 无状态的组件标记为函数式组件
    • 子组件分隔

二、vue的生命周期

1、说一说vue的生命周期

Vue组件的完整生命周期始于组件的创建阶段

在数据观测与初始化阶段尚未启动时,在此状态下data的响应式追踪等event/watcher相关机制尚未配置完成;因此,在此阶段无法访问到与data、computed、watch及methods相关的数据与操作。

完成后

beforeMount(挂载前) :在beforeMount(挂载前)之前被调用时会触发一次相关的渲染函数的首次执行。实例已完成如下的配置:编译模板并将数据与模板整合生成HTML内容。当前阶段尚未完成将HTML内容 onto 页面的映射。

mount完成

beforeUpdate(更新前) :在响应式数据更新的过程中调用,在此阶段虽然响应式数据已经完成完成过程,并未影响到对应的原始DOM内容;然而对应的原始DOM内容尚未被重新呈现。

updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

在实例被销毁之前调用,在该操作中该实例仍保持有效状态;通过this引用的对象依然有效

实例销毁(destroyed)

其独有的生命周期分为两个阶段:激活态(Activated)和退化态(Deactivated)。当组件切换时会被缓存至内存而不进行销毁操作,并触发相应的退化态钩子函数;当渲染命中缓存后将触发激活态钩子函数。

2、Vue 子组件和父组件执行顺序

加载渲染过程:

  1. 父组件 pre-render
  2. 父组件 rendered
  3. 父组件 pre-mount
  4. 子组件 pre-render
  5. 子组件 rendered
  6. 子组件 pre-mount
  7. 子组件 pre-mounted
  8. 父组件 pre-mounted

更新过程:

  1. 父组件在初始化前触发的更新事件处理
  2. 子组件在初始化前触发的更新事件处理
  3. 子组件在更新后触发的事件处理
  4. 父组件在更新后触发的事件处理

销毁过程:

Parent component's pre-destroy hook

3、created和mounted的区别

  • 创建事件监听器通常在DOM渲染之前绑定到元素上。
  • 初始化页面完成时绑定到DOM节点上的事件监听器通常会在DOM渲染之后。
  • 即通常会初始化某些属性,在随后的视图(通常是DOM树构建完成后)进行处理。
  • 通常是初始化页面完成后再对HTML DOM节点执行一些必要的操作。

4、一般在哪个生命周期请求异步数据

在服务生命周期的不同阶段(如created事件触发的钩子脚本、在模块加载前触发的beforeMount钩子以及首次mount事件触发的mounted钩子)中执行操作时,请注意以下事项:由于这些阶段均位于服务启动流程的不同环节,在每个阶段的数据都已经成功创建完毕

建议在 created hook function 中使用异步请求,在 created hook function 中执行异步操作具有诸多优势

  • 提高访问数据的效率,并使页面加载速度加快的同时提升了用户体验。 *

  • SSR无法注册beforeMount和mounted事件钩子,在创建阶段放置钩子函数有助于确保一致性的实现 *

5、keep-alive中的生命周期有哪些

keep-alive是一种由Vue提供的内置组件,用于缓存组件的状态,在组件切换时将状态保留在内存中,从而避免重复渲染DOM.

如果为一个组件进行了封装处理,并启用了keep-alive功能,则该组件会新增两个生命周期:分别是deactivated(非活跃)状态和activated(活跃)状态。值得注意的是,在这种情况下,beforeDestroy和destroyed这两个生命周期将不再被触发,因为组件并不会真正地从系统中被删除或销毁。

当一个组件被替换时,在内存中进行存储并激活已灭活的生命周期;当该组件被重新加载时,在内存中查找该组件并执行激活其对应的钩子函数。

三、组件通信

1、props/$emit

父组件通过props向子组件传递数据,子组件通过$emit和父组件通信

● props仅允许从父组件单向传递给子组件,在此过程中建立了单向的上下行绑定关系。这种设计使得子组件的数据会随之同步更新至父组件的状态。
● props 支持接收一个或多个数据类型,并且还可以传递处理函数用于后续操作。
● props 属性命名规范规定:若在props中采用驼峰命名法,则模板中需统一采用下划线形式以避免混淆。

子组件向父组件传值

$emit,在该场景中实现了一个动态消息传输机制。具体来说,系统会注册一个自定义事件,在该事件被触发时会将指定的数据包传递给上级组件,而上级组件则通过相应的订阅机制接收并处理这些数据包

2、eventBus事件总线(emit/on)

eventBus事件总线可用于处理直接上级组件与非直接上级组件之间的通信具体操作流程如下:

(1)创建事件中心管理组件之间的通信

复制代码
    // event-bus.js
    
    import Vue from 'vue'
    export const EventBus = new Vue()

(2)发送事件
假设有两个兄弟组件firstCom和secondCom:

复制代码
    <template>
      <div>
    <first-com></first-com>
    <second-com></second-com>
      </div>
    </template>
    
    <script>
    import firstCom from './firstCom.vue'
    import secondCom from './secondCom.vue'
    export default {
      components: { firstCom, secondCom }
    }
    </script>

在firstCom组件中发送事件:

复制代码
    <template>
      <div>
    <button @click="add">加法</button>    
      </div>
    </template>
    
    <script>
    import {EventBus} from './event-bus.js' // 引入事件中心
    
    export default {
      data(){
    return{
      num:0
    }
      },
      methods:{
    add(){
      EventBus.$emit('addition', {
        num:this.num++
      })
    }
      }
    }
    </script>

3)接收事件
在secondCom组件中发送事件:

复制代码
    <template>
      <div>求和: {{count}}</div>
    </template>
    
    <script>
    import { EventBus } from './event-bus.js'
    export default {
      data() {
    return {
      count: 0
    }
      },
      mounted() {
    EventBus.$on('addition', param => {
      this.count = this.count + param.num;
    })
      }
    }
    </script>

3)依赖注入(provide/ inject)
这是Vue框架中的依赖注入机制,默认用于实现父组件与子组件之间的数据交互。需要注意的是,在这种架构下,并非只有直接父子关系的组件适用——祖孙关系同样适用,在复杂的层级结构中可以通过这种方法实现数据传输——无需逐层递送。

3)ref / $refs
这种方式也是实现父子组件之间的通信。

ref: 该属性被使用于子组件上时,其引用会指针到子组件的实例。通过获取实例的方式可访问组件的数据与方法。

总结:
父子组件间通信
● 子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。
● 通过 ref 属性给子组件设置一个名字。父组件通过 refs 组件名来获得子组件,子组件通过 parent 获得父组件,这样也可以实现通信。
● 使用 provide/inject,在父组件中通过 provide提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provide中的数据。
(2)兄弟组件间通信
● 使用 eventBus 的方法,它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。
● 通过 parent/refs 来获取到兄弟组件,也可以进行通信。
(3)任意组件之间
● 使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。

当业务流程较为复杂时(段落保持不变),若多个组件需要共同处理一组关键数据(注意:此处"很多组件"改为"多个组件"以避免重复表达),这种情况下(注意:此处"采用上面这一些方法可能不利于项目的维护"可简化为"可能导致维护难度增加"),建议优先考虑采用Vuex技术(注意:此处"这个时候可以使用 vuex"可简化为"此时推荐采用Vuex技术")。其核心理念在于将这些关键数据提取出来作为全局管理的对象(注意:此处"将它作为一个全局的变量来管理"可简化为"作为统一的管理单元"),从而使各个组件能够访问并修改这组数据(注意:此处"让各个组件就可以对这个公共数据进行读写操作"可简化为'实现对这组数据的操作')。通过这种方式(注意:此处'这样达到了解耦的目的'可简化为'从而实现了系统的解耦管理'),我们得以在不同模块之间保持独立性的同时确保系统的一致性和稳定性。

四、路由

1. Vue-Router 的懒加载如何实现

复制代码
    //路由懒加载
    
    //正常的模式
    import List from '@/components/list.vue'
    const router = new VueRouter({
      routes:[
    {path:'list',component:List}
      ]
    })
    
    //方法一:使用箭头函数+import动态加载
    const List = () => import('@/components/list.vue');
    const router = new VueRouter({
      routes: [
    { path: '/list', component: List }
      ]
    })
    
    //方案二:使用箭头函数+require动态加载
    const router = new Router({
      routes: [
       {
     path: '/list',
     component: resolve => require(['@/components/list'], resolve)
       }
      ]
    })

2、路由的hash和history模式的区别

Vue Router有两种路由模式:哈希路由和历史路由。默认采用的是哈希路由。

简介:哈希模式作为开发中的默认选择,在其URL中包含一个#符号(例如:http://www.abc.com/#/vue),其中的哈希值即为#/vue。
特点:其哈希值会包含在URL中却不会出现在HTTP请求中也不会对后端产生任何影响因此修改哈希值时无需担心页面刷新问题而低版本的IE浏览器同样适用这种模式。
原理:基于onhashchange()事件机制这一核心机制使得该模式能够高效地管理路由变化从而保证前后端的一致性与响应式加载效果使其成为构建单页面应用的理想选择。

复制代码
    window.onhashchange = function(event){
    	console.log(event.oldURL, event.newURL);
    	let hash = location.hash.slice(1);
    }

采用onhashchange()事件的好处在于,在页面发生hash值变化时, window即可捕获事件变化,并按照规定加载相关代码.此外, hash值的变化会自动记录对应的所有URL,从而使浏览器能够实现向前或向后导航.值得注意的是,虽然未发送至后端服务器进行请求,但同时确保了页面的hash值与对应URL之间的关联.

简介: 在历史模式下使用的URL中没有特殊符号#号位符(即#),这种设计采用传统的路由分发机制。当用户输入一个完整的URL地址时(如:http://abc.com/user/id),服务器会接收到该请求并解析该地址。随后根据地址路径执行相应的逻辑处理流程。
特点: 与之相比,在history模式下生成的URL路径显得更加整洁美观(相较于hash模式而言)。然而需要注意的是,在使用history模式时需要后端服务器具备一定的配置支持。如果相关服务器端参数设置不当,则会导致访问时返回404错误。
API: history模块的主要功能可划分为两大类:历史状态的历史记录管理和状态更新操作。
● 历史状态管理: 遵循HTML5 History接口规范(即pushState()和replaceState()方法)。这两个方法主要用于浏览器的历史记录栈管理(即对浏览器浏览记录进行增删操作)。但需要注意的是,在调用这些方法修改url后端并不会立即响应请求。因此若要实现url修改但又避免页面刷新的效果,则需在前端调用相关API方法。
● 历史状态切换: 包括forward()、back()、go()三个基本操作命令(对应浏览器前进、后退和跳转功能)。通过这些操作命令可以在网页浏览过程中实现丰富的导航体验。
尽管history模式删除了冗余的#号位符标识符(即#),但是这一设计也存在一定的局限性:当页面刷新时如果没有对应的路由资源支持则会触发404错误返回信息。

两种模式的对比

调用history.pushState()相较于直接修改hash具有显著优势:

  • 该方法可以让新URL与现有URL共享同源关系,并非仅限于修改#后面的固定部分;
  • 除了能够实现与现有URL完全一致的效果外(从而将记录完整地压入栈中),还可以允许状态对象注入各种类型的元数据到记录中;
  • 此外特别地还可以设置title属性供后续处理使用;
  • 相较于直接修改hash符号本身的方式,在某些情况下能够减少后端处理逻辑的复杂度;
  • 在hash模式下生成的请求链路具有更高的健壮性:即使后端未能实现全路由覆盖也能正常工作;
  • 而在history模式下由于前端必须严格匹配实际请求路径才能保证请求的有效性

哈希模式与历史模式各有优缺点,在具体情况具体分析下灵活运用。

3、如何获取页面hash的变化

复制代码
    // 监听,当路由发生变化的时候执行
    watch: {
      $route: {
    handler: function(val, oldVal){
      console.log(val);
    },
    // 深度观察监听
    deep: true
      }
    },

window.location.hash字段支持read和write操作;通过获取该字段的值来判断状态的变化情况;而无需重新加载页面时,在历史记录中新增一次访问日志。

4、route 和 router的区别

route 代表「路由信息对象」这一概念,在其属性中涵盖了 path、params、hash、query、fullPath、matched 和 name 等多个方面的路由信息参数 ● router 代表「路由实例」这一类型,在功能配置上集成了多种组件与服务机制

5. 如何定义动态路由?如何获取传过来的动态参数?

基于参数值的方式

6、Vue-router 路由钩子在生命周期的体现

有时需要通过路由来执行相应的操作。例如最常见的登录权限验证,在满足条件时,则允许其进入导航流程;否则则会阻止跳转至该页面,并引导用户至登录界面完成认证流程。
为此存在多种方法用于实现这一过程:全局性的方案、单个路由独享的方式以及组件级别的设计。

具体使用:beforeEach()判断是否登录,没登陆就跳转到登录页

复制代码
    router.beforeEach(to,from,next) => {
      let ifInfo = Vue.prototype.$common.getSession('userData');
      if(!ifInfo){
    if(to.path == '/'){
      next();
    }else{
      Message.warning("请重新登录");
      window.location.href = Vue.prototye.$loginUrl;
    }}else{
      return next()
    
      }
    }

7、Vue-router跳转和location.href有什么区别

● 使用链接地址 /url 进行导航操作相对便捷,在此过程中页面进行了刷新;
● 通过调用 history.pushState( /url ) 的方法实现静态路由跳转;
● 引入路由机制后采用 router.push( /url ) 进行路由推移操作,在此过程中采用了分段式加载策略以降低DOM操作频率;实际上这两种方法并无本质区别……主要得益于Vue Router的核心设计理念……在运行历史模式时两者表现一致。

8、 params和query的区别

实现方式:通过path变量引入query数据,并通过名称变量引入参数;所有接收的参数具有相似的结构,请分别对应的是this.route.query.name和this.route.params.name

查询部分更像AJAX中的GET方式传递参数,在这种情况下params就像POST一样传输数据。如果要更直观一些的话,则可以理解为查询部分会直接出现在URL中而不会出现在网页内容里。而后者则不会出现在URL中

注意:搜索操作不会保存查询结果中的数据 重置会清除参数中的数据

9、Vue-router 导航守卫有哪些

● 全局前置/钩子::beforeEach:beforeResolve:afterEach
● 路由独享的守卫是用于处理请求前的行为::beforeEnter
● 组件内部设置的安全检查会在以下阶段执行:
:componentBeforeRouteEntry用于组件进入时的安全检查,
:componentBeforeRouteUpdate用于组件更新时的安全检查,
:componentBeforeRouteExit用于组件离开时的安全检查。

10、 对前端路由的理解

五、Vuex的原理

Vux是一种专为Vue.js应用程序设计的状态管理方案。每个Vux应用的关键就是"存储库"(库)。它本质上就是一个用于存储应用状态的容器,在这个容器中包含了大量表示状态的数据信息(state)。

● Vuex的状态以响应式的方式被存储。当Vue组件从store中获取状态时,在store的状态发生变化时(即state发生变化时),组件会随之获得高效的更新。
● 改变store中状态的唯一方式是显式提交(commit)mutation操作。这样可以方便追踪每一个state的变化。

Vuum为Vue Components构建了一个完整的生态系统,并涵盖了API调用的关键环节。以下是该系统的核心流程的主要功能:
● Vuum将Vue组件与一系列关键环节连接在一起;
● 这些环节通常都是为了获取或修改组件内的数据;
● 在组件内部执行的动作需要通过提交机制(Commit)将操作记录到Mutations中;
● Mutations则负责对State进行更新;
● 一旦State发生更新后会立即反馈回组件展示新的状态

总结:
Vuex引入了单向数据流机制,在全局采用一个统一的State变量存储所有数据内容。每当组件试图更新State中的数据时,则必须使用Mutation提交状态更新请求,并提供订阅机制供外部插件调用以获取State更新信息。对于所有异步操作(如调用后端接口以获取更新数据)或批量同步操作(如同步获取大量状态变更),虽然它们都需要执行相应的处理流程以完成任务,但这些流程同样无法直接修改Global State的数据值;因此仍需通过Mutation来实现对State的变更操作。最终根据System State的变化动态更新视图内容以实现界面反馈功能

1、vuex中action和mutation的区别

在mutation过程中执行的一系列同步函数...用于修改state中的变量状态

所以,在比较mutation与action的区别时:
● mutation主要作用于state,并且是state理论上的唯一修改途径;而action主要用于业务代码处理以及实现异步操作。
● mutation必须在同步模式下执行;action则可以在异步模式下运行,并且不能直接修改state。
● 在视图更新的过程中,触发顺序为先action再执行mutation
● mutation的参数包括state中的数据集合;而store通过context传递了更高层次的信息

2、Vuex 和 localStorage 的区别

(1)核心区别在于其数据位于内存中;而localstorage则采用文件形式保存于本地设备上。值得注意的是,在这种存储方式下仅支持字符串类型的简单数据;对于复杂对象的数据,则需先将其转换为JSON格式(通过stringify方法),然后再解析回原始数据(通过parse方法)。相较于硬盘而言,在内存中的数据访问速度更快

(2)应用场景
● Eyx被视为一个专为Vue.js设计的状态管理系统工具。它整合了所有组件的状态,并遵循规则保证状态以可预测的方式发生变化。该库用于实现组件间的通信功能。
● localStorage主要用于跨页面数据传输。
● Eyx支持响应式的跨组件数据交换功能,而localStorage不具备这一功能

(3)永久性
在页面刷新时vuex存储的数据会被清除,而localstorage中的数据不会丢失。
注意:对于那些不变的数据,我们确实可以用localstorage作为替代方案,但特别地,当两个组件共享同一个数据源(如对象或数组)时,如果其中一个组件修改了该数据源后,希望另一个组件能够响应该变化,则localstorage就无法做到,原因在于它们之间的区别主要体现在这一方面。

3、Vuex和单纯的全局对象有什么区别

● Vuex采用了响应式的设计方案。每当Vue组件从存储中获取状态信息时,在存储状态发生变动的情况下,则会随之更新以保证数据的一致性与高效性。
● 无法直接干预存储中的状态变更。唯一的修改方式即通过显式提交(commit)mutation操作来实现状态变更的记录与追踪。这种做法不仅能够清晰记录每一次的状态变化情况,并且还能够为开发人员提供一套有效的工具辅助路径来深入理解应用运行机制。

4、为什么vuex里面的mutation不能做异步操作

● 在Vuex生态系统中,默认情况下所有状态更新的主要方式均为Mutation操作,在进行异步操作时可以通过Action组件来提交Mutation实例,并通过这种方式精确地追踪每一个状态的变化情况。这使得开发者能够更好地利用一些辅助工具来分析和理解应用的状态管理流程。
● 当每次Mutation执行完成后都会触发一个新的状态变更事件时,则可以通过DevTools捕获一个快照并存储下来。这样一来就实现了对应用行为的'时间旅行'功能(time-travel)。然而如果Mutation操作支持异步模式,则会丧失明确知道具体是在哪个时间点发生更新的能力,在调试过程中将面临诸多不便。

5、如何在组件中批量使用Vuex的getter属性

借助mapGetters工具辅助功能, 通过对象展开运算符将获取器融入计算结果对象中

复制代码
    import {mapGetters} from 'vuex'
    export default{
    computed:{
        ...mapGetters(['total','discountTotal'])
    }
    }

六、Vue3.0

1、Vue3.0有什么更新

(1)监测机制将 undergo 优化
● 3.0 将 introduce 基于代理 Proxy 的 observer 实现,并实现跨语言实时监控。
● 改进了传统定义式注入方式所存在的诸多不足:

(2)不具备监控操作的对象
● 会评估属性的状态变化情况;
● 主要负责追踪数组结构的变化情况;
● 支持以下数据结构:Map(映射表)、Set(集合)、WeakMap(弱映射表)和 WeakSet(弱集合)。

(3)模板
● Slot contexts underwent changes in version 2.x, leading to parent components triggering re-renders. Starting with version 3.0, slots are managed as functions, which restricts the impact to child components' re-renders and enhances rendering efficiency.
● As far as render functions are concerned, Vue 3.0 introduces a series of improvements to streamline the process of generating VDMO through APIs.

(4)对象式的组件声明方式
● 在Vue 2.x版本中,默认组件是通过声明方式接收一系列 option参数,并与 TypeScript语言结合时需要借助装饰器等手段进行操作,在功能上确实能够实现但操作起来较为繁琐。
● 在3.0版本中对组件的声明方式进行调整,在遵循原有功能的基础上将声明方式转变为类式的写法,在与 TypeScript语言结合时变得更加简便易行。

(5)其他方面的改进
● 提供自定义渲染器的支持, 以便我们可以通过自定义的渲染逻辑进行扩展, 而非直接对源码进行 fork 的方式进行修改。
● 支持 Fragment(具有多个根节点)和 Protal(在 DOM 的其他部分展示组件)组件, 对一些特定场景进行了相应的处理。
● 基于树 shake 优化技术, 带来了更多内置功能的支持。

2、defineProperty和proxy的区别

在Vue实例初始化过程中,系统会遍历data中的每一个属性,并通过Object.defineProperty将这些属性转换为getter和setter接口.每当data中的追踪信息发生变更时,默认情况下这些setter方法会自动被触发.

在 ES5 中,Object.defineProperty 作为一个不能被 shim 化的特性,在其设计时就未考虑到这些较旧版本浏览器的需求。因此,在这些情况下,默认情况下 Vue 并不支持 IE8 及更低版本的浏览器。

但是这样做有以下问题:

  1. 当修改对象属性时, Vue 检测不到这些变化. 这是因为我们在初始化阶段未做响应式处理, 在这种情况下,默认会使用$set方法来为对象定义初始属性.
  2. 我们无法实时跟踪数组索引与长度的变化情况.

Vue 3 通过 Proxy 监控数据变化情况。Proxy 是 ES6 提供的一项功能, 其主要作用在于定义一系列针对基本操作的自定义行为(例如属性访问、赋值、枚举以及函数调用等)。相较于 Object.defineProperty(), 它具有以下特点:
3. Proxy 不仅能直接代理整个对象本身, 而非其各个具体属性, 因此只需一层代理便能全面监听到同级结构下所有属性的变化情况, 包括新增、删除以及修改现有属性的变化情况。
4. Proxy 还能追踪数组元素的具体变化情况

3、vue3.0为什么要用proxy??

在Vue2中, Object.defineProperty会对原始数据产生影响,而Proxy则是一种用于创建对象的虚拟表示,它配备有set、get和deleteProperty等处理器,这些处理器会在对原始对象上的属性进行访问或修改时触发响应。其特点包括:无需使用Vue.set或Vue.delete来触发响应式操作;实现了全面的数组变化检测,从而避免了Vue2中可能出现的一些无效边界情况;支持Map、Set、WeakMap以及WeakSet等数据结构。

Proxy 实现的响应机制与 Vue2 的实现手法类似。Proxy 实现中采用 get 操作用于收集相关依赖项。Set 和 delete 等操作则会在相应场景下触发相关的依赖收集。对于数据结构类型而言,在其方法中会进行额外处理:在原方法执行后自动完成相关依赖的收集或触发逻辑。

七、虚拟dom

1、对虚拟dom的理解

本质上讲:Virtual DOM是一种JS对象形式,在其内部以对象的方式反映Web标准中的DOM节点体系结构。在实际应用中,默认情况下将页面状态抽象为一种JS对象形式,并配合不同的渲染工具使用(如基于DOM树或基于事件驱动的方式),从而使跨平台渲染成为可能。为了实现对页面上的DOM修改结果进行一次性的更新(即所谓的事务处理机制),通过这种机制就可以减少因多次DOM修改导致的重绘与排版操作次数,在提升用户体验的同时也减少了计算资源的消耗。

虚拟DOM是对DOM的一种抽象表示,在这一抽象层次上设计了一个更为简洁高效的对DOM的行为模拟机制。这种设计初衷是为了使得跨平台能力更强,并非所有场景都依赖传统的DOM实现;例如Node.js完全不依赖DOM,在这种情况下我们就可以借助虚拟DOM来实现SSR功能。具体来说,在页面渲染前Vue将代码转换为虚拟DOM对象,并以对象形式表示真实的DOM结构完成后续操作;在每次数据发生变化时会生成新的缓存副本,并通过预先封装好的基于diff算法进行比较;最终会根据比较结果决定哪些部分需要进行修改更新。

另外现代前端框架的基本要求是无需开发者进行DOM操作。一方面由于手动进行DOM操作难以确保程序性能,在团队协作中若未严格执行代码审查标准可能导致编写低效的代码;另一方面通过避免冗余的DOM操作能够显著提升开发效率

2、虚拟dom的解析过程

虚拟dom的解析过程:

  • 首先分析将要插入文档中的 DOM 树结构,并将其表示为一个包含TagName、props和Children属性的对象树。
  • 然后将此对象树保存下来。
  • 最后应用这些差异到实际存在的DOM树中以更新视图。

3、为什么要用虚拟DOM

(1)保证性能下限,在不进行手动优化的情况下,提供过得去的性能

分析页面渲染的过程:解析HTML代码 -> 构建DOM结构 -> 构建CSSOM对象 -> 处理布局 -> 绘制图像 -> 编译器处理逻辑

比较修改DOM时真实DOM操作与Virtual DOM的过程及其性能消耗情况

为实现更新DOM所需的准备工作需要投入更多的时间;即从JavaScript层面上来看,在进行大量DOM操作的同时成本极为低廉。尤雨溪曾分享观点称∶ 在框架提供的保障下,在无需手动进行优化的情况下仍能获得较为理想的表现

Virtual DOM是一个核心概念,在现代前端开发中扮演着重要角色。它基于JavaScript的对象模型设计,在实现上具有高度的灵活性和可扩展性。作为广泛使用的前端框架技术之一,Virtual DOM能够非常方便地在不同平台上运行,并且支持多种开发场景和技术架构选择。例如,在服务端渲染、uniapp等应用中都能见到它的身影。

4、虚拟dom真的比真实的dom性能好吗?

● 首次加载大量DOM内容时,在引入了额外的虚拟DOM计算后相较于直接使用innerHTML插入方法较慢。
● 该方法能够确保在真实DOM操作中进行针对性优化时依然更快。

5、DIFF算法的原理

在新老虚拟节点对比的时候:

  • 第一步是对比当前的树形结构中的父结点是否与目标树形结构中的父结点完全一致
    • 如果不一致,则删除当前父结点下的所有子树数据后重新生成父结点并完成连接操作
    • 如果一致,则进入下一步骤:对patchVnode的操作
      • 首先区分哪一方拥有孩子(child),哪一方没有孩子(childless children)
      • 如果新的child列表中没有孩子(即new_children是空列表),则将旧的child列表中的所有数据移除
    • 比较双方是否有孩子的共同祖先结构存在的情况下:
      • 进行updateChildren操作,并根据diff核心逻辑对新旧两代孩子的数据进行统一整理与优化
    • 当双方都存在多个层级的孩子时:
      • 找到双方都存在的直接后代结点(即相同的兄弟姐妹)
      • 对这些共同的孩子项执行递归比较操作

在diff操作中,我们仅对同层次的子节点进行对比分析,并避免跨层节点的对比过程。这使得算法的时间复杂度从O(n³)降到O(n)。具体而言,在新旧版本的对象结构均包含多个子节点时才需要用到核心的Diff算法来进行同层次范围内的比较工作。

6、vue中key的作用

vue中key的值的作用可以分为两种情况来讨论:

在VUE框架中的v-if指令中使用key属性时需要注意以下几点:VUE框架在渲染元素时通常会采用优化策略,在这种情况下当切换状态时可以复用已存在的相同类型元素而不必重新创建。如果输入域(Input)元素相同的话,在切换前后状态下显示的内容可能会共享相同的UI资源从而导致用户在切换前后输入信息不会被清除。因此可以通过设置唯一的key属性值来标识每个输入域(Input)元素这表明key属性值的作用是确保每个输入域都是独立且互不干扰的。

在 v-for 中实现键(key)的使用,在这种情况下,默认会采用本地复用策略(local reuse)。当数据项顺序发生变化时,请注意这一点:由于Vue框架不会动地复制DOM元素以匹配新数据项的位置(new data item positions),而是直接复用当前处的所有DOM元素。为了高效地实现这种本地复制机制(local reuse mechanism),我们会在每个列表项中引入一个键(key)值是为了帮助Vue追踪每个DOM元素的身份。此时键(key)的作用是帮助Vue高效地更新并重构虚拟DOM结构

key 作为 Vue 节点存储(vnode)的唯一标识符,在 diff 操作中能够实现更加精准和高效的执行。
具体而言:
准确性得以提升的原因在于相同节点(sameNode)函数中的 a.key 与 b.key 对比时避免了重复计算。
而快速性则得益于通过键值唯一性构建 map 对象来直接定位所需节点。

7、为什么不建议用index作为key?

以index为键,在其位置上与未定义的情况相比差别不大;无论数组元素的顺序如何变化(即0,1,2…这样的排列),这使得Vue重复利用旧子节点而无需处理元素位置的变化问题,并因此产生不必要的额外工作量。

全部评论 (0)

还没有任何评论哟~