Vue高频面试题

发布时间 2024-01-07 00:37:59作者: 前端自信逐梦者

1. 谈谈你对 Vue 的理解?

Vue 是一个渐进式的 js 框架,专注于构建用户界面。
Vue 的核心思想是数据驱动和组件化,通过将页面拆分为多个独立的组件,可以更好的管理到吗,
提高代码的复用性和可维护性。
Vue 的优势在于:简单易用,灵活性高,性能卓越和拓展性强。
Vue 的模板语法易于理解和学习,快速搭建 web 应用程序,同事 Vue 还提供了生命周期钩子和自定义函数等功能,可以实现较为复杂的业务场景,另外 Vue 还有强大的生态,提供了 Vuex,Vue router 等,进一步扩展 Vue 的功能。
Vue 的响应式数据绑定是 Vue 最核心的特性之一,通过数据劫持和监听,实现数据的双向绑定,即数据变化可以引起视图更新,同时视图的变化也会反映在数据上。

2. Vue 的 nextTick()原理

官网定义:等待下一次 DOM 更新刷新的工具方法。
在 Vue 中,有一个异步更新策略,它的原理是基于浏览器的异步任务队列,即当数据发生变化的时候,Vue 并不会立即去更新 DOM,而是开启一个异步任务队列,把 DOM 更新的操作保存在队列中,等待下一次事件循环时执行。
Vue.nextTick()则是将一个回调函数推入到异步任务队列中,等待 DOM 更新完成后执行。

2.1 具体实现方式

注意: 由于不同浏览器支持的异步任务方法不同,Vue 会根据浏览器的支持情况选择合适的异步任务方法(微任务到宏任务的降级处理)。

Promise 的 then -> MutationObserver 的回调函数 -> setImmediate -> setTimeout

  1. 使用原生 setTimeout 方法:在 Vue2 中,如果浏览器支持 Promise,则优先使用 Promise,如果不支持,则会使用 setTimeout 模拟异步操作。
  2. 使用 MutationObserver:如果浏览器支持 MutationObserver,vue 会使用 MutationObserver 监听 DOM 更新,并在 DOM 更新完成后执行函数。

    MutationObserver 是 HTML5 新增的属性,用于监听 DOM 修改事件,属于微任务

  3. 使用 setImmediate 方法: 在 IE 中,setImmediate 可以用来延迟执行异步任务,在 Vue2 中,如果浏览器支持 setImmediate,则会有限使用 setImmediate,否则使用 setTimeout。

3. 谈谈你对 Vuex 的理解

3.1 定义

Vuex 是一个专门为 vue.js 开发的一个状态管理库,提供了一个集中式的状态管理机制,管理多个组件的共享状态。

3.2 必要性

我们通常希望以一种单向数据流的方式去管理应用,即 state-->view-->actions 这种单向循环的方式,但是当个组件共享一个状态的时候,比如多个视图需要依赖同一状态,挥着来自不同视图的行为需要变更某一个状态,这就容易破坏这个单向数据流,这就需要将多个组件共享的状态提取出来,以一种全局单例管理模式去管理。

3.3 五大概念以及使用

import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';
export default {
  data() {
    return {};
  },
  computed: {
    ...mapState(['count', 'num']),
    ...mapGetters(['age']),
  },
  methods: {},
};
3.3.1. state

提供了一个公共数据源,存放组件的共享数据。

使用方法

  1. this.$store.state.变量名
  2. 使用辅助函数...mapState(['count','name'])
3.3.2. mutations

mutations 是一个对象,里面存放了修改 state 中数据的方法,想要修改 state 中的数据,只能通过 mutations,且 mutations 必须是同步的。
mutations 中方法的参数: 第一个是 state 属性,第二个的参数是传递参数,且只能传递一个参数,若想传递多个,可以传递一个对象。

使用方法

  1. this.$store.commit('addCount', 2)
  2. 使用辅助函数...mapMutations(['addCount'])
3.3.3. actions

mutations 中的方法都是同步,要想处理异步的逻辑,可以写在 actions 中,
actions 中的方法提交的是 mutation
actions 中方法的参数: 第一个是具有和 store 实例相同属性和方法的 context 对象,第二个参数是传递参数,用来传值

使用方法

  1. this.$store.dispatch('setAsyncCount', 2)
  2. 使用辅助函数...mapActions(['setAsyncCount'])
3.3.4. getters

有时候需要派生出 state 中的一些状态,这些状态又是依赖于 state 的。
getters 中方法的参数: 接收 state 参数

使用方法

  1. this.$store.getters.getDoubleCount
  2. 使用辅助函数...mapGetters(['getDoubleCount'])
3.3.5. module

由于 vuex 使用的是全局单一管理模式,所有的状态都会放在一个 state 中,这就会造成 state 臃肿,当 state 中存储的数据越来越大,就会变得难以维护。

// moduleA和moduleB都是一个
const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB,
  },
})
/*
    store.state.a // -> moduleA 的状态
    store.state.b // -> moduleB 的状态
*/

3.4 Vuex 的优缺点?

优点:

  • 实现了多个组件的状态共享
  • 提供了插件机制,可以对 vuex 的功能进行扩展

缺点:

  • 无法持久化数据
  • 使用了模块化,在访问 state 时,需要带上模块名,如果模块多层嵌套的话,代码会显得冗余,容易出错
  • 对 TS 的支持不友好
拓展:命名空间

如果在模块中设置了 namespaced: true, 在使用辅助函数的时候一定要传入模块名。

...mapState('moduleA', ['count', 'name'])
...mapMutations('moduleA', ['addCount'])

4. vue router 路由管理

Vue Router 是 Vue 提供的一款路由管理器,通过监听 URL 的变化,匹配路由规则,展示对应的组件,实现单页面应用的路由控制。

4.1 路由匹配

Vue Router 通过定义路由规则来匹配 URL 路径,根据对应匹配的结果展示对应的路由。同时也支持嵌套路由。

4.2 路由模式

Vue Router 支持两种路由模式,分别是 hash 模式和 history 模式。

hash 模式

在 hash 模式下,路由信息会被保存在 url 中的 hash 部分,通过原生事件 hashChange 监听 hash 部分变化来进行路由控制,url 发生变化,不会导致页面刷新。

history 模式

在 history 中,路由信息会被保存在浏览器的 history API(history.pushState、history.replaceState)中,通过修改浏览器的历史记录来进行路由控制。

4.3 路由导航

Vue Router 中的导航钩子可以监听路由变化,进行路由拦截、身份验证等操作。
导航钩子包括全局路由导航和组件内路由导航,可以在路由跳转前后执行不同的逻辑。

4.4 路由组件

Vue Router 通过组件的动态加载实现异步路由组件,可以根据需要动态加载路由组件。

使用 history 模式上线项目需要注意哪些?

首先使用 history 路由模式可以更好 moni 传统的多页面应用的 url 地址,让用户体验更加自然,但是需要考虑一些注意事项。

  1. 使用 history 模式,需要后端对可能出现的路由路径都进行处理,避免在刷新页面的时候出现 404 的情况。

    为什么使用 history 模式会出现刷新后 404 的情况呢?
    这是因为浏览器在刷新页面的时候偶会向服务器发送 GET 请求,但是服务器上没有对应的资源来匹配这个请求。
    如何解决呢?
    需要服务端进行响应的配置,让所有的路由都指向同一个入口文件,比如 index.html,
    具体的配置取决于服务端的类型,常见的 Apache 或 Nginx 等.

// 以Nginx为例:
server{
  listen: 80;
  server_name yourdomain.com;
  location / {
    root /usr/share/nginx/html;
    index index.html;
    try_files $uri $uri/ /index.html;
  }
}
/*
  使用上述配置后,Nginx会尝试查找请求的文件,如果找不到就重定向到index.html页面。这样用户访问时使用history路由模式的url时,Nginx会重定向到index.html页面,然后再解析URL,加载对应的组件。
*/
  1. 兼容性: History 模式是一种使用 HTML5 History API 的路由方案,在老的浏览器上可能会出现兼容问题。

  2. 安全性:使用 history 模式会在 url 上暴露出服务器上的文件路径,所以要确保恶意请求导致的安全问题。

  3. 在使用 webpack 打包工具打包时,要配置正确的 publicPath,保证 HTML 中引用的资源路径正确。

5. vue 项目中 scoped 的作用和原理?

scoped 是 Vue 组件中的 style 标签上的一个属性。
当一个 style 标签上拥有 scoped 属性时,这个 css 样式只能作用于当前组件,从而使得组件之前的样式不会冲突。
原理:给当前组件的实例生成了一个唯一标识,即给每个组件中的每个标签都加了一个标签属性 data-v-xxxx,这里的 xxxx 是一个 hash 值,根据文件的路径和内容生成的。

样式穿透
在开发中,我们会用到第三方库,但是第三方库的样式不是想要的,需要修改样式时就可以使用样式穿透。

常用的样式穿透有哪些?

  1. >>> ,使用在原生 css 中
  2. /deep/ ,使用在 less 中
  3. ::v-deep ,使用在 sass 中
  4. 在 vue3 中,建议使用 :deep()

6. Vue2 修改了数组中的哪些方法?

在 Vue2 中,使用 Object.defineProperty 无法监听数组内数据变化,所有重写了数组身上的方法。所以比如在 Vue2 中,调用数组的 push 其实是先调用了原生的数组 push 方法,然后再重新解析模板。
vue2 修改了数组身上的 push()、pop()、shift()、unshift()、splice()、sort()、reverse(),这些方法都是可以改变原数组的。

7. vue 中如何监听一个数组变化?

  1. 使用 watch 中的深度监听 deep:true
  2. 使用计算属性,返回数组的长度,但是这个无法监听数组元素的变化

8. vue 中父子组件的钩子执行顺序?

在加载阶段:

父组件 beforeCreated() -> 父组件 created() -> 父组件 beforeMount() -> 子组件 beforeCreated() -> 子组件 created() -> 子组件 beforeMount() -> 子组件 mounted() -> 父组件 mounted()

在更新阶段:

父组件 beforeUpdate() -> 子组件 beforeUpdate() -> 子组件 updated() -> 父组件 updated()

在销毁阶段:

父组件 beforeDestroy() -> 子组件 beforeDestroy() -> 子组件 destroyed() -> 父组件 destroyed()

如果使用了 keep-alive,被 keep-alive 包含的组件会交由 keep-alive 管理,因此在组件被激活或者停用时,执行生命周期也会发生变化。
在激活缓存中的组件时,先调用父组件的 activated(),再调用子组件的 activated()
在停用缓存中的组件时,先调用父组件的 deactivated(),再调用子组件的 deactivated()
注意缓存的组件没有销毁时期的生命周期函数,因为组件被缓存了,没有销毁。
注意
子组件的 mounted()只会在组件首次被加载时才会执行,当组件被缓存后再次激活时,mounted()不会执行,而是执行 activated()钩子,同时组件停用时不会执行 destroyed(),而是执行 deactivated()。

拓展: mounted()生命周期和 keep-alive 中 activated()的优先级?

当一个组件第一次被挂载时,会触发 mounted(),这是 keep-alive 中的缓存组件还没有被渲染,所以 activated()不会触发,只有当一个被缓存的组件被激活后,activated()才会被触发,因此在优先级上,mounted()的优先级高于 activated();

9. 父子组件传参的方式有哪些?

(区分 vue2 和 vue3)

  1. props 和 自定义事件

    props
    vue2 中:父传子直接在子组件定义从父组件传过来的值,用 props 接收
    vue3 中:父组件用 definePorops 定义接收的参数
    自定义事件
    vue2 中:使用 this.$emit('方法名')触发自定义事件
    vue3 中:子组件用 defineEmits 注册一个自定义事件,通过 emit('方法名')触发自定义事件

  2. 全局事件总线(vue2)

new Vue({
  beforeCreate() {
    Vue.prototype.$bus = this; // 安装总线
  },
});
  1. vuex
  2. 依赖注入 Provide / Inject
  3. 子组件暴露属性给父组件 defineExpose(vue3)

    当父组件想直接调用父组件的属性或者方法时,子组件可以使用 defineExpose 暴露自身的属性或者方法,父组件中使用 ref 调用子组件暴露的属性或方法。

  4. 本地存储
  5. mitt(需要安装,在项目中用过,封装过)