前言
前端面试题很多,但高频的其实就那几十道。这篇按”由浅及深”整理了一份题库,每道题不只给答案,还会讲清楚:这个知识点在真实项目里用在哪、能关联到什么知识。背答案只能应付一轮,理解场景才能通过后面的追问。
一、JS 基础(浅)
1. == 和 === 的区别
答案:== 会做类型转换再比较,=== 要求类型和值都相等。
应用场景:日常代码里几乎都用 ===。有个真实坑:用 == 判断 if (x == null) 时,undefined 和 null 都成立——这是少数刻意用 == 的场景(为了同时判断两种空值),但可读性差,不如 x === null || x === undefined 或 x == null 加注释。
活学活用:== 的类型转换规则引出另一个常考题——[] == false 为什么是 true?因为 [] 先转字符串 "",再转数字 0,false 转数字也是 0。这背后是 ECMAScript 的 Abstract Equality 算法,理解它就知道:永远别依赖隐式转换。
2. 闭包是什么,有什么用途
答案:函数执行后,内部函数仍能访问外层函数作用域的变量,这个”内部函数 + 被记住的作用域”就是闭包。
应用场景:
1 | // 防抖——经典闭包应用 |
活学活用:闭包必然和”垃圾回收”关联。闭包会一直持有外层作用域,用完不释放会造成内存泄漏——这在长列表、频繁创建的组件里是隐患。延伸考点:let 块级作用域 + 闭包解决循环绑定事件的问题(var 时代经典题)。
3. 原型链是什么
答案:每个对象都有 __proto__ 指向其构造函数的 prototype,层层向上最终到 Object.prototype,形成原型链。属性查找先找自身,找不到沿原型链向上。
应用场景:Vue 3 的 app.config.globalProperties 挂载全局方法、Element Plus 的组件继承、以及所有”给所有实例加方法”的场景,本质都是操作原型链。
活学活用:关联 instanceof 原理——obj instanceof Fn 判断的就是 Fn.prototype 是否在 obj 的原型链上。再关联”如何实现继承”(原型继承/寄生组合继承/ES6 class),面试官问原型链必追问继承。
4. 事件循环(Event Loop)
答案:JS 是单线程。执行栈清空后,先取微任务队列(Promise.then、MutationObserver),再取宏任务队列(setTimeout、setInterval)。宏任务里又会产生新的微任务,循环往复。
应用场景:
1 | console.log('1') // 同步 |
活学活用:事件循环和”性能优化”直接相关——为什么 setState / ref 的更新要异步批量处理?因为同步多次修改状态会阻塞主线程。Vue 的 nextTick 就是利用了微任务机制,把 DOM 更新推迟到本次同步代码执行完。面试官常追问:宏任务之间会执行微任务队列吗?会,这是 Node 和浏览器事件循环的核心差异点。
二、JS 进阶(中)
5. 深拷贝和浅拷贝
答案:浅拷贝只复制第一层,引用类型仍共享内存;深拷贝递归复制所有层级。
应用场景:Vue 里改 props 前深拷贝一份再改,避免直接修改父组件数据;表格编辑时”复制当前行 → 在副本上编辑 → 确认后替换”,也是浅拷贝就够的场景。
活学活用:JSON.parse(JSON.stringify(obj)) 是最简单深拷贝,但丢失 undefined、函数、Symbol、循环引用会报错。真正要写深拷贝得处理:循环引用(WeakMap 记录)、特殊类型(Date/RegExp/Map/Set)、原型保留。关联考点:structuredClone(浏览器原生深拷贝 API,2022 年后可用,但也不支持函数)。
6. Promise 是什么,和回调有什么不同
答案:Promise 是异步编程的解决方案,用链式调用替代嵌套回调,有 pending / fulfilled / rejected 三种状态,状态一旦改变不可逆。
应用场景:
1 | // 并发请求——Promise.all 的典型场景 |
活学活用:Promise 必然关联 async/await(语法糖)、Promise.allSettled(部分失败不中断,适合批量上传)、Promise.race(接口超时控制)、微任务(见第 4 题)。追问高频:Promise.all 一个失败怎么办?默认整体 reject,要”部分成功”就用 allSettled 或给每个 Promise 加 .catch。
7. this 指向问题
答案:普通函数中 this 取决于调用方式;箭头函数没有自己的 this,继承外层作用域。
应用场景:
1 | const obj = { |
活学活用:Vue 组件方法用普通函数(需要 this 指向组件实例),而 watch 回调、setTimeout 里若要用组件实例就得箭头函数或提前 const vm = this。关联 call/apply/bind 三者的区别,以及 new 绑定的优先级(new > 显式绑定 > 隐式绑定)。
8. ES Module 和 CommonJS 的区别
答案:CommonJS 是同步加载、运行时确定、require 返回值的拷贝;ES Module 是静态分析、编译期确定、import 引用同一份内存,支持 tree-shaking。
应用场景:Vite 用 ES Module 做开发服务器,所以快;Node 项目 CJS 常见。混用会报错,Vite 里默认 ESM,遇到 CJS 包要用 @rollup/plugin-commonjs 转换。
活学活用:关联”为什么 Vite 开发快、Webpack 慢”(原生 ESM 按需加载 vs 全量打包)、关联 tree-shaking 为什么只对 ESM 生效(静态 import 才能分析出哪些导出没被用)。
三、浏览器与网络(中)
9. 从输入 URL 到页面显示,发生了什么
答案(经典八股,按阶段答):DNS 解析 → TCP 连接(HTTP 的话还有三次握手)→ 发送 HTTP 请求 → 服务器响应 → 浏览器解析 HTML 构建 DOM → 解析 CSS 构建 CSSOM → 合成渲染树 → 布局(Layout)→ 绘制(Paint)→ 合成(Composite)。
应用场景:讲”为什么 CDN 快”——DNS 就近解析 + 静态资源边缘缓存。讲”为什么首屏优化要从这几个环节入手”——DNS 预解析(<link rel="dns-prefetch">)、HTTP 缓存(强缓存/协商缓存)、CSS 放 head、JS 放 body 尾加 defer。
活学活用:这题是”总开关”,后面所有网络题都能挂在它下面。关联:HTTP 缓存(Cache-Control/ETag)、HTTPS 握手、HTTP/2 多路复用、渲染阻塞(render-blocking)。
10. 浏览器缓存机制
答案:强缓存(Cache-Control: max-age,命中不请求)→ 协商缓存(Last-Modified/ETag,发请求问服务器是否过期)→ 兜底重新拉取。
应用场景:
1 | 打包产物(JS/CSS)→ 文件名带 hash(main.a3f2b1.js),max-age 一年,改了就换文件名 |
活学活用:这题关联”为什么改了代码刷新还是旧的”——可能是强缓存没过期,强制刷新(Ctrl+F5)会绕过强缓存;也可能是部署了但 index.html 也被缓存了。再关联 Service Worker(PWA 离线缓存)。
11. 跨域是什么,怎么解决
答案:浏览器同源策略(协议+域名+端口任一不同即跨域),请求可以发出但响应被浏览器拦截。解决方案:CORS(后端配 Access-Control-Allow-Origin)、JSONP(script 标签不受同源限制,只能 GET)、代理转发(开发用 Vite proxy,生产用 nginx 反代)。
应用场景:前后端分离项目 90% 的跨域问题是开发环境用代理解决:
1 | // vite.config.js |
活学活用:注意一个坑——cookies 跨域要配 withCredentials: true + 后端 Access-Control-Allow-Credentials,且此时 Access-Control-Allow-Origin 不能是 *。关联预检请求(Preflight,OPTIONS)为什么会发两次请求。
12. 重绘和回流(Repaint & Reflow)
答案:修改样式引起布局变化 → 回流(Layout);只改颜色等不影响布局的 → 重绘。回流必然伴随重绘,重绘不一定回流。回流开销大得多。
应用场景:
1 | // ❌ 循环里读 offsetWidth 会强制回流(布局抖动) |
活学活用:Vue/React 的虚拟 DOM 本质就是”批量操作 + 最小化回流”。关联 requestAnimationFrame(动画相关优化)、documentFragment(批量插入)、will-change(合成层优化)。
四、框架(Vue 为主,中深)
13. Vue 2 和 Vue 3 的核心区别
答案:
| 维度 | Vue 2 | Vue 3 |
|---|---|---|
| 响应式 | Object.defineProperty 劫持 | Proxy 代理(可监听新增属性/删除) |
| 组合式 | Options API | Composition API(setup) |
| 类型 | JS,TS 支持弱 | TS 一等公民 |
| 性能 | 全量 diff | 静态标记 + PatchFlag,更快 |
| 生态 | Vuex/Router 3 | Pinia/Router 4 |
应用场景:面试官让讲 Vue3 新增了什么,就用这张表展开。重点讲 Proxy 相比 defineProperty 的优势:能监听对象属性新增、数组索引变化,不需要 Vue.set 这种补丁 API。
活学活用:响应式原理是必追问——defineProperty 和 Proxy 的底层机制、ref 和 reactive 的区别(ref 包装成 { value },reactive 是直接代理对象)、为什么 ref 访问要 .value。
14. Vue 生命周期
答案(Vue 3):setup → onBeforeMount → onMounted → onBeforeUpdate → onUpdated → onBeforeUnmount → onUnmounted。onMounted 后 DOM 已挂载可操作,onUnmounted 前清理定时器/事件监听。
应用场景:
1 | onMounted(() => { |
活学活用:关联 nextTick(数据变了但 DOM 还没更新,等下一帧)、keep-alive 带来的 onActivated/onDeactivated、以及”为什么请求要放 onMounted 而不是 setup”——setup 时组件还没挂载,某些依赖 DOM 的操作会失败。
15. v-if 和 v-show 的区别
答案:v-if 是真正销毁/创建 DOM(条件切换开销大);v-show 只是切换 display: none(初始渲染开销大)。
应用场景:频繁切换的 Tab 用 v-show;不常出现的弹窗、权限控制的内容用 v-if。表格里每行的权限按钮用 v-if(不渲染就没有安全隐患)。
活学活用:关联”组件缓存”——v-if 切换会销毁重建组件(状态丢失),要保留状态用 keep-alive;关联首屏优化——初始不可见的区域用 v-if 可以减少首屏 DOM 量。
16. computed 和 watch 的区别
答案:computed 依赖响应式数据,有缓存,值被用到才计算,适合”由其他数据推导出来的值”;watch 监听数据变化执行副作用(异步请求、DOM 操作),无缓存。
应用场景:
1 | // computed——购物车总价(依赖数量×单价,缓存避免重复计算) |
活学活用:追问常客——“computed 里能发请求吗?”不能,computed 要纯函数,有副作用用 watch。关联 watchEffect(自动收集依赖,无需指定监听谁)与 watch 的区别。
17. 组件通信方式
答案(Vue 3):props 向下传、emit 向上发、v-model(父子双向)、provide/inject(跨层级)、Pinia(全局)、$refs/defineExpose(拿子组件实例)、mitt(跨组件事件总线)。
应用场景:封装组件库时用 props + emit 保持”受控”;祖孙嵌套组件(如 layout → 多级页面)用 provide/inject 避免层层透传;全局用户信息、权限菜单用 Pinia。
活学活用:面试官追问”什么时候用 provide/inject 什么时候用 Pinia”——provide/inject 是组件树内传递(同页面内),Pinia 是全局状态(跨页面)。再关联 Vue 3 中 defineExpose(父组件拿子组件的方法)和 Vue 2 的 $children 为什么被移除。
18. 虚拟 DOM 是什么,为什么要它
答案:用 JS 对象描述真实 DOM 结构,数据变化时先对比新旧虚拟 DOM(diff),找出最小差异批量更新真实 DOM。
应用场景:为什么要它而不是直接操作 DOM?因为直接操作 DOM 会频繁触发回流重绘(见第 12 题),虚拟 DOM 把多次修改合并成一次批量更新。框架的价值是”声明式”——你只管描述 UI 长什么样,更新交给框架。
活学活用:追问——“虚拟 DOM 一定比直接操作 DOM 快吗?”不一定,简单场景直接操作更快,虚拟 DOM 的优势是保证可维护性和跨平台(渲染到浏览器、小程序、原生 App 都行)。关联 diff 算法:同层比较、key 的作用(为什么列表 key 不能用 index)。
五、工程化与综合(深)
19. Webpack 打包流程,和 Vite 的区别
答案:Webpack 从入口出发,递归解析依赖生成依赖图,用 loader 转换非 JS 文件,plugin 扩展能力,最后打包成 bundle。Vite 开发环境用原生 ESM 按需加载(不打包),生产环境用 Rollup。
应用场景:项目构建慢 → 排查是不是 Webpack 全量打包;用 Vite 后热更新快到毫秒级,因为浏览器只加载被修改的模块。
活学活用:追问 loader 和 plugin 的区别——loader 处理文件转换(vue-loader 处理 .vue、babel-loader 转 ES6),plugin 做打包流程的钩子(HtmlWebpackPlugin 生成 HTML、MiniCssExtractPlugin 抽 CSS)。再关联 tree-shaking、code-splitting。
20. 前端性能优化,从哪些方面入手
答案(面试常考,按维度答):
1 | 加载阶段:打包分包、路由懒加载、图片懒加载、CDN、HTTP 缓存、Gzip |
应用场景:大表格渲染 1 万行 → 虚拟滚动只渲染可视区域;图表数据量大 → 增量更新而不是整体重绘。
活学活用:这题是综合题,答得越有条理越加分。建议按”加载 → 渲染 → 运行时”三段式组织,每段举一个真实例子。关联第 9、10、12 题,形成知识网络。
21. 什么是内存泄漏,怎么排查
答案:该释放的内存没释放,持续累积导致页面卡死。常见原因:全局变量、闭包持有、事件监听没解绑、定时器没清理。
应用场景:
1 | // ❌ 泄漏:组件销毁了但定时器还在跑 |
活学活用:Chrome DevTools 的 Performance(录制看内存曲线)和 Memory(heap snapshot 对比)是实战排查工具。关联第 2 题闭包、第 14 题生命周期卸载清理。
22. 说说你做过的最复杂的项目/组件
答案思路:不用背模板,但要按”背景 → 难点 → 方案 → 结果”讲。举例:做过一个带权限 + 多标签页 + 大数据表格的中后台项目,难点是权限与路由的联动和表格性能,方案是动态路由 + 虚拟滚动,结果构建体积降了 60%、页面切换从卡顿变流畅。
活学活用:这题考的是表达和总结能力,不是技术。注意:难点要说”你负责的那部分”,别说整个项目;结果要有数字(见”应用场景”)。
六、活学活用——如何串联知识
题库背下来只是第一步,面试官真正想考的是知识的关联能力。举个串联例子:
面试官问:”页面加载慢,你怎么排查?”
你可以串起来答:
- 先看网络面板(第 9 题:URL 加载过程)——有没有大文件、缓存有没有命中(第 10 题)
- 再看渲染——是不是渲染阻塞、有没有频繁回流(第 12 题)
- 看打包——vendor 是不是太大(第 19 题:Webpack/Vite 分包)
- 最后看运行时——有没有内存泄漏、定时器没清理(第 21 题)
一道”性能优化”题能展开到四五个知识点,面试官对你的评价会完全不同。
串联建议
| 主考点 | 可以关联 |
|---|---|
| 闭包 | 内存泄漏、防抖节流、事件循环 |
| Promise | async/await、微任务、并发请求 |
| 事件循环 | 渲染机制、nextTick、性能 |
| 跨域 | HTTP 缓存、代理、cookie |
| 虚拟 DOM | diff、key、性能优化 |
| 生命周期 | nextTick、keep-alive、内存泄漏 |
| 组件通信 | Pinia、provide/inject、组件设计 |
下面逐条拆解:每个主考点讲清”内在联系”(为什么能串在一起)、”面试怎么顺着展开”(带模拟话术)、”容易漏的盲点”(面试官的追问点)。
拆解 1:闭包 → 内存泄漏、防抖节流、事件循环
内在联系:闭包 = 函数 + 被记住的作用域。”能记住变量”是这一切的根源——
- 能保存状态 → 防抖里保存 timer、节流里保存上次时间戳
- 能保存状态 → 变量不释放 → 内存泄漏
- 防抖/节流里 setTimeout 的回调是闭包 → 又牵出事件循环(宏任务)
面试怎么展开:
闭包最典型的应用是防抖节流,本质是让内部函数记住 timer 这个变量。但闭包持有外层作用域,组件销毁时不清 timer 就是内存泄漏——所以要讲闭包,必然要讲到”怎么释放”,这就串到了事件循环(setTimeout 是宏任务)和 Vue 生命周期(onUnmounted 里清理)。
容易漏的盲点:面试官追问”闭包为什么能保存状态”——因为内部函数被外部引用后,它所在的词法环境不会被垃圾回收。这一句点出”引用”和”GC”两个关键词,比背定义强。
拆解 2:Promise → async/await、微任务、并发请求
内在联系:Promise 三状态 + 链式调用是底座,其他都是它的衍生——
- async/await 是语法糖(本质还是 then 链)
- then 回调注册进微任务队列 → 牵出事件循环(第 4 题)
- 多个互不依赖的请求 →
Promise.all并发;要超时 →Promise.race;要部分失败 →allSettled
面试怎么展开:
Promise 解决的是回调地狱。async/await 只是让它读起来像同步,执行上依然走微任务。实际项目里并发拉用户和菜单用 Promise.all,接口超时用 race 兜底,批量上传想”失败的跳过”就用 allSettled——一个 Promise 能串出事件循环、并发、错误处理三块知识。
容易漏的盲点:async 函数里 throw 等价于 reject;await 之后的代码都在微任务里执行(所以 await 后面的顺序符合”同步先、微任务后”)。追问”Promise.all 一个挂了怎么办”——默认整体 reject,这就是要用 allSettled 的原因。
拆解 3:事件循环 → 渲染机制、nextTick、性能
内在联系:JS 线程和渲染线程互斥,一次宏任务执行完才可能渲染,所以”任务什么时候执行”和”页面什么时候画”是同一根时间线上的两个刻度——
- 同步多次改数据会阻塞 → 框架做批量更新(Vue 更新队列、React setState)
- 数据变了 DOM 还没更新 →
nextTick等下一轮 - 高频率事件(resize/scroll)合并 → 防抖节流,避免每帧都触发布局
面试怎么展开:
事件循环不光是背输出顺序,它直接决定渲染时机。同步代码执行完、微任务清空后浏览器才渲染。Vue 改 ref 后 DOM 不立刻变,就是因为在微任务里统一渲染——所以 nextTick 能拿到最新 DOM,而 setTimeout 拿不到(它排在渲染之后)。渲染帧率和防抖节流的动机也在这里。
容易漏的盲点:微任务队列清空后、宏任务执行前,浏览器也可能渲染。所以 nextTick(微任务)比 setTimeout(0)(宏任务)更早拿到更新后的 DOM——这是常考的一句话对比。
拆解 4:跨域 → HTTP 缓存、代理、cookie
内在联系:跨域的根源是浏览器同源策略,而解决方案几乎全发生在 HTTP 层——
- CORS 是官方解法(响应头)
- 代理是绕过(服务端之间没有同源限制)
- cookie 是附带坑(
credentials、SameSite) - 缓存则是同源下才谈得上的策略(第 10 题)
面试怎么展开:
跨域先讲清”浏览器为什么拦”——同源策略是安全模型,请求发出去了、响应被拦截。然后分三类:开发环境用 Vite 代理、生产用 nginx 反代(绕过);后端配 CORS 头(官方);带 cookie 要 withCredentials 且 Origin 不能是
*。顺带把预检请求 OPTIONS 为什么出现讲一下,就能自然引到 HTTP 协议层。
容易漏的盲点:简单请求和复杂请求的区别——只有简单请求才不发预检。触发预检的是自定义头、非简单方法(PUT/DELETE)、application/json 请求体。追问到这里,跨域题就变成 HTTP 题了。
拆解 5:虚拟 DOM → diff、key、性能优化
内在联系:虚拟 DOM 的意义是”声明式 + 批量更新”,讲清它为什么要存在、怎么对比,是一条线:
- 为什么需要 → 直接操作 DOM 触发回流重绘(第 12 题),且跨平台
- 怎么对比 → 同层 diff
- 怎么复用 → key
- 怎么更优 → 静态标记(PatchFlag)、虚拟列表
面试怎么展开:
虚拟 DOM 不是”比直接操作快”,而是把多次修改合并成一次批量更新,同时保证声明式开发。diff 只在同层比较,所以 key 决定了节点复不复用——用 index 做 key,列表头插一条时,后面所有节点的状态会错乱。Vue3 加了静态标记,只 diff 动态部分,性能进一步提升。
容易漏的盲点:”虚拟 DOM 一定更快吗”——不是,简单场景直接操作 DOM 更快,虚拟 DOM 卖点是可维护性和跨端。以及 key 用 index 的具体后果(组件状态、表单输入值错位),比背”不要用 index”更有说服力。
拆解 6:生命周期 → nextTick、keep-alive、内存泄漏
内在联系:生命周期定义”组件状态切换的时机”,所有”依赖时机”的操作都挂在它下面——
- DOM 就绪 →
onMounted(发请求、绑监听) - 数据更新但 DOM 未更新 →
nextTick - 组件被缓存 →
onActivated/onDeactivated(keep-alive 改写生命周期) - 组件销毁 →
onUnmounted(清理,否则泄漏)
面试怎么展开:
生命周期是钩子时机。请求放 onMounted 是因为那时 DOM 已就绪;改完数据要拿新 DOM 用 nextTick;组件加了 keep-alive 后 onUnmounted 不会触发,清理逻辑要挪到 onDeactivated。内存泄漏的排查,本质就是检查”每个 onMounted 绑的东西,onUnmounted 有没有解”。
容易漏的盲点:keep-alive 下生命周期不按常规走——onUnmounted 不执行、onMounted 只执行一次、切换走的是 onActivated/onDeactivated。追问”setup 里注册的生命周期和 Options API 有什么区别”——setup 里是函数注册,天然是闭包,能访问定义时的变量。
拆解 7:组件通信 → Pinia、provide/inject、组件设计
内在联系:组件通信的本质是”数据在组件树里按什么作用域流动”,选哪种方式取决于作用域——
- 父子局部 → props + emit(受控、显式)
- 祖孙跨层 → provide/inject(避免逐层透传)
- 全局跨页面 → Pinia
- 临时跨组件 → mitt
面试怎么展开:
通信方式按数据作用域分层讲:父子用 props + emit,祖孙用 provide/inject,兄弟或全局用 Pinia。关键是别混用——如果 Pinia 里什么都放,组件树的信息就失去可见性。再补一句 Vue3 为什么删掉 $children/$listeners——因为 defineExpose 和 provide/inject 更显式、类型更友好。
容易漏的盲点:provide/inject 的响应式坑——inject 拿到的如果是普通对象,改动不会触发更新,要传 computed 或 reactive。另一个:defineExpose 只对模板 ref 生效,对函数参数传入的组件实例无效。
专题:Vue3 中 Promise、Proxy、闭包的实际应用
这三个是 JS 面试必考点,而在 Vue3 里它们不是孤立的——Proxy 管数据监听,闭包管状态保留,Promise 管异步调度,三者拼起来恰好就是 Vue3 的响应式骨架。下面逐个讲透,再串成一条链路。
1. Proxy:Vue3 响应式的基石
答案:reactive() 就是给对象包了一层 Proxy,在 get 里收集依赖(track),在 set 里触发更新(trigger)。
1 | const state = reactive({ count: 0 }) |
应用场景:
- 相比 Vue2 的
defineProperty,Proxy 能拦截新增属性、删除属性、数组索引,所以 Vue3 不需要Vue.set/Vue.delete这些补丁 API - 为什么
reactive不支持基本类型?因为 Proxy 只能代理对象,基本类型没法劫持,所以ref先把值包装成{ value }再代理 ref.value的读写就是触发 Proxy 的get/set——模板里自动解包(因为编译时知道是 ref),JS 里必须.value
活学活用:追问”ref 和 reactive 到底差在哪”——底层是同一个东西,ref 就是 reactive({ value: x }) 的语法糖。追问”为什么解构 reactive 会丢响应式”——解构后拿到的是原始值,不再经过 Proxy 的 get/set,要用 toRefs 保住代理。
2. Promise:Vue3 里的异步调度器
答案:Promise 在 Vue3 里有三个层级——请求层(数据获取)、调度层(更新队列 / nextTick)、代码层(async setup、动态导入)。
1 | // ① 数据请求:互不依赖的接口并发拉取 |
应用场景:
- 数据请求放
onMounted而不是 setup 顶层:setup 里await会推迟组件渲染,且 setup 里没有this - Vue 的更新队列就是”同步改数据 → 微任务里统一渲染”(见第 4 题事件循环),所以改完
ref立刻读 DOM 是旧值,要await nextTick()
活学活用:追问”数据变了为什么 DOM 不立刻更新”——异步批量渲染,减少回流。追问”Promise.all 一个挂了怎么办”——用 allSettled 保底,或给每个请求单独 .catch,别让一个失败拖垮整页。
3. 闭包:Vue3 的状态容器
答案:组合式函数、computed、watch、effect 的本质全是闭包——函数 + 被捕获的变量。
1 | // ① 组合式函数:状态和逻辑封进闭包,只暴露接口 |
应用场景:
- 自定义 hooks 的”私有状态”全靠闭包隔离,多组件用同一个 hook 互不污染——因为每次调用都会新建一个闭包
watch回调、watchEffect执行函数、onMounted回调都是闭包,能访问定义时的变量- 闭包持有
setInterval、事件监听时,组件卸载必须释放,否则内存泄漏(见第 21 题)
活学活用:追问”组合式函数为什么能共享逻辑不共享状态”——每次调用生成独立闭包;而 useXxx 放在模块顶层则全局共享(类似 Pinia)。追问”闭包会泄漏吗”——会,onUnmounted 里清理定时器和监听就是标准解法。
4. 三者串联:一条链路看懂 Vue3 响应式
把上面三个知识点拼起来,就是一个完整的响应式故事:
1 | const count = ref(0) |
- Proxy(①②):管”数据怎么被监听”
- 闭包(③④):管”谁依赖了这个数据”——effect 闭包在
get时被收集进依赖集合,set时被逐一触发 - Promise(⑤⑥):管”什么时候执行更新”——批量、异步、不阻塞主线程
面试官问”Vue3 响应式原理”,顺着这条链路答,每个环节都能展开追问,就是完整回答。
5. 业务里的串联:一个搜索组件
1 | export function useSearch(fetchApi, delay = 300) { |
一个组件里三个知识点全用上:闭包管防抖和状态隔离,Promise 管请求和超时,Proxy 管数据响应。面试时讲这一个例子,能覆盖三连问。
6. 面试追问自测
| 追问 | 用到的知识点 |
|---|---|
为什么 reactive 能监听新增属性? |
Proxy 的 set 拦截 |
为什么 ref 访问要 .value? |
包装成对象才能被 Proxy 代理 |
| computed 为什么有缓存? | 闭包保存上次结果 + 依赖集合 |
为什么改了 ref 立刻读 DOM 是旧值? |
Promise 微任务异步渲染 |
nextTick 内部怎么实现的? |
Promise.resolve().then 推微任务 |
| 组合式函数为何共享逻辑不共享状态? | 每次调用新建一个闭包 |
总结
- 由浅及深:JS 基础 → 进阶 → 浏览器 → 框架 → 工程化,逐层递进
- 每道题都问自己”用在哪”:能讲出应用场景的才是真理解
- 面试是考关联不是考背诵:一个”性能优化”能串起五个知识点,比背 50 个八股强
- 踩过的坑讲出来就是加分项:真实经历比标准答案有说服力
内容由 AI 生成,仅供参考。本文发布于 码上学习,转载请注明出处。