Vue面试题及答案有哪些

这篇文章主要介绍了Vue面试题及答案有哪些的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue面试题及答案有哪些文章都会有所收获,下面我们一起来看看吧。

十多年的金山网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都营销网站建设的优势是能够根据用户设备显示端的尺寸不同,自动调整金山建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“金山网站设计”,“金山网站推广”以来,每个客户项目都认真落实执行。

1. 简述 Vue 生命周期

答题思路:

  • Vue 生命周期是什么?

  • Vue 生命周期有哪些阶段?

  • Vue 生命周期的流程?

  • 结合实践

  • 扩展:在 Vue3 变化 Vue 生命周期的变化 

回答范例:

  • 生命周期这个词应该是很好理解的,在我们生活中就会常常碰到,比如谈到一个人的生命周期,我们会说人这一生会经历婴儿、儿童、少年、青年、中年、老年这几个阶段。 而 Vue 的生命周期也是如此,在 Vue 中的每个组件都会经历从创建挂载更新再到销毁这几个阶段,而在这些阶段中,Vue 会运行一种叫做生命周期钩子的函数,方便我们在特定的阶段有机会添加上我们自己的代码。

  • Vue 生命周期总共可以分为 8 各阶段:创建前后挂载前后更新前后销毁前后,以及一些特殊场景的生命周期(keep-alive 激活时捕获后代组件错误时)。Vue3 中还新增了三个用于调试和服务端渲染场景

  • 这几个阶段对应的钩子函数 API依次为:beforeCreate create beforeMount mounted beforeUpdate updated activated(keep-alive 激活时调用) deactivated(keep-alive 停用时调用) beforeDestory destoryed errorCaptured(捕获子孙组件错误时调用)

    在 Vue3 中的变化 绝大多数只要加上前缀 on 即可,比如 mounted变为 onMounted,除了 beforeDestroydestroyed被重新命名为 beforeUnmountunMounted(这样与前面的 beforeMountmounted对应,强迫症表示很赞?)

  • beforeCreate在组件创建前调用,通常用于插件开发中执行一些初始化任务;created组件创建完毕调用,可以访问各种数据,请求接口数据等;mounted组件挂载时调用 可以访问数据、dom 元素、子组件等;beforeUpdate更新前调用 此时 view 层还未更新,可用于获取更新前的各种状态;updated完成更新时调用 此时view层已经完成更新,所有状态已经是最新的了;beforeUnmount实例被销毁前调用,可用于一些定时器或订阅的取消;unMounted销毁一个实例时调用 可以清理与其他实例的链接,解绑它的全部指令以及事件监听器。

  • 在 Vue3 中: setup 是比 created 先执行的而且没有 beforeCreate 和  created

2. Vue 中如何做权限管理

  • 权限管理一般需求就是对页面权限和按钮权限的管理

  • 具体实现的时候分前端实现和后端实现两种方案: 前端方案会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后根据角色过滤出路由表,然后在动态添加路由。比如我会配置一个 asyncRoutes数组,需要认证的页面在路由的 meta中添加一个 roles字段,等获取用户角色之后取两者的交集,若结果不为空则说明可以访问。过滤结束后剩下的路由就是用户能访问的页面,最后通过 router.addRoutes(accessRoutes)方式动态添加路由即可。

    后端方案会把所有页面路由信息存在数据库中,用户登录的时候根据其角色查询得到其能访问的所有路由信息返回给前端,前端再通过 addRoute动态添加路由信息。

    按钮权限的控制通常会实现一个指令,例如 v-permission,将按钮要求角色通过值传给 v-permission指令,在指令的 mounted钩子中可以判断当前用户角色和按钮是否存在交集,有就保留按钮,没有就移除按钮。

  • 纯前端方案的优点是实现简单,不需要额外权限管理页面,但是维护起来问题比较大,有新的页面和角色需求就要修改前端代码和重新打包部署服务端方案就不存在这个问题,通过专门的角色和权限管理页面,配置页面和按钮权限信息到数据库,应用每次登陆时获取的都是最新的路由信息。

自己的话:权限管理一般分页面权限和按钮权限,而具体实现方案又分前端实现和后端实现,前端实现就是会在前端维护一份动态的路由数组,通过用户登录后的角色来筛选它所拥有权限的页面,最后通过 addRoute 将动态添加到 router 中;而后端实现的不同点就是这些路由是后端返回给前端,前端再动态添加进去的。 按钮权限一般会实现一个 v-permission ,通过判断用户有没有权限来控制按钮是否显示。 纯前端方案的优点是实现简单,但是维护问题大,有新的页面和角色需求都需要改代码重新打包部署,服务端则不存在这个问题。

3. Vue 中双向绑定的使用和原理

回答思路:

  • 什么是双向绑定?

  • 双向绑定的好处?

  • 在什么地方使用双向绑定?

  • 双向绑定的使用方式、使用细节、Vue3中的变化

  • 原理实现描述

回答:

  • Vue中的双向绑定是一个指令 v-model ,它可以绑定一个响应式数据到视图,同时视图中变化也能改变该值。

  • v-model 是一个语法糖,它的原理(默认请情况下)就是通过 :value 将变量挂到 dom 上,再通过 input事件 监听 dom 的变化改变变量的值。使用 v-model的好处就是方便呀,减少了大量的繁琐的事件处理,提高开发效率。

  • 通常在表单上使用 v-model,还可以在自定义组件上使用,表示某个值得输入和输出控制。

  • 可以结合修饰符做进一步限定(lazy/number/trim),用在自定义组件上时有些不同,它相当于是给了子组件一个 modelValue 的 属性 和 update:modelValue 的 事件; 在 Vue3 中还可以用参数形式指定多个不同的绑定,如 v-model:foo 这个时候就相当于 给了子组件一个 foo 的 属性 和 update:foo 的事件。

  • v-model作为一个指令,它的原理就是 Vue 编译器会把它转换成 value属性绑定和input的监听事件,上面说过是默认情况下,实际上编译器会根据表单元素的不同分配不同的事件,比如 checkboxradio 类型的input 会转换为 checkedchange 事件。

4. Vue 组件之间通信有哪些?

Vue 组件之间通信有以下这么几种:

  • props

  • $emit$on$off$once(后三者在Vue3中已被废除)

  • $children(Vue3中废除)$parent

  • $attrs$listeners(Vue3中废除)

  • ref

  • $root

  • eventbus (Vue3中不好使了,需要自己封装)

  • vuexpinia

  • provide + inject

以上的方法长按使用场景可以分为:

  • 父子组件之间可以使用

    props /$emit/ $parent/ ref /$attrs

  • 兄弟组件之间可以使用

    $parent / $root/ eventbus / vuex

  • 跨层及组件之间可以使用

    eventbus / vuex pinia / provide + inject

5.你了解哪些 Vue 性能优化方法?

  • 路由懒加载:有效拆分 App 尺寸,访问时才异步加载

const router = createRouter({
    routes: [
        { path : '/foo', component: () => import('./foo.vue)}
    ]
})

  • keep-alive 缓存页面:避免重复创建组件实例,且能保存缓存组件状态


  

  • 使用 v-show 复用 DOM:避免重复创建组件

  • v-for 遍历避免同时使用 v-if(实际上这在 Vue3 中是错误的写法)

  • v-oncev-memo: 不再变化的数据使用 v-once;按条件跳过更新时使用 v-memo

  • 长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容。一些开源库(vue-virtual-scroller / vue-virtual-scroll-grid

  • 事件的销毁:Vue组件销毁时,会自动解绑它的全部指令以及事件监听器,但是仅限于组件本身的事件。

  • 图片懒加载,自定义 v-lazy 指令 (参考项目:vue-lazyload

  • 第三方插件按需引入 element-plus 避免体积太大

  • 子组件分割策略:较重的状态组件适合拆分

  • SSR 服务端渲染 解决首屏渲染慢的问题

6. 刷新后 Vuex 状态丢失怎么解决?

思路:

  • 刷新后 Vuex 状态为什么会丢失?

  • 解决方法

  • 第三方库以及原理探讨

  • 个人理解

回答:

  • 因为 Vuex 只是在内存中保存状态,刷新后就会丢失,如果要持久化就要存起来。

  • 可以是用 localStorage 存储 Vuex 的状态,store 中把值取出来作为 state 的初始值,提交 mutation 的时候就存入 localStorage

  • 可以用 vuex-persistvuex-persistedstate 这种插件,可以通过插件选项控制哪些需要持久化。内部的原理就是通过订阅 mutation 变化做统一处理。

  • 这里有两个问题,一是如果用户手动改了 localStorage怎么办?那我 Vuex 里的状态不是也改变了?二是由于 localStorage API 的原因只能存储字符串,所以我们只能将数据通过 JSON.stringify 转换为字符串,而当我们存储的数据为 MapSetFunction 这种引用类型的数据时,JSON.stringify 转换后会变味 {} 而丢失。

对应第一个问题我的解决方法是可以通过 监听 storage 事件来清除数据

window.addEventListener("storage", function () {
    localStorage.clear();
    window.location.href = '/login'
    console.error("不要修改localStorage的值~~~");
});

对于第二个问题没办法了,只能选择不适用 MapSet 这种引用类型。

7. Vue3 为什么用 Proxy 替代 defineProperty ?

思路:

  • 属性拦截的几种方式

  • defineProperty的问题

  • Proxy的优点

  • 其他考量

回答:

  • JS 中做属性拦截常见的方式有三种:definePropertygetter/settersProxy

  • Vue2 中使用 defineProperty 的原因是, 2013 年只能使用这种方式,由于该 API 存在一些局限性,比如对于数组的拦截有问题,为此 Vue 需要专门为数组响应式做一套实现。另外不能拦截那些新增、删除属性;最后 defineProperty 方案在初始化时需要深度递归遍历处理对象才能对它进行完全拦截,明显增加了初始化的时间。

  • 以上两点在 Proxy 出现后迎刃而解,不仅可以对数组实现拦截,还能对 MapSet 实现拦截;另外 Proxy 的拦截也是懒处理行为,如果用户没有访问嵌套对象,那么也不会实施拦截,这就让初始化的速度和内存占用改善了。

  • Proxy 有兼容性问题,完全不支持IE

8. 怎么实现路由懒加载?

思路:

  • 必要性

  • 何时用

  • 怎么用

  • 使用细节

回答:

  • 当打包构建时,Javascript 抱回变得非常大,影响页面加载。利用路由懒加载我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应最贱,这样更加高效,是一种优化手段。

  • 一般来说,对于所有的路由都使用动态导入是个好主意

  • component 选项配置一个返回 Promise组件的函数就可以定义懒加载路由.例如:

{
  path: '/login',
  component: () => import('../views/login/Login.vue')
},

  • 结合注释

{
  path: '/login',
  component: () => import(/* webpackChunkName: "login" */'../views/login/Login.vue')
},

vite中结合rollupOptions定义分块 5. 路由中不能使用异步组件

9. history模式 和 hash 模式有何区别?

  • Vue-Router 有三个模式,其中 history 和 hash 更为常用。两者差别主要在显示形式和部署上,

  • hash模式在地址栏现实的时候有一个 #,这种方式使用和部署都较简单;history模式url看起来更优雅没关,但是应用在部署时需要做特殊配置,web服务器需要做回退处理,否则会出现刷新页面404的问题。

  • 在实现上 hash模式是监听hashchange事件触发路由跳转,history模式是监听popstate 事件触发路由跳转。

10. 说说 nextTick 的使用和原理?

  • VuenextTick 是等待下一次 DOM 更新刷新的工具方法。

  • Vue 有一个异步更新策略,意思是如果数据变化,Vue 不会立刻更新 DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一时间循环中发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不会立刻体现在 DOM 上,此时如果想要获取更新后的 DOM 状态,就需要使用 nextTicknextTick 接受一个函数,我们可以在这个函数内部访问最新的 DOM 状态 在开发时,有两个场景我们会用到 nextTick

    • created 中想要获取 DOM;

    • 响应式数据变化后获取 DOM 更新后的状态;

  • nextTick 的原理:在 Vue 内部,nextTick 之所以能够让我们看到 DOM 更新后的结果,是因为我们传入的 callback 会被添加到队列刷新函数的后面,这样等队列内部的更新函数都执行完毕,所有 DOM 操作也就结束了,callback 自然能够获取最新的 DOM 值。

11. v-for 和 v-if 优先级

先回答答案:vue2 中, v-for 的优先级更高 但是在 vue3 中, v-if 的优先级更高

拓展:无论什么时候,我们都不应该把 v-forv-if 放在一起, 怎么解决呢?一是可以定义一个计算属性,让 v-for 遍历计算属性。二是可以把 if 移到内部容器里(ul ol)或者把v-for移植外部容器(template)中

vue2文档vue3文档

12. 如何监听 Vuex 状态变化?

  • watch

  • store.subscribe()

watch 方式,可以以字符串形式监听 $store.state.xx; subscribe 方法参数是一个回调函数,回调函数接受mutation 对象和 state 对象,可以通过 mutation.type 判断监听的目标。 wtach 方法更简单好用, subscribe 会略繁琐,一般用 vuex 插件中(可以提一下vuex的持久化插件vuex-persistvuex-persistedstate

13. 你觉得 Vuex 有什么缺点?

  • 不支持持久化,页面刷新状态就会丢失

  • 使用模块比较繁琐

  • 不支持 ts (或者说很不友好)

vue3 + pinia 会是更好的组合。

14. ref 和 reactive 异同点?

  • 两者都能返回响应式对象,ref 返回的是一个响应式Ref 对象, reactive 返回的是响应式代理对象。

  • ref 通常是处理单值得响应式,reactive 用于处理对象类型的数据响应式

  • ref 需要通过 .value 访问, 在视图中会自动脱 ref,不需要 .valueref 可以接收对象或数组但内部依然是 reactive 实现的;reactive 如果接收 Ref 对象会自动脱 ref ;使用展开运算符展开 reactive 返回的响应式对象会使其失去响应性,可以结合 toRefs()将值转换为 Ref 对象后再展开。

  • reactive 内部使用 Prxoy 代理拦截对象各种操作,而 ref 内部封装一个 RefImpl 类,设置 get value/set value,拦截用户对值得访问。

16. Vue 中如何扩展一个组件?

  • 逻辑扩展:mixinsextendscomposition api:

  • 内容扩展:slots

mixins 很灵活,但是会冲突很混乱。extends 是一个不太常用的选项,更 mixins 的不同是它只能扩展单个对象,优先级比 mixins 高。

混入的数据和方法 不能明确判断来源而且可能和当前组件内变量 产生命名冲突,composition api 可以很好解决这些问题,利用独立出来的响应式模块可以很方便的编写独立逻辑并提供响应式数据局,增强代码的可读性和维护性。

扩展:Vue.mixin(全局混入)  Vue.extend(有点像是 类/组件的继承 创建一个子类)

17. vue-loader 是什么?

  • vue-loader 是用于处理单文件组件(SFC)的webpack loader

  • 因为有了 vue-loader,我们才能用 .vue 文件形式编写代码,将代码分割为 template script style

  • webpack 在打包的时候,会以 loader 的方式调用 vue-loader

  • vue-loader 被执行时,它会对 SFC 中的每个语言块用单独的 loader 链处理,最后将这些单独的块装配成最终的组件模块

18. 子组件能否修改父组件数据

不能直接改。

组件化开发中有一个单向数据流原则,不在子组件修改父组件数据是个常识

如果你确实需要改,请通过emit向父组件发送一个事件,在父组件中修改

19. 怎么定义动态路由,怎么获取传过来的动态参数?

我么可以在路径中使用一个动态字段来实现,例如/users/:id 其中 :id 就是路径参数。 可以通过 this.$route.parmas获取,参数还可以有多个, $route 对象还公开了其他有用的信息如 query hash等。

20. 说说对 Vue 数据响应式的理解

思路:

  • 什么是响应式?

  • 为什么vue需要响应式?

  • 有什么好处?

  • vue的响应式怎么实现的,有哪些优缺点?

  • vue3中的响应式的新变化

回答:

  • 数据响应式就是 能够监测到数据变化并且做出响应的一种机制

  • vue 中要解决的一个核心问题就是连接数据层和视图层,通过数据变化驱动视图更新,要做到这点就需要对数据做响应式处理。

  • 通过数据响应式加上虚拟 DOMpatch 算法,我们只需要操作数据,关心业务,完全不需要接触繁琐的 DOM 操作,打打提升了开发效率,降低开发难度。

  • vue2 中实现数据响应式的核心就是通过 Object.defineProperty() 方法对数据进行拦截,当 get 数据时做依赖收集 set 数据时做更新通知。这种机制很好的及绝了数据响应式的问题,但是实际使用也存在缺点,比如在初始化时的递归遍历会造成性能损失;无法监听新增或删除属性,在 vue 中要通过像 Vue.set/delete 这种特定的 API 才能实现对对象数组属性的添加和删除,而且也不支持 MaSet这些数据结构,

  • 为了解决这些问题,Vue3 重写了这部分实现,利用的是 ES6 中的 Proxy 代理要响应化的数据。它有很多好处,初始化性能和内存都大幅改善,也不需要特殊的 API ,但是不支持 IE 浏览器。

21. 从 template 到 render 做了什么

templaterender 的过程其实是问的 vue 编译器 工作原理。

思路:

  • 引入编译器概念

  • 说明编译器的必要性

  • 阐述编译器工作流程

回答:

  • Vue 中有个独特的编译模块,称为 compiler,它的主要作用是将 template 编译为 js 可执行的 render 函数

  • 之所以需要这个编译过程是为了便于我们高校的编写试图模版。相比而言,我们还是更愿意用 HTML来编写视图,直观且高效。手写 render 函数不仅效率低下,而且失去了被编译器的优化能力。

  • Vue 编译器 首先会对 template进行解析Parse ),结束后会得到一个抽象语法树AST,然后对 AST 进行深加工转换(transform),最后将得到的 AST 生成为 js 代码,也就是 render 函数

22. 如何缓存和更新组件

  • 缓存组件可以使用 keep-alive 组件,include 和 exclude 可以指定包含不包含哪些组件。

  • Vue3 结合 vue-router 使用变化非常大,之前是 keep-alive 包含 router-view,现在是 router-view 包含 keep-alive

  • 缓存后如果想要获取数据可以使用 actived 钩子 或者 beforeRouteEntervue-router 的一个守卫)

  • keep-alive 是一个通用组件,它内部定义了一个 map,缓存创建过的组件实例,它返回的渲染函数内部会查找内嵌的 component 组件对应组件的 vnode,如果改组件在map中存在就直接返回它。由于 componentis 属性是一个响应式数据,因此只要它变化,keep-aliverender 函数就会重新执行。

23. 虚拟DOM

  • 虚拟 DOM 是什么? 虚拟 DOM 的本质就是一个 Javascript 对象。

  • 为什么要引入虚拟 DOM?(好处) 它能有效减少操作 DOM 的次数,方便实现跨平台

  • 虚拟DOM如何生成?compiler 编译器会把 template 模版编译成渲染函数,接下来在 mount 挂载的过程会调用这个渲染函数,返回的对象就是 虚拟DOM 。挂载结束后,会进入更新流程。如果某些响应式数据发生变化,将会引起组件重新 render,此时会生成新的 虚拟DOM,和上次渲染结果做 diff 操作,最小量的操作 dom,从而高效更新视图。

24. 什么是异步组件

  • 异步组件就是不会立即加载而是会在需要的时候加载的组件。在大型应用中,我们需要分割代码为更小的块试就可以用异步组件。

  • 不仅可以在路由切换时懒加载组件,还可以在组件中使用异步组件,从而更细的分割代码。

  • 使用异步组件最简单的方式是直接给 defineAsyncComponet 指定一个 loader 函数,结合 ES 模块 动态导入函数 import 可以快速实现。Vue3 还可以结合 Suspense 组件使用异步组件。

  • 异步组件容易和路由懒加载混淆,实际上不是一个东西。异步组件不能被用于定义懒加载路由上,处理它的是 Vue 框架,处理路由组件加载的是 vue-router。但是可以在懒加载的路由组件中使用异步组件。

25. 说说Vue长列表优化思路

  • 避免大数据量:可以采用分页的方式获取

  • 避免渲染大量数据:vue-virtual-scroller等虚拟滚动方案,只渲染视口范围内的数据

  • 避免更新:可以使用v-once方式只渲染一次

  • 优化更新:通过v-memo缓存组数,有条件更新,提高服用,避免不必要更新

  • 按需加载数据:可以采用 懒加载 方式,在用户需要的时候在加载数据。

26. computed & watch

  • computed 是计算属性,watch 是侦听器。

  • computed 通常用于处理模版中复杂的逻辑,而 watch 通常用于需要监听一个响应式对象的变化而做一些操作的时候

  • watch 可以进行异步操作,computed 不行。

  • 计算属性传递一个对象 有 setget 两个选项,是它称为即可读又可写的计算属性,如果传递的是函数的话默认就是 get 选项,watch 可以传递一个对象,设置deep、immediate等选项

  • vue3watch 发生了一些变化,例如不能再侦测一个点操符之外的字符串表达式,reactivity API 中新出的 watchwatchEffect 可以完全替代 watch 选项,而且功能更加强大

27. SPA 和 SSR的区别是什么?

  • SPA(Single Page Application)是单页面应用。一般也称为客户端渲染,简称 CSR 。SSR(Server Side Render) 即服务端渲染。一般也称为多页面应用(Mulpile Page Application),简称 MPA。

  • SPA 只会首次请求 html 文件,后续只需要请求 JSON 数据即可,因此用户体验更好,节约流量,服务端压力也较小。但是首屏加载的时间会变长,而且 SEO 不友好。为了解决以上缺点,就有了 SSR 方案,由于 HTML 内容在服务器一次性生成出来,首屏加载快,搜索引擎也可以很方便的抓取页面信息。但同时 SSR 方案也会有性能,开发受限等问题。

  • 选择上,如果有首屏加载优化需求,SEO需求时,就可以考虑SSR。

  • 但并不是只有这一种替代方案,比如对一些不常变化的静态网站,SSR反而浪费资源,我们可以考虑预渲染的方案。另外 nuxt.js/next.js 中给我们提供了SSG静态网站生成方案也是很好的静态站点解决方案,结合一些CI手段,可以起到很好的优化效果。

28. diff 算法

回答思路:

  • diff算法是干什么的?

  • 必要性

  • 何时执行

  • 具体执行方式

  • 拔高:说一下vue3中的优化

回答:

  • Vue 中的 diff 算法称为 patching 算法,虚拟DOM要想转化为真实DOM就需要通过 patch 方法转换。

  • 最初 Vue1.x 视图中农每个依赖均有更新函数对应,可以做到精确更新,因此不需要 虚拟DOMpatching 算法支持,但是这样粒度过细导致 Vue1.x 无法承载较大应用;Vue2.x 中为了降低 Watcher 粒度,每个组件只有一个 Watcher 与之对应,此时就需要引入 patching 算法才能精确找到发生变化的地方并高效更新。

  • vuediff 执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行 render函数 获得最新的 虚拟DOM ,然后执行 patch函数对比新旧虚拟DOM,将其转化为对应的 DOM 操作。

  • patch 过程是一个递归过程,遵循深度优先、同层比较的策略;以 vue3patch 为例:

    • 新的子节点是文本,老的子节点是数组则清空,并设置文本;

    • 新的子节点是文本,老的子节点是文本则直接更新文本;

    • 新的子节点是数组,老的子节点是文本则清空文本,并创建新子节点数组中的子元素;

    • 新的子节点是数组,老的子节点也是数组,那么比较两组子节点,更新细节blabla

    • 首先判断两个节点是否为相同同类节点,不同则删除重新创建

    • 如果双方都是文本则更新文本内容

    • 如果双方都是元素节点则递归更新子元素,同时更新元素属性

    • 更新子节点时又分了几种情况:

  • vue3 中引入的更新策略:编译期优化 patchFlagsblock

29. 如何从0到1架构一个Vue项目,说说有哪些步骤,插件,目录结构怎么组织

  • 从 0 创建项目我大致会做以下事情:项目构建、引入必要插件、代码规范、提交规范、常用库和组件

  • 目前vue3项目我会用vite或者create-vue创建项目

  • 接下来引入必要插件:vue-router、vuex/pinia、element-plus、antd-vue、axios等等

  • 其他常用的库有 像lodash、dayjs、nprogress等等..

  • 下面是代码规范: editorconfig、prettier、eslint

  • 最后是提交规范,可以使用husky、Commitizen

  • 目录结构我喜欢按照下面的结构来

+ |- /src
+   |- /assets 存放资源
+     |- /img   
+     |- /css   
+     |- /font   
+     |- /data   
+   |- base-ui  存放多个项目中都会用到的公共组件
+   |- components 存放这个项目用到的公共组件
+   |- hooks 存放自定义hook
+   |- views 视图
+   |- store 状态管理
+   |- router 路由
+   |- service 网络请求
+   |- utils 工具
+   |- global 全局注册、全局常量..

30. 你如何实现一个Vue-Router

一个 SPA 应用的路由需要解决的问题时页面跳转内容改变同时不刷新,同时路由还需要已插件形式存在,所以:

  • 首先我会定义一个 createRouter 函数,返回路由器实例,实例内部做几件事;

    • 保存用户传入的配置项

    • 监听 hash 或者 popstate 事件

    • 回调里根据 path 匹配对应路由

  • router 定义成一个 Vue 插件,即实现 install 方法,内部做两件事:

    • 实现两个全局组件:router-linkrouter-view,分别实现页面跳转和内容显示

    • 定义两个全局变量:$router$route,组件内可以访问当前路由和路由器实例

31. 什么情况需要使用Vuex模块?

  • 在项目规模变大的之后,单独一个store对象会过于庞大臃肿,此时通过模块方式可以拆分来便于维护

  • 可以按之前规则单独编写资规模代码,然后在主文件中通过 modules 选项组织起来:createStore({modules: {...}})

  • 使用时需要注意访问子模块状态时需要加上注册模块名。但同时gettersmutationsactions又在全局空间中,使用方式和之前一样。如果要做到完全拆分,需要在子模块加上 namespace选项,此时再访问它们就要加上命名空间前缀。

  • 模块的方式可以拆分代码,但是缺点也很明显,使用起来比较繁琐,容易出错,而且类型系统支持很差,不能给我们带来帮助。pinia 显然在这方面有了很大改进,是时候切换过去了。

32. vue 组件为什么只能有1个根节点

  • vue2 中组件确实只能有一个跟,但 vue3 中组件已经可以多根组件了。

  • 之所以需要这样是因为 vdom 是一颗单根树形结构,patch 方法在遍历的时候从根节点开始遍历,它要求只有一个根节点。组件也会转换为一个 vdom,自然应该满足这个要求。

  • vue3 中之所以可以写多个根节点,是因为引入了 Fragment 的概念,这是一个抽象的节点,如果发现组件时多根的,就创建一个 Fragment 节点,把多个根节点作为它的 children。将来 pathch 的时候,如果发现是一个 Fragment 节点,则直接遍历 children 创建或更新。

33. v-once 使用场景有哪些?

  • v-oncevue 的内置指令,作用是仅渲染指定组件或元素一次,并跳过未来对其更新。

  • 如果我们有一些元素或者组件再初始化渲染之后不再需要变化,这种情况下适合使用 v-once,这样哪怕这些数据变化,vue 也会跳过更新,是一种代码优化手段。

  • 我们只需要作用的组件或元素上加上 v-once 即可。

补充:

  • vue3.2 之后,又增加了 v-memo,这个指令可以有条件的缓存模板并控制他们的更新。

  • v-once 的原理:编译器发现有 v-once 时,会将首次计算结果存入缓存对象,组件再次渲染时就会从缓存获取,从而避免再次计算。

34. 什么场景使用嵌套路由

  • 在平时开发中,应用的有些界面是由多层嵌套的组件组合而来的,这种情况下,url 各部分通常对应某个嵌套的组件,vue-router 中可以使用嵌套路由表示这种关系。

  • 表现形式是在两个路由间切换时,他们有公用的视图内容。此时通常提取一个父组件,内部放上 view-router,从而形成物理上的嵌套,和逻辑上的嵌套对应起来。定义嵌套路由时使用 children 属性组织嵌套关系

  • 原理上是在 router-view 组件内部判断其所处嵌套的深度,将这个深度作为匹配组件数组 matched 的索引,获取对应渲染组件并渲染之。

如果你说不出来,可以直接举例子。当我开发一个页面时,如果需要显示一个顶部导航栏,通过导航栏跳转到不同的页面,而顶部的导航栏又必须要在每个页面显示时,就可以使用嵌套路由;还可以举例,当我需要查看某个列表的详情页面时,往往需要嵌套路由 (detail/:id

35. 如何监听 Vuex 状态变化?

  • watch

  • store.subscribe()

watch 方式,可以以字符串形式监听 $store.state.xx; subscribe 方法参数是一个回调函数,回调函数接受mutation 对象和 state 对象,可以通过 mutation.type 判断监听的目标。 wtach 方法更简单好用, subscribe 会略繁琐,一般

36. Vue 实例挂载过程发生了什么?

  • 挂载实例的过程就是 app.mount()的过程,整体上就做了两件事:初始化建立更新机制

  • 初始化会创建组件实例初始化组件状态创建各种响应式数据

  • 简历更新机制这一步会立即执行一次组件更新函数,这会首次执行渲染函数并执行 patch 将前面获得vnode 转换为 dom;同时会创建它内部响应式数据和组件更新函数之间的依赖关系,这使得以后数据变化时会执行对应的更新函数。

37. key 的作用

  • key 的作用主要是为了更高效的更新虚拟 DOM

  • keyvuepatch 过程中判断两个节点是否是相同节点的关键条件(另一个是元素类型),如果不设置 key,它的值就是 undefinedvue 则可能永远认为这是两个相同节点,只能去做更新操作,这造成了大量的 dom 更新操作,明显是不可取的。

  • 实际使用的过程中必须设置 key,而且应该尽量避免使用数组索引,这可能导致一些隐藏 bug

38. watch 和 watchEffect

  • watchEffect立即运行函数,被动地追踪它的依赖,传入的函数即是依赖收集的数据源,也是回调函数;watch 侦测一个或多个响应式数据源,在数据源变化时调用一个回调函数,通过 immediate 选项也可以设置立即执行一次。

  • watchEffect是一种特殊的 watch。如果不关心响应式数据前后的值,可以使用 watchEffect。其他情况都可以用 watch

39. 父子组件创建、挂载顺序

parent created  -> child created -> child mounted -> parent mounted

原因:Vue 创建是一个递归的过程,先创建父组件有子组件就会创建子组件,因此创建时先有父组件再有子组件;子组件首次创建时会添加 Mounted 钩子到队列,等到 patch 结束再执行它们,可见子组件的 mounted 钩子是选进入到队列中的,因此等到 patch 结束执行这些钩子时也先执行。

40. 说说你对 Vuex 的理解

  • vuex是一个专门为vue应用开发的状态管理模式库,

  • 当你遇到多个组件共享状态时或者项目中的组件难以管理的时候就可以使用vuex,它以一个全局单例模式管理全局的状态。

  • 基本核心概念有 state、mutation、action、getters、module等

  • 说些使用过程的感受 ts不友好 模块使用繁琐 页面刷新数据也会消失

41. 什么是递归组件?使用场景有哪些?

  • 如果某个组件通过组件名称引用它自己,这种情况就是递归组件。

  • 类似 TreeMenu 这类组件,它们的节点往往包含子节点,子节点结构和父节点往往是相同的。这类组件的数据往往也是树形结构,这种都是使用递归组件的典型场景。

42. 你写过自定义指令吗?

使用自定义指令分为定义、注册、和使用

  • 定义有两种方式,对象和函数形式,前者类似组件定义,有各种生命周期;后者只会在 mountedupdated 时执行

  • 注册:可以使用 app.directive 全局注册 也可以通过选项局部注册

  • 使用时在注册名称前加上 v-即可。

  • v-copy 复制粘贴

  • v-lazy 图片懒加载

  • v-debounce 防抖

  • v-permission 按钮权限

  • v-longpress 长按

43. Vue3新特性

API 层面

  • Composition API

  • setup 语法糖

  • Teleport 传送门

  • Fragments 可以多个根节点

  • Emits

  • createRenderer 自定义渲染器

  • SFC 状态驱动 css 变量 (v-bind in