vue3 面试题汇总
vue3 相比 vue2 有什么变化?
vue3
相比vue2
的整体变化可以大致分类四类。分别是源码优化
、性能优化
、语法api优化
及引入RFC
源码优化:
- 使用 ts 重构了整个 vue
- 对 vue2 中冷门的功能进行了删除(如
inline-template
、filter
等) - 使用 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 的全局构造函数带来了很多问题:
- 调用构造函数的静态方法会对所有 vue 应用生效,不利于隔离不同的应用
- Vue2 的构造函数集成了太多功能,不利于 tree sharking;vue3 把这些功能使用普通函数导出,能够充分利用 tree sharking 进行优化打包
- 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 响应原理的细节(代码对应)
- 问:proxy 只会代理对象的第一层,那么 vue3 是怎么处理这个问题的呢?
答:判断当前 Reflect.get
的返回值是否为 Object
, 如果是则在通过 reactive 做代理,这样就实现了深度观测。
- 问:检测数组的时候可能触发多次 get/set, 那么如何防止触发多次呢?
答:可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger。
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 版本前:
<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>
<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
<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>
<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 算法。进行同层级比较。