Skip to content

Vue.js 基础面试题

1. Vue 基础概念

问题:Vue.js 的核心特性是什么?

答案

核心特性

  1. 响应式数据绑定
javascript
const { createApp, ref } = Vue;

createApp({
  setup() {
    const count = ref(0);
    return { count };
  }
}).mount('#app');

// 修改数据
count.value++;
  1. 组件化
javascript
// 定义组件
const MyComponent = {
  template: '<div>{{ message }}</div>',
  data() {
    return {
      message: 'Hello Vue!'
    };
  }
};

// 使用组件
createApp({
  components: {
    MyComponent
  }
}).mount('#app');
  1. 指令系统
javascript
// v-bind
<div v-bind:href="url"></div>
<div :href="url"></div>

// v-on
<div v-on:click="doSomething"></div>
<div @click="doSomething"></div>

// v-model
<input v-model="message">

// v-if/v-else
<div v-if="show">Visible</div>
<div v-else>Hidden</div>

// v-for
<li v-for="item in items" :key="item.id">
  {{ item.name }}
</li>
  1. 虚拟 DOM
  • Vue 使用虚拟 DOM 来提高渲染性能
  • 通过 diff 算法比较新旧虚拟 DOM
  • 只更新需要变化的部分

2. Vue 生命周期

问题:Vue 的生命周期有哪些?

答案

生命周期钩子

javascript
export default {
  // 创建阶段
  beforeCreate() {
    console.log('beforeCreate');
    // 实例刚被创建,数据观测和事件配置之前
  },
  
  created() {
    console.log('created');
    // 实例创建完成,可以访问 data、methods 等
  },
  
  beforeMount() {
    console.log('beforeMount');
    // 挂载开始之前
  },
  
  mounted() {
    console.log('mounted');
    // 实例挂载完成,可以访问 DOM
  },
  
  // 更新阶段
  beforeUpdate() {
    console.log('beforeUpdate');
    // 数据更新,DOM 重新渲染之前
  },
  
  updated() {
    console.log('updated');
    // DOM 重新渲染完成
  },
  
  // 销毁阶段
  beforeUnmount() {
    console.log('beforeUnmount');
    // 实例销毁之前
  },
  
  unmounted() {
    console.log('unmounted');
    // 实例销毁完成
  }
};

Composition API 生命周期

javascript
import { onMounted, onUpdated, onUnmounted } from 'vue';

export default {
  setup() {
    onMounted(() => {
      console.log('mounted');
    });
    
    onUpdated(() => {
      console.log('updated');
    });
    
    onUnmounted(() => {
      console.log('unmounted');
    });
  }
};

3. 组件通信

问题:Vue 组件之间如何通信?

答案

父子通信

javascript
// 父组件
<template>
  <ChildComponent 
    :message="parentMessage" 
    @update="handleUpdate"
  />
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  data() {
    return {
      parentMessage: 'Hello from parent'
    };
  },
  methods: {
    handleUpdate(newMessage) {
      console.log('Received:', newMessage);
    }
  }
};
</script>

// 子组件
<template>
  <div>
    <p>{{ message }}</p>
    <button @click="sendUpdate">Send Update</button>
  </div>
</template>

<script>
export default {
  props: {
    message: {
      type: String,
      required: true
    }
  },
  methods: {
    sendUpdate() {
      this.$emit('update', 'Hello from child');
    }
  }
};
</script>

兄弟组件通信

javascript
// 事件总线
// eventBus.js
import { createApp } from 'vue';

const eventBus = createApp({});
export default eventBus;

// 组件 A
import eventBus from './eventBus';

export default {
  methods: {
    sendMessage() {
      eventBus.$emit('message', 'Hello');
    }
  }
};

// 组件 B
import eventBus from './eventBus';

export default {
  mounted() {
    eventBus.$on('message', (msg) => {
      console.log('Received:', msg);
    });
  },
  beforeUnmount() {
    eventBus.$off('message');
  }
};

跨层级通信(Provide/Inject)

javascript
// 祖先组件
export default {
  provide() {
    return {
      theme: 'dark',
      updateTheme: this.updateTheme
    };
  },
  methods: {
    updateTheme(newTheme) {
      this.theme = newTheme;
    }
  }
};

// 后代组件
export default {
  inject: ['theme', 'updateTheme'],
  methods: {
    changeTheme() {
      this.updateTheme('light');
    }
  }
};

4. Vuex 状态管理

问题:Vuex 如何使用?

答案

基本使用

javascript
// store.js
import { createStore } from 'vuex';

export default createStore({
  state: {
    count: 0,
    user: null
  },
  
  mutations: {
    increment(state) {
      state.count++;
    },
    setUser(state, user) {
      state.user = user;
    }
  },
  
  actions: {
    async fetchUser({ commit }) {
      const user = await api.getUser();
      commit('setUser', user);
    }
  },
  
  getters: {
    doubleCount: state => state.count * 2,
    isAuthenticated: state => !!state.user
  }
});

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';

const app = createApp(App);
app.use(store);
app.mount('#app');

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

模块化

javascript
// modules/user.js
export default {
  namespaced: true,
  state: {
    user: null
  },
  mutations: {
    setUser(state, user) {
      state.user = user;
    }
  },
  actions: {
    async fetchUser({ commit }) {
      const user = await api.getUser();
      commit('setUser', user);
    }
  }
};

// store.js
import user from './modules/user';

export default createStore({
  modules: {
    user
  }
});

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

5. Vue Router

问题:Vue Router 如何使用?

答案

基本配置

javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';

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

const router = createRouter({
  history: createWebHistory(),
  routes
});

export default router;

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app.use(router);
app.mount('#app');

路由导航

javascript
// 声明式导航
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'User', params: { id: 123 } }">User</router-link>

// 编程式导航
import { useRouter } from 'vue-router';

export default {
  setup() {
    const router = useRouter();
    
    const goToHome = () => {
      router.push('/');
    };
    
    const goToUser = (id) => {
      router.push({ name: 'User', params: { id } });
    };
    
    const goBack = () => {
      router.back();
    };
    
    return { goToHome, goToUser, goBack };
  }
};

路由守卫

javascript
// 全局前置守卫
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login');
  } else {
    next();
  }
});

// 全局后置钩子
router.afterEach((to, from) => {
  document.title = to.meta.title || 'My App';
});

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

// 组件内守卫
export default {
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    next();
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    next();
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开该组件的对应路由时调用
    next();
  }
};

6. Composition API

问题:Vue 3 的 Composition API 如何使用?

答案

基本使用

javascript
import { ref, reactive, computed, watch } from 'vue';

export default {
  setup() {
    // ref - 基本类型响应式
    const count = ref(0);
    
    // reactive - 对象类型响应式
    const user = reactive({
      name: 'John',
      age: 30
    });
    
    // computed - 计算属性
    const doubleCount = computed(() => count.value * 2);
    
    // watch - 监听
    watch(count, (newValue, oldValue) => {
      console.log(`Count changed from ${oldValue} to ${newValue}`);
    });
    
    // 方法
    const increment = () => {
      count.value++;
    };
    
    const updateName = (name) => {
      user.name = name;
    };
    
    // 返回给模板使用
    return {
      count,
      user,
      doubleCount,
      increment,
      updateName
    };
  }
};

setup 语法糖

javascript
<script setup>
import { ref, computed } from 'vue';

const count = ref(0);
const doubleCount = computed(() => count.value * 2);

function increment() {
  count.value++;
}
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

生命周期

javascript
import { onMounted, onUpdated, onUnmounted } from 'vue';

export default {
  setup() {
    onMounted(() => {
      console.log('Component mounted');
    });
    
    onUpdated(() => {
      console.log('Component updated');
    });
    
    onUnmounted(() => {
      console.log('Component unmounted');
    });
  }
};

7. 响应式原理

问题:Vue 的响应式原理是什么?

答案

Vue 2 响应式原理

javascript
// 简化版响应式实现
function defineReactive(obj, key, val) {
  const dep = new Dep();
  
  Object.defineProperty(obj, key, {
    get() {
      Dep.target && dep.addSub(Dep.target);
      return val;
    },
    set(newVal) {
      if (val === newVal) return;
      val = newVal;
      dep.notify();
    }
  });
}

class Dep {
  constructor() {
    this.subs = [];
  }
  
  addSub(sub) {
    this.subs.push(sub);
  }
  
  notify() {
    this.subs.forEach(sub => sub.update());
  }
}

class Watcher {
  constructor(vm, key, cb) {
    Dep.target = this;
    this.cb = cb;
    this.vm = vm;
    this.key = key;
    this.value = vm[key];
    Dep.target = null;
  }
  
  update() {
    const newValue = this.vm[this.key];
    if (this.value !== newValue) {
      this.value = newValue;
      this.cb.call(this.vm, newValue);
    }
  }
}

Vue 3 响应式原理

javascript
// 使用 Proxy 实现响应式
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        trigger(target, key);
      }
      return result;
    }
  });
}

// 依赖收集
const targetMap = new WeakMap();

function track(target, key) {
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  dep.add(activeEffect);
}

// 触发更新
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const dep = depsMap.get(key);
  if (dep) {
    dep.forEach(effect => effect());
  }
}

8. 虚拟 DOM 和 Diff 算法

问题:Vue 的虚拟 DOM 和 Diff 算法是如何工作的?

答案

虚拟 DOM

javascript
// 虚拟 DOM 节点结构
function h(tag, props, children) {
  return { tag, props, children };
}

// 创建虚拟 DOM
const vnode = h('div', { id: 'app' }, [
  h('p', {}, 'Hello'),
  h('p', {}, 'World')
]);

// 渲染为真实 DOM
function render(vnode) {
  if (typeof vnode === 'string') {
    return document.createTextNode(vnode);
  }
  
  const el = document.createElement(vnode.tag);
  
  // 设置属性
  if (vnode.props) {
    Object.entries(vnode.props).forEach(([key, value]) => {
      el.setAttribute(key, value);
    });
  }
  
  // 渲染子节点
  if (vnode.children) {
    vnode.children.forEach(child => {
      el.appendChild(render(child));
    });
  }
  
  return el;
}

Diff 算法

javascript
// 简化版 Diff 算法
function patch(oldVnode, newVnode) {
  // 节点类型不同,直接替换
  if (oldVnode.tag !== newVnode.tag) {
    const newEl = render(newVnode);
    oldVnode.el.parentNode.replaceChild(newEl, oldVnode.el);
    return;
  }
  
  const el = newVnode.el = oldVnode.el;
  
  // 比较属性
  if (oldVnode.props !== newVnode.props) {
    updateProps(el, oldVnode.props, newVnode.props);
  }
  
  // 比较子节点
  if (oldVnode.children !== newVnode.children) {
    updateChildren(el, oldVnode.children, newVnode.children);
  }
}

function updateChildren(el, oldChildren, newChildren) {
  // 简单的 Diff 实现
  const oldLen = oldChildren.length;
  const newLen = newChildren.length;
  const maxLen = Math.max(oldLen, newLen);
  
  for (let i = 0; i < maxLen; i++) {
    if (i < oldLen && i < newLen) {
      patch(oldChildren[i], newChildren[i]);
    } else if (i < newLen) {
      el.appendChild(render(newChildren[i]));
    } else {
      el.removeChild(el.childNodes[i]);
    }
  }
}

9. 组件优化

问题:如何优化 Vue 组件性能?

答案

v-if vs v-show

javascript
// v-if - 条件渲染(适合不频繁切换)
<div v-if="show">Content</div>

// v-show - CSS 切换(适合频繁切换)
<div v-show="show">Content</div>

计算属性缓存

javascript
export default {
  data() {
    return {
      items: [1, 2, 3, 4, 5]
    };
  },
  computed: {
    // 计算属性有缓存,只在依赖变化时重新计算
    evenItems() {
      return this.items.filter(item => item % 2 === 0);
    }
  },
  methods: {
    // 方法每次调用都会重新计算
    getEvenItems() {
      return this.items.filter(item => item % 2 === 0);
    }
  }
};

v-for 使用 key

javascript
// 使用唯一 key 提高渲染性能
<li v-for="item in items" :key="item.id">
  {{ item.name }}
</li>

组件懒加载

javascript
const routes = [
  {
    path: '/about',
    component: () => import('../views/About.vue')
  }
];

keep-alive 缓存

javascript
<template>
  <keep-alive>
    <component :is="currentComponent" />
  </keep-alive>
</template>

10. Vue 3 新特性

问题:Vue 3 有哪些新特性?

答案

Composition API

javascript
import { ref, reactive, toRefs } from 'vue';

export default {
  setup() {
    const state = reactive({
      count: 0,
      name: 'John'
    });
    
    // 解构响应式对象
    return {
      ...toRefs(state)
    };
  }
};

Teleport

javascript
<template>
  <button @click="showModal = true">Show Modal</button>
  
  <teleport to="body">
    <div v-if="showModal" class="modal">
      <p>Modal content</p>
      <button @click="showModal = false">Close</button>
    </div>
  </teleport>
</template>

Suspense

javascript
<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

Fragments

javascript
<template>
  <!-- 支持多个根节点 -->
  <header>Header</header>
  <main>Main</main>
  <footer>Footer</footer>
</template>

Tree-shaking

javascript
// Vue 3 模块化,支持 Tree-shaking
import { ref, computed } from 'vue';