Vue2 与 Vue3 常见面试题精选 - 综合宝典

一、基础概念题

Q1:Vue3 相比 Vue2 的主要改进有哪些?

参考答案:

1. 性能方面:

  • 响应式系统重写(Proxy 替代 Object.defineProperty)
  • 虚拟 DOM 优化(静态提升、Patch Flag、Block Tree)
  • 编译优化(编译时标记、预字符串化)
  • 初次渲染快 55%,更新快 133%,内存占用减少 54%

2. API 设计:

  • Composition API(更好的逻辑复用和代码组织)
  • 更好的 TypeScript 支持
  • 支持 Tree-shaking(按需引入)
  • <script setup> 语法糖

3. 新特性:

  • Teleport 组件(传送门)
  • Fragments(多根节点)
  • Suspense(异步组件加载)
  • 更好的自定义渲染器 API

4. 生态系统:

  • Vite 构建工具(极速的开发体验)
  • Pinia 状态管理(替代 Vuex)
  • Vue Router 4 和 DevTools 3 的改进

Q2:为什么 Vue3 使用 Proxy 而不是 Object.defineProperty?

参考答案:

Object.defineProperty 的限制(Vue2):

javascript 复制代码
// 1. 无法检测属性的添加和删除
const obj = { name: 'John' }
Object.defineProperty(obj, 'name', { /* ... */ })

obj.age = 25  // ❌ 不会触发响应式
delete obj.name  // ❌ 不会触发响应式

// Vue2 需要使用 $set 和 $delete
this.$set(obj, 'age', 25)
this.$delete(obj, 'name')

// 2. 无法监听数组索引和长度
const arr = [1, 2, 3]
arr[0] = 100  // ❌ 不会触发响应式
arr.length = 0  // ❌ 不会触发响应式

// 3. 必须递归遍历对象的所有属性
// 初始化时性能开销大

Proxy 的优势(Vue3):

javascript 复制代码
// 1. 可以检测属性的添加和删除
const state = reactive({ name: 'John' })
state.age = 25  // ✅ 自动响应式
delete state.name  // ✅ 自动响应式

// 2. 可以监听数组索引和长度
const arr = reactive([1, 2, 3])
arr[0] = 100  // ✅ 自动响应式
arr.length = 0  // ✅ 自动响应式

// 3. 懒递归,按需代理
const state = reactive({
  level1: {
    level2: {
      level3: {}
    }
  }
})
// 只有访问到 level2 时才会创建其 Proxy

// 4. 支持更多数据类型
const map = reactive(new Map())
const set = reactive(new Set())

性能对比:

特性 Object.defineProperty Proxy
初始化性能 需要递归遍历所有属性 懒递归,按需处理
运行时性能 getter/setter 调用 拦截器调用
内存占用 每个属性需要闭包 对象级别拦截
新增属性 不支持 支持
删除属性 不支持 支持
数组索引 不支持 支持

Q3:Composition API 解决了什么问题?

参考答案:

Options API 的问题(Vue2):

javascript 复制代码
// 问题 1:逻辑分散
export default {
  data() {
    return {
      // 用户相关
      userName: '',
      userAge: 0,
      // 产品相关
      productList: [],
      productCount: 0
    }
  },
  
  computed: {
    // 用户相关计算属性在这里
    userInfo() { /* ... */ },
    // 产品相关计算属性在这里
    totalProducts() { /* ... */ }
  },
  
  methods: {
    // 用户相关方法在这里
    fetchUser() { /* ... */ },
    // 产品相关方法在这里
    fetchProducts() { /* ... */ }
  },
  
  mounted() {
    // 初始化逻辑混在一起
    this.fetchUser()
    this.fetchProducts()
  }
}

// 一个功能的代码被强制分散到不同的选项中
// 维护时需要频繁上下跳转
javascript 复制代码
// 问题 2:Mixins 的缺陷
// mixin1.js
export default {
  data() {
    return { count: 0 }  // 命名冲突风险
  },
  methods: {
    increment() { }  // 命名冲突风险
  }
}

// mixin2.js
export default {
  data() {
    return { count: 0 }  // 同名属性!
  }
}

// 组件
export default {
  mixins: [mixin1, mixin2],  // 哪个 count 会生效?
  mounted() {
    this.increment()  // 这个方法从哪来的?不清楚!
  }
}

// Mixins 的问题:
// 1. 命名冲突
// 2. 来源不清晰
// 3. 难以传递参数
// 4. 隐式依赖关系

Composition API 的解决方案:

javascript 复制代码
// 解决方案 1:按功能组织代码
// composables/useUser.js
export function useUser() {
  const userName = ref('')
  const userAge = ref(0)
  const userInfo = computed(() => `${userName.value} - ${userAge.value}`)
  const fetchUser = async () => { /* ... */ }
  
  return { userName, userAge, userInfo, fetchUser }
}

// composables/useProduct.js
export function useProduct() {
  const productList = ref([])
  const productCount = ref(0)
  const totalProducts = computed(() => productList.value.length)
  const fetchProducts = async () => { /* ... */ }
  
  return { productList, productCount, totalProducts, fetchProducts }
}

// 组件中使用
export default {
  setup() {
    // 用户相关逻辑都在一起
    const { userName, userAge, userInfo, fetchUser } = useUser()
    
    // 产品相关逻辑都在一起
    const { productList, totalProducts, fetchProducts } = useProduct()
    
    onMounted(() => {
      fetchUser()
      fetchProducts()
    })
    
    return {
      userName, userInfo,
      productList, totalProducts
    }
  }
}
typescript 复制代码
// 解决方案 2:更好的类型推导
// Vue2 + Options API
export default {
  data() {
    return {
      count: 0  // 类型推导困难
    }
  },
  computed: {
    double() {
      return this.count * 2  // this 的类型推导很困难
    }
  }
}

// Vue3 + Composition API
export default {
  setup() {
    const count = ref(0)  // Ref<number>
    const double = computed(() => count.value * 2)  // ComputedRef<number>
    
    // 完美的类型推导,无需手动标注
    return { count, double }
  }
}

优势总结:

特性 Options API Composition API
逻辑组织 按选项类型 按功能
代码复用 Mixins(有缺陷) 组合函数(清晰)
TypeScript 支持有限 完美支持
可测试性 需要组件实例 函数独立测试
Tree-shaking 不支持 支持

二、响应式系统题

Q4:ref 和 reactive 有什么区别?应该如何选择?

参考答案:

基本区别:

typescript 复制代码
// ref - 适用于基本类型
const count = ref(0)
const message = ref('Hello')
const isActive = ref(false)

// 访问和修改需要 .value
console.log(count.value)  // 0
count.value++  // 修改

// reactive - 适用于对象类型
const user = reactive({
  name: 'John',
  age: 25
})

// 直接访问和修改属性
console.log(user.name)  // John
user.age++  // 修改

深层原理:

typescript 复制代码
// ref 的实现(简化版)
function ref(value) {
  return {
    _isRef: true,
    get value() {
      track(this, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(this, 'value')
    }
  }
}

// reactive 的实现(简化版)
function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      track(target, key)
      return Reflect.get(target, key)
    },
    set(target, key, value) {
      const result = Reflect.set(target, key, value)
      trigger(target, key)
      return result
    }
  })
}

使用场景:

typescript 复制代码
// ✅ 推荐使用 ref
const count = ref(0)  // 基本类型
const user = ref<User | null>(null)  // 可能为 null 的对象
const list = ref<Item[]>([])  // 整个数组需要替换

// ✅ 推荐使用 reactive
const form = reactive({  // 表单对象,属性多
  username: '',
  email: '',
  password: ''
})

const state = reactive({  // 复杂的状态对象
  user: { name: '', age: 0 },
  settings: { theme: 'dark' }
})

常见陷阱:

typescript 复制代码
// 陷阱 1:reactive 解构丢失响应式
const user = reactive({ name: 'John', age: 25 })
const { name, age } = user  // ❌ 丢失响应式

// 解决方案:使用 toRefs
const { name, age } = toRefs(user)  // ✅ 保持响应式

// 陷阱 2:reactive 整体替换
const state = reactive({ count: 0 })
state = reactive({ count: 1 })  // ❌ 错误!不能重新赋值

// 解决方案:使用 ref 或修改属性
const state = ref({ count: 0 })
state.value = { count: 1 }  // ✅ 正确

// 或者
const state = reactive({ count: 0 })
state.count = 1  // ✅ 修改属性

// 陷阱 3:ref 在 reactive 中自动解包
const count = ref(0)
const state = reactive({
  count  // 自动解包
})

console.log(state.count)  // 0,不需要 .value
state.count++  // ✅ 正确

// 但在数组中不会自动解包
const arr = reactive([ref(0)])
console.log(arr[0].value)  // 需要 .value

选择建议:

typescript 复制代码
// 1. 基本类型 → ref
const count = ref(0)
const message = ref('hello')

// 2. 对象类型 → reactive 或 ref
// 选择 reactive:
const user = reactive({ name: '', age: 0 })

// 选择 ref:
const user = ref({ name: '', age: 0 })

// reactive 的优势:不需要 .value
// ref 的优势:可以整体替换

// 3. 数组 → ref(因为经常需要整体替换)
const list = ref([])
list.value = newList  // 常见操作

// 4. 在组合函数中统一返回 ref(推荐)
export function useCounter() {
  const count = ref(0)
  const double = computed(() => count.value * 2)
  return { count, double }  // 都是 ref 类型,使用一致
}

Q5:watchEffect 和 watch 的区别?

参考答案:

基本区别:

typescript 复制代码
// watchEffect - 自动追踪依赖
const count = ref(0)
const message = ref('Hello')

watchEffect(() => {
  // 自动追踪 count 和 message
  console.log(`Count: ${count.value}, Message: ${message.value}`)
})
// 立即执行,并在 count 或 message 变化时重新执行

// watch - 显式指定监听源
watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`)
})
// 默认不立即执行,只在 count 变化时执行

详细对比:

typescript 复制代码
// 1. 依赖追踪方式
// watchEffect - 自动
watchEffect(() => {
  console.log(count.value)  // 自动追踪 count
  console.log(message.value)  // 自动追踪 message
})

// watch - 手动指定
watch([count, message], ([newCount, newMessage]) => {
  console.log(newCount, newMessage)
})

// 2. 是否立即执行
// watchEffect - 立即执行
watchEffect(() => {
  console.log('立即执行一次')
})

// watch - 默认不立即执行
watch(count, () => {
  console.log('只在 count 变化时执行')
})

// watch 可以配置立即执行
watch(count, () => {
  console.log('立即执行一次,之后在 count 变化时执行')
}, { immediate: true })

// 3. 访问旧值
// watchEffect - 无法访问旧值
watchEffect(() => {
  console.log(count.value)  // 只能访问新值
})

// watch - 可以访问新值和旧值
watch(count, (newValue, oldValue) => {
  console.log(`从 ${oldValue} 变为 ${newValue}`)
})

// 4. 监听多个源
// watchEffect - 自动追踪所有依赖
watchEffect(() => {
  console.log(count.value, message.value, user.name)
  // 自动追踪 count, message, user.name
})

// watch - 需要显式指定
watch([count, message, () => user.name], ([newCount, newMsg, newName]) => {
  console.log(newCount, newMsg, newName)
})

使用场景:

typescript 复制代码
// 场景 1:副作用(推荐 watchEffect)
// 示例:根据用户 ID 获取数据
const userId = ref(1)

watchEffect(async () => {
  const response = await fetch(`/api/users/${userId.value}`)
  const data = await response.json()
  // 处理数据
})
// userId 变化时自动重新获取

// 场景 2:需要旧值(必须用 watch)
const count = ref(0)

watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`)
  if (newValue > oldValue) {
    console.log('Increased')
  } else {
    console.log('Decreased')
  }
})

// 场景 3:惰性执行(推荐 watch)
const searchQuery = ref('')

// 不希望初始执行,只在搜索词变化时执行
watch(searchQuery, async (newQuery) => {
  if (!newQuery) return
  const results = await search(newQuery)
  // 处理搜索结果
})

// 场景 4:监听深层对象(watch 配置 deep)
const user = reactive({
  profile: {
    name: 'John',
    age: 25
  }
})

watch(user, (newUser) => {
  console.log('User changed')
}, { deep: true })

// user.profile.age++ 会触发回调

清理副作用:

typescript 复制代码
// watchEffect - 使用 onCleanup
watchEffect((onCleanup) => {
  const timer = setTimeout(() => {
    console.log('Delayed log')
  }, 1000)
  
  // 在副作用重新执行或组件卸载前清理
  onCleanup(() => {
    clearTimeout(timer)
  })
})

// watch - 第三个参数
watch(userId, async (newId, oldId, onCleanup) => {
  let cancelled = false
  
  onCleanup(() => {
    cancelled = true
  })
  
  const data = await fetch(`/api/users/${newId}`)
  if (!cancelled) {
    // 使用数据
  }
})

性能考虑:

typescript 复制代码
// watchEffect 的性能陷阱
const bigData = reactive({
  items: Array(10000).fill(0)
})

// ❌ 不好:会追踪所有 items
watchEffect(() => {
  console.log('Items:', bigData.items)
})

// ✅ 更好:只追踪需要的部分
watchEffect(() => {
  console.log('First item:', bigData.items[0])
})

// ✅ 或使用 watch 精确控制
watch(() => bigData.items[0], (newValue) => {
  console.log('First item:', newValue)
})

选择指南:

场景 推荐方案 原因
简单的副作用 watchEffect 代码简洁
需要旧值 watch watchEffect 不支持
惰性执行 watch 避免初始执行
监听特定源 watch 精确控制
异步操作 watchEffect 自动追踪依赖

三、组件通信题

Q6:Vue3 中父子组件通信有哪些方式?

参考答案:

1. Props 和 Emits(最常用)

vue 复制代码
<!-- 父组件 -->
<template>
  <ChildComponent
    :message="parentMessage"
    :count="count"
    @update="handleUpdate"
    @delete="handleDelete"
  />
</template>

<script setup>
const parentMessage = ref('Hello from parent')
const count = ref(0)

const handleUpdate = (newValue) => {
  console.log('Updated:', newValue)
}

const handleDelete = (id) => {
  console.log('Delete:', id)
}
</script>

<!-- 子组件 -->
<script setup lang="ts">
// 定义 props
interface Props {
  message: string
  count: number
}

const props = defineProps<Props>()

// 定义 emits
interface Emits {
  (e: 'update', value: string): void
  (e: 'delete', id: number): void
}

const emit = defineEmits<Emits>()

const handleClick = () => {
  emit('update', 'New value')
  emit('delete', 123)
}
</script>

2. v-model(双向绑定)

vue 复制代码
<!-- 父组件 -->
<template>
  <CustomInput v-model="searchText" />
  <!-- 多个 v-model -->
  <UserProfile
    v-model:name="userName"
    v-model:email="userEmail"
  />
</template>

<!-- CustomInput 子组件 -->
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<!-- UserProfile 子组件 -->
<script setup>
defineProps(['name', 'email'])
defineEmits(['update:name', 'update:email'])
</script>

<template>
  <input :value="name" @input="$emit('update:name', $event.target.value)" />
  <input :value="email" @input="$emit('update:email', $event.target.value)" />
</template>

3. Provide / Inject(跨层级通信)

typescript 复制代码
// 祖先组件
import { provide, ref } from 'vue'

const theme = ref('dark')
const updateTheme = (newTheme: string) => {
  theme.value = newTheme
}

// 提供数据和方法
provide('theme', theme)
provide('updateTheme', updateTheme)

// 或使用对象一次提供多个
provide('themeContext', {
  theme,
  updateTheme
})

// 后代组件(任意层级)
import { inject } from 'vue'

// 注入数据和方法
const theme = inject('theme')
const updateTheme = inject('updateTheme')

// 或注入整个上下文
const themeContext = inject('themeContext')

// 使用默认值
const theme = inject('theme', 'light')  // 如果没有提供,使用 'light'

使用 InjectionKey 提供类型安全:

typescript 复制代码
// keys.ts
import { InjectionKey, Ref } from 'vue'

interface ThemeContext {
  theme: Ref<string>
  updateTheme: (theme: string) => void
}

export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme')

// 祖先组件
import { provide } from 'vue'
import { ThemeKey } from './keys'

const theme = ref('dark')
const updateTheme = (newTheme: string) => {
  theme.value = newTheme
}

provide(ThemeKey, {
  theme,
  updateTheme
})

// 后代组件
import { inject } from 'vue'
import { ThemeKey } from './keys'

const themeContext = inject(ThemeKey)
// themeContext 有完整的类型提示!

4. Expose / Template Refs(父组件访问子组件)

vue 复制代码
<!-- 子组件 -->
<script setup>
import { ref } from 'vue'

const count = ref(0)
const message = ref('Hello')

const increment = () => {
  count.value++
}

const reset = () => {
  count.value = 0
}

// 暴露给父组件
defineExpose({
  count,
  increment,
  reset
})
</script>

<!-- 父组件 -->
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const childRef = ref()

const handleClick = () => {
  // 访问子组件暴露的属性和方法
  console.log(childRef.value.count)
  childRef.value.increment()
  childRef.value.reset()
}
</script>

<template>
  <ChildComponent ref="childRef" />
  <button @click="handleClick">操作子组件</button>
</template>

5. Attrs(透传属性)

vue 复制代码
<!-- 子组件 -->
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()

// attrs 包含所有未被 props 声明的属性
console.log(attrs.class)
console.log(attrs.onClick)
</script>

<template>
  <!-- 自动继承父组件传递的属性 -->
  <div v-bind="$attrs">
    Content
  </div>
  
  <!-- 或手动控制 -->
  <div>
    <input v-bind="$attrs" />
  </div>
</template>

<!-- 禁用自动继承 -->
<script setup>
defineOptions({
  inheritAttrs: false
})
</script>

6. Pinia(全局状态管理)

typescript 复制代码
// stores/user.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  
  const login = async (credentials) => {
    // 登录逻辑
  }
  
  return { user, login }
})

// 任意组件中使用
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
console.log(userStore.user)
userStore.login(credentials)

通信方式选择指南:

场景 推荐方式 原因
父 → 子 Props 单向数据流
子 → 父 Emits 事件通知
双向绑定 v-model 简化代码
跨层级 Provide/Inject 避免 prop drilling
访问子组件实例 Expose/Ref 直接调用子组件方法
全局状态 Pinia 跨组件共享
兄弟组件 Pinia 或共同父组件 状态管理或提升状态

四、生命周期题

Q7:Vue3 生命周期有哪些变化?

参考答案:

Options API 生命周期(与 Vue2 类似):

javascript 复制代码
export default {
  beforeCreate() {
    // 实例初始化之后,数据观测和事件配置之前
  },
  
  created() {
    // 实例创建完成,数据观测、属性和方法运算、watch/event 事件回调
    // 还未挂载,$el 不可用
  },
  
  beforeMount() {
    // 挂载开始之前,render 函数首次调用
  },
  
  mounted() {
    // 实例挂载完成,可以访问 DOM
  },
  
  beforeUpdate() {
    // 数据更新时,虚拟 DOM 打补丁之前
  },
  
  updated() {
    // 虚拟 DOM 重新渲染和打补丁之后
  },
  
  beforeUnmount() {  // Vue2 中叫 beforeDestroy
    // 实例卸载之前
  },
  
  unmounted() {  // Vue2 中叫 destroyed
    // 实例卸载完成
  }
}

Composition API 生命周期:

typescript 复制代码
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

export default {
  setup() {
    // ⚠️ 没有 beforeCreate 和 created
    // setup() 本身就相当于这两个钩子
    
    console.log('相当于 beforeCreate 和 created')
    
    onBeforeMount(() => {
      console.log('before mount')
    })
    
    onMounted(() => {
      console.log('mounted')
      // 可以访问 DOM
    })
    
    onBeforeUpdate(() => {
      console.log('before update')
    })
    
    onUpdated(() => {
      console.log('updated')
    })
    
    onBeforeUnmount(() => {
      console.log('before unmount')
      // 清理工作:取消定时器、解绑事件等
    })
    
    onUnmounted(() => {
      console.log('unmounted')
    })
  }
}

新增的生命周期钩子:

typescript 复制代码
import {
  onActivated,
  onDeactivated,
  onErrorCaptured,
  onRenderTracked,
  onRenderTriggered
} from 'vue'

// 1. Keep-alive 组件激活时
onActivated(() => {
  console.log('Component activated')
})

// 2. Keep-alive 组件停用时
onDeactivated(() => {
  console.log('Component deactivated')
})

// 3. 捕获子孙组件错误
onErrorCaptured((err, instance, info) => {
  console.error('Error:', err)
  console.log('Component:', instance)
  console.log('Info:', info)
  return false  // 阻止错误继续传播
})

// 4. 开发环境:追踪响应式依赖
onRenderTracked((event) => {
  console.log('Tracked:', event)
  // 调试响应式依赖
})

// 5. 开发环境:响应式依赖触发重新渲染
onRenderTriggered((event) => {
  console.log('Triggered:', event)
  // 调试为什么重新渲染
})

生命周期执行顺序:

复制代码
父组件 setup
父组件 onBeforeMount
  子组件 setup
  子组件 onBeforeMount
  子组件 onMounted
父组件 onMounted

--- 更新时 ---
父组件 onBeforeUpdate
  子组件 onBeforeUpdate
  子组件 onUpdated
父组件 onUpdated

--- 卸载时 ---
父组件 onBeforeUnmount
  子组件 onBeforeUnmount
  子组件 onUnmounted
父组件 onUnmounted

实际使用示例:

typescript 复制代码
export default {
  setup() {
    const data = ref(null)
    const timer = ref(null)
    
    // 相当于 created
    console.log('组件创建')
    
    onMounted(async () => {
      // 获取数据
      data.value = await fetchData()
      
      // 启动定时器
      timer.value = setInterval(() => {
        console.log('Tick')
      }, 1000)
      
      // 添加事件监听
      window.addEventListener('resize', handleResize)
    })
    
    onBeforeUnmount(() => {
      // 清理定时器
      if (timer.value) {
        clearInterval(timer.value)
      }
      
      // 移除事件监听
      window.removeEventListener('resize', handleResize)
    })
    
    return { data }
  }
}

五、性能优化题

Q8:Vue3 中有哪些性能优化手段?

参考答案:

1. 编译时优化(自动)

vue 复制代码
<!-- 静态提升 -->
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>Static content</p>  <!-- 自动提升 -->
    <span>{{ message }}</span>
  </div>
</template>

<!-- 编译后 -->
<script>
const _hoisted_1 = createVNode('p', null, 'Static content')

export function render() {
  return createVNode('div', null, [
    createVNode('h1', null, _ctx.title),
    _hoisted_1,  // 复用
    createVNode('span', null, _ctx.message)
  ])
}
</script>

2. v-memo 指令

vue 复制代码
<template>
  <!-- 大列表优化 -->
  <div
    v-for="item in list"
    :key="item.id"
    v-memo="[item.id, item.selected]"
  >
    <!-- 只有 id 或 selected 变化时才重新渲染 -->
    <div>{{ item.name }}</div>
    <div>{{ item.description }}</div>
  </div>
</template>

3. 虚拟列表

typescript 复制代码
// 使用 vue-virtual-scroller 或自己实现
import { ref, computed } from 'vue'

export function useVirtualList(items, itemHeight, visibleCount) {
  const scrollTop = ref(0)
  
  const visibleItems = computed(() => {
    const start = Math.floor(scrollTop.value / itemHeight)
    const end = start + visibleCount
    return items.value.slice(start, end)
  })
  
  const totalHeight = computed(() => items.value.length * itemHeight)
  
  const offsetY = computed(() => 
    Math.floor(scrollTop.value / itemHeight) * itemHeight
  )
  
  return {
    visibleItems,
    totalHeight,
    offsetY,
    scrollTop
  }
}

4. 异步组件

typescript 复制代码
// 路由懒加载
const routes = [
  {
    path: '/dashboard',
    component: () => import('@/views/Dashboard.vue')
  }
]

// 组件懒加载
import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent({
  loader: () => import('./Heavy.vue'),
  loadingComponent: LoadingSpinner,
  delay: 200,
  timeout: 3000
})

5. 使用 shallowRef 和 shallowReactive

typescript 复制代码
// 对于大型数据结构
const bigData = shallowRef({
  items: Array(10000).fill(0)
})

// 整体替换会触发更新
bigData.value = { items: newItems }  // ✅

// 修改内部不触发更新(性能更好)
bigData.value.items[0] = 1  // ❌ 不触发更新

// 手动触发更新
import { triggerRef } from 'vue'
triggerRef(bigData)

6. computed 缓存

typescript 复制代码
// ❌ 不好:每次都计算
const getTotal = () => {
  return items.value.reduce((sum, item) => sum + item.price, 0)
}

// ✅ 好:缓存结果
const total = computed(() => {
  return items.value.reduce((sum, item) => sum + item.price, 0)
})

7. 避免不必要的响应式

typescript 复制代码
// ❌ 不好:配置对象不需要响应式
const config = reactive({
  apiUrl: 'https://api.example.com'
})

// ✅ 好
const config = {
  apiUrl: 'https://api.example.com'
}

// ❌ 不好:大型静态数据
const bigStaticData = reactive({
  /* 10000+ 项 */
})

// ✅ 好:使用 markRaw
const bigStaticData = markRaw({
  /* 10000+ 项 */
})

8. 使用 KeepAlive 缓存组件

vue 复制代码
<template>
  <KeepAlive :max="10">
    <component :is="currentComponent" />
  </KeepAlive>
</template>

性能优化检查清单:

markdown 复制代码
- [ ] 使用 computed 代替 method
- [ ] 大列表使用虚拟滚动
- [ ] 使用 v-memo 优化列表
- [ ] 路由和组件懒加载
- [ ] 使用 shallowRef/shallowReactive
- [ ] 避免不必要的响应式
- [ ] 使用 KeepAlive 缓存组件
- [ ] 合理使用 v-show 和 v-if
- [ ] 使用 key 优化列表渲染
- [ ] 避免在模板中使用复杂表达式

总结: 这份文档涵盖了 Vue2 和 Vue3 的核心面试题,包括基础概念、响应式系统、组件通信、生命周期和性能优化等方面。掌握这些内容可以应对大部分 Vue 相关的面试问题。

相关推荐
We་ct2 小时前
LeetCode 383. 赎金信:解题思路+代码解析+优化实战
前端·算法·leetcode·typescript
BYSJMG2 小时前
Python毕业设计选题推荐:基于大数据的美食数据分析与可视化系统实战
大数据·vue.js·后端·python·数据分析·课程设计·美食
东东5162 小时前
OA自动化居家办公管理系统 ssm+vue
java·前端·vue.js·后端·毕业设计·毕设
周某人姓周2 小时前
DOM型XSS案例
前端·安全·web安全·网络安全·xss
程序员鱼皮2 小时前
前特斯拉 AI 总监:AI 编程最大的谎言,是 “提效”
前端·后端·ai·程序员·开发
Irene19912 小时前
Vue3 规范推荐的 <script setup> 中书写顺序(附:如何响应路由参数变化)
vue.js·路由
pusheng20252 小时前
普晟传感2026年新春年会总结与分析
前端·javascript·html
谢尔登2 小时前
React19事件调度的设计思路
前端·javascript·react.js
Emma_Maria3 小时前
本地项目html和jquery,访问地址报跨域解决
前端·html·jquery