vue 面试题总结

182 阅读6分钟

1. vue 的优缺点:

优点

  1. 模板式的语法,简单易学,容易上手;
  2. 虚拟 DOM 保证了在操作 dom 的形式下,性能的下限。

缺点:

  1. 模板式的语法不够灵活;
  2. 缺乏高阶教程与文档;

2. vue 组件的传值方式?

  1. props$emit 的实现 父子 组件的传值;
  2. event bus,将父级作为桥梁,实现同级组件传值;
  3. provide/inject 实现深度嵌套组件之间的传值,顶级定义的数据,任何一层的子级都可以访问,虽然官方不推荐使用,但是用好了,能带来意想不到的好处;
  4. vuex 状态管理工具,能实现任意传。

3. 为什么使用 key ?

使用 key 来给每个节点做一个唯一标识,在 Diff 算法就可以正确的识别此节点。作用主要是为了高效的更新虚拟 DOM。

4. v-showv-if 指令的区别?

  • 共同点: 都能控制元素的显示和隐藏;

  • 不同: 实现本质方法不同,v-show 是通过 cssdisplay 属性来控制隐藏;v-if 是动态的向 DOM 树内添加或者删除 DOM 元素。

  • 性能上比较:

    • v-show 不管是设置为 true 还是 false,都会加载,而且只会加载一次,对于一些需要频繁切换的,建议使用 v-show,总结就是: 切换开销比较小,初始开销较大;
    • v-if 如果设置为 false,则它就不会编译,这样对于一些不会频繁切换的场景,建议是用 v-if,总结就是: 初始渲染开销较小,切换开销比较大

5. v-ifv-for 的优先级:

  1. 2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用;
  2. 3.x 版本中 v-if 总是优先于 v-for 生效。

推荐写法:

<template v-if="isTrue">
  <p v-for="item in arr">{{ item }}</p>
</template>

6. v-modal 的使用

v-model 本质上就是一个语法糖, 本质是, 父组件将 keyvalue 的属性传递给子组件, 子组件默认使用名为 input 的事件,将新值传递给父组件;

vue3 重构了该指令,形式为 v-mode:msg="" ,子组件事件名为 update:msg ,让 v-model 可以绑定在任何组件上,而不仅仅是表单。

7. v-on 可以监听多个方法吗?

可以。

<input type="text" v-on="{ input:onInput,focus:onFocus,blur:onBlur, }" />

8. slot

组件的插槽,在组件内部定义好位置,但是不设置内容,内容由用户手动传递;

  1. 匿名插槽:
<!-- 组件名 submit-button -->
<button type="submit">
  <slot></slot>

  <!-- 可以设置默认值,如果调用时不传值,则使用该默认值 -->
  <slot>submit</slot>
</button>

使用
<submit-button> Save </submit-button>
  1. 具名插槽:使用语法为 v-slot:name
base-layou
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

使用
<base-layout>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>
  1. 作用域插槽:v-slot="record" , record 为组件内部传递的值
<span>
  <slot v-bind:user="user"> {{ user.lastName }} </slot>
</span>

<!-- 使用 -->
<current-user>
  <!-- 没有设置插槽名字时,使用 default 代替 -->
  <template v-slot:default="record"> {{ record.user.firstName }} </template>
</current-user>

9. 说出几种 vue 当中的指令和它的用法?

  • v-text: 将值渲染到标签内部;
<span v-text="msg"></span>
<!-- 等价于 -->
<span>{{msg}}</span>
  • v-html: 更新元素的 innerHTML。注意:内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译;
  • v-if/v-show 显示与隐藏;
  • v-for 循环;
  • v-on: 绑定事件;
  • v-bind: 绑定动态属性;
  • v-model 双向数据绑定;
  • v-slot: 具名插槽;
  • v-once: 只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过;

10. vue 常用的修饰符;

  • .stop: 阻止点击事件冒泡。等同于 event.stopPropagation();
  • .prevent: 用于取消默认事件,等同于 event.preventDefault();
  • .capture: 与事件冒泡的方向相反,事件捕获由外到内,捕获事件;
  • .once: 只执行一次,如果我们在@click 事件上添加.once 修饰符,只要点击按钮只会执行一次
  • .enter: 键盘事件时添加关键修饰符,表示按下了 enter,还有 tab, esc 等;
  • .trim: 自动过滤用户输入的首尾空格;
  • .sync: 语法糖,类似与 v-model,vue3 已移除。

11. keep-alive

  • 作用: 缓存组件,当切换页面时,让页面不被销毁,再切回来时,还能保证之前的状态。

  • 使用场景: 比如从列表页中跳转到详情页,再从详情页返回列表页。

  • 语法: keep-alive 包裹动态组件,而不能包裹原生标签;

    1. include - string | RegExp | Array。只有名称匹配的组件会被缓存;
    2. exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存;
    3. max - number | string。最多可以缓存多少组件实例;
    4. activated: 当引入 keep-alive 的时候,页面第一次进入,钩子的触发顺序 created-> mounted-> activated ,当再次进入(前进或者后退)时,只触发 activated
    5. deactivated: 退出时执行。

12. 什么是 vue 生命周期

  • beforeCreate: 在 new 一个 vue 实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。在 beforeCreate 生命周期执行的时候, datamethods 中的数据都还没有初始化。不能在这个阶段使用 data 中的数据和 methods 中的方法;

  • create: datamethods 都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作;

  • beforeMount: 执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中;

  • mounted: 执行到这个钩子的时候,就表示 Vue 实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。如果我们想要通过插件操作页面上的 DOM 节点,最早可以在和这个阶段中进行;

  • beforeUpdate: 当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步;

  • updated: 页面显示的数据和data中的数据已经保持同步了,都是最新的;

  • activated: 被 keep-alive 缓存的组件激活时调用;

  • deactivated: 被 keep-alive 缓存的组件失活时调用;

  • beforeDestory: vue3 改名为 beforeUnmount, Vue 实例从运行阶段进入到了销毁阶段,这个时候上所有的 datamethods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁

  • destroyed: vue3 改名为 unmounted, 这个时候上所有的 datamethods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。

13. 父子组件的执行顺序

  1. 加载:
父 beforeCreate -> 父created -> 父beforeMount-> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
  1. 更新:
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
  1. 销毁
父 beforeUnmount -> 子 beforeUnmount -> 子 unmounted -> 父 unmounted

14. 分别简述 computedwatch 的使用场景:

  • computed: 返回一个新属性,会缓存数据;可以依赖其他属性,比如根据其他多个属性来返回一个新的属性;也可以不依赖其他属性,主要是使用它的缓存属性,如用来缓存一个不经常改变的大容量数据;
  • watch: 需要依赖其他属性,且只能监听一个,作用是当该属性发生变化后,要执行什么样的操作;

15. vue 组件中 data 为什么是一个函数?

如果使用的是一个对象,那么当复用组件的时候,这些组件中的 data 指向的是同一个指针,他们之间的数据会重新冲突,而用函数返回的形式就没有这种问题;

但是很多人问,vue 组件中 data 为什么必须是一个函数,这种说法其实并不准确,在 vue 的根组件中, data 就可以使用对象,主要是因为根组件是单例。

16. $nextTick 的作用:

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

本质(简单的说):

  1. vue 用异步队列的方式来确保 nextTick 回调在 dom 更新后执行;
  2. vue 的异步队列优先使用 microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕,但因为兼容性问题,vue 不得不做了 microtaskmacrotask 的降级方案,最后一层就是 setTimeout.

17. vue-router 的两种模式

  1. hash 模式: 兼容性好,但是在 url 会存在 #,所以前端锚点功能也失效,且是前端路由,不会发送请求;
  2. history 模式: h5 新增,兼容性没 hash 好,路由改变,会向服务器发送请求(一般需要 nginx 配置),会在浏览器留下一个执行栈,通过 api 实现页面的切换。

18. paramsquery 的区别

  • params 传参:

    • 跳转页面时,需要配合路由的 name 使用,不能使用 path,否则 params 失效;
    • 效果类似于 post 请求,请求内容不会显示在 url,所以页面刷新会导致数据丢失。
  • query 传参:

    • query 配合 path 使用;
    • query 效果类似于 get,会将内容拼接到 url 上。

19. vue-router 有哪几种导航钩子

  • 全局钩子: beforeEach (全局前置守卫)、 afterEach (全局后置钩子) ;
  • 路由独享的守卫(单个路由里面的钩子): beforeEnter
  • 组件路由: beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave

20. 路由的 mate 属性

路由的 元信息,一般用来判断哪些页面有特定的权限,配合钩子函数使用;

const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    children: [
      {
        path: 'new',
        component: PostsNew,
        // 只有经过身份验证的用户才能创建帖子
        meta: { requiresAuth: true }
      },
      {
        path: ':id',
        component: PostsDetail
        // 任何人都可以阅读文章
        meta: { requiresAuth: false }
      }
    ]
  }
]

router.beforeEach((to, from) => {
  // 而不是去检查每条路由记录
  // to.matched.some(record => record.meta.requiresAuth)
  if (to.meta.requiresAuth && !auth.isLoggedIn()) {
    // 此路由需要授权,请检查是否已登录
    // 如果没有,则重定向到登录页面
    return {
      path: '/login',
      // 保存我们所在的位置,以便以后再来
      query: { redirect: to.fullPath },
    }
  }
})

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

  • 使用 location.href 来跳转,会刷新了页面;
  • 使用 Vue-router 不会刷新页面,不会跳转;

22. vuex 是什么?怎么使用?哪种功能场景使用它?

  • state => 基本数据(数据源存放地);
  • mutations => 提交更改数据的方法,只有在这个方法中才可以修改数据,同步;
    1. 通过 mapMutations 将方法结构绑定到组件中,然后直接使用;
    2. 通过 this.$store.commit 的方式触发;
  • actions => 像一个装饰器,包裹 mutations,使之可以异步;
    1. 通过 mapActions 将方法结构绑定到组件中,然后直接使用;
    2. 通过 this.$store.dispatch 的方式触发;
  • getters => 从基本数据派生出来的数据,能缓存数据,类似于 computer
  • modules => 模块化 Vuex

23. Vue 中双向数据绑定是如何实现的?

  1. 使用 Object.defineProperty 实现了数据劫持的效果;
  2. 通过 发布-订阅模式,实现了数据改变后,通知页面同步修改;
    1. Compile: 编译器;
    2. Observer: 递归实现对属性的监听;
    3. Watcher: 收到属性的变化,然后更新视图,一般会将 Watcher 存在 Dep 数组中。
  3. 在初始化编译的时候,根据模板语法,收集依赖。

缺点:

  1. 需要通过递归的形式,为每个属性绑定;
  2. 无法监听数组的变化;

vue3 中,使用 Proxy 来实现双向绑定:

  1. 可以对整个对象监听,而不是对象的每个属性;
  2. 可以监听倒数组的变化;
  3. 但它是 es6 的语法,所以兼容性略差;

24. Vue 中数组变化监听?

重写了数组的 push、pop、shift、unshift、splice、sort、reverse 七种方法,重写方法在实现时除了将数组方法名对应的原始方法调用一遍并将执行结果返回外,还通过执行ob.dep.notify()将当前数组的变更通知给其订阅者(也就是发布-订阅模式通知其他订阅者数据发生了变化)。

但是 vue3 就不需要这种, proxy 可以直接监听倒数组的变化。

25. vue3vue2 的异同?

  1. 底层支持 tree-shaking,使包的体积更小;
  2. ts 兼容的更好;
  3. 新的 setup 语法,与 react 的 hooks 语法类似,解放了 js 代码的灵活性;
  4. 使用 Proxy 代替 Object.defineProperty 来实现双向绑定,性能更好,同时能监听到数组的变化,但是兼容性略差;

26. 介绍一下 Vue 中的 Diff 算法

在 diff 中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从 O(n^3)降低值 O(n),也就是说,只有当新旧 children 都为多个子节点时才需要用核心的 Diff 算法进行同层级比较。

  • 首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换
  • 如果为相同节点,进行 patchVnode ,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子节点移除)
  • 比较如果都有子节点,则进行 updateChildren,判断如何对这些新老节点的子节点进行操作(diff 核心)。 匹配时,找到相同的子节点,递归比较子节点

27. active-class 是哪个组件的属性?嵌套路由怎么定义?

router-link 组件中的属性,用来做选中样式的切换; 嵌套路由一般设置在路由的 children 属性中。

28. 单页面应用有什么 SEO 方案?

为什么 SEO 对单页面应用不友好?

  1. 爬虫在爬取的过程中,不会去执行 js,所以隐藏在 js 中的跳转也不会获取到
  2. vue , react 通过 js 控制路由,但是页面永远是 index.html, 导致搜索引擎蜘蛛只能收录 index.html 一个页面,在百度中就搜索不到相关的子页面的内容,也导致模板中 mate 固定不变。

怎么解决?

  1. 使用 ssr 服务端渲染;
  2. 使用 prerender-spa-plugin 插件,打包的时候,生成部分静态页面;

29. 单页面应用首屏显示比较慢有什么解决方案?

  1. 路由懒加载;
  2. 使用 CDN 资源,减小包的体积;
  3. 将一些静态 js css 放到其他地方(如 OSS),减小服务器压力;
  4. 按需加载三方资源,如 iview,建议按需引入 iview 中的组件;
  5. 若首屏为登录页,可以做成多入口,登录页单独分离为一个入口;
  6. 如果项目过大,也可以考虑微应用,将按着模块将项目进行拆分。