Skip to content

vue3 面试题汇总

vue3 相比 vue2 有什么变化?

vue3相比vue2的整体变化可以大致分类四类。分别是源码优化性能优化语法api优化及引入RFC

  • 源码优化:

    1. 使用 ts 重构了整个 vue
    2. 对 vue2 中冷门的功能进行了删除(如inline-templatefilter等)
    3. 使用 monorepo 重构了整个项目架构,将 vue 每个功能模块单独分包,使得整个框架更具有颗粒度,每个功能模块都可以单独引入使用
  • 性能优化: 性能上的优化是最核心的变化,vue3 重构优化了响应式、diff 算法、模版编译,整体相比 vue2 性能有了飞跃的提升

  • 语法 api 优化: 体现在新增了composition Api, 用于替代 vue2 时期的option Api,这种语法结构让功能逻辑代码更加集中。另外,conposition Api 让代码的颗粒度上更细,不需要再像之前使用mixin复用逻辑

  • RFC(Request For Comments) 引入了 RFC 的流程来处理新功能和重大更改

Vue 中的 v-for 和 vi-if 哪个优先级高?并说明造成的影响。

  • vue2 中,v-for 优先级高于 v-if;
  • vue3 中,v-if 优先级高于 v-for。

后果:开发时,增加了心智负担,需要在 v-if 或 v-for 嵌套一层(虽然 vue2 风格指南中也强烈不推荐同时使用)

为什么 Vue3 去掉了 vue2 时的构造函数

vue2 的全局构造函数带来了很多问题:

    1. 调用构造函数的静态方法会对所有 vue 应用生效,不利于隔离不同的应用
    1. Vue2 的构造函数集成了太多功能,不利于 tree sharking;vue3 把这些功能使用普通函数导出,能够充分利用 tree sharking 进行优化打包
    1. Vue2 没有把组件实例和 Vue 应用两个概念分开,在 Vue2 中,通过 new Vue()创建的对象,既是一个 vue 应用,又是一个特殊的 vue 组件,没有把两个概念区别开来,通过 createApp 创建的对象是一个 Vue 应用,它内部提供的方法是针对整个应用的,而不再是一个特殊的组件

Vue3 的响应式原理?

Vue3 的响应式通过使用 JS 的Proxy对象新的响应式 API 提供了更强大和灵活的响应式系统,解决了 Vue2 中的部分限制。

  • Proxy 对象: 使用 proxy 来代理对象的访问和修改操作,proxy 可以监听对象的所有操作,包括属性的读取、写入和删除。

  • 响应式 API: 如 ref、reactive。用于创建响应式对象和单个响应式值。

  • 依赖收集: 当响应式对象的属性被访问时,Vue 会记录依赖于该数据副作用(组件渲染等)。

  • 触发更新: 当响应式对象的属性被修改时,Vue 会通知所有依赖于该属性的副作用重新执行

Object.defineProperty 和 Proxy 的区别

  • Object.defineProperty 实现的响应式无法检测对象属性的添加和删除。需要使用 Vue 重写的 Vue.set 和 Vue.delete 手动添加或删除以保持响应式

    【Proxy 可以直接监听对象属性的添加或删除】

  • Object.defineProperty 无法监听数组索引和长度变化(如直接设置数组的长度来截断数组)

    【Proxy 可以监听数组的索引和长度的变化】

  • Object.defineProperty 的实现方式 响应式系统在处理大量数据时可能会有性能问题

    【Proxy 能更高效的跟踪依赖关系,减少不必要的更新,提高性能】

Vue3 响应原理的细节(代码对应)

    1. 问:proxy 只会代理对象的第一层,那么 vue3 是怎么处理这个问题的呢?

答:判断当前 Reflect.get的返回值是否为 Object, 如果是则在通过 reactive 做代理,这样就实现了深度观测。

    1. 问:检测数组的时候可能触发多次 get/set, 那么如何防止触发多次呢?

答:可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger。

javascript
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)
      // 进行依赖收集
      track(target, key)
      // 如果结果是一个对象,递归地将其转换为响应式对象
      return isObject(result) ? reactive(result) : result
    },
    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      const result = Reflect.set(target, key, value, receiver)
      // 如果值发生变化,触发依赖更新
      if (oldValue !== value) {
        trigger(target, key)
      }
      return result
    }
  })
}

function isObject(value) {
  return value !== null && typeof value === 'object'
}

// 示例用法
const state = reactive({
  count: 0,
  user: {
    name: 'Alice'
  }
})

console.log(state.count) // 输出: 0
console.log(state.user.name) // 输出: Alice

v-model 双向绑定的原理是什么?

v-model 本质上就是一个语法糖,可以看成是 value + input 方法的语法糖。可以通过 model 属性的 prop 和 event 属性来进行自定义。原生的 v-model,会根据标签的不同生成不同的时间和属性。

Vue3.x 自定义组件实现数据双向绑定,要求能够多个 v-model,如 title、content

3.4 版本前:

vue
<script setup>
const props = defineProps({
  a: String,
  b: String
})
</script>

<template>
  <div>
    <input :value="a" @input="$emit('update:a', $event.target.value)"></input>
    <input :value="b" @input="$emit('update:b', $event.target.value)"></input>
  </div>
</template>
vue
<script setup>
import { ref } from 'vue'
import MoreComp from './MoreComp.vue'

const a = ref(null)
const b = ref(null)
</script>

<template>
  <MoreComp v-model:a="a" v-model:b="b" />
</template>

3.4 版本后: defineModel

vue
<script setup>
const a = ref(null)
const b = ref(null)
</script>

<template>
  <div>
    <input v-model="a"></input>
    <input v-model="b"></input>
  </div>
</template>
vue
<script setup>
import { ref } from 'vue'
import MoreComp from './MoreComp.vue'

const a = defineModel('a')
const b = defineModel('b')
</script>

<template>
  <MoreComp v-model:a="a" v-model:b="b" />
</template>

Vue3 的 diff 算法?

Vue3.x 借鉴了 ivi 算法 和 inferno 算法, 在创建 VNode 时就确定其类型,以及在 mount/patch 的过程中采用 位运算来判断一个 VNode 的类型,在这个基础之上在配合核心的 Diff 算法,使得性能上较 Vue2.x 有了提升。

diff 算法

  • 同级比较,在比较子节点
  • 先判断一方有子节点,一方没有子节点的情况(如果新的 children 为空,则删除旧的 children)
  • 比较都有子节点的情况(核心 diff)
  • 递归比较子节点

正常 Diff 两个输的时间复杂度是 0(n^3), 但实际情况下我们很少会进行跨层级的移动 DOM, 所以 Vue 将 Diff 进行优化, 从 0(n^3) -> 0(n), 只有当新旧 children 都为多个子节点时才需要用核心的 diff 算法。进行同层级比较。