面试官:你说你精通Vue3?这10道题能答对3道算我输!
一、Vue3的响应式系统如何抛弃了Object.defineProperty?
Vue2的响应式依赖Object.defineProperty劫持数据,但存在致命缺陷:无法监听数组索引/长度变化、对象属性新增删除需手动触发更新。
Vue3改用Proxy代理对象,直接拦截整个对象的操作(包括动态新增属性、数组方法等),并配合WeakMap优化依赖收集,内存占用减少50%。
隐藏考点:Proxy如何解决“深层嵌套对象监听”性能问题?答案在于“惰性劫持”——仅在访问时递归劫持子属性。
二、Composition API如何颠覆Options API的设计哲学?
Options API(如data、methods分块)在复杂组件中会导致逻辑分散,而Composition API通过setup()实现功能聚合:
// 复用逻辑封装为函数
function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
// 组件中按需组合
setup() {
const { count, increment } = useCounter()
return { count, increment }
}
陷阱题:为什么setup中不能直接解构reactive对象?需用toRefs保持响应性。
三、<script setup>如何实现“零样板代码”开发?
1.自动暴露机制与编译优化
<script setup>通过编译器实现了自动变量提升,所有顶层绑定(变量、函数、导入组件)都会直接暴露给模板:
<script setup>
import { ref } from 'vue'
const count = ref(0) // 自动暴露,无需手动return
</script>
此处的count无需通过setup()函数返回,编译阶段会将其直接注入渲染函数作用域。
2.自定义指令的两种注册方式
(1) 局部指令定义
在<script setup>中,以v开头的驼峰式变量可直接作为局部指令:
<script setup>
const vLoading = {
mounted(el) { el.loading = createLoadingElement() },
updated(el, binding) { /* 控制显示逻辑 */ }
}
</script>
(2) 全局指令复用
全局指令需通过app.directive()在入口文件注册,使用时需显式导入并重命名:
<script setup>
import { myDirective as vMyDirective } from '@/directives'
</script>
四、Teleport如何解决模态框的z-index地狱?
当模态框嵌套在复杂DOM中时,CSS层级可能被父容器限制。Teleport可将组件渲染到任意DOM节点:
内容
实战技巧:常配合Suspense实现异步加载,用展示加载状态。
五、Vue3如何实现“静态标记”优化diff算法?
编译阶段对静态节点打标记,跳过diff比较:
- 静态提升(HoistStatic):将纯静态节点提取到渲染函数外,避免重复创建
- 补丁标记(PatchFlag):动态节点标注变更类型(如TEXT、CLASS),diff时仅对比标记部分
实测该优化使更新性能提升1.3~2倍。
六、自定义指令的生命周期有哪些?实现一个v-loading指令
const vLoading = {
mounted(el, binding) {
el.loading = createLoadingElement()
if (binding.value) el.appendChild(el.loading)
},
updated(el, binding) {
binding.value ?
el.appendChild(el.loading) :
el.removeChild(el.loading)
}
}
易错点:指令生命周期命名变化——Vue3中bind改为beforeMount,unbind改为unmounted。
七、为什么ref需要.value而reactive不需要?
o ref:封装基本类型(如字符串、数字),通过.value访问响应式对象
o reactive:直接代理整个对象,属性可直接访问
底层揭秘:ref本质是{ value: xxx }的响应式包装,而reactive返回Proxy对象。
八、watch与watchEffect的差异及性能陷阱
// watch需明确监听源
watch(count, (newVal) => { ... })
// watchEffect自动收集依赖
watchEffect(() => {
console.log(count.value) // 自动追踪count
})
内存泄漏风险:watchEffect若在回调中访问DOM元素,需在onUnmounted中手动清除副作用。
九、全局组件注册的三大弊端,你中招了吗?
- 命名冲突:多团队协作时易重复
- Tree-shaking失效:未使用的组件无法被剔除
- 维护困难:难以追踪组件来源
最佳实践:优先使用局部注册,配合Vite的自动导入插件优化开发体验。
十、Provide/Inject如何替代Vuex实现跨层级通信?
// 祖先组件
const theme = ref('dark')
provide('theme', theme)
// 后代组件
const theme = inject('theme')
安全方案:建议提供默认值并校验类型,避免未提供时的运行时错误。
结语
以上10题,若能流畅回答5道以上,说明已深入Vue3核心机制。若被难住,不妨对照参考资料查漏补缺。在框架迭代飞快的今天,唯有持续深挖底层原理,才能在大厂面试中脱颖而出!
参考资料
: <script setup>发展历程与优势解析
: Composition API与setup执行机制
: Proxy响应式原理与性能优化
: <script setup>编译原理与性能优化
: 响应式变量解构与生命周期
: watchEffect内存管理技巧
: 自定义指令注册与钩子函数
: 全局/局部指令实现差异
: 指令参数绑定与修饰符处理