前端工程师-曹林超
# 面试口述稿 · 前端工程师
适用岗位:Web 前端工程师 / 高级前端(中后台 + 跨端 + 可视化方向) 用法:先背「自我介绍」和「项目主线」,再按项目准备追问预案。技术八股见 前端笔记 里的 JavaScript / Vue / 浏览器相关章节。
一、自我介绍(60-90 秒,背熟)
面试官你好,我是曹林超,5 年前端开发经验,现在在一家做新能源充电的公司做前端负责人。
我是 2021 年入行的,早期在中控文旅做 ERP 后台、H5 和微信小程序,组件化和工程化的底子是在那段时间打下来的。2023 年去了北京聚点互动做医疗方向,主要是 React 18 中后台加 Taro 跨端小程序,还做过在线问诊的聊天模块,那块用虚拟滚动加 SSE 做了万级消息渲染和流式输出。
2024 年 5 月入职现在这家公司做充电业务,前端这块整体是我负责的:C 端多端充电应用、运营 SaaS 后台、商户端小程序,还有一个 Three.js 的 3D 运营大屏。技术上主要做了三件事:一是把存量的原生小程序渐进式重构成 uni-app,一套代码覆盖微信、支付宝和 APP;二是搭 SaaS 后台的复杂表格、动态表单和 RBAC 权限体系;三是 3D 大屏的性能优化,保证它能长时间稳定运行不卡顿。
我比较擅长中后台和可视化这两块,React 和 Vue 两条线都熟,比较习惯从需求评审、技术方案一直推进到上线。
口述要点:一句话定位「中后台 + 跨端 + 可视化」三条线,然后用时间顺序串起三段经历。
二、职业主线
Q:这几年技术栈是怎么变化的?
分三个阶段:第一阶段做 ERP 和商城,主要是 Vue2 + uni-app,偏业务组件沉淀;第二阶段做医疗中后台,转到 React 18 + TypeScript,开始接触数据看板、虚拟滚动、SSE 这些偏性能的场景;第三阶段就是现在,Vue3 + React 双栈并行,SaaS 后台用 Vue3 + Ant Design Vue,3D 大屏用 React 18 + Three.js。
Q:Vue 和 React 你更熟哪个?两个都用的原因是什么?
两个都在生产项目里用过,不存在只写过 demo 的情况。选型主要看场景:SaaS 后台和跨端用 Vue 系,因为 Element/Ant Design Vue 的表格和表单生态更成熟,团队上手也快;大屏用 React,主要是 Three.js 和 D3.js 在 React 生态里的封装例子多,我自己也更喜欢 Hooks 里做副作用管理。跨端小程序两边都用过,uni-app 是 Vue 系,Taro 是 React 系。
三、项目一:充电跨端应用(uni-app 重构)|2024.05 — 至今
Q:为什么要把原生小程序重构成 uni-app?(必问)
核心是成本问题。我们最初只有一个原生微信小程序,后来要接支付宝小程序、还要出 APP,如果继续维护原生,等于三套代码各写一遍,业务迭代要同步三次,长期一定崩。
uni-app 是当时权衡下来最合适的:一是 Vue 语法团队本来就熟,迁移成本低;二是能同时编译到小程序、APP、H5;三是有成熟的条件编译机制,各端差异不用靠 if-else 硬写。
但存量代码已经有大量业务逻辑,所以没推倒重来,做的是渐进式重构。
Q:渐进式重构具体怎么落地的?
三步走:
第一步是抽公共层,先把请求拦截、登录授权、定位、扫码、支付这些跨端高频能力抽成独立模块,保证新老代码都能调;
第二步是按页面迁移,一个页面一个页面迁,迁完一个验收一个,没迁完的老页面继续留在原生里跑;
第三步是收口切换,等页面基本迁完、验证稳定之后再统一切到 uni-app 构建发布。
关键是整个过程业务需求是并行迭代的,不能让重构把业务停掉,所以每一步都要保证线上可用。
Q:各端差异怎么抹平的?
分三种处理方式:
- 能力差异:统一封装一层,比如登录授权,微信是
wx.login、支付宝是my.getAuthCode,内部用条件编译区分,对外暴露同一个方法;- 样式差异:主要是单位和小程序特有的样式限制,统一转 rpx、把不支持的 CSS 特性做降级;
- 生命周期差异:小程序页面生命周期和 APP 不一样,我们在公共 composable 里做了统一抽象,业务页面只写一份逻辑。
原则是「差异收敛在基础层,业务层不感知平台」。
Q:首屏和弱网怎么优化的?
主要是三块:
- 分包加载:把商城这类低频模块拆到分包,主包只留扫码充电的主链路,主包体积降下来首屏就快;
- 页面懒加载 + 按需引入:组件不一次性全注册,用到才加载;
- 接口层优化:高频页面的请求做了防抖和缓存,弱网下减少无效请求,同时给扫码链路加了兜底的加载态和重试。
扫码充电是我们最高频的入口,用户很多时候是在地下车库信号很差的环境下用,所以这条链路是重点优化的对象。
Q:支付这块踩过什么坑?
主要是回调时序和异常兜底。支付的坑在于前端拿到支付成功不等于订单已到账,渠道的回调是异步的,可能会晚于前端返回。
所以我们的做法是:前端支付完成后不直接改本地状态,而是去轮询查订单真实状态,查到了再刷新 UI;同时后端有回调兜底,超时未支付会关单。另外提现这块做了异常兜底,比如渠道返回处理中,前端展示「处理中」而不是直接报失败,避免用户重复提交。
Q:多端打包、审核、发布有什么要注意的?
小程序审核有几个固定的坑:类目和隐私协议要一次配好、涉及支付的必须有对应资质、预发布环境要单独配域名白名单。我们的做法是把各端的构建配置和发布流程写成文档,出一版走一遍清单,避免每次都漏。版本这块做了灰度发布能力,出问题可以回退。
四、项目二:运营 SaaS 管理后台|2024.05 — 至今
Q:项目整体是怎么搭的?
基于 Vue3 + TypeScript + Ant Design Vue + Vite + Pinia。工程上用 Turbo Monorepo 管理,因为后台、商户端、公共组件库要共享代码,Monorepo 能让公共组件和工具函数只维护一份。图表用 ECharts,地图用高德。
Q:复杂表格怎么做的?(高频)
后台的订单、财务这些列表,字段多、数据量大、还要求列定制和导出,所以选了 VxeTable 做底座。做了几层封装:
- 列配置化:把列定义抽成配置数组,支持动态显隐、列宽记忆、排序筛选,用户自己配的列偏好会持久化;
- 大数据量处理:走服务端分页 + 虚拟滚动,不做一次性全量渲染,否则几千行直接卡死;
- 导出:大数据量导出走异步任务,后端生成文件后前端拿下载地址,避免前端一次性把几十万行拉到内存里;
- 单元格自定义渲染:状态、金额、操作列这些用插槽做业务化的展示。
这样业务侧接入一个新列表,只需要写列配置,不用重复写表格逻辑。
Q:动态表单怎么设计的?
后台有大量结构类似的表单,每个都手写一遍成本很高。所以做了一套配置化的表单方案:
用配置描述字段的类型、校验规则、联动条件,渲染层根据配置动态生成。支持多组件类型(输入、选择、日期、上传、级联)、联动校验(A 字段变了 B 字段的规则要跟着变)、动态显隐(比如选了某种结算方式才显示某个字段)。
效果是常规表单的开发从「写一个页面」变成「写一份配置」。
Q:RBAC 权限前端怎么做?
前后端一体的方案。前端拿到菜单树之后做三级控制:
- 路由级:没有权限的路由不注册,用户访问不到,动态路由在拿到权限数据之后
addRoute挂载;- 按钮级:封装自定义指令,按权限点控制按钮的显示;
- 数据级:比如场站合伙人只能看自己场站的订单,这块是后端在查询时拼数据范围,前端不做过滤。
这里有个原则我会强调:权限一定在后端兜底,前端做权限只是体验优化,不能当作安全边界。
Q:看板图表怎么做的?
用 ECharts 做经营数据、订单趋势、场站运营这些看板。做了两件事:一是把常用图表封装成统一组件,只传数据和配置,不用每个页面重新写 option;二是图表实例做了统一管理和销毁,组件卸载时
dispose,避免页面反复切换导致 ECharts 实例泄漏、内存越来越高。
五、项目三:3D 运营可视化大屏|2025.04 — 至今
Q:为什么要用 Three.js,ECharts 不够用吗?
需求是全国场站地图 + 飞线动画 + 热力 + 视角切换。ECharts 能做 2D 地图和简单飞线,但要做带纵深的 3D 地图、区域挤出和相机视角动画就吃力了。所以地图主体用 Three.js + D3.js 的地理数据来做,指标图表还是 ECharts,两者联动。
Q:3D 性能优化具体做了什么?(重点,能体现深度)
大屏最怕的是跑久了越来越卡,所以优化分「渲染量」和「渲染频率」两条线:
减少渲染量:
- 几何体合并:几千个场站如果每个都是一个独立 mesh,draw call 会爆炸。做法是把相同材质的几何体合并成一个 BufferGeometry,用实例化/合批把 draw call 从几千降到个位数;
- 视锥体剔除:相机外的对象不参与渲染;
- 按需渲染:不是无限
requestAnimationFrame空转,静态场景只在数据变化或交互时渲染,动画结束后就停。控制资源:
- 纹理和几何体在场景切换时做
dispose,不然显存只增不减;- 图表和 3D 的场景对象都做了统一的生命周期管理。
另外大屏是 7×24 挂着跑的,所以还加了异常兜底,WebGL 上下文丢失这类情况能自动恢复,不能白屏。
Q:地图和图表怎么联动?
用一个中心化的状态层管当前选中的区域(省/市),地图点击区域之后更新状态,图表监听到状态变化就带着区域参数重新请求数据。反过来图表上的条件变化也一样。这样避免了组件之间互相直接调用,联动逻辑好维护。
Q:大屏的动画怎么做的?
用 GSAP 做场景入场、地图视角切换、数字滚动这些。选 GSAP 是因为它的时间轴能力比手写 CSS 动画强,多个动画要按顺序编排的时候,用 timeline 排会比一堆
setTimeout清晰得多,而且它底层也是走的requestAnimationFrame,性能上没问题。
六、项目四:运营及商户管理平台|2024.08 — 2026.01
Q:从 0 到 1 搭建怎么选型的?
面向的是公司运营人员和场站合伙人两个角色,一个是后台、一个是小程序端,所以选了 Vue2/Vue3 + Element UI/Element Plus 做主后台,商户端助手用 uni-app。这样后端接口一套、公共业务逻辑能复用,多端发布也不用重复开发。
Q:你提到排查过重复渲染和冗余请求,具体是什么问题?
两个典型问题:
一是组件重复渲染:有个经营看板页面,父组件每次轮询更新数据,把整个大对象重新赋值,导致下面所有子组件全部重渲染,虽然子组件的数据没变。解决方式是拆分响应式数据粒度,把子组件不依赖的字段拆出去,子组件用
props精确依赖需要的字段,另外对纯展示组件做了缓存。二是冗余请求:多个组件在同一个页面各自请求了同一份基础数据(比如场站列表),同一份数据被请求了四五次。解决方式是把这个数据提到公共状态里做一次请求 + 缓存,其他组件从状态里取。
这类问题单看代码不容易发现,都是靠 Performance 面板和 Network 面板看出来的。
七、医疗系列项目(React 18)|2023.01 — 2024.05
Q:登录态持久化和动态路由权限怎么做的?
医疗管理平台用的是 React 18 + Zustand + React Router 6。
登录态用 Zustand 存 token 和用户信息,配合持久化中间件存到 localStorage,刷新页面不会掉登录。动态路由的做法是:路由表分静态和动态两部分,静态的放登录页、404 这些公共路由,动态路由根据后端返回的权限在后端数据回来之后动态生成并挂载,同时用路由守卫拦截未授权的访问。
Q:看板里的请求竞态问题怎么解决的?
这个是医疗数据看板的典型问题:页面上有多个筛选条件,用户快速切换的时候,先发的请求可能后返回,把新数据覆盖掉了。
解决方式三个层面:
- 筛选变化防抖,减少无效请求;
- 请求取消,新请求发出时取消上一次未完成的请求;
- 标记校验,给每个请求打上当前筛选条件的标记,回来的时候比对,不是最新条件的响应直接丢弃。
另外多个独立维度的数据用
Promise.all并行请求,而不是串行等,首屏能快不少。
Q:聊天模块的万级消息怎么渲染的?
用
@tanstack/react-virtual做虚拟滚动。核心思路是只渲染视口内的 DOM,列表多长对 DOM 节点数都没影响——只有可视区域加少量缓冲区的元素真实存在,滚动的时候动态计算位置并复用节点。主要处理了三个问题:一是动态高度,消息长度不一样,需要用测量机制拿到真实高度并缓存;二是滚动锚定,新消息来的时候如果用户在看历史消息,不能强行拉到底部,要提示「有新消息」;三是底部定位,初始化时要精确定位到最后一条。
Q:为什么用 SSE 不用 WebSocket?
场景是问诊时 AI/医生回复的流式输出,是单向的——服务端推、客户端收就够了,用 WebSocket 属于杀鸡用牛刀。
SSE 的优势是:基于 HTTP 协议,走标准请求,不用额外的协议升级和心跳维护,服务端实现也简单,而且浏览器自带断线重连。WebSocket 更适合双向高频通信的场景,比如实时协作、游戏。这个场景 SSE 是更合适的选择。
补充一句:SSE 本身不支持自定义请求头,所以鉴权信息我们是放在 URL 参数或者 cookie 里带过去的,这也是选型时要考虑的点。
Q:Taro 多端是怎么做的?
家小医那个项目要覆盖微信、支付宝、百度、头条四个平台。做法和 uni-app 类似:
- 用条件编译处理平台差异,
p判断当前平台,各端不同的 API 和组件在编译期就分开了,不会打进同一个包;rocess.env.TARO_ENV - 状态用 Zustand 统一管理登录态、用户设置这类全局数据,保证跨页面一致;
- 样式统一用 Tailwind + weapp-tailwindcss,把小程序不支持的 CSS 转成兼容写法,避免每个平台各写一套。
沉淀下来的是跨端组件和编码规范,后面新页面直接复用。
八、高频技术追问
Q:Vue2 和 Vue3 的响应式区别?
Vue2 用的是
Object.defineProperty,问题是没法监听数组下标和对象新增属性,得用$set这种 hack,而且初始化时要递归遍历所有属性,数据量大时性能差。Vue3 换成
Proxy,能监听整个对象包括新增删除和数组操作,而且配合effect是懒执行的,只有真正访问到的属性才会被追踪,初始化开销小很多。另外 Vue3 通过编译时优化(静态提升、Patch Flag)减少了 diff 的开销。我在项目里从 Vue2 迁 Vue3 的时候,最直观的收益就是不再需要到处写
$set了。
Q:你的前端性能优化体系是怎样的?
我会分四个层面讲:
- 加载:资源拆包、路由懒加载、组件按需引入、图片压缩和 CDN、关键资源预加载;
- 渲染:减少 DOM 数量(虚拟滚动)、减少重排重绘、合并 draw call(3D 场景)、缓存纯展示组件;
- 请求:防抖节流、请求取消、并发请求、缓存复用(SWR 那类思路)、减少冗余请求;
- 运行时:内存泄漏排查(事件监听、定时器、ECharts 实例的销毁),长时间运行场景的资源回收。
优化一定要先测量再动手,用 Performance 和 Network 面板定位瓶颈,不能凭感觉。
Q:Monorepo 为什么用 Turbo?
因为后台、商户端、组件库要共享代码。传统做法是组件库单独发包,改一个组件要发版、升级、重新构建,链路太长。Monorepo 让公共包以源码形式直接引用,改完立即生效。
Turbo 的作用是提供任务编排和缓存,比如能识别哪些包变了只构建受影响的包,构建结果做缓存,CI 时间能明显降下来。
Q:前端安全你会关注什么?
主要是 XSS 和 CSRF。XSS 上,用户输入的内容一律转义,富文本/Markdown 渲染用白名单过滤,不用
innerHTML直接插;CSRF 上,状态变更的请求用 POST,token 放在请求头而不是 cookie 里,配合后端的校验。另外我们的权限体系虽然前端做了菜单和按钮控制,但我一直强调那只是体验优化,真正的权限校验必须在后端。
九、容易被问倒的点 & 应对
1. 「你没在大厂待过,技术深度够吗?」
直接接:大厂和小公司在技术问题上没有本质区别,我们的场景里同样有高并发、性能优化、工程化治理的诉求,比如 3D 大屏的性能瓶颈、订阅消息和海量列表渲染。可以现场挑一个深聊。
2. 「3D 这块你是一直在做吗?还是只做过一个项目?」
坦诚说:Three.js 是项目驱动学的,从 0 做了这个大屏,中间踩了很多性能的坑。不能说自己是图形学专家,但业务场景下从建模数据接入、场景搭建到性能优化的完整链路我是走通的。
3. 「跨端框架你更推荐 uni-app 还是 Taro?」
看团队技术栈:Vue 系团队选 uni-app,React 系选 Taro。功能上都能满足多端需求,差异主要在生态和团队学习成本。我们两个都用过,结论是「没有最好,只有和团队最匹配」。
4. 「重构期间怎么保证不出线上事故?」
三个保障:一是分页面迁移,每迁一个验收一个;二是新老代码并存期做好兼容,公共层先抽,保证两边都能调;三是上线走灰度,有回滚预案。
5. 「为什么看机会?」
准备一个正向理由:希望在技术挑战更大的环境里,做更复杂的前端系统。
十、反问面试官(准备 3 个)
- 团队前端现在有几个人?技术栈是统一的还是各个业务线各自选型?
- 这个岗位主要负责哪块业务?是偏中后台、C 端还是可视化?
- 团队对前端工程化和性能这块有明确的要求或规划吗?
十一、表达纪律(自查清单)
- [ ] 说「我主导 / 我负责」,不说「参与」,也不把团队成果全揽
- [ ] 讲优化先讲「怎么测量出来的瓶颈」,再讲方案,最后讲收益
- [ ] 跨端重构强调「渐进式 + 不影响业务迭代」,不说「推倒重来」
- [ ] 权限话题一定补一句「前端只是体验优化,安全在后端兜底」
- [ ] 3D / 图形学相关不装专家,说清「项目驱动、业务场景走通了」
- [ ] 不确定的细节坦承「这块记不太清了」,不硬编
十二、延伸面试题(按简历技能点拆解)
简历上每一项技能都可能被单独拎出来问。下面按模块把「可能问什么」列全,答案只写结合项目的口径,通用八股见 前端笔记 里的 JavaScript / Vue / 浏览器相关章节。 后端技能点的延伸题见 全栈开发工程师口述稿。
JS / TS
1. ES6+ 你常用哪些特性?
箭头函数与 this、解构、可选链
?./ 空值合并??、模板字符串、模块化、Promise / async-await、扩展运算符、Map/Set、Proxy/Reflect。 项目里 Proxy 用得最多的地方是 Vue3 的响应式和做数据代理层。
2. 事件循环(宏任务/微任务)怎么理解?
同步代码先跑完;然后清空微任务队列(Promise.then、queueMicrotask、MutationObserver);再取一个宏任务(setTimeout、setInterval、IO、UI 渲染)执行,执行完又清空微任务,如此循环。 实际意义:想让 UI 先更新再做重计算,用
setTimeout或requestAnimationFrame;await后面相当于then,是微任务。
3. 手写题会考什么?
防抖节流、深拷贝(处理循环引用和特殊类型)、Promise.all / 并发控制、柯里化、数组扁平化、EventEmitter。 这类题的重点不是背答案,而是说清边界:比如深拷贝要处理 Date/RegExp/Map/Set 和循环引用,Promise.all 要处理 reject 和空数组。
4. TS 泛型怎么用?什么是类型收窄?
泛型是把类型当参数传,比如统一封装请求
request<T>(url): Promise<T>,调用方request<OrderList>(...)就能拿到类型提示。 类型收窄是 TS 通过条件判断(typeof、in、instanceof、可辨识联合)把宽类型缩小到具体类型。项目里接口返回的联合类型、组件的 props 都靠它。
5. interface 和 type 的区别?
interface 能被继承和合并声明,适合描述对象结构;type 更灵活,能定义联合、交叉、元组、条件类型,但不能重复声明。 我们的习惯:对象结构用 interface,联合类型和工具类型用 type。
Vue
6. Vue2 和 Vue3 响应式原理区别?
Vue2 用
Object.defineProperty,不能监听数组下标和新增属性,要$set兜底,且初始化时递归遍历所有属性,数据量大时慢。 Vue3 用Proxy,能监听整个对象含新增删除和数组操作,配合 effect 是懒执行的,只有访问到的属性才被追踪,初始化开销小。
7. Vue3 的编译优化做了什么?
静态提升(把静态节点提到 render 外只创建一次)、Patch Flag(标记动态绑定类型,diff 时只比较会变的部分)、Block Tree(用动态节点集合跳过静态子树)、事件缓存。 结果就是 diff 的量级从「整棵树」降到「动态节点」。
8. ref 和 reactive 怎么选?
ref什么类型都能装,模板里自动解包、脚本里要.value;reactive只能接对象,且解构会丢响应式。 我们的约定是统一用ref,避免 reactive 解构踩坑。
9. 组合式 API 怎么组织复用逻辑?和 mixin 比优势在哪?
把相关逻辑写成一个
useXxx函数返回 ref 和方法,按功能而不是按选项聚合。 相比 mixin:来源清晰(变量从哪来的能直接看到)、不会命名冲突、类型推导友好、可以传参。我们跨端登录、分页列表、表格配置都抽成了 composable。
10. Pinia 和 Vuex 的区别?
Pinia 去掉了 mutation,直接改 state;对 TS 支持更好(不需要额外类型声明);没有嵌套模块,每个 store 独立且能互相引用;体积也更小。新项目我们直接用 Pinia。
11. 路由权限怎么做?
路由分静态(登录、404)和动态两部分。登录后拿到后端权限数据,动态路由通过
router.addRoute挂载,同时在全局前置守卫里做拦截:没登录跳登录页,没权限跳 403。菜单树也是同一份权限数据渲染出来的。
React
12. Hooks 的原理是什么?为什么不能在条件语句里用?
Hooks 在 Fiber 节点上以链表形式按调用顺序存储,靠调用顺序匹配状态。 如果在条件里调用,顺序变了,后面的 hook 就会读到别人的状态。所以规则是「只在顶层调用」。
useState返回的 setState 之所以稳定,也是因为它存在这个链表节点上。
13. useEffect 的闭包陷阱是什么?
effect 捕获的是当次渲染的作用域,如果依赖数组写漏了,就会一直读到旧的值,比如定时器里读到的永远是初始 state。 解法:依赖写全、用
useRef存最新值、或者把逻辑放进 setState 的函数式更新里。清理函数也必须有——定时器、事件监听、ECharts 实例都要在 cleanup 里销毁,否则就是内存泄漏。
14. useMemo / useCallback / React.memo 什么时候用?
缓存计算结果、缓存函数引用、缓存组件渲染。但不要无脑加,缓存本身也有比较成本,加多了反而更慢。 我的判断标准是:这个值确实会传给子组件或作为其他 hook 的依赖,且计算/渲染成本可观,才包。
15. React 18 有哪些新特性?
并发特性:
useTransition/useDeferredValue把不紧急的更新降优先级;自动批处理(现在 setTimeout、Promise 里也会批处理了);Suspense配合懒加载;useId;流式 SSR。 我们在 3D 大屏里用useTransition处理切换区域时的大量图表更新,避免阻塞地图交互。
16. Zustand 和 Redux 怎么选?
Zustand 没有 Provider 包裹、模板代码极少、按选择器订阅所以重渲染范围小,对中小型项目明显更轻。Redux 的优势是生态(DevTools、中间件)和严格的单向数据流约束,大团队协作规范上更强。 我们医疗项目选 Zustand,主要就是不想写 action/reducer 那套样板。
17. React Router 6 动态路由 + 路由守卫怎么做?
路由表用
useRoutes或createBrowserRouter配置。守卫没有 Vue 那种全局钩子,通常封装一个<AuthRoute>组件包在需要鉴权的路由外层,内部判断登录态和权限,不满足就<Navigate>重定向。
跨端 / 小程序
18. uni-app 的条件编译原理?
编译期处理:
#ifdef MP-WEIXIN ... #endif这类注释语法在构建时被解析,只有当前平台的代码会被编进产物,其他平台的代码在 AST 层面就被剔除了,不会进包。 所以我们把平台差异都收敛在基础层,业务代码不感知。
19. Taro 的编译原理?和 uni-app 的区别?
Taro 3 之后走的是运行时方案:用 React/Vue 的运行时 + 一套 DOM/BOM 的模拟层(Taro runtime),把组件的增删改映射成小程序的
setData,所以能用完整的 React 语法和生态。 区别:uni-app 更偏编译时 + Vue 生态,Taro 3 偏运行时 + React 生态。选型主要看团队栈。
20. 小程序分包怎么做?为什么要分包?
微信主包体积有 2M 限制,超过就发布不了。做法是把低频模块(比如商城、活动)拆到分包,主包只留核心链路。 配套优化:分包预下载(进入某页面时提前下载关联分包)、按需注入、组件和图片资源懒加载。我们把扫码充电放主包,其它拆走,首屏明显变快。
21. 小程序登录流程?
wx.login拿临时 code → 传给后端 → 后端用 code + appid + secret 调code2session换 openid / session_key → 后端签发自己的登录态给前端。 关键点:session_key 不能下发到前端;前端只存自己的 token。支付宝是my.getAuthCode,流程类似。
22. 微信支付完整流程?
前端调后端下单接口 → 后端调微信统一下单拿 prepay_id 并返回签名参数 → 前端
wx.requestPayment拉起收银台 → 用户支付 → 微信异步通知后端 → 后端验签、改订单状态 → 前端轮询查订单真实状态刷新 UI。 重点:前端拿到支付成功不等于订单已支付,异步回调可能晚到,所以必须以后端订单状态为准。
23. 渐进式重构怎么落地?
三步:先抽公共层(请求、登录、支付、定位这些跨端能力)让新老代码都能调;再按页面逐个迁移、迁一个验收一个;最后统一切构建发布。整个过程业务需求是并行迭代的,所以每一步都要保证线上可用,上线走灰度 + 有回滚预案。
工程化
24. Vite 为什么快?和 Webpack 的区别?
开发阶段:Vite 不做打包,直接利用浏览器原生 ESM 按需请求模块,启动只做依赖预构建(用 esbuild,Go 写的,比 JS 快一个量级),所以冷启动基本是秒级。Webpack 要先打包完整个依赖图才能起服务。 生产阶段:Vite 用 Rollup 打包,产物质量不差。Webpack 的优势是 loader/plugin 生态更全,处理老项目和非标准资源更成熟。
25. HMR 的原理?
Vite 的 HMR 基于 ESM:文件变更后,服务端通过 WebSocket 通知浏览器,浏览器只重新请求那个变更的模块,并沿着
import链向上查找,找到接受更新的边界模块(i)就地替换,不需要刷新页面。 所以 Vite 的 HMR 是模块级的,项目越大优势越明显。mport.meta.hot.accept
26. Webpack 构建优化做过哪些?
时间维度:
cache持久化缓存、thread-loader/esbuild-loader提速、resolve.alias和extensions减少查找。 体积维度:splitChunks拆包、tree-shaking+sideEffects、compression-webpack-plugin开 gzip/brotli、图片压缩、路由懒加载、externals把大依赖走 CDN。
27. Tree-shaking 的原理?什么情况会失效?
基于 ESM 的静态结构做死代码消除:import/export 在编译期就能确定,打包器能分析出哪些导出没被用到。 失效场景:用了 CommonJS(require 是动态的)、有副作用(顶层执行代码、修改全局)且没标
sideEffects: false、通过对象属性间接访问导出、Babel 把 ESM 转成了 CJS。
28. Monorepo 为什么用 Turbo?
后台、商户端、组件库要共享代码。传统「组件库单独发包」链路太长(改一个组件要发版、升级、重新构建)。 Monorepo 让公共包以源码直接引用,改完立即生效。Turbo 提供任务编排和缓存:能识别哪些包变了只构建受影响的包,构建结果按输入哈希缓存,CI 时间明显下降。
29. 你的前端性能优化体系?
分四层:
- 加载:路由懒加载、按需引入、拆包、图片压缩 + CDN、关键资源预加载;
- 渲染:减少 DOM 数量(虚拟滚动)、减少重排重绘、合并 draw call(3D)、纯展示组件缓存;
- 请求:防抖节流、请求取消、并发请求、缓存复用、消除冗余请求;
- 运行时:内存泄漏排查(监听器、定时器、ECharts 实例、WebGL 资源)。
原则是先测量再优化:用 Performance / Network 面板定位瓶颈,再动手,不做凭感觉的优化。
可视化
30. ECharts 大数据量怎么优化?
数据层面:服务端聚合,别把几十万点直接丢给前端;必须前端渲染时开
sampling(降采样)、large: true(大数据量模式)、关掉动画和 symbol。 资源层面:setOption用notMerge只更新变化的部分而不是整体重建;组件卸载和页面切换时一定dispose,否则实例泄漏、内存越来越高。
31. Three.js 3D 大屏的性能优化做了什么?(简历重点)
分「渲染量」和「渲染频率」:
- 减少渲染量:几何体合并(几千个场站如果每个一个 mesh,draw call 爆炸,合并成 BufferGeometry 后 draw call 从几千降到个位数)、视锥体剔除、按需渲染;
- 控制频率:静态场景不跑无限
requestAnimationFrame,只在数据变化或交互时渲染,动画结束就停;- 资源管理:场景切换时
dispose几何体、材质、纹理,否则显存只增不减。大屏是 7×24 挂着跑的,所以还加了 WebGL 上下文丢失的恢复兜底,不能白屏。
32. D3.js 在项目里做什么?
我们不拿 D3 画图,主要用它做地理数据处理:把 GeoJSON 投影、三角剖分转成 Three.js 能用的顶点数据,还用它做过色阶映射和数值比例尺。画图还是 ECharts。
33. 大屏长时间运行怎么保证不卡不崩?
三点:一是资源回收(ECharts 实例、WebGL 几何体/纹理、事件监听都在生命周期里销毁);二是渲染按需触发,不要空转;三是加异常兜底和内存监控,WebGL 上下文丢失、接口异常都要能自动恢复。上线前会挂着跑一两天观察内存曲线。
交互 / 数据
34. 虚拟滚动的原理?难点在哪?
只渲染视口内的 DOM:监听滚动 → 算出可视区起始索引 → 渲染可视区 + 缓冲区的元素 → 用 padding 或绝对定位撑出完整滚动高度。 难点是:动态高度(消息长度不一,要测量并缓存真实高度)、滚动锚定(用户在看历史消息时新消息来了不能强行拉底)、初始化定位(要精确落到最后一条)、滚动抖动(快速滚动时用 requestAnimationFrame 节流计算)。
35. SSE 和 WebSocket 怎么选?
SSE 是单向(服务端 → 客户端)、基于 HTTP、浏览器自带断线重连、服务端实现简单;WebSocket 是全双工、需要协议升级和心跳维护。 我们的流式输出是单向的,SSE 就够了。注意 SSE 不支持自定义请求头,所以鉴权信息要放 URL 参数或 cookie。
36. SWR 的缓存策略是什么?
名字就是策略:先返回缓存里的旧数据(stale),同时后台发请求拿新数据(revalidate),回来再更新,用户感觉是瞬间加载。 配套能力:
focus重新验证(切回标签页刷新)、revalidateOnInterval、去重(同一 key 并发只发一次请求)、错误重试。很适合「数据实时性要求不那么极致、但要求快」的场景。
37. 内存泄漏怎么排查?
先看 Performance 面板的内存曲线:反复操作同一功能,内存只涨不落就是泄漏。再用 Memory 面板抓两次堆快照对比,看什么对象在增长。 常见来源:没清除的定时器/事件监听、全局缓存无上限、ECharts 实例和 WebGL 资源没销毁、闭包里持有大对象、
ResizeObserver没 disconnect。
安全 / 网络
38. XSS / CSRF 怎么防?
XSS:用户输入一律转义(Vue/React 默认做了),富文本和 Markdown 渲染用白名单过滤(我们接的 markdown-it 就是白名单思路),原则上不用
innerHTML,必要时用 CSP 兜底。 CSRF:状态变更请求用 POST,鉴权 token 放请求头而不是 cookie,配合后端的 Referer / Origin 校验和 SameSite。 另外补一句:前端的权限控制只是体验优化,真正的权限校验必须在后端。
39. 跨域怎么解决?
开发环境用 Vite / Webpack 的 devServer proxy 代理解掉;生产环境同源部署或者后端配 CORS(
Access-Control-Allow-Origin+ 预检请求处理)。带 cookie 的跨域要同时开withCredentials和Allow-Credentials,且 Origin 不能是*。 兜底方案还有 Nginx 反向代理,最稳。
40. HTTP 缓存怎么配?
强缓存:
Cache-Control: max-age/immutable,浏览器直接用本地副本,不发请求。协商缓存:ETag/Last-Modified,发请求带If-None-Match/If-Modified-Since,没变返回 304。 我们的实践是:HTML 不缓存(否则发版后拿到旧页面引用旧 JS),带 hash 的静态资源用长缓存 + immutable,接口按业务特性单独配。
Cao Note