Vue2和Vue3面试题整理(一)

451 阅读18分钟

1.谈谈你对vue的理解

Vue是一套用于构建用户界面的渐进式框架,核心是数据驱动和组件化开发。

(1)声明式框架

(2)MVVM模式

(3)采用虚拟DOM

(4)区分编译时(打包)和运行时(浏览器)

(5)组件化开发

  • 高内聚、低耦合、单向数据流。
  • 更新范围,只重新渲染变化的组件。

2.谈谈对SPA的理解

  • 只有一个页面,多个页面组件
  • 页面切换速度快,用户体验较好,服务端压力小
  • 不利于SEO搜索引擎优化
  • 首屏渲染时间会相对较长

3.声明式编程和命令式编程

  • 声明式编程:

    声明式更关注的是结果。就以vue为例,在页面上要展示一个变量,那么就要先定义这个变量,后边通过改变这个变量,来触发页面的刷新。我们只需要结果,vue内部去处理过程,这就是声明式编程

  • 命令式编程:

    命令式更关注的是过程。例如jquery,想通过点击事件改变页面中的一个元素,那么首先就要获取这个元素,在给元素添加点击事件,执行事件,进而改变页面元素,这种按步骤一步一步操作的,就是命令式编程。

  • 命令式编程更为严谨,但是步骤多,开发效率慢。声明式编程开发效率高,可读性相对较差些。

4.谈谈虚拟DOM

  • 虚拟DOM把整个DOM树抽象成了一个js对象,这样开发者就可以直接操作对象,而不需要频繁的操作DOM,提升性能。
  • 在vue运行时,它将虚拟DOM和真实的DOM树同步,当数据发生变化时,Vue会重新计算虚拟的DOM树,通过diff算法比对差异,查找和标记发生变化的节点,并将他们更新到实际DOM数上。
  • 虚拟DOM不依赖于真实平台环境,从而可以实现跨平台。

补充:VDOM是如何做diff的

  • 组件挂载结束后,会记录第一次生成的VDOM -- oldVnode
  • 当响应式数据发生变化时,将会引起组件重新渲染,此时就会生成新的VDOM --newVnode
  • 使用oldVnode和newVnode做diff操作,将更改的部分应到真实DOM上,从而转换为操作最少量的dom,高效更新视图

5.既然Vue通过数据劫持可以精准探测数据的变化,为什么还需要虚拟DOM进行diff检测差异?

Vue内部设计原因导致的,vue设计的是每个组件一个watcher(渲染watcher),没有采用一个属性对于一个watcher,这样会导致大量watcher的产生,造成内存浪费,如果颗粒度过低也会无法精准检测变化。所以采用diff算法+组件级watcher。

6.对响应式数据的理解

vue2中的对象使用Object.defineProperty对数据进行劫持,给每个属性进行重写,添加getter和setter方法。①当新增属性和删除属性时,无法监控变化,需要通过setset和delete来实现。多层对象则会递归来实现劫持,数组的话会单独处理,通过重写数组的方法来实现。②由于遍历,性能会有些差。③对于map和set的数据也不支持。

vue2的不足之处:① ② ③

vue3则使用proxy来实现数据劫持。

7.Vue如何检测数组的变化

(1)数组考虑到性能原因,没有采用Object.defineProperty来对数组的每一项进行拦截,而是选择重写数组(push、shift、pop、unshift、splice、sort、reverse)方法

(2)数组中如何是对象数据类型也会进行递归劫持。

(3)缺点:数组的索引和长度变化时无法监控到的

8.v-show && v-if

v-show: dom节点渲染,display为none

v-if:dom节点不渲染

9.computed && watch

(1)computed

  • 仅当用户取值时才会执行对于的方法

  • 计算属性不支持异步逻辑

  • 具有缓存作用,依赖的值不变,不会再次执行

    原理描述:

    • 每一个计算属性内部都维护了一个dirty:true的属性。当dirty为true的时候,就会执行用户的方法,拿到值后把值川村起来this.value,dirty改为false。再次取值,dirty为false,直接返回值。
    • 更改数据的时候,dirty会被修改为true,并且会触发更新,页面重新渲染,重新计算计算属性的值

(2)watch

  • 监控数据的变化,当值发生变化时候会触发回调

  • 监控基本类型的,直接使用。

  • 监控对象,可以指定某一个属性,也可以配置deep:true属性监控整个对象

    注意点:异步要注意竞态问题,vue3提供了第三个入参onCleanup函数,让用户更加方便的解决清理问题。

10.ref和reactive区别

(1)ref

既可以处理基本类型,也可以处理引用类型。

(2)reactive

处理引用类型的数据。

11.watch 和 watchEffect的区别

(1)watch

接收两个入参,第一个为侦听的对象,第二个为回调函数。

侦听一个或多个响应式数据源,在数据源变化的时候调用一个回调函数

(2)watchEffect

接收一个入参,立即执行,后续的回调函数也是自身。

立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时,重新执行该函数

// 1.getter函数 2.cb
watch(
    ()=>state.name,  // 数据变化之后,会执行回调函数
    (newValue, oldValue) => {}
)
​
// getter函数
watchEffect(()=>{
    // 数据变化后,会再次执行这个getter函数
    app.innerHTML = state.name
})

12.如何将template转换成render函数

vue中含有模板编译的功能,它的主要作用就是将用户编写的remplate编译为js可以执行的render函数。

(1)将template目标转换成ast语法树 - parseHTML

(2)对静态语法做静态标记 - markup diff算法来优化的 静态节点 跳过diff操作

(3)重新生成代码 - codeGen

13. new Vue这个过程中究竟做了什么?

  • 在new Vue的时候,会做一些初始化操作
  • 内部会初始化组件绑定的事件,初始化组件的父子关系:parentparent children $root
  • 初始化响应式数据data、computed、props、watch、methods。同时也初始化了provider、inject方法。内部会对数据进行劫持,对象采用defineProperty,数组采用方法重写
  • 再看一些用户是否传入了el属性和template或者render。render的优先级更高,如果用户写的是template,会做模板编译,将template转化成render函数
  • 内部挂载的时候会产生一个watcher,会调用render函数来触发依赖收集。内部还会给所有的响应式数据增加dep属性,让属性记录当前的watcher(用户后续修改的时候,可以触发watcher重新渲染)
  • vue更新的时候采用虚拟DOM的方式,进行diff算法更新

14.Vue.observable有了解过吗?说说看

原理就是创建一个响应式对象,可以实现让组件共享数据,不管是平级 跨级或者父子组件,类似于pinia或者vuex类的状态管理工具。但是有缺陷,数据修改比较随意,开发中用的不多,vue3也不会用这个api。

15.v-if和v-for哪个优先级更高

v-for和v-if应避免在同一个标签里使用,如果遇到这样的场景,可以考虑用计算属性来优化。用计算属性搭配v-if条件,来计算出需要v-for的数据。

在Vue2中,先解析v-for,后解析v-if。会导致循环后再判断,浪费性能。

在Vue3中,v-if的优先级高于v-for

16.生命周期有哪些?

1.vue2的生命周期

主要的生命周期有:创建前后、挂载前后、更新前后、销毁前后

  • beforeCreate:初始化父子关系及事件,数据观测(data observer)之前被调用,一般在编写插件的时候会用到这个钩子函数

  • created:实例已经创建完成后调用,在这一步,实例已经完成了如下配置:数据观测、属性和方法等。但是这时候还没有$el,可以用于发送一些异步请求等

  • beforeMount:在挂载开始之前被调用,相关的render函数首次被调用

  • mounted:el被新建的vm.$el替换,并挂载到实例上去之后调用该钩子函数,可以用于获取dom元素

  • beforeUpdate:数据更新时候被调用,发生在虚拟DOM重新渲染和打补丁之前,此时修改数据不会再次触发更新方法

  • updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子函数

  • beforeDestory:实例销毁之前被调用,此时,实例依然可用

  • destoryed:实例销毁后调用,调用后,Vue实例的所有东西都会解绑,事件监听会被移除,所有的子实例也会被销毁。

  • keep-alive(activated和deactivated):组件会被缓存下来

    • activated:进入组件就会被调用,可做一些数据更新,初始化数据等操作
    • deactivated:离开组件的时候调用,可做一些清除操作

2.Vue2和Vue3的生命周期对比

  • Vue2

    • beforeCreate / created / beforeMount / mounted / beforeUpdate / updated / beforeDestory / destoryed / activated / deactivated / errorCapture(捕获一个来自子孙组件的错误时被调用)
  • Vue3

    • setup / onBeforeMount / onMounted / onBeforeUpdate / onUpdated / onBeforeUmount / onMounted / onActivated / onDeactivated / onErrorCaptured

Vue3的生命周期都是on开头,新增了组合式API setup, 没了create俩钩子。destory换成了unmounted

17.父组件和子组件的生命周期执行顺序

渲染时候:父组件beforeCreate => 父组件created => 父组件 beforeMount => 子组件beforeCreate - created - beforeMount - mounted => 父组件mounted

更新时候:父组件beforeUpdate => 子组件beforeUpdate - updated => 父组件updated

卸载时候:父组件beforeDestory => 子组件beforeDestory - destoryed => 父组件destoryed

父组件等待子组件完成后,才会执行收尾操作。

18.Vue中diff算法原理

(1)diff概念

vue基于虚拟DOM来做更新。diff的核心就是比较两个虚拟节点的差异,Vue的diff算法是平级比较,不会考虑跨级比较的情况。内部采用深度递归的方式 + 双指针的方式进行比较。

(2)diff比较流程

  1. 先比较是否有相同节点:key 标签

  2. 相同节点比较属性,并复用老节点(将老的虚拟dom复用给新的虚拟节点dom)

  3. 比较子节点,考虑老节点和新节点的子节点的情况。

    1. 老节点有子节点,新节点没有子节点:直接删除
    2. 老节点没有子节点,新节点有子节点:直接插入
    3. 老节点有子节点,新节点有子节点:更新子节点updateChildren
  4. 优化比较:头头、尾尾、头尾、尾头

  5. 对比查找进行复用

vue3中采用最长递增子序列来实现diff优化。

  • 最长递增子序列:在一个给定的数值序列中,找到一个子序列,使得这个子序列元素的数值依次递增,并且这个子序列的长度尽可能的大。最长递增子序列的元素在原序列中不一定是连续的。

19.Vue中key的作用和原理

(1)key的概念

  • key的特殊attribute主要用在vue的虚拟DOM算法,在新旧nodes比对时,辨识VNodes。如果不使用key,Vue会使用一种最大限度减少动态元素,并且尽可能的尝试就地修改/复用相同类型元素的算法
  • 当Vue正在更新使用v-for渲染的元素列表时,他默认使用的是“就地更新”的策略。如果数据项的顺序被修改,Vue不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保他们在每个索引位置正确渲染

(2)key的作用

  • Vue在patch过程中,通过key可以判断两个虚拟节点是否是相同节点(可以复用老节点)
  • 没有key会导致更新的时候出现问题
  • 尽量不要使用索引来作为key

20.Vue的use是干什么的?

安装Vue.js插件,如果插件是一个对象,则需要提供install方法。如果是一个函数,它会被作为install方法。install方法调用时,会将Vue作为参数传入,这样插件中就不需要依赖Vue了。

插件的功能:

  • 添加全局指令、全局过滤器、全局组件
  • 通过全局混入来添加一些组件选项
  • 添加Vue实例方法,通过把他们添加到Vue.prototype上实现
let plugin1 = {
    install(_vue, ...args){
        console.log(_vue, args)
    }
}
let plugin2 = (_vue, ...args) => {
    console.log(_vue, args)
}
Vue.use(plugin1)
Vue.use(plugin2)

21.谈谈你对nextTick的理解

Vue中视图更新是异步的,使用nextTick方法可以保证用户定义的逻辑在更新之后执行的,可以拿到渲染后的dom元素

多次调用nextTick的话,会被合并执行。

22.Vue的data为什么要是一个函数

  • 根实例对象data可以使对象,也可以是函数,不会产生数据污染的情况
  • 组件实例对象data必须时函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。所以要通过工厂函数返回全薪的data作为组件的数据源

23.函数组件的优势在哪儿

函数式组件特点:

  • stateless:无状态,不需要管理任何状态

  • instanceless:无实例,也就没有this

  • 没有生命周期函数,不需要初始化

  • 只接受一些props的函数

  • 渲染开销很低,渲染相对较快

  • 函数式组件没有this,参数就靠context来传递

    • context包括:children、props、parent、data、linstener、injections、slots
// 定义一个函数式组件
export default{
    name: 'functional-button',
    functional: true,
    render(createElement, context){
        return createElement('button', context.children)
    }
}
// app中引入函数式组件
<template>
    <FunctionalButton>
        click me
    </FunctionalButton>
</template>

24.Vue过滤器的应用场景有哪些?

过滤器不会改变原始数据,它只是对数据进行加工处理后,返回过滤后的数据,最终将过滤后的数据展示在页面上。我们也可以将它理解成是纯函数。

(1)注册过滤器

全局注册:

Vue.filter(name, callback)

局部注册:

new Vue({ filters: { ... } })

(2)使用过滤器

{{ xxx | 过滤器 }} 或 :属性="xxx | 过滤器"

// 过滤器也可以串联:

{{ xxx | 过滤器1 | 过滤器2 }} 或 :属性="xxx | 过滤器 | 过滤器2"

(3)传参

// message是作为第一个参数被传递的,参数1 参数2 分别作为第2 3 参数传递

{{ message | 过滤器('参数1', '参数2') }}

全局和局部命名重复时,以局部为准。

Vue3放弃了过滤器。

常用场景:单位转换、时间格式化、文本格式化等

25.v-once的使用场景有哪些

v-once是Vue的内置指令,只渲染组件和元素一次,随后的重新渲染,元素、组件以及其所有的子节点都将被视为静态内容并跳过。这可以用于优化更新性能。

Vue3.2之后,增加了v-memo指令,通过依赖列表的方式控制页面的渲染。

// 只有在依赖项value1 value2发生变化的时候,内部元素才会重新渲染
<div v-memo="[value1, value2]">
    {{ message }}
</div>

26.Vue.mixin的使用场景和原理

mixin可以用来扩展组件,将公共逻辑抽离出来,在需要该逻辑的组件里进行混入,采用策略模式针对不通的属性进行合并。如果混入的数据和组件本身的数据产生冲突,以组件自身的为准。

分为局部混入和全局混入。局部混入一般是复用逻辑,全局混入一般是编写插件。

Vue.mixin(mixin) // 全局混入

new Vue({ mixins: [mixins] }) // 局部混入

合并策略(核心就是对象的合并处理):

  1. props、methods、inject、computed同名时会被替换
  2. data会被合并
  3. 生命周期和watch会被合并成队列
  4. components(组件)、directives(指令)、filters(过滤器)会在原型链上叠加

27.Vue中slot是如何实现的?什么时候使用它?

插槽是利用slot标签进行占位,在使用组件的时候,组件标签内容会分发到对于的slot中。

通过插槽可以更好的让用户对组件进行扩展和定制化,可以通过name来定义具名插槽,指定渲染的位置。

可以设置后备内容,为插槽指定默认值:我是插槽的默认值

分为:默认插槽、具名插槽,另外还有作用域插槽:让插槽能够访问子组件中才有的数据

28.你对双向绑定的理解以及它实现的原理

Vue中双向绑定靠的是v-model指令,可以将一个动态值绑定到视图上,修改视图上的值,数据对于的值也会同步改变。

组件上的v-model就是value+input事件的语法糖(vue3中为modalValue和update:modalValue的语法糖)

表单上v-model:文本为value+input;复选框为checked+change

问题:如果有多个属性想做双向绑定,怎么办?

vue2不支持。

vue3可以通过如下(通过:改名字):

// v-model 等同于 v-model:modalValue
<my-com v-model='value1' v-model:a='valueA' v-model:b='valueB'></my-com>

29.Vue中.sync修饰符的作用

有时候,我们需要对一个prop进行“双向绑定”,这时可以使用.sync来实现。v-model默认只能双向绑定一个属性,通过.sync修饰符,就可以绑定多个属性

<my-com :属性名.sync="xxx">

my-com组件里通过:$emit('update:属性名', value) 来触发

vue3中 .sync 语法被移除了

30.Vue中递归组件的理解

递归组件就是层层递进,最后拼接到一起,特点就是层级分明。例如组件库里的树组件,其实现就是通过递归的方式。

核心思路:

(1)循环出一级类别

(2)判断如果有多级,再调用自身

每个组件都会有一个name属性,这个name其实就是为了帮助我们实现递归的。

代码逻辑也很简单,重点在我的子组件。但父组件传过来的树形数据结构到子组件后,我们需要拿到数据并做遍历,然后再下一行加入核心逻辑:

if 发现我们有子数据,那么我们直接调用自身组件,也就是直接使用name值做组件声明。最关键的是要把子数据结构再传入我们自身组件,那么我们就成功的实现了数据的层层遍历。

31.组件中写name选项有什么好处?

  1. 增加name选项会在component属性中增加组件自身,实现组件的递归调用
  2. 可以标识组件的具体名称,方便调试和查找对应组件

32.最常用的修饰符,有哪些应用场景?

  1. 表单修饰符:lazy(表单失去焦点或回车再更新) number trim
  2. 事件修饰符:stop prevent self once capture passive native
  3. 鼠标按键修饰符:left right middle
  4. 键值修饰符: 对keycode处理
  5. 双向绑定修饰符:sync

33.Vue中异步组件的作用和原理

(1)概念

Vue允许你以一个工厂函数的方式,定义你的组件,这个工厂函数会异步解析你的组件定义,在需要的时候再去加载。推荐做法是将异步组件和webpack的code-splitting功能一起搭配使用。

(2)写法

2.1 回调写法

{
    components: {
        "my-component": (resolve, reject) => {
            setTimeout(function(){
                resolve({
                    render(h){
                        return h('div', 'hello')
                    }
                })
            }, 1000)
        }
    }
}

2.2 promise写法(类似路由懒加载的写法,引入异步组件)

{
    components: {
        "my-component": ()=>{ 
            import(/*webpackChunkName: "B4"*/ "./component/B4.vue")
        }
    }
}

2.3 对象写法

const asyncComponent = () => {
    // 需要加载的组件,应该是一个Promise
    components: import("./myComponent.vue"),
    // 加载中展示的组件
    loading: LoadingComponent,
    // 加载失败时展示的组件
    error: ErrorComponent,
    // 展示加载时组件的时间,毫秒
    delay: 200,
    // 超过时间没有加载出,则使用失败的组件。默认值是:Infinity
    timeout: 3000
}

(3)异步组件的原理

  1. 默认渲染异步占位符节点
  2. 组件加载完毕后,调用$forceUpdate强制更新,渲染加载完毕后的组件

34.动态组件的使用方法

(1)基本用法

// currentTabComponent可以包括已注册组件的名字或者一个组件的选项对象
<component :is="currentTabComponent"></component>

(2)进阶用法

2.1 搭配异步组件,延迟加载

<template>
    <div>
        <button @click="loadComponent">load component</button>
        <component :is="currentComponent"/>
    </div>
</template>
<script>
    data(){
        return { currentComponent:null }
    },
    methods: {
        async loadComponent(){
            const component = await import('./componentA.vue')
            this.currentComponent = component.default
        }
    }
</script>

2.2 使用命名视图

使用vue-router时候,需要通过router-view来定义视图出口。有时候需要多个视图出口,渲染不同的内容的时候,我们来可以通过name属性来定义命名视图。

// 定义阶段
<template>
  <!-- 默认name为defaut -->
  <router-view></router-view>
  <!-- 此处渲染Header组件内容 -->
  <router-view name="header"></router-view>
  <!-- 此处渲染footer组件内容 -->
  <router-view name="footer"></router-view>
</template>
// 路由层面配置
routes: [
    {
      path: '/',
      name: 'home',
      components: {
        header: Header,
        footer: Footer,
        default: MainBody
      }
    }
  ]

2.3 传入动态props

动态组件传递props和其他组件都一样,通过v-bind来动态绑定动态数据。

<component :is="currentTabComponent" :message="message"/>

35.keep-alive平时在哪里使用?

(1)概念

keep-alive是Vue的内置组件,能在组件切换的过程中缓存组件的实例,而不是销毁它们。在组件再次重新激活的时候,可以通过缓存的实例拿到之前渲染的DOM进行渲染,无需重新生成节点。

被keep-alive包裹的组件会多两个钩子函数:activated和deactivated

有三个参数:

  • include:要缓存的组件name,为数组 或者 字符串
  • exclude:不需要缓存的组件name,为数组 或者 字符串。(优先级大于include)
  • max:最大缓存的组件个数

(2)使用场景

1.动态组件可以采用keep-alive进行缓存

<keep-alive :include="array | string" :exclude="array | string" :max="count">
    <component :is="component"></component>
</keep-alive>

2.在路由视图中使用keep-alive(也可以通过name定义命名视图)

<keep-alive :include="array | string" :exclude="array | string" :max="count">
    <router-link></router-link>
</keep-alive>

3.也可以通过meta属性指定哪些页面需要缓存。需要在路由规则里配置参数

<div id="app">
    <keep-alive>
        // 需要缓存的组件
        <router-view v-if="$route.meta.keepAlive" />
    </keep-alive>
    // 不需要缓存的组件
    <router-view v-if="!$route.meta.keepAlive" />
</div>

36.Vue中使用了哪些设计模式?

  • 单例模式:单例模式就是整个程序有且仅有一个实例,Vuex中的store
  • 工厂模式:传入参数就可以创建实例
  • 发布订阅模式:event bus,需要手动订阅和手动触发
  • 观察者模式:自动通知,如数据变化,页面自动更新(如:watcher和&dep)

37.自定义指令的应用场景

指令的目的就是将操作DOM的逻辑进行复用

(1)指令的生命周期

  1. bind:只调用一次,指令第一次绑定到元素时候调用
  2. inserted: 被绑定元素插入父节点时调用
  3. update:组件的VNode更新时候调用,但可能发生在其子VNode更新之前
  4. componentUpdated:指令所在组件VNode以及其子VNode全部更新后调用
  5. unbind:只调用一次,指令与元素解绑的时候调用

(2)常见的指令编写

  • 图片懒加载 v-lazy
  • 防抖 v-debounce
  • 按钮权限 v-has
  • 自动聚焦 v-focus
  • 加载中 v-loading

38.Vue的性能优化有哪些

  1. 数据层级不应过深,合理设置响应式数据:数据重写递归
  2. 通过Object.freeze方法冻结属性:冻结的不会重写get和set方法
  3. 使用数据时,缓存值的结果,不频繁取值
  4. 合理使用key属性
  5. v-show和v-if合理选择
  6. 控制颗粒度,Vue采用组件级更新,将复杂页面组件化,拆分组件
  7. 采用函数式组件,没生命周期和响应式数据,开销低
  8. 采用异步组件,将包进行拆分
  9. 使用keep-alive缓存组件 v-once

39.单页应用如何优化首屏加载速度较慢的问题

  1. 使用路由懒加载、异步组件,实现组件拆分,减少入口文件体积
  2. 抽离公共代码,采用splitChunks进行代码分割
  3. 组件采用按需加载方式
  4. 静态资源缓存,采用HTTP缓存,使用localStorage实现缓存资源
  5. 图片资源压缩,小图片采用base64方式内嵌到代码里,减少http请求
  6. 打包时开启gzip压缩处理,compression-webpack-plugin插件
  7. 静态资源采用CDN加速,终极手段
  8. 使用SSR(Service Side Render:服务端渲染)对首屏做好服务端渲染。后台直接返回渲染好的HTML

40.Vue项目中如何解决跨域问题

跨域是浏览器同源策略导致的,协议、主机名、端口不同都会导致跨域问题。

服务端和服务端之间进行通信是没有跨域问题的。

一般常用的解决方式有以下几种:

  1. CORS(Cross-Origin Resource Sharing,跨域资源共享)。由服务端设置,允许指定的客户端访问服务器
  2. 构建工具中设置反向代理,使用Nginx做反向代理
  3. 使用Websocket进行通信
  4. 搭建BFF(Backend For Frontend)层解决跨域问题

41.要做权限管理怎么做?如何控制到按钮级别?

(1)登录鉴权

用户登录后返回token,前端将token保存到本地,作为用户登录的凭证,每次发送请求的时候都要携带token。后端对token进行验证。当页面刷新时我们可以使用token来获得用户权限

(2)访问权限

根据用户是否登录来判断能不能访问某个页面,通过路由守卫来实现判断用户是否有此权限

(3)页面权限

前端配置的路由分为两部分“通用路由配置”和“需要权限的路由配置”。在权限路由中增加访问权限meta(备注)。用户登录后可得到对应的权限列表。通过权限列表筛查出对应符合的路由信息,最后通过addRoutes方法,动态添加路由

(4)按钮权限

按钮权限一般采用自定义指令来实现。当用户登录时,后端会返回用户的权限。在按钮上使用此指令,指令内部会盘点是否有此按钮权限,如果没有则会移除按钮

42.Vue-router有几种钩子函数,具体是什么,执行流程是怎么样的

  1. 导航被触发
  2. 在失活的组件里调用 beforeRouteLeave 守卫
  3. 调用全局的 beforeEach 守卫
  4. 在重用的组件里调用 beforeRouteUpdate 守卫
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫
  9. 导航被确认
  10. 调用全局的 afterEach 钩子
  11. 触发更新DOM
  12. 调用beforeRouteEnter 守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入

43.Vue-router几种模式的区别

(1)hash:url里有#

(2)history:historyAPI

44.本地项目开发完,部署到服务器上报404是什么原因

hash模式刷新不会有这个问题

history模式刷新时候会向服务端发起请求,服务端无法响应对于的资源,所以会出现404。这个时候就需要后端最一些配置,增加一个覆盖所有情况的候选资源:如果url匹配不到任何静态资源,那就返回同一个index.html页面。这就是app依赖的页面

45.谈谈你对Vuex的个人理解

(1)概念

  • Vuex是一个专门为Vue.js应用程序开发的状态管理模式。采用集中式存储管理应用的所有组件的状态。核心就是解决数据的共享。
  • 以相应的规则保证状态以一种可预测的方式发生变化

(2)状态修改

  • 组件中同步修改: commit() => mutation => 修改状态
  • 组件中异步修改: dispatch() => actions => commit() => mutation => 修改状态

(3)缺点

Vuex中store只有一份,对于复杂的数据,则需要依赖与模块。Vuex状态是一个树状结构,最终会将模块的状态挂载到跟模块上。

  • 模块需要增加namespaced:true(独立的命名空间)
  • 更改状态 mutation和action选取
  • 模块和状态的名字冲突

(4)原理

Vuex3:核心就是创建一个new Vue实例,进行数据共享。

Vuex4:核心就是通过创建一个响应式对象,进行数据共享 reactive()

46.如何监听Vuex中数据的变化

  • 通过Vue的watch监控Vuex中数据的变化
  • 通过store的subscribe监控状态变化(多在插件里使用,平时业务基本用不到)

47.页面刷新后,Vuex的数据丢失怎么解决

  • 每次获取数据前先检测Vuex里是否存在,不存在则重新调服务获取
  • 把Vuex里的数据持久化处理,存储到localStorage里。获取的时候默认先从存储里取

48.mutation和action的区别

  • action处理异步,action中可以提交mutation。mutation处理同步逻辑,修改数据
  • 在action里可以多次进行commit操作,也可以调用其他的action
  • 在非mutation里修改数据,严格模式下是会发生异常
  • dispatch会将action包装成promise,mutation不会进行包装

49.有使用过Vuex的module吗?什么情况下会使用

使用单一状态树,应用所有的状态会集中到一个比较大对象,当应用比较复杂时,store对象就会变得比较臃肿,这个时候就最好使用模块。

Vuex允许我们将store分割成模块(module),每个模块都有自己的state、getter、mutation、action,甚至还会有自己的子模块

50.Vue3中CompositionAPI的优势是什么

  • Vue2中采用的是选项式API(options api),用户提供data、methods、props、computed等。编写业务代码,复杂逻辑时会存在反复横跳的问题,来回上下找相关代码。组合式API可以将相关代码放到一起,方便维护。
  • Vue2中所有的属性都是this访问的,this存在指向明确的问题
  • Vue2采用mixins实现组件之间的逻辑共享,但是会有数据来源不明确、命名冲突等问题。Vue3采用CompositionAPI,提供公共逻辑也比较方便
  • 简单的组件可以采用option API,复杂的组件组合式api优势更明显。

51.Vue3有了解过吗?跟Vue2有什么区别

52.Vue项目中的错误如何处理

(1)errorCapture钩子

可以捕获来自后代组件的错误。如果全局的config.errorHandler被定义,所有的错误仍会发送它。因此这些错误仍然会向单一的分析服务的地方进行汇报

组件错误会沿着组件层级一层一层的往上抛,如果errorCapture返回了false,则阻断传播。

(2)全局设置错误处理

Vue.config.errorHandler = (err, vm, info) => {
    console.log( err, vm, info )
}

(3)接口异常处理

instance.inteceptors.response.use((res)=>{ ... },(err) => {
    let res = err.respponse
    if(res.status > 400){
        handleError(res)  // 统一处理错误信息
    }
})

53.Vue3的内置组件Teleport

(1)概念

Teleport可以将一个组件内部的一部分模板“传送”到该组件的DOM结构外层的位置去。

例如定义一个模态框,嵌套的层级很深,维护样式的时候容易与外层的元素发生冲突。

(2)基本用法

Teleport接收一个to属性:指定传送的位置(也就是Teleport所包裹的组件渲染的位置)。to可以是CSS选择器、字符串、DOM元素对象。

渲染完成后,组件挂载的位置发生变化,但是父组件传入的props和触发的事件等逻辑仍旧正常,不会影响组件间的逻辑关系。

// main-body组件会挂载到#teId的元素内
<Teleport to="#teId" :disabled="false">
    <main-body/>
</Teleport>

(3)禁用

Teleport绑定disabled属性,为true的时候,传送不会生效。

(4)多个Teleport共享目标

如果配置了多个Teleport,目标都一样的话,不会替换,而是会将内容叠加到一起。多个组件内容挂载在同一个DOM元素上,顺序就是简单的依次累加。

注意:Teleport挂载时,to目标必须已经存在于DOM中。

54.你知道那些Vue3新特性

  • 组合式API: Composition API

  • SFC(single-file component:单文件组件) 组合式API语法糖:

  • Teleport内置组件:改变组件挂载位置,不会影响组件层级间的交互通信

  • Fragments:不再要求提供一个根节点,编译时会自动添加一个根节点Fragment

  • SFC内样式可以绑定动态数据

    <script setup>
        const theme = { color: 'red' }
    </script>
    <style scoped>
        p{
            color: v-bind('theme.color')
        }
    </style>
    

55.有封装过axios吗?主要封装哪方面?

  1. 设置请求超时时间
  2. 根据项目环境,设置请求路径(测试环境、生产环境)
  3. 设置请求拦截,添加token等
  4. 设置响应拦截,对响应的状态码进行判断,对数据进行格式化
  5. 添加请求队列,实现loading效果
  6. 登录超时,token失效,跳转登录页
  7. 维护取消请求token,在页面切换时通过导航守卫可以取消上个页面中正在发送的请求

56.isRef、unRef、toRef、toRefs、storeToRefs的用法

const obj = reactive({ age: 0 })
const count = ref(0)

(1)isRef

检查某个值是否为ref类型

isRef(obj)   // false
isRef(count) // true

(2)unRef

如果参数是ref,则返回内部的值,如果不是,则返回参数本身

unref(count)  // 0
unRef(obj)    // Proxy(age:0)

(3)toRef

基于响应式对象上的一个属性,创建一个对于的ref。这样创建的ref与其源属性保持同步:改变源属性的值将会同步更新ref的值,反之亦然。

const age = toRef(obj, 'age')
isRef(age)   // 
const changeAge = () => {
    // 以下两种方式更改数据后,obj.age 和 age都会同步改变
    obj.age ++;
    // age.value ++;
}

(4)toRefs

将一个非ref对象(普通对象或者reactive创建的对象)转成一个普通对象,对象里的每个属性都是具有响应式的ref。

使用的时候也可以直接将非ref对象里的属性解构出来,解构出的值具有响应式。

对象的每个属性都是指向源对象响应属性的ref,每个单独的ref都是使用toRef()创建的

const obj = ref({ age: 1 })
const obj1 = { a: 12 }
const nameObj = reactive({ name: 'herry' })
const { age } = toRefs(obj)      // age:  undefined
const { name } = toRefs(nameObj) // name: ObjectRefImpl {}
// objA还是普通对象,只是对象objA里的每个属性都有响应式
const objA = toRefs(obj1)     // objA:    {objA: ObjectRefImpl}

(5)storeToRefs

作用跟toRefs基本相同,都是作为解构对象使用,解构后的每个值都保持响应式。

区别是:

toRefs是vue的,处理的是组件内的响应式数据

storeToRefs是pinia的,处理的是pinia内的数据