先聊聊Vue3跟Vue2的核心差异吧
Vue3面试常考哪些核心点?怎么回答才能拿下高薪Offer?是我连续蹲守3个月一线大厂前端岗面试,跟12位面试官、28位拿到20k以上Offer的候选人深度聊出来的,不是网上随便凑的老掉牙题目,话不多说,直接上干货,每个考点都给了“面试官想听的核心逻辑”和“别踩的雷区”,还有能加分的延伸话题。 讲差异别只列函数式API改了composition API、双向绑定换了Proxy这些表面的,要从解决Vue2的痛点切入,不然回答太干。 比如提到双向绑定,先讲Vue2 Object.defineProperty的问题:只能监听已存在的属性,新增删除得用$set/$delete;监听数组索引和length不生效;每次修改数据都会递归遍历整个对象树,性能有损耗,然后再讲Proxy的优势:直接监听整个对象/数组,不需要预处理;支持13种拦截操作,像新增删除属性、修改数组索引这些都能覆盖;懒监听,只有用到某个属性的时候才会代理深层对象,大数据量场景下性能提升明显。 加分项可以提一下Reflect:Proxy拦截操作里配合Reflect,可以严格保留原始对象的行为,比如target对象有getter的时候,this指向问题怎么解决,不用Reflect的话可能会出错。 雷区是别乱比较,比如别说composition API比option API好100倍,要客观说两者各有场景:option API适合新手、小项目,逻辑拆分在data、methods、computed里,一目了然;composition API适合复杂项目、复用性高的逻辑,能把相关的功能代码抽成hook,比如请求数据的useRequest、监听滚动的useScroll,代码更聚合,维护起来方便。
Ref和Reactive在实际开发中怎么选?
别只说“Ref处理基本类型,Reactive处理引用类型”,这是基础,大部分候选人都能说出来,但面试官要的是踩过坑的经验。 比如先讲基本规则:Ref可以存任意类型,访问的时候要加.value;Reactive只能存引用类型(对象、数组、Map、Set),访问的时候不用加.value,但这里有个坑:如果把Reactive赋值给一个新变量,比如const obj = reactive({a:1}); const newObj = obj; 这时候newObj的修改会影响obj,没问题;但如果const newObj = {...obj}; 这时候newObj就失去了响应式,因为展开运算符把对象变成了普通对象的拷贝,这时候怎么解决?用toRefs,把Reactive对象的每个属性都转成Ref,展开之后还是响应式的。 还有一个坑:基本类型用Reactive会怎么样?比如const num = reactive(1); 这时候Vue3会报错,提示不能把基本类型传给Reactive,那如果我想把基本类型和引用类型放在一起存呢?可以用Ref包一个对象,比如const state = ref({a:1, b:{c:2}}); 这时候state.value.a和state.value.b.c都是响应式的,修改起来也方便,这种写法在很多项目里都很常用,特别是组件内部需要管理多个状态的时候,不用写一堆Ref。 加分项可以提一下shallowRef和shallowReactive:只监听第一层,深层数据不做响应式处理,比如你要存一个很大的JSON配置文件,不需要修改深层数据,这时候用shallowRef/Reactive可以提升性能;还有triggerRef,手动触发Ref的更新,比如你修改了shallowRef里的深层数据,想让视图更新,就可以用triggerRef。 雷区是别随便给Ref包.value以外的属性,比如const num = ref(1); num.xxx = 2; 这时候xxx是普通属性,不会触发更新,也不会在模板里自动解包。
Composition API里的setup函数执行时机是什么?和Vue2的created有什么区别?
执行时机要准确:setup函数是在组件实例创建之前执行的,这时候this还没绑定到组件实例上,所以不能在setup里用this访问data、methods这些,具体的执行顺序是:beforeCreate → setup → created,没错,setup夹在两个钩子中间,但它比created还要早拿到props和context。 和created的区别除了this和执行顺序,还有就是setup不能直接修改props,因为props是只读的,如果你想修改props传进来的值,得用computed转一下,或者在子组件内部定义一个自己的状态,把props的值赋过去。 context参数是setup里的核心,里面有attrs、slots、emit、expose四个属性,每个都要讲清楚场景:attrs是未声明的props和事件监听器(Vue3里事件监听器默认会挂载到attrs上,除非你用inheritAttrs: false);slots是插槽对象,跟Vue2的$slots差不多,但Vue3推荐用setup里的slots.renderSlot来渲染插槽;emit是触发父组件事件的方法,跟Vue2的$emit一样;expose是暴露子组件的属性和方法给父组件的,Vue3里默认不暴露,所以父组件想用ref拿到子组件的方法,子组件必须用expose显式暴露。 加分项可以提一下Vue3.2新增的script setup语法糖:不用写setup函数,直接在script标签里写代码,自动帮你return所有定义的变量、函数,也不用显式绑定expose,直接用defineExpose就行;defineProps和defineEmits是宏,不需要引入,可以直接用,这两个宏在编译阶段就会被处理,所以性能更好。 雷区是别在script setup里用var声明变量,因为var会提升到全局作用域,应该用const或者let;还有别在setup里用Vue2的生命周期钩子,比如created、mounted,要用Vue3的onMounted、onUnmounted这些,而且这些钩子需要从vue里引入。
Vue3的虚拟DOM优化了哪些地方?
这个考点比较深,适合中高级岗面试,讲的时候要结合Vue2的虚拟DOM来对比。 首先是静态提升:Vue2里每次更新都会重新创建所有的虚拟DOM节点,不管是不是静态的;Vue3里会把静态的节点(比如没有绑定任何数据、没有使用v-if/v-for的节点)提升到函数外面,只创建一次,更新的时候直接复用,这样可以减少不必要的创建和比较。 然后是补丁标记(Patch Flags):Vue2里比较虚拟DOM节点是全量比较,不管节点有没有变化;Vue3里会给有动态绑定的节点加一个二进制的补丁标记,比如TEXT标记表示节点的文本内容是动态的,CLASS标记表示节点的class是动态的,比较的时候只看有补丁标记的属性,跳过静态属性,这样可以减少比较的时间。 还有就是静态根节点的折叠:如果有多个连续的静态根节点,Vue3会把它们合并成一个文本节点,比如
Vue3的响应式原理是怎么实现的?
这个是必考题,不管是初级、中级还是高级岗都会问,初级岗只要讲Proxy+effect+track+trigger的大概流程就行,中高级岗要讲源码级别的细节。 大概流程是这样的:当你用Ref/Reactive创建响应式数据的时候,Vue3会用Proxy把数据包裹起来;当你访问响应式数据的某个属性的时候,会触发Proxy的get拦截器,get拦截器里会调用track函数,把当前的副作用函数(比如组件的render函数、computed的getter函数)收集到这个属性的依赖集合里;当你修改响应式数据的某个属性的时候,会触发Proxy的set拦截器,set拦截器里会调用trigger函数,把这个属性的依赖集合里的所有副作用函数都拿出来执行,从而更新视图。 源码级别的细节可以讲一下effect栈:因为副作用函数可能会嵌套调用,比如A函数里调用了B函数,B函数里访问了响应式数据,这时候要把B函数压入栈,收集完依赖之后再弹出,保证依赖收集的正确性;还有WeakMap和Map的使用:依赖集合是用WeakMap存的,WeakMap的key是响应式对象,value是一个Map,这个Map的key是响应式对象的属性名,value是一个Set,Set里存的是所有依赖这个属性的副作用函数,用WeakMap可以避免内存泄漏,因为当响应式对象被销毁的时候,WeakMap会自动把对应的键值对删除。 加分项可以提一下computed和watch的实现原理:computed是一个懒执行的effect,只有当你访问computed的返回值的时候才会执行,而且如果依赖的响应式数据没有变化,会直接返回缓存的值;watch是一个立即执行或者延迟执行的effect,它可以监听一个或者多个响应式数据的变化,当数据变化的时候会执行回调函数。 雷区是别把Vue2的Object.defineProperty混进来,要专讲Vue3的Proxy;还有别讲得太浅,比如只说“Proxy用来监听数据,effect用来更新视图”,要讲清楚track和trigger的作用。
Vue3的生命周期钩子有哪些变化?
首先要明确Vue3的生命周期钩子都是以on开头的,需要从vue里引入,除了setup函数里提到的beforeCreate和created,这两个钩子还是和Vue2一样,但Vue3推荐用setup函数来代替。 然后是变化的钩子:
- Vue2的beforeMount → Vue3的onBeforeMount
- Vue2的mounted → Vue3的onMounted
- Vue2的beforeUpdate → Vue3的onBeforeUpdate
- Vue2的updated → Vue3的onUpdated
- Vue2的beforeDestroy → Vue3的onBeforeUnmount
- Vue2的destroyed → Vue3的onUnmounted
- Vue2的activated → Vue3的onActivated
- Vue2的deactivated → Vue3的onDeactivated
- Vue2的errorCaptured → Vue3的onErrorCaptured 另外Vue3.2新增了两个钩子:onRenderTracked和onRenderTriggered,用来调试响应式系统,onRenderTracked会在每次依赖收集的时候触发,onRenderTriggered会在每次依赖触发的时候触发。 加分项可以提一下script setup里怎么使用生命周期钩子:直接在script标签里调用就行,不需要return;还有组合式API的生命周期钩子可以多次调用,比如你可以在同一个setup函数里写多个onMounted,它们会按顺序执行。 雷区是别用错钩子的名字,比如别把onBeforeUnmount写成onBeforeDestroy;还有别在onBeforeUnmount里做太多耗时的操作,因为组件即将被销毁,耗时操作会影响性能。
Vue3的组件通信方式有哪些?
别只列props/emit、provide/inject、ref/parent这些,要讲每个方式的适用场景,还有Vue3新增的方式。 适用场景:
- props/emit:父子组件通信,最常用的方式,props是父组件传给子组件的,只读;emit是子组件触发父组件的事件。
- provide/inject:跨层级组件通信,比如爷爷组件传给孙子组件,不需要通过中间的父组件传递,适合全局状态的共享,但注意provide的数据默认不是响应式的,如果需要响应式,得用Ref/Reactive包一下。
- ref/parent:父子组件通信,父组件用ref拿到子组件的实例,从而调用子组件的属性和方法;子组件用$parent拿到父组件的实例,但$parent在Vue3里不推荐使用,因为它会破坏组件的封装性,最好用props/emit或者provide/inject。
- Vuex/Pinia:全局状态管理,适合多个组件共享状态的场景,Vue3推荐用Pinia,因为Pinia是Vue3官方推荐的,它的API更简洁,支持TypeScript,没有mutations,只有actions,而且actions可以同步也可以异步。
- mitt/emitter:兄弟组件或者跨层级组件通信,是一个轻量级的事件总线库,Vue2里常用$on/$off/$once来实现事件总线,但Vue3里把这些API移除了,所以需要用第三方库。 加分项可以提一下script setup里的defineExpose:父组件想用ref拿到子组件的方法,子组件必须用defineExpose显式暴露,比如子组件里有一个handleClick方法,父组件想调用,子组件就要写defineExpose({handleClick})。 雷区是别过度使用事件总线,因为事件总线会导致代码难以维护,不知道事件是从哪里触发的,也不知道哪些组件监听了这个事件;还有别在provide里直接修改数据,要通过actions或者回调函数来修改。
最后说一下怎么准备Vue3面试才能拿下高薪Offer
基础要牢:上面提到的这些核心点必须掌握,不能只会用,还要知道为什么这么设计。 然后是要有项目经验:简历里的项目要真实,不能瞎编,面试官会问你项目里用到了Vue3的哪些特性,为什么要用这些特性,遇到过什么问题,怎么解决的。 还要刷一些算法题:现在大厂前端岗面试都会刷算法题,比如LeetCode的简单题和中等题,不用刷太难的,但要掌握常见的算法和数据结构,比如数组、链表、栈、队列、树、动态规划、贪心算法。 还要熟悉TypeScript:Vue3是用TypeScript写的,而且官方推荐用TypeScript开发,所以面试的时候大概率会问TypeScript的问题,比如类型注解、接口、类型别名、泛型、装饰器这些。 最后要保持自信:面试的时候不要紧张,把面试官当成你的同事,跟他正常交流,要是遇到不会的问题,不要瞎说,可以诚实说不知道,但可以说一下自己的思路,或者问面试官能不能提示一下。 对了,面试前可以模拟面试一下,找你的朋友或者同学当面试官,问你一些常见的Vue3面试题,这样可以提前适应面试的节奏,减少紧张感。
(全文完,字数2763)
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网

