前端知识点之Vue 相关

53 阅读13分钟

Vue 相关

1.Vue响应式原理和双向数据绑定

Vue的响应式就是数据驱动视图,非常的优雅。在Vue2中是通过defineProperty实现的,在Vue3中通过proxy(处理数组和增加响应式属性处理上更加方便)代理实现的,核心思路相同:通过getter函数进行依赖的收集并存到一个集合中,通过setter函数触发依赖的视图更新。

数据的双向绑定通常是指我们常说的v-model的实现,v-model即是input事件和value的语法糖,在vue中数据不是双向绑定的,但是它提供了数据双向绑定的手段。具体实现:1.对input标签绑定value属性(所绑定的数据改变Input显示的数据变化)。2.绑定input标签的input事件,当事件触发时执行modelValue.value = e.target.value(Input中数据发生变化更新modelValue的值)这样就实现

++个简单的双向绑定。

2.nextTick实现原理

nextTick作用:获取更新后的Dom节点。

实现:返回传入函数的Promise对象,将其变为一个微任务。等微任务执行完毕页面渲染后对其进行调用。

使用nextTick其实主要还是为了性能优化问题,如果一个数据在短时间内改变了100次那么就会进行100的页面重新渲染,这样极大的消耗了性能。如果把其变成一个微任务,这样在每次微任务执行完毕后只针对最后一个更新的数据进行渲染,这样就极大的优化了性能,因为页面的渲染是及其消耗性能的,包括html,css的解析,渲染树的构建,回流和重绘,分层和合成等步骤。

3.Vue2中定义的data为什么是一个函数

当多次进行组件复用时,因为函数具有私有作用域,每个复用的组件中的data是相互独立的。而如果不是一个函数,那么所复用的组件共享data,一个组件对data进行改变,其他组件中的data也会发生改变。

4.vue2与vue3的区别

  • vue2中使用选项式api,而在vue3中使用组合式api。
  • vue2中必须有个根元素div,而vue3中可以没有。
  • 生命周期不同,vue2中的生命周期为beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestory,destoryed。vue3中的生命周期为setup,onBeforeMounted,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmount。
  • diff算法不同:在vue2中diff算法会遍历所有的虚拟节点,进行节点之间的比较。而vue3中给每个节点新增了一个boolean形的标志,只比较标志变化的节点,优化了性能。
  • 响应式的实现方式不同,在vue2中使用的是defineProperty,在vue3中使用的是proxy代理。
  • v-for与v-if的优先级不同,在vue2中v-for的优先级高于v-if的优先级,在vue3中,v-if的优先级则高于v-for的优先级。
  • vue3提供了更好的ts支持。
  • vue3中新增了teleport组件,可以将dom元素渲染到dom树的其他任意位置。

5.v-if和v-show的区别

v-if直接创建或者删除标签,具有比较大的切换消耗。v-show是通过元素的display属性控制元素的显示和隐藏,具有比较大的初始渲染消耗。

6.v-for与v-if为什么不能一起使用?

在vue2中,v-for的优先级高于v-if,可以一起使用,但是一起使用时在每次的列表渲染的时候都会执行一次v-if的判断,影响性能。而在vue3中,v-if的优先级高于v-for,一起使用时会报错,如果真的要一起使用,可以在外面再包裹一层标签写v-for。

7.vue2和vue3响应式原理

vue2

vue2中的响应式原理是通过Object.defineProperty实现的,即在初始化一个对象时,遍历对象的所有属性,并在其getter方法中进行依赖的收集,在setter方法触发时进行依赖的更新。但是这样做有一个问题,在对对象的属性进行添加和删除时并不会触发视图的响应式更新。对于数组而言,则放弃了Object.defineProperty,采用对数组7个函数重写的方式实现数组的响应式,这7个函数包括pop,push,shift,unshift,splice,reverse,sort。这样做的原因并不是Object.defineProperty无法监听数组的变化,而是因为即使采用Object.defineProperty的方式去监听数组的变化,当对数组使用7个函数操作时也不能触发视图的响应式更新,依然要重写以上7个数组操作的方法。

vu3

vue3中实现响应式的方式是通过ES6的proxy代理实现的,其中存在一个富作用函数,当对对象进行读操作时进行依赖的收集,对对象进行写操作时进行依赖的更新。而依赖则存储在WeakMap中。

vue2与vue3相比存在的缺陷
  • 在初始化时,需要遍历对象的所有属性以及嵌套的属性,性能消耗较大。
  • 在进行依赖更新时,需要维护大量的dep和watcher实例,内存占用较大。
  • 在对对象属性进行增加删除操作时,无法触发视图的响应式更新,只能使用特定的set/delete方法。
  • 在对数组按照索引修改值时或者通过.length属性置空数组时,无法保持其响应式。
  • 不支持Map,Set等数据结构。

8.vue3中的计算属性

vue3中的计算属性也是一个响应式对象,也是对其封装了一层带value的对象,通过类的属性访问器实现依赖的收集和更新。但是它跟普通函数的区别是计算属性具有缓存,当计算属性所依赖的数据发生变化后,会重新计算计算属性的值,否则将使用缓存中的值。计算属性缓存实现的原理是使用一个dirty的boolean值去判断计算属性所依赖的数据是否发生变化。

9.vue2和vue3中diff算法的区别

vue2 diff算法

vue2中的diff算法采用双端比较的方式,即头和头比,尾和尾比,头和尾比,尾和头比。在比较的过程中进行节点的移动,删除和增加。如果头尾指针所对应的结点key相同,则进行移动。如果新的队列中没有对应的key则进行删除,如果增加了新的key,则进行增加。

vue3 diff算法

主要分为五个步骤:

  1. 前序对比:从头开始对比,直到找到不相等的结点。
  2. 后序对比:从尾部开始对比,直到找到不相等的结点。
  3. 之后对于新增的结点进行添加。
  4. 对于需要删除的结点进行删除。
  5. 对于乱序的结点,采用求其最长递增子序列的方式,如果该结点在其最长递增子序列中,则进行移动。

vue3 diff比vue2 diff算法快的原因:

  • vue3的diff算法采用求最长递增子序列的方式,提升了性能。
  • vue3中采用静态标记,对不会改变的元素进行标记,在比较时直接跳过。
  • vue3中采用缓存的方式,将比较的新旧虚拟Dom数组缓存起来,再进行比较时只比较虚拟Dom改变的部分。

10.vue3中父子组件的生命周期的执行顺序

vue3的生命周期:setup,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted

初始加载时:

Father setup --> Father onBeforeMount --> Son setup --> Son onBeforeMount --> Son onMounted --> Father onMounted

数据更新时:

Father onBeforeUpdate --> Son onBeforeUpdate --> Son onUpdated --> Father onUpdated

组件卸载时:

Father onBeforeUnmount --> Son onBeforeUnmount --> Son onUnmounted --> Father onUnmounted

11.nextTick的原理

nextTick的作用就是在Dom更新之后获取最新的Dom信息,因在在Vue中数据的更新是同步的,但是Dom的更新是异步的(异步更新队列),所以数据的更新不一定对应着Dom的更新,使用这种方式主要是对其进行优化,比如数据短时间内改变了100次,那Dom总不能也更新100次,dom的更新是很消耗性能的。

nextTick在Vue3中实现原理很简单就是使用Promise对其进行了封装,将其变成一个微任务,在Dom更新完成之后调用回调函数。在Vue2中进行了一系列的判断,主要就是适配IE浏览器,但在Vue3中已经放弃Ie浏览器了。

nextTick有两种使用方法:第一种是传入回调函数的方式。第二种是使用async和await,使用这种方式时,await nextTick()之后的代码全部变为异步代码,在Dom更新完成后才会执行。

12.vue中的组件通信方式

父子组件通信
  1. props和emit:props是父传子,emit是子传父。
  2. v-model: defineModel,父子组件之间的双向数据绑定。
  3. attrs: getCurrentInstance函数可以获取当前组件的一个实例,里边的attrs属性表示所有在子组件标签上定义的属性(父传子)。
  4. exposed: getCurrentInstance函数可以获取当前组件的一个实例,可以通过设置里面的exposed属性将子组件的属性和方法传递给父组件,然后父组件可以通过ref获取。
兄弟组件通信

可以使用props和emit通过父组件进行中转传递参数(需要写的代码逻辑太多了)。

父孙组件通信

provide和inject:依赖注入

全局通信
  1. Pinia/vuex。
  2. 事件总线/mitt。
  3. sessioneStorage/localStorage。
  4. 把属性和方法放到windows对象里边。

13.虚拟Dom

虚拟Dom其实就是对Dom的一个抽象,就是一个js的对象。在Vue中引入虚拟Dom的目的就是尽量减少Dom的回流或者重绘,因为Dom的渲染是及其消耗性能的。在Vue中,利用了事件循环的机制,在每个事件循环结束后进行Dom的重绘。此时,需要利用diff算法计算新旧虚拟Dom的差异,更新Dom树中改变的部分。

14.vue-router的hash模式和history模式

hash模式实现原理:hash模式利用了location.hash以及window.onhashchange事件,当hash值改变的时候触发onhashchage事件,并渲染相应的组件(有个#号)。当浏览器刷新时hash值不会被一起发送到后端,所以这种方式后端不需要进行适配。

history模式原理:history模式使用了html5新增的pushState和replaceStace两个api,这两个api可以在不触发页面刷新的情况下进行url地址的跳转,并且不会向后端发起请求,但是onpopState事件无法监听pushState和replaceStace。但好在可以拦截pushState和replaceStace的赋值去获取页面url的变化。使用这种方式当页面进行刷新时,会重新像后端发送请求,如果后端没有进行适配,那么就会返回404错误。后端适配的方法也很简单,原理就是获取前端路由的请求地址,然后当访问时返回index.html页面(另外也有node中间件去适配history模式)。

15.SPA(单页面应用)原理

单页面应用就是在访问页面时加载最基本的html,js,css等资源,只有一个index.html文件,通过路由去改变页面。像vue,react都是单页面应用。路由的两个模式(history/hash)。

16.vuex/pinia的持久化

可以将数据保存在localStorage,sessionStorage或者IndexedDB中。也可以用相关的插件进行数据的持久化存储,其实也是把数据存储到localStorage(默认)中。

17.watch和computed的区别

watch用于监听数据的变化,当数据发生变化时触发相应的回调函数,更适用于数据发生变化需要重新发送异步请求的场景。

computed是计算属性,是被设计用来处理比较复杂的数据操作。并且它是有缓存的,当数据发生变化时才会重新计算计算属性的值。

18.style标签中的scoped属性

如果在style标签中添加了scoped属性,那么这个vue组件的样式是相互独立的,不影响其他组件。其实就是在组件的每个标签中添加了一个 data-v-hash 值的属性选择器。如果遇见其他组件或者第三方库的元素,会在其根元素下添加 data-v-hash 值的属性选择器。但是如果需要更改第三方库中的样式,最好使用:deep,否则它会把 data-v-hash 属性选择器添加到所有选择器的最后面,这样是找不到的。而使用:deep时,会把这个属性选择器添加到所有选择器的最前面。

19.watch和watchEffect的区别

两者都是用来监听数据变化的,区别就是watch是惰性的,每当数据发生变化的时候才会执行相应的回调函数,而watchEffect会监听所有其回调函数中的数据,并且在初始时会自动执行一次,如果回调函数中使用了对象的某些属性,那么它只会监听这些属性的变化,不会监听这个对象所有属性的变化。使用watch的immediate也可以达到类似的效果,并且watch默认是深度监听的。

20.vue-router动态路由的持久化

在vue-router中,如果使用addRoute动态的添加路由,那么会出现一个问题就是当页面刷新的时候,动态添加的路由会失效,因此可以监听页面刷新的beforeunload事件将动态添加的路由存储到sessionStorage或者localStorage中,可以使用pinia存储所有动态添加的路由。

21.vue的编译原理

对于.vue文件,vue是这么编译的:在vite中是通过@vitejs/plugin-vue处理的,在webpack中是通过vue-loader处理的,主要就是将.vue文件分为三部分,对于style标签定义的在编译时会处理比如sass,less的代码,将代码编译成一个单独的css文件再进行动态引入,如果这个style标签上存在scoped属性,那么在编译的过程中会动态的为每个元素加上一个data-v-hash值的属性选择器,这么做就是为了使各个.vue文件的样式互不影响。对于script标签中的代码,会把它编译为浏览器可以识别的js代码。而对于template中的代码,在编译的过程中首先会生成抽象语法树,之后对抽象语法树进行一定的操作,包括添加静态标记(主要是diff算法使用,如果该标签含有静态标记,则在diff算法比较时直接跳过)等,最后生成render函数用于将代码渲染到页面上。

其实最终就是通过编译编译成浏览器可以识别的js和css代码。