Skip to content

Vue 面试题精选

Vue.js 作为主流前端框架,是面试中的重点考察内容。这里整理了最常见的 Vue 面试题和详细解答。

🔥 Vue2 vs Vue3 对比

问题:Vue2 和 Vue3 有什么主要区别?

答案

1. 响应式系统

javascript
// Vue2 - Object.defineProperty
Object.defineProperty(obj, 'name', {
  get() {
    console.log('获取 name')
    return value
  },
  set(newValue) {
    console.log('设置 name')
    value = newValue
  }
})

// Vue3 - Proxy
const reactive = new Proxy(target, {
  get(target, key) {
    console.log('获取', key)
    return target[key]
  },
  set(target, key, value) {
    console.log('设置', key, value)
    target[key] = value
    return true
  }
})

Vue3 优势

  • 可以监听数组索引和 length 变化
  • 可以监听对象属性的添加和删除
  • 支持 Map、Set、WeakMap、WeakSet
  • 更好的性能表现

2. 组合式 API vs 选项式 API

javascript
// Vue2 选项式 API
export default {
  data() {
    return {
      count: 0,
      user: null
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    this.fetchUser()
  }
}

// Vue3 组合式 API
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = ref(null)
    
    const doubleCount = computed(() => count.value * 2)
    
    const increment = () => {
      count.value++
    }
    
    const fetchUser = async () => {
      // 获取用户数据
    }
    
    onMounted(() => {
      fetchUser()
    })
    
    return {
      count,
      user,
      doubleCount,
      increment
    }
  }
}

3. 性能提升

  • 编译时优化:静态提升、补丁标记、树摇优化
  • 运行时优化:更快的组件初始化、更小的内存占用
  • 包体积:Vue3 核心库更小,支持 tree-shaking

🔄 Vue 响应式原理

问题:详细解释 Vue 的响应式原理

答案

Vue2 响应式原理

javascript
// 简化版 Vue2 响应式实现
class Observer {
  constructor(data) {
    this.walk(data)
  }
  
  walk(data) {
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key])
    })
  }
  
  defineReactive(obj, key, val) {
    const dep = new Dep()
    
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 依赖收集
        if (Dep.target) {
          dep.depend()
        }
        return val
      },
      set(newVal) {
        if (newVal === val) return
        val = newVal
        // 派发更新
        dep.notify()
      }
    })
  }
}

class Dep {
  constructor() {
    this.subs = []
  }
  
  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target)
    }
  }
  
  notify() {
    this.subs.forEach(watcher => {
      watcher.update()
    })
  }
}

class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm
    this.cb = cb
    this.getter = expOrFn
    this.value = this.get()
  }
  
  get() {
    Dep.target = this
    const value = this.getter.call(this.vm)
    Dep.target = null
    return value
  }
  
  update() {
    const newValue = this.get()
    const oldValue = this.value
    this.value = newValue
    this.cb.call(this.vm, newValue, oldValue)
  }
}

Vue3 响应式原理

javascript
// 简化版 Vue3 响应式实现
const targetMap = new WeakMap()
let activeEffect = null

function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      // 依赖收集
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      // 触发更新
      trigger(target, key)
      return true
    }
  })
}

function track(target, key) {
  if (!activeEffect) return
  
  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())
  }
}

function effect(fn) {
  activeEffect = fn
  fn()
  activeEffect = null
}

🎨 组件通信方式

问题:Vue 组件间通信有哪些方式?

答案

1. Props / $emit

vue
<!-- 父组件 -->
<template>
  <ChildComponent 
    :message="parentMessage" 
    @child-event="handleChildEvent"
  />
</template>

<script>
export default {
  data() {
    return {
      parentMessage: 'Hello from parent'
    }
  },
  methods: {
    handleChildEvent(data) {
      console.log('收到子组件数据:', data)
    }
  }
}
</script>
vue
<!-- 子组件 -->
<template>
  <div>
    <p>{{ message }}</p>
    <button @click="sendToParent">发送给父组件</button>
  </div>
</template>

<script>
export default {
  props: ['message'],
  methods: {
    sendToParent() {
      this.$emit('child-event', 'Hello from child')
    }
  }
}
</script>

2. $parent / $children / $refs

vue
<template>
  <div>
    <ChildComponent ref="child" />
    <button @click="callChild">调用子组件方法</button>
  </div>
</template>

<script>
export default {
  methods: {
    callChild() {
      // 通过 ref 调用子组件方法
      this.$refs.child.childMethod()
      
      // 通过 $children 访问(Vue2)
      this.$children[0].childMethod()
    }
  }
}
</script>

3. provide / inject

vue
<!-- 祖先组件 -->
<script>
export default {
  provide() {
    return {
      theme: 'dark',
      user: this.user
    }
  },
  data() {
    return {
      user: { name: 'Alice' }
    }
  }
}
</script>
vue
<!-- 后代组件 -->
<script>
export default {
  inject: ['theme', 'user'],
  created() {
    console.log(this.theme) // 'dark'
    console.log(this.user)  // { name: 'Alice' }
  }
}
</script>

4. EventBus(Vue2)

javascript
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()

// 组件A - 发送事件
import { EventBus } from './event-bus'

export default {
  methods: {
    sendMessage() {
      EventBus.$emit('message-sent', 'Hello from A')
    }
  }
}

// 组件B - 接收事件
import { EventBus } from './event-bus'

export default {
  created() {
    EventBus.$on('message-sent', (message) => {
      console.log('收到消息:', message)
    })
  },
  beforeDestroy() {
    EventBus.$off('message-sent')
  }
}

5. Vuex 状态管理

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

export default createStore({
  state: {
    user: null,
    theme: 'light'
  },
  mutations: {
    SET_USER(state, user) {
      state.user = user
    },
    SET_THEME(state, theme) {
      state.theme = theme
    }
  },
  actions: {
    updateUser({ commit }, user) {
      commit('SET_USER', user)
    }
  }
})

🔄 生命周期详解

问题:详细说明 Vue 组件的生命周期

答案

Vue2 生命周期

javascript
export default {
  // 1. 实例创建前
  beforeCreate() {
    console.log('beforeCreate: 实例初始化之后,数据观测和事件配置之前')
    // 此时 data、methods 都不可用
  },
  
  // 2. 实例创建后
  created() {
    console.log('created: 实例创建完成')
    // data、methods 可用,但 DOM 未挂载
    // 适合进行数据初始化、API 调用
  },
  
  // 3. 挂载前
  beforeMount() {
    console.log('beforeMount: 挂载开始之前')
    // 模板编译完成,但未挂载到 DOM
  },
  
  // 4. 挂载后
  mounted() {
    console.log('mounted: 挂载完成')
    // DOM 挂载完成,可以进行 DOM 操作
    // 适合初始化第三方库、获取 DOM 元素
  },
  
  // 5. 更新前
  beforeUpdate() {
    console.log('beforeUpdate: 数据更新时调用')
    // 数据更新,但 DOM 未重新渲染
  },
  
  // 6. 更新后
  updated() {
    console.log('updated: DOM 重新渲染完成')
    // 避免在此钩子中修改数据,可能导致无限循环
  },
  
  // 7. 销毁前
  beforeDestroy() {
    console.log('beforeDestroy: 实例销毁之前')
    // 适合清理定时器、取消订阅、解绑事件
  },
  
  // 8. 销毁后
  destroyed() {
    console.log('destroyed: 实例销毁后')
    // 所有指令解绑、事件监听器移除、子实例销毁
  }
}

Vue3 生命周期

javascript
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

export default {
  setup() {
    // beforeCreate 和 created 在 setup 中不需要
    
    onBeforeMount(() => {
      console.log('onBeforeMount')
    })
    
    onMounted(() => {
      console.log('onMounted')
    })
    
    onBeforeUpdate(() => {
      console.log('onBeforeUpdate')
    })
    
    onUpdated(() => {
      console.log('onUpdated')
    })
    
    onBeforeUnmount(() => {
      console.log('onBeforeUnmount')
    })
    
    onUnmounted(() => {
      console.log('onUnmounted')
    })
  }
}

🎯 虚拟 DOM 和 Diff 算法

问题:解释虚拟 DOM 的工作原理和 Diff 算法

答案

虚拟 DOM 概念

javascript
// 真实 DOM
<div id="app">
  <p class="text">Hello World</p>
  <button onclick="handleClick()">Click me</button>
</div>

// 虚拟 DOM 表示
const vnode = {
  tag: 'div',
  props: { id: 'app' },
  children: [
    {
      tag: 'p',
      props: { class: 'text' },
      children: ['Hello World']
    },
    {
      tag: 'button',
      props: { onclick: 'handleClick()' },
      children: ['Click me']
    }
  ]
}

Diff 算法核心

javascript
// 简化版 Diff 算法
function diff(oldVNode, newVNode) {
  // 1. 节点类型不同,直接替换
  if (oldVNode.tag !== newVNode.tag) {
    return { type: 'REPLACE', newVNode }
  }
  
  // 2. 文本节点
  if (typeof newVNode === 'string') {
    if (oldVNode !== newVNode) {
      return { type: 'TEXT', text: newVNode }
    }
    return null
  }
  
  // 3. 属性对比
  const propsPatches = diffProps(oldVNode.props, newVNode.props)
  
  // 4. 子节点对比
  const childrenPatches = diffChildren(oldVNode.children, newVNode.children)
  
  return {
    type: 'UPDATE',
    props: propsPatches,
    children: childrenPatches
  }
}

function diffChildren(oldChildren, newChildren) {
  const patches = []
  const maxLength = Math.max(oldChildren.length, newChildren.length)
  
  for (let i = 0; i < maxLength; i++) {
    const oldChild = oldChildren[i]
    const newChild = newChildren[i]
    
    if (!oldChild) {
      patches[i] = { type: 'ADD', newVNode: newChild }
    } else if (!newChild) {
      patches[i] = { type: 'REMOVE' }
    } else {
      patches[i] = diff(oldChild, newChild)
    }
  }
  
  return patches
}

💡 答题技巧

1. 结构化回答

  • 概念解释:先说明是什么
  • 原理分析:解释为什么这样设计
  • 代码示例:用代码演示
  • 应用场景:说明实际用途
  • 注意事项:提及常见陷阱

2. 常见追问

  • 响应式原理:Vue2 和 Vue3 的区别、性能对比
  • 组件通信:各种方式的适用场景、优缺点
  • 生命周期:具体的使用场景、注意事项
  • 虚拟 DOM:与真实 DOM 的性能对比、优化策略

3. 加分项

  • 能够手写简单的响应式系统
  • 了解 Vue3 源码实现
  • 知道性能优化技巧
  • 能够举出实际项目经验

下一步:查看 React 面试题 或 手写代码题