尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Vue2面试核心知识点全解析:从响应式原理到工程实践

Vue2面试核心知识点全解析:从响应式原理到工程实践 1. 从2023到2026Vue2面试题还值得背吗先直接给结论值得而且现阶段依然是前端面试的“高性价比投资”。我这两年面过不少人也帮朋友做过不少模拟面试发现一个很有意思的现象Vue3的题目大家背得滚瓜烂熟什么ref、reactive、composition API张口就来但一问到底层原理十有八九会卡壳。为什么因为Vue3的源码封装的太好了你很难通过“读代码”去理解它到底怎么实现的。反而Vue2的源码很直白Object.defineProperty、Dep、Watcher、Observer这几个核心概念捋清楚你对整个MVVM框架的理解会一下子通透起来。更重要的是很多公司线上项目依然跑着Vue2。我见过不少“面试造火箭、入职拧螺丝”的案例面试聊的是组合式函数入职改的是两年前写的datamethods。你要是对Vue2的响应式原理、组件通信、生命周期理解不够扎实上来改业务代码很容易踩坑——最典型的就是“数据改了视图没更新”这类问题追半天发现是对象属性没声明响应式。所以这篇内容我打算把Vue2面试中最高频的知识点按“原理→应用→工程化→面试策略”的脉络重新梳理一遍。不是为了让你死记硬背而是帮你建一个完整的知识框架面试的时候不管面试官从哪个角度切入你都能接得住。2. 响应式原理Vue2的核心地基2.1 Object.defineProperty到底做了什么Vue2的响应式系统说穿了就是三个角色的协作Observer把一个普通对象变成响应式对象。Dep依赖收集器每个响应式属性都会有一个对应的Dep实例。Watcher视图更新或用户自定义的侦听器是依赖的“消费者”。核心代码就是Object.defineProperty重写属性的get和setfunction defineReactive(obj, key, val) { const dep new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { if (Dep.target) { dep.depend() } return val }, set: function reactiveSetter(newVal) { if (newVal val) return val newVal dep.notify() } }) }这段代码看起来简单但面试官特别喜欢深挖几个细节为什么get里要判断Dep.target因为依赖收集必须发生在“正在计算的Watcher”上下文中。只有你读取属性的时候才知道这个属性到底被谁使用了。Dep.target是一个全局静态属性指向当前正在执行的Watcher保证依赖不收集错对象。为什么set里要先判断新旧值是否相等如果相等直接返回可以避免不必要的更新。这个细节很多人在面试时容易忽略但它是性能优化的基础。为什么Vue2无法监听数组的变化这是经典问题中的经典。Object.defineProperty本身是可以监听数组索引的——arr[0] 1这种操作其实能触发set。但Vue官方为了性能考虑选择了重写数组的原型方法push、pop、shift、unshift、splice、sort、reverse来拦截变更操作而不是对每个数组元素做响应式处理。原因有二一是数组通常很长逐个处理开销太大二是大多数开发者操作数组都是通过方法调用的而不是直接改索引。2.2 依赖收集和派发更新的完整链路如果你能把下面这条链路讲清楚面试官基本就认可你对响应式的理解了模板编译时会为每个组件生成一个render函数同时创建一个Watcher实例render watcher。render函数执行时会访问模板中依赖的数据属性触发属性的get。get中会把当前Watcher收集到该属性的Dep中dep.depend()。当数据发生变化时触发属性的set调用dep.notify()。dep.notify()会遍历所有收集到的Watcher调用它们的update方法。update方法会把当前Watcher加入一个异步队列在下一个事件循环中统一执行更新。这里有个容易被追问的点为什么是异步队列因为如果同步更新你在一个事件循环里修改了一个数据可能触发多次set就会做多次无意义的渲染。Vue把Watcher放入队列后用nextTick做去重和合并保证同一事件循环内多次数据变更只触发一次视图更新。2.3 深度监听和Vue.set的底层逻辑面试官经常问“data里声明了对象obj后来给obj加了新属性为什么视图不更新”原因就是新属性没有走Object.defineProperty的流程没有经过get/set的代理自然不响应。解决办法有三种Vue.set(obj, key, value)Vue2全局API会先判断目标是不是响应式对象然后对新增属性做响应式处理再手动触发更新。this.$set(obj, key, value)实例方法本质是Vue.set的封装。对象整体重新赋值this.obj { ...this.obj, newKey: value }这样新对象会整体走一遍响应式转换。数组同理arr[index] value是监听不到的需要用splice或整体替换。这里我踩过一个坑实际业务里遇到了才知道问题有多隐蔽在created里调接口拿到数据后给返回的对象动态添加了一个isSelected字段结果页面上怎么点都不变色。排查了半天发现是因为这个字段压根没有进入响应式系统。所以后来我在团队里立了个规矩动态添加字段一律用this.$set别怕麻烦这个习惯能帮你避免大量“数据变了视图不更新”的诡异bug。3. 组件通信从Props到Vuex的完整图谱3.1 父传子、子传父最容易被问到的细节props和$emit是Vue2组件通信的基石但面试题往往不止问你“怎么用”而是问一些细节props是单向数据流的为什么因为如果允许子组件直接修改父组件的状态数据流向会变得不可预测一旦出现bug很难排查是谁改了数据。Vue2在props的set上做了屏蔽子组件直接修改props会触发警告。props支持哪些校验type、required、default、validator自定义校验函数。很多人只记得type和required但面试官如果你能说出validator并且举一个实际例子比如校验传入的status必须是active | pending | deleted中的一个印象分会高不少。$emit的本质是什么$emit实际上是触发当前组件实例上的自定义事件然后在父组件的模板上通过v-on监听。很多新手容易把$emit想象成“向父组件传值”实际上它更像是“通知父组件我这里发生了一件事具体怎么处理交给你”。3.2 事件总线与跨层级通信的注意事项非父子组件之间的通信很多人第一反应是Vuex。但如果只是某个简单的跨层级事件用时间总线EventBus其实更轻量。// 创建事件总线 // utils/bus.js import Vue from vue export default new Vue() // 组件A发送事件 import bus from ../utils/bus export default { methods: { handleSend() { bus.$emit(refresh-list, { id: 123 }) } } } // 组件B监听事件 import bus from ../utils/bus export default { created() { bus.$on(refresh-list, this.handleRefresh) }, beforeDestroy() { bus.$off(refresh-list, this.handleRefresh) } }这里有几个容易踩坑的点为什么beforeDestroy里要$off如果组件注销了但事件监听还留着下次组件重新创建时会重复执行监听回调导致业务逻辑重复执行。这是EventBus最典型的“内存泄漏”问题。如何避免事件名冲突建议维护一个事件名常量文件集中管理所有事件名。大型项目里如果没有这个习惯后期找人维护真的是灾难。比EventBus更推荐的方式是provide/inject。Vue2.2.0支持适合跨多级组件传递数据且不需要逐层传递props。但需要注意它不是响应式的除非传的是响应式对象本身所以不适合做数据源更适合做“配置信息”传递比如主题颜色、用户权限等。3.3$parent/$children与$refs的场景边界$parent和$children看似方便但用多了代码耦合度高面试时如果主动提到它们的局限性反而显得你经验丰富。$parent可以直接访问父组件的data和methods改起来很爽但问题在于你不知道父组件是谁也不知道它内部结构长什么样。今天能用明天父组件加一层嵌套你的逻辑可能就断了。$refs则是模板引用适合操作子组件的实例方法或DOM元素template child-component refchildComp / div refbox/div /template script export default { mounted() { this.$refs.childComp.someMethod() this.$refs.box.style.color red } } /script但$refs有个特性很坑它是响应式渲染完成之后才填充的。也就是说如果你在created里访问this.$refs拿到的是undefined。必须在mounted之后才能访问。另外在v-for中使用$refs拿到的是一个数组顺序不一定稳定所以尽量避免在循环中使用。4. 生命周期钩子从创建到销毁的全过程4.1 八个钩子的执行时机和典型应用Vue2的生命周期可以分成四组创建阶段beforeCreate→created。beforeCreate时data和methods都还没初始化通常做什么很少用最多做一些全局配置读取。created时data和methods已经可用但DOM还没挂载所以适合做一些初始化逻辑比如调接口拉数据。这里有个点面试官常问created里和mounted里调接口有什么区别答案是created时组件实例已经创建成功数据获取后可以在beforeMount渲染时直接用少一次闪烁mounted时DOM已经渲染完成如果调接口改数据必然经历一次“先渲染空数据→再渲染真实数据”的过程。挂载阶段beforeMount→mounted。beforeMount时render函数第一次执行模板编译成虚拟DOM但还没插入真实DOM。mounted时视图已经渲染完成可以操作DOM、访问$refs、绑定原生事件。更新阶段beforeUpdate→updated。beforeUpdate在数据变化、虚拟DOM打补丁之前此时访问真实DOM还是旧值适合做更新前的状态保存。updated时DOM已经更新完成但注意不要在updated里修改数据否则会陷入无限循环。销毁阶段beforeDestroy→destroyed。beforeDestroy是清理工作的最后机会比如清除定时器、解绑全局事件、销毁第三方库实例。destroyed后组件实例的所有指令解绑、事件监听移除、子组件全部销毁不过Vue2源码里实际是“递归销毁子组件”的。除了这八个还有两个特殊钩子activated和deactivated用于keep-alive包裹的组件组件被缓存激活或失活时触发。这个在面试中经常结合“列表页缓存滚动位置”的场景来问。4.2 父组件和子组件生命周期的执行顺序这个面试必问直接背结论加载渲染过程父beforeCreate→ 父created→ 父beforeMount→ 子beforeCreate→ 子created→ 子beforeMount→ 子mounted→ 父mounted。子组件更新过程父beforeUpdate→ 子beforeUpdate→ 子updated→ 父updated。父组件更新过程父beforeUpdate→ 父updated如果没有子组件参与只走父的。销毁过程父beforeDestroy→ 子beforeDestroy→ 子destroyed→ 父destroyed。理解这个顺序的关键点是父组件的mounted一定在所有子组件mounted之后触发。所以如果你要在mounted里做一些依赖子组件DOM尺寸的计算可能拿不到准确的数值需要用到$nextTick或者自行判断时机。4.3actived和deactivated实战中的妙用再说深层一点的keep-alive缓存组件时如果第一次进入组件会走created→mounted→activated。但第二次再进入时不会重新走created和mounted只会触发activated而且data不会被重置。这意味着什么如果你的页面用了keep-alive缓存且每次进入页面都需要重置表单、重新拉列表数据那你必须在activated里做这些操作而不是created或mounted。我之前做过一个搜索列表页用户从列表进入详情再返回期望是保留搜索条件和滚动位置。这时候keep-alive是刚需但等用户重新进入页面时列表还需要重新拉取吗我当时的方案是在activated里判断有没有缓存数据有缓存就不重新调接口没有缓存才拉取。这个思路在面试时也可以讲因为能体现你对生命周期的理解和场景化应用。5. 计算属性与侦听器Computed、Watch、Methods的三者对比5.1 Computed和Watch的核心差异先说结论computed有缓存依赖的响应式数据变化时才重新计算。适合“一个值依赖多个值且需要被模板多次引用”的场景。watch没有缓存监听的数据变化时就执行回调。适合“一个数据变化后需要执行异步操作或复杂逻辑”的场景。methods没有缓存每次访问都会重新执行函数。适合“事件处理函数”场景不适合模板中大量使用的“计算逻辑”。所以面试时如果有人问“computed和methods的区别”核心只有一点computed有缓存methods没有。模板里如果多次调用同一个methods方法函数会执行多次computed只有依赖变化时才重新计算。5.2 Computed的getter和setter很多人只知道computed是个值不知道它其实是一个对象可以包含get和setcomputed: { fullName: { get() { return this.firstName this.lastName }, set(val) { const names val.split( ) this.firstName names[0] this.lastName names[1] } } }这样你可以直接this.fullName John Doe来同时修改firstName和lastName。实战中这个写法适合表单联动的场景比如一个“选择省市区”的下拉框computed里把三个值拼成一个字符串用户修改时通过set拆分成三个字段。5.3 Watch的高级选项deep和immediatewatch默认在数据变化时才触发回调但如果你的场景是“页面初始化就要执行一次之后数据变化也要执行”需要加immediate: truewatch: { searchText: { handler(newVal, oldVal) { this.fetchData(newVal) }, immediate: true } }还有deep: true用于深度监听对象属性的变化。但注意深度监听会递归遍历对象的每一个属性开销比较大能不用就不用。如果只想监听某个特定属性的变化建议使用字符串形式watch: { user.profile.name(newVal, oldVal) { // 只有name变化时触发 } }这个写法很多人不知道但面试时提到会很加分因为能体现你“关注性能避免滥用deep”。6. 指令与渲染机制v-if、v-for、key的底层逻辑6.1 v-if和v-show怎么选v-if是真正的条件渲染条件为假时元素根本不会渲染到DOM中v-show则是通过display: none来控制显示和隐藏元素始终存在。所以结论很简单频繁切换用v-show首屏渲染条件很少变化用v-if。v-if有更高的切换开销销毁和重建DOMv-show有更高的初始渲染开销多渲染了不显示的元素。面试进阶题v-if和v-for一起使用有什么问题答案是在Vue2中v-for的优先级高于v-if所以即使某个数据不需要渲染它也依然会被遍历一遍造成性能浪费。更好的做法是用计算属性先过滤再v-for渲染。到了Vue3中v-if的优先级高于v-for行为刚好相反。这个对比在面试中经常被追问到“Vue2和Vue3有什么区别”里是个典型的考点。6.2 key的作用和diff算法key的主要作用是新旧节点对比时能准确判断节点是否为同一节点。没有keydiff算法会采用“就地复用”策略可能出现状态错乱的问题有了key用唯一iddiff算法能精确定位移动、删除、新增的节点同时复用真实DOM减少创建和销毁的开销。这里面试官常问一个问题为什么不建议用数组索引作为key原因很微妙如果数组顺序变化比如排序、插入用索引作为key会导致组件状态复用错误。比如一个列表项里有一个input用户在第一项输入了内容之后列表排序第一项变成了另一个数据但input里的内容却没跟着变因为组件被复用了。所以key必须与“数据本身”相关联而不是“位置”。6.3 自定义指令不只是个加分项Vue2中自定义指令用directives选项注册常见的指令有v-loading、v-focus、v-debounce、v-permission等。生命周期钩子有bind、inserted、update、componentUpdated、unbind。面试中如果主动提到“我给项目封装过一个v-permission指令来控制按钮级别的权限”会非常加分。因为它展示的不只是你会用API而是你有工程化封装思维。实现思路是在bind钩子里判断当前用户的权限列表如果不包含权限标识直接移除当前DOM元素。Vue.directive(permission, { inserted(el, binding) { const permissionList store.getters.permissions if (!permissionList.includes(binding.value)) { el.parentNode el.parentNode.removeChild(el) } } })7. Vue2与Vue3的核心差异面试官最爱问的对比题7.1 响应式系统的实现差异Vue2用Object.definePropertyVue3用Proxy。这个差异带来的结果是革命性的可以监听数组的索引操作和length变化不再需要重写数组方法。可以监听属性的动态新增和删除不再需要Vue.set和Vue.delete。可以监听Map、Set等原生数据结构。性能更好因为Proxy是懒代理访问到才代理不是初始化时递归遍历。面试时如果能补充一点性能细节——Proxy本质上是对目标对象的“操作拦截”而不是“属性改写”所以代理对象不会像Vue2那样为每个属性都创建一份闭包内存开销更小——会显得你对原理有深入研究。7.2 组合式API和选项式API的取舍Vue3的composition API把同一功能的代码集中在一起解决了Vue2中“一个功能的逻辑分散在data、methods、watch、computed多个选项中”的问题。但如果面试官问“Vue2能不能用组合式API”答案是可以的通过vue/composition-api插件。不过我不建议为了用而用小项目用选项式API反而更简单直接。面试时可以这么说“组合式API更适合中大型项目或逻辑复用场景但如果项目本身不大选项式API的组织方式反而更容易理解和维护。”这体现的是你的工程判断力而不是技术炫技。7.3 虚拟DOM和diff算法的演进Vue3的diff算法相比Vue2有几个重要优化PatchFlag模板编译时把动态绑定的节点标记出来diff时只对比有标记的节点。静态提升静态节点直接缓存不参与diff。事件缓存事件监听器缓存起来不重复创建。Fragments组件支持多根节点不需要包裹一层div。面试时如果能准确说出这几点面试官基本就确认你“研究过Vue3源码”而不是只知道“Proxy比defineProperty好”。8. 从面试题到实战Vue2项目里的高频场景与坑8.1 手写电子签名组件canvas核心要点热搜词里有个“vue2实现手写电子签名功能”这其实是个很好的面试实战题。实现思路template canvas refcanvas touchstartstart touchmovemove touchendend / /template script export default { data() { return { ctx: null, drawing: false, lastX: 0, lastY: 0 } }, mounted() { const canvas this.$refs.canvas this.ctx canvas.getContext(2d) // 设置画布宽高考虑设备像素比 const dpr window.devicePixelRatio || 1 canvas.width canvas.offsetWidth * dpr canvas.height canvas.offsetHeight * dpr this.ctx.scale(dpr, dpr) }, methods: { start(e) { this.drawing true const point this.getPoint(e) this.lastX point.x this.lastY point.y }, move(e) { if (!this.drawing) return const point this.getPoint(e) this.ctx.beginPath() this.ctx.moveTo(this.lastX, this.lastY) this.ctx.lineTo(point.x, point.y) this.ctx.stroke() this.lastX point.x this.lastY point.y }, end() { this.drawing false }, getPoint(e) { const rect this.$refs.canvas.getBoundingClientRect() const touch e.touches e.touches[0] return { x: (touch ? touch.clientX : e.clientX) - rect.left, y: (touch ? touch.clientY : e.clientY) - rect.top } } } } /script这里我想说一个大多数人容易忽略的细节canvas画出的签名在手机上和电脑上看起来粗细不一样。原因在于触摸事件和鼠标事件的坐标精度不同还有设备像素比的适配问题。实际项目中如果你不处理devicePixelRatio在2倍屏甚至3倍屏手机上签名线条会显得模糊。所以我在做这个组件时固定用dpr把canvas的实际像素放大再用ctx.scale把绘制坐标系缩小才能保证清晰度。8.2 H5里预览Word和PDF不可能三角热搜里还有“vue2 h5页面想要预览word文件”和“file-viewer vue2”。这块我必须说实话H5端预览Word文件没有任何一个方案是“完美”的。你只能在以下三个维度中取舍文档解析质量是否保留原始排版、图片、表格。加载速度文件多大、解析多久。依赖体积引入的库有多大、是否需要后端支持。如果Word文件是简单的文本和图片可以用mammoth.js把docx转成HTML在页面里渲染。但如果有复杂表格和样式mammoth的表现就比较拉胯了。PDF预览倒是相对成熟pdf.js配合pdfjs-dist可以在前端直接渲染支持缩放、翻页、文字选择。但注意pdf.js的worker文件在Vue2的打包配置里需要特殊处理否则会报错。我踩过这个坑直接npm install pdfjs-dist然后import结果构建后worker路径找不到白屏。解决方案是把worker文件拷贝到public目录然后手动指定GlobalWorkerOptions.workerSrc。8.3 iframe缓存和keep-alive失效问题“vue2 iframe 缓存”这个词我一看就知道是真实项目里的痛点。keep-alive缓存的是组件的虚拟DOM和实例状态但如果你在页面里嵌了一个iframekeep-alive只能缓存iframe标签本身iframe内部的页面状态是独立于Vue实例的切走再切回来iframe会重新加载。解决方案有几个各有取舍用v-show控制iframe的显示和隐藏保证iframe不销毁自然不重新加载。这是最粗暴但有效的方案。把iframe的src动态设置切走时保存当前页面的URL切回来时重新设置。但这种方法只能恢复URL不能恢复iframe内部的操作状态。用postMessage做跨域通信让iframe内部自己管理状态。这适合你有iframe内部代码控制权的情况。8.4 富文本编辑器wangeditor与quill热搜里同时出现了“vue2使用wangeditor”和“vue2 quill富文本编辑器”说明这两个都在项目里广泛使用。wangeditor最大的优势是“开箱即用”中文文档友好UI风格适合后台管理系统。但Vue2版本和Vue3版本的API差异很大如果你做的是“vue2升级vue3”项目升级编辑器是个不小的坑。quill则更轻生态丰富也有很多好用的模块。但quill的数据模型是Delta格式如果你要把内容提交给后端再回显要么存Delta要么转成HTML。我项目里遇到过一个问题quill渲染服务端返回的HTML时偶尔会把样式直接内联进去导致编辑区域显示不正常。后来我养成了习惯用quill的clipboard.dangerouslyPasteHTML之前先自己清理一遍DOM树。9. 面试场景的工程化问题从“会写”到“会聊”9.1 HBuilderX里的Vue2实战初始化一个能跑的项目热搜词里居然有“hbuilderx vue2实战项目”看来不少朋友在用HBuilderX这个工具练手。HBuilderX本身是DCloud出品的前端IDE对uni-app支持最好但也支持创建纯Web项目。如果你用它创建一个Vue2项目我更推荐直接用Vue CLI初始化然后在HBuilderX里打开工程目录npm install -g vue/cli vue create my-project cd my-project npm run serve用HBuilderX打开这个目录后它就是一个普通的代码编辑器不影响Vue CLI的工作流。这样你能获得两个工具的优势HBuilderX的代码提示和内置终端Vue CLI的工程化能力。9.2 vue2升级vue3要不要全部替换写法热搜里有一条“vue2升级vue3 前端代码怎么处理更好需要全部替换vue3写法吗”这个问题的答案很明确不需要也不能全部替换。Vue3是兼容Vue2的选项式API的你完全可以用Vue3的运行时去跑一部分Vue2风格的代码。迁移的优先级应该是先保持现状只做运行时升级换掉Vue.use、new Vue()等入口写法把Vue2项目迁移到Vue3的createApp。等稳定运行后逐步迁移全局APIVue.set改成this.$set在Vue3里是this.$set还是用ref答案是要看你的响应式数据是选项式还是组合式——如果是reactive直接用obj.key value就行不需要$set。最后按模块以“组合式API”重构不用一次性全部改完。有几点是必须处理的“硬差异”Vue.prototype.$xxx改成app.config.globalProperties.$xxx。filter过滤器在Vue3中彻底移除要用computed或methods替代。$on、$off、$once被移除EventBus方案需要替换成mitt或Vuex的状态管理。v-model的属性名和事件名从value/input变成了modelValue/update:modelValue。异步组件写法变了() import()需要包一层defineAsyncComponent。9.3 微前端场景下的vue2主应用与vue3子应用“vue2主应用vue3子应用路由出现异常”这个是微前端相关的经典问题。我做过qiankun的微前端方案主应用是Vue2子应用是Vue3。遇到最多的路由问题就两类第一类是主应用路由的base配置不对。微前端要求主应用把所有子应用的路由都当作自己的路由的一部分通常需要设置base: /sub-app1并且在注册子应用时配置activeRule。第二类是子应用在和主应用共享同一个history时路由跳转导致主应用URL变化触发主应用的路由守卫。解决方案是子应用不要用router-link而是用window.history.pushState或者在子应用的beforeDestroy里清理路由守卫避免主应用被拦截。这类问题面试中不太会直接考但如果你能主动讲“我在微前端中遇到过路由冲突”并且说出排查思路先看路由history模式、再看activeRule、再看子应用是否独立运行面试官会认为你有真实项目经验不是背题。10. 面试场景的话术与答题框架10.1 如何回答“Vue2和Vue3的区别”这个题几乎是必考的。我的建议是不要罗列区别而是用“一条主线”串起来比如从“响应式系统变化”这条线出发“Vue2的响应式基于Object.defineProperty它在初始化时递归遍历data里的每个属性通过getter/setter做依赖收集和派发更新。这个方案的局限性主要有三点一是无法监听数组索引和length变化二是新增和删除属性时不会触发更新三是递归遍历导致大对象初始化性能较差。Vue3改用Proxy来代理整个对象拦截get、set、deleteProperty等操作所以可以监听动态新增删除、也能代理Map和Set。另外Vue3的编译优化比如PatchFlag和静态提升让虚拟DOM的diff性能提升了一个量级。”然后再补充一句工程迁移相关的内容“这些差异决定了从2到3的迁移最核心的工作不是改模板而是改数据声明方式把this.xxx的写法改成ref和reactive把需要响应式处理的结构尽量静态化避免动态添加属性。”这样回答既有深度又有实战参考比单纯的背诵List强太多。10.2 遇到没做过的问题怎么应对面试官问到你没做过的领域不要直接说“我不会”。你先拆解问题把它映射到你已经掌握的知识上。比如问“Vue2中EventBus的内存泄漏问题怎么解决”你即使没实际写过也可以从原理上推导内存泄漏的本质是“对象被引用但不再使用”EventBus是全局单例组件销毁后如果没解绑事件组件实例就被事件回调引用着无法被垃圾回收。解决办法就是生命周期中$off。你看这正是前面讲的组件通信知识换了个角度问而已。10.3 自我介绍的加分项很多人自我介绍就是“我叫xx有x年经验会vue2做过xx项目”太流水账了。我建议用“技术栈业务领域一个代表作”的结构来组织比如“我主要做中后台方向的前端技术栈是Vue2和Vue3。近两年负责的核心项目是一个多租户的CRM系统最大的难点是权限模型复杂我封装了一套基于指令和路由守卫的权限控制同时支持按钮级和路由级两种粒度。最近在做的技术优化是把项目从Vue2迁移到Vue3过程中踩了不少坑积累了一些迁移经验。”这段话既有技术深度又有业务场景还展示了主动性面试官自然有兴趣继续追问下去。11. 最后的真心话写到这里我想收个尾。每次帮人看Vue2面试题我都有种感觉很多人是在“背答案”而不是在“理解框架”。Vue2的核心思想其实非常朴素它帮你把DOM操作和数据绑定之间的那层胶水写好了你用的时候只要遵循它的规则。如果你理解了Object.defineProperty、Dep和Watcher这三者的关系理解了为什么模板里能用{{ }}取到data里的值理解了v-for的key为什么不能用索引你不需要背任何题也能答上大部分问题。面试的目的是什么不是考察你记住了多少个API而是考察你遇到问题时的思维方式。Vue2的那些“坑”比如数组索引更新、动态添加属性不响应、keep-alive缓存了过期数据本质上都是同一个底层逻辑的延伸。你把底层逻辑吃透了这些坑自然就绕开了。最后分享一个我自己练习的小技巧每做完一个Vue2项目就试着把项目里最复杂的一个功能用纯JavaScript实现一遍不依赖Vue。比如手写一个“响应式表单联动组件”自己用Object.defineProperty模拟一份迷你响应式系统。做完之后你会发现再看Vue2源码很多东西都会豁然开朗。
返回列表