2024前端Vue相关高频面试题(三)

262 阅读4分钟

前端高频面试题3.png

1. Vue的核心概念有哪些

Vue.js的核心概念包括以下几点:

  • 声明式渲染:使用简单的模板语法将数据与DOM连接起来,你只需要描述要显示什么,并且Vue会自动处理DOM更新。
  • 组件化:Vue鼓励将UI分解为可重用的组件。每个组件可以封装自己的数据和行为。
  • 响应式数据绑定:Vue利用对象监听和依赖追踪的机制,实现数据的响应式变化,确保数据变化时UI能自动更新。
  • 计算属性:对一些复杂的逻辑进行计算并缓存,只有在依赖的响应式数据发生变化时才会重新计算,有助于提高性能。
  • 指令:Vue提供了一些内置指令(如v-if, v-for, v-bind, v-model等)来处理DOM的显示和数据的绑定。
  • 生命周期钩子:Vue组件在不同的生命周期阶段(创建、更新、销毁)中提供的钩子方法,可以让开发者在特定时机执行代码。
  • 虚拟DOM:Vue.js使用虚拟DOM实现高效的DOM更新,减少了直接操作DOM带来的性能开销。

2. Vue组件的生命周期有哪些

Vue组件的生命周期可以分为以下几个主要阶段,每个阶段都提供了相应的钩子函数:

  1. 创建阶段

(1) beforeCreate

  • 组件实例创建之前调用
  • 此时data、methods等还未初始化
  • 应用场景:可以用来添加loading事件,在加载实例时触发

(2) created

  • 组件实例创建完成
  • 可以访问data、computed、methods等
  • 应用场景:进行异步数据获取、DOM操作(需要注意此时$el还未挂载)
  1. 挂载阶段

(3) beforeMount

  • 组件挂载到DOM前调用,相关的render函数首次被调用。
  • 应用场景:可以在这里对template进行最后的修改

(4) mounted

  • 组件已被挂载到DOM上,表示可以进行DOM操作。
  • 可以访问到DOM元素
  • 应用场景:进行DOM操作,调用第三方库初始化
  1. 更新阶段

(5) beforeUpdate

  • 组件数据发生变化,DOM尚未更新。
  • 应用场景:在更新之前访问现有的DOM,比如手动移除添加的事件监听器

(6) updated

  • 组件数据变化导致的DOM更新完成。
  • 应用场景:当数据更改导致的重新渲染完成后,执行DOM操作
  1. 销毁阶段

(7) beforeDestroy

  • 组件实例即将销毁,实例销毁之前调用
  • 应用场景:清理定时器、解绑全局事件、销毁插件对象等

(8) destroyed

  • 组件实例已被销毁,实例销毁后调用
  • 应用场景:做最后的清理操作

3. Vue中的响应式原理是怎样的

Vue的响应式原理主要依赖于“数据劫持”和“发布-订阅模式”两个概念。具体步骤如下:

  1. 数据劫持:Vue使用Object.defineProperty()技术对数据对象的每个属性进行劫持,拦截对这些属性的读写操作。
  2. 依赖收集:在属性被访问时,Vue会收集该属性的依赖(即使用该属性的组件或计算属性),记录下这些相关的“观察者”。
  3. 触发更新:当属性值发生变化时,Vue会触发相应的setter,发布通知,通知所有依赖于该属性的观察者(组件)重新渲染。
  4. 异步更新:为了优化性能,Vue会将重复的DOM更新合并为一次批量更新,在下一个"tick"时进行执行。

4. Vue指令是什么

Vue指令是特殊前缀v-的属性,用于在DOM元素上应用特定的行为。指令的主要功能包含条件渲染、循环渲染、事件绑定等,指令会在其表达式的值变化时自动执行。

常见指令

  • v-if、v-else、v-else-if:条件渲染。当条件满足时渲染元素,不满足则不渲染。
<div v-if="isVisible">可见</div>
  • v-for:循环渲染数组或对象。
<ul>
  <li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
  • v-model:实现双向数据绑定。
<input v-model="inputValue" />
  • v-bind:动态绑定属性。
<img v-bind:src="imageSrc" />
  • v-show:根据表达式的真假来控制元素的显示与否,表现为 CSS 的 display 属性的切换。
  • v-on:用于监听事件。
<button v-on:click="handleClick">Click me</button>

5. 计算属性和侦听器有什么区别

计算属性和侦听器都是 Vue 用来响应数据变化的功能,但它们的使用场景和行为有显著的不同:

计算属性

  • computed属性是一个函数,用于返回一个计算后的属性值,当依赖的属性发生变化时,computed属性会重新计算。
  • computed属性具有缓存特性,只有依赖的数据发生改变时才会重新计算。
  • computed属性通常用于对数据进行计算或处理后返回,不会直接修改数据。
export default {
    data() {
        return {
            firstName: 'John',
            lastName: 'Doe'
        }
    },
    computed: {
        // 基础用法
        fullName() {
            return `${this.firstName} ${this.lastName}`;
        },
        // 带getter和setter
        fullName: {
            get() {
                return `${this.firstName} ${this.lastName}`;
            },
            set(newValue) {
                const names = newValue.split(' ');
                this.firstName = names[0];
                this.lastName = names[1];
            }
        }
    }
}

watch:watch则是监听数据的变化并执行相应的回调函数。你可以在watch选项中监听一个或多个数据的变化,在数据发生变化时执行相应的回调函数。watch更适用于数据变化时需要进行异步操作或其他复杂操作的场景。

new Vue({
  data: {
    num: 10
  },
  watch: {
    num: function(newVal, oldVal) {
      console.log('Num的值从 ' + oldVal + ' 变为 ' + newVal);
    }
  }
});

6. Vue中的混入(mixins)是什么

混入是Vue的一种灵活且简单的代码复用方式。通过混入,可以将一组功能复用到多个组件中,混入对象的属性和方法将合并到每个组件中。这对于多个组件共享一些逻辑十分有用。

示例

const mixin = {
  data() {
    return {
      sharedData: 'This is shared!'
    }
  },
  methods: {
    sharedMethod() {
      console.log('This is a shared method');
    }
  }
}

const ComponentA = Vue.extend({
  mixins: [mixin],
  created() {
    console.log(this.sharedData); // "This is shared!"
  }
})

const ComponentB = Vue.extend({
  mixins: [mixin],
  methods: {
    anotherMethod() {
      this.sharedMethod(); // "This is a shared method"
    }
  }
})

7. Vue Router的基本用法是什么

Vue Router是Vue.js的官方路由管理器,为SPA(单页面应用)提供路由功能。基本用法包括以下步骤:

  1. 安装Vue Router
npm install vue-router
  1. 创建路由实例
import Vue from 'vue';
import Router from 'vue-router';
import Home from './components/Home.vue';
import About from './components/About.vue';

Vue.use(Router);

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
];

const router = new Router({
  routes
});
  1. 实例化Vue并挂载路由
new Vue({
  el: '#app',
  router,
  render: h => h(App)
});
  1. 在模板中使用<router-view><router-link>
<div id="app">
  <router-link to="/">Home</router-link>
  <router-link to="/about">About</router-link>
  <router-view></router-view>

</div>

8. Vuex是什么?如何使用

Vuex是一个专为Vue.js应用程序开发的状态管理库。它通过集中存储管理所有组件的状态,并以一种可预测的方式来更新状态。

组成部分:

  • State:应用的主要状态,存储状态数据
const store = new Vuex.Store({
    state: {
        count: 0,
        todos: []
    }
});

// 组件中访问
computed: {
    count() {
        return this.$store.state.count;
    }
}
  • Getters:从 State 中派生出的状态,可以认为是store的计算属性。
const store = new Vuex.Store({
    state: {
        todos: []
    },
    getters: {
        doneTodos: state => {
            return state.todos.filter(todo => todo.done);
        }
    }
});

// 组件中访问
computed: {
    doneTodos() {
        return this.$store.getters.doneTodos;
    }
}
  • Mutations:改变 State 的唯一方法,必须是同步的。
const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state, payload) {
            state.count += payload.amount;
        }
    }
});

// 组件中调用
methods: {
    increment() {
        this.$store.commit('increment', {
            amount: 10
        });
    }
}
  • Actions:可以包含异步操作,可以通过 commit 调用 mutation。
const store = new Vuex.Store({
    actions: {
        async fetchTodos({ commit }) {
            const response = await axios.get('/api/todos');
            commit('setTodos', response.data);
        }
    }
});

// 组件中调用
methods: {
    fetchTodos() {
        this.$store.dispatch('fetchTodos');
    }
}
  • Modules:当应用变得复杂时,可以将 Store 分割成模块,每个模块拥有自己的 state、getters、mutations 和 actions。
const moduleA = {
    state: { ... },
    mutations: { ... },
    actions: { ... },
    getters: { ... }
};

const moduleB = {
    state: { ... },
    mutations: { ... },
    actions: { ... }
};

const store = new Vuex.Store({
    modules: {
        a: moduleA,
        b: moduleB
    }
});

基本使用步骤:

  1. 安装Vuex
npm install vuex
  1. 创建Store
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    doubleCount: state => state.count * 2
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    }
  }
});
  1. 在Vue实例中注册Store
new Vue({
  el: '#app',
  store,
  render: h => h(App)
});
  1. 在组件中使用Store
computed: {
  count() {
    return this.$store.state.count;
  }
},
methods: {
  increment() {
    this.$store.dispatch('increment');
  }
}

9. Vue中的自定义事件是如何工作的

自定义事件用于在子组件中与父组件进行通信。可以通过$emit方法在子组件中触发事件,并通过父组件的事件监听来处理。

示例

// 子组件
Vue.component('child', {
  template: `<button @click="notifyParent">点击我</button>`,
  methods: {
    notifyParent() {
      this.$emit('childClicked', 'Hello Parent!');
    }
  }
});

// 父组件
Vue.component('parent', {
  template: `
    <div>
      <child @childClicked="handleChildClick"></child>
      <p>{{ message }}</p>
    </div>

  `,
  data() {
    return {
      message: ''
    };
  },
  methods: {
    handleChildClick(payload) {
      this.message = payload; // 更新父组件的数据
    }
  }
});

10. Vue的特性和优缺点是什么

优点

  • 简洁性:Vue.js的API简单易学,模板语法直观。
  • 响应式机制:数据模型的变化自动更新视图,对开发者很友好。
  • 灵活性:Vue可逐步采用,可以与现有项目轻松整合。
  • 强大的社区支持:丰富的文档和插件,帮助开发者快速上手。

缺点

  • 缺乏大型项目经验:对于大型项目的状态管理(虽然有Vuex)会有一定的学习曲线。
  • SEO支持不足:单页面应用的SEO能力相比传统多页面应用较弱,虽然可以使用Nuxt.js等解决方案。
  • 固定的生态系统:对一些开发者来说,Vue的生态系统较为固定,灵活性稍差。

11. Vue 组件的该如何设计

设计 Vue 组件时,需要遵循一些原则来提高代码的可维护性和重用性:

  1. 单一职责原则:每个组件只应该负责一项功能,避免组件过于复杂。
  2. 可复用性:考虑到组件的可复用性,设计时要将输入和输出明确,提供 props 和 events。
  3. 组件结构:合理的组织和结构化组件。例如,可以使用子组件来处理子功能,从而保持主组件的简洁。
  4. 样式封装:可以使用 scoped CSS 来限定样式的作用域,避免样式冲突。
  5. 文档化:为每个组件添加文档注释,明确其 API 和使用方式。
  6. 使用插槽:通过插槽提供灵活的内容填充方式,可以提高组件的复用性。
  7. 合理命名:组件的命名应能反映其功能,便于理解。

12. Vue 的性能优化方法有哪些

  1. 懒加载:使用 Vue Router 的懒加载特性,按需加载路由组件。
const MyComponent = () => import('./MyComponent.vue');
  1. 使用 keep-alive:对于不频繁变化的组件,可以使用 <keep-alive> 标签缓存组件状态。
  2. 使用v-ifv-show
    v-if 会在条件为真时添加/移除 DOM,而 v-show 会只切换元素的 display 样式。对于需要频繁切换的组件,选择 v-show 可以提升性能。
  3. 避免使用 v-for 中的索引作为键:应使用独特的标识符作为 key,以便 Vue 能够高效地更新 DOM。
  4. 优化计算属性:确保计算属性依赖于具体的数据,而不是使用复杂的逻辑,避免不必要的重计算。
  5. 减少 watchers 和 listeners:避免组件中使用过多的 watch 和 listen,导致性能负担。
  6. 使用异步组件:将不必要的组件延迟加载。
  7. 使用 Web Workers:处理 CPU 密集型任务时考虑使用 Web Worker,避免阻塞主线程。
  8. 第三方库按需引入
import { Button } from 'element-ui'
Vue.use(Button)

13. Vue中的过渡和动画是如何实现的

Vue.js通过内置指令v-transition和v-animation提供了过渡和动画效果的支持。过渡效果可以通过transition组件或transition属性实现,动画效果可以通过animate.css等库来实现。下面是一些过渡和动画的用法示例:

  • 使用transition组件:
<transition name="fade">
  <div v-if="isShow">我在这里</div>
</transition>
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
  • 使用animate.css库:
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
  <div v-if="isShow">我在这里</div>
</transition>

14. Vue组件通信有哪些方式

(1) props/$emit:父子组件通信

// 父组件
<template>
    <child-component 
        :message="message" 
        @update-message="handleUpdate"
    />
</template>

<script>
export default {
    data() {
        return {
            message: 'Hello'
        }
    },
    methods: {
        handleUpdate(value) {
            this.message = value;
        }
    }
}
</script>

// 子组件
<template>
    <div>
        <p>{{ message }}</p>
        <button @click="updateMessage">更新</button>
    </div>
</template>

<script>
export default {
    props: {
        message: {
            type: String,
            required: true
        }
    },
    methods: {
        updateMessage() {
            this.$emit('update-message', 'New Message');
        }
    }
}
</script>

(2) eventBus:跨组件通信

// eventBus.js
import Vue from 'vue';
export const EventBus = new Vue();

// 组件A
import { EventBus } from './eventBus';
methods: {
    sendMessage() {
        EventBus.$emit('custom-event', 'Hello from A');
    }
}

// 组件B
import { EventBus } from './eventBus';
created() {
    EventBus.$on('custom-event', (message) => {
        console.log(message);
    });
},
beforeDestroy() {
    EventBus.$off('custom-event');
}

(3) Vuex:全局状态管理

// store.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        INCREMENT(state) {
            state.count++;
        }
    },
    actions: {
        increment({ commit }) {
            commit('INCREMENT');
        }
    }
});

// 组件中使用
export default {
    computed: {
        count() {
            return this.$store.state.count;
        }
    },
    methods: {
        handleIncrement() {
            this.$store.dispatch('increment');
        }
    }
}

(4) provide/inject:依赖注入

// 父组件
export default {
    provide() {
        return {
            theme: this.theme
        }
    },
    data() {
        return {
            theme: 'dark'
        }
    }
}

// 子组件
export default {
    inject: ['theme']
}

15. Vue的路由实现原理是什么

答:Vue路由实现原理主要基于两种模式:hash模式和history模式。

(1) 实现原理:

Hash模式:

class HashRouter {
    constructor() {
        // 用于存储路由配置
        this.routes = {};
        // 监听hash变化
        window.addEventListener('hashchange', this.onHashChange.bind(this));
        // 初始化时触发一次
        window.addEventListener('load', this.onHashChange.bind(this));
    }

    // 注册路由
    register(path, callback) {
        this.routes[path] = callback;
    }

    // hash变化处理
    onHashChange() {
        const hash = location.hash.slice(1) || '/';
        this.routes[hash] && this.routes[hash]();
    }
}

History模式:

class HistoryRouter {
    constructor() {
        this.routes = {};
        this.bindPopState();
    }

    // 注册路由
    register(path, callback) {
        this.routes[path] = callback;
    }

    // 路由跳转
    push(path) {
        history.pushState({ path }, null, path);
        this.routes[path] && this.routes[path]();
    }

    // 监听popstate事件
    bindPopState() {
        window.addEventListener('popstate', (e) => {
            const path = e.state && e.state.path;
            this.routes[path] && this.routes[path]();
        });
    }
}

16 如何实现路由守卫?

import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

const router = new VueRouter({
    routes: [
        {
            path: '/',
            component: Home,
            meta: {
                requiresAuth: true
            }
        },
        {
            path: '/login',
            component: Login
        }
    ]
});

// 全局前置守卫
router.beforeEach((to, from, next) => {
    // 检查路由是否需要认证
    if (to.matched.some(record => record.meta.requiresAuth)) {
        // 检查用户是否已登录
        if (!isAuthenticated()) {
            next({
                path: '/login',
                query: { redirect: to.fullPath }
            });
        } else {
            next();
        }
    } else {
        next();
    }
});

// 全局后置钩子
router.afterEach((to, from) => {
    // 更新页面标题
    document.title = to.meta.title || 'Vue App';
});

// 路由独享守卫
const route = {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
        if (isAdmin()) {
            next();
        } else {
            next('/403');
        }
    }
};

// 组件内守卫
export default {
    beforeRouteEnter(to, from, next) {
        // 在渲染该组件的对应路由被验证前调用
        // 不能获取组件实例 `this`
        next(vm => {
            // 通过 `vm` 访问组件实例
        });
    },
    beforeRouteUpdate(to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 可以访问组件实例 `this`
        next();
    },
    beforeRouteLeave(to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
        const answer = window.confirm('确定要离开吗?');
        if (answer) {
            next();
        } else {
            next(false);
        }
    }
}

17. Vue中key的作用是什么

(1) key的主要作用:

  • 用于标识虚拟DOM中的节点
  • 优化DOM的更新
  • 提高列表渲染性能

(2) 使用示例:

// 不推荐
<div v-for="item in items">
    {{ item.text }}
</div>

// 推荐
<div v-for="item in items" :key="item.id">
    {{ item.text }}
</div>

(3) 原理解释:

  • 当数据发生变化时,Vue会根据key值来判断节点是否需要更新
  • 如果key值相同,Vue会复用已有节点
  • 如果key值不同,Vue会创建新节点并删除旧节点

18. Vue中的事件修饰符有哪些

Vue中的事件修饰符用于处理DOM事件,主要有.prevent、.stop、.capture、.self、.once等。

  • .prevent:阻止默认事件行为,相当于event.preventDefault()。
  • .stop:事件冒泡停止,相当于event.stopPropagation()。
  • .capture:事件捕获模式,捕获阶段触发事件处理程序。
  • .self:只在触发事件的元素自身上触发事件时才执行事件处理程序。
  • .once:事件只触发一次。