面灵AI→

帆软前端二面三面面经

轮次
二面、三面
时间
2026-10
来源
牛客网

《面试题目》

二面(约 55 分钟)

  1. 描述用户从说话到文字呈现到屏幕的完整流程,你负责哪部分?
  2. revision 快照覆盖是什么?为什么用覆盖而不是增量同步?
  3. 缓冲调度解决什么问题?
  4. 断线重连时,断线期间正在说的语音怎么处理?
  5. renderToPipeableStream 和 renderToString、renderToStream 有什么区别?
  6. Tauri 项目用 ready/get 事件暂存加顺序补发解决初始化竞态,是什么场景?怎么解的?
  7. Tauri 通信哪些走 command、哪些走 event?返回值上有什么差别?
  8. 虚拟滚动的数据量有多大?怎么实现?定高改成不定高怎么办?
  9. 十万行乘两百列的大表格怎么做?合并单元格对不上怎么办?
  10. 分片上传的 hash 在哪里算?大文件怎么避免卡页面?内网改公有云要做什么调整?
  11. 现在大部分代码由 AI 写,你们怎么 review AI 代码?
  12. AI 写代码又写单测,怎么确定测试不是在迎合代码?
  13. 有没有遇到 AI 怎么改都改不对的情况?
  14. 场景题:BI 仪表板有几十个图表加筛选器,怎么设计数据请求和渲染?
  15. 追问:筛选器只影响部分图表(按字段联动),React 下怎么处理?
  16. 追问:同一个图表三次修改、后台并行返回乱序,怎么保证最终渲染是最后一次的结果?

三面(约 20 分钟)

  1. 为什么做前端?什么时候开始接触的?
  2. JSBridge 的原理是什么?H5 怎么调到原生能力?
  3. 混合开发常见的技术选型有哪些?RN 的原理了解吗?
  4. 平时学习前端的渠道有哪些?
  5. 出于兴趣做过什么项目?3D 项目是需求还是兴趣?用了什么框架?
  6. WebGL 和 WebGPU 的差别是什么?用过 WebGPU 吗?
  7. AI coding 占比多少?举一个 AI 方案不满意、你去纠正的案例,你是怎么发现问题的?
  8. 用 AI 做过最大的项目是什么?
  9. 你的主要工作是什么?最难的问题是什么?内存问题从现象到定位到大对象再到优化,内存降了多少?
  10. 找工作看重哪些点?城市意向是什么?
  11. 在校期间最有成就感的一件事是什么?
  12. 有直接跟用户沟通需求吗?是你主导还是导师带着?有没有达不成一致的矛盾?

《参考解析》

语音链路的端到端时序:快照覆盖、缓冲调度与断线期间的音频。 从说话到屏幕出字要经过采集、降噪与分帧、识别(流式返回中间结果与最终结果)、文本整理、渲染几段,容易出问题的是状态对齐而不是单点性能。中间识别结果会不断修正前面已出的字,如果按增量追加就会越写越乱,所以用「以 revision 为单位的快照覆盖」:服务端为每次修正生成新版本号,前端整段替换对应范围的文本,渲染结果与最新版本一致;代价是传输量比增量大一点,但换来的是幂等——同一 revision 重复到达、乱序到达都不会产生脏数据,这个收益远大于带宽成本。缓冲调度解决的是「识别产出速率与渲染节奏不匹配」:字来得太密会让 React 每收到一个 token 就重渲染一次,用 requestAnimationFrame 或固定时间窗把多次更新合并成一次提交,既压掉抖动也避免布局反复计算。断线期间的音频是最容易被追问的点:纯前端的做法是本地缓存未确认的音频分片,重连后按序号补发,服务端按会话与序号去重拼接;更稳的做法是前端先本地出临时占位文本,重连拿到最终识别结果后整体覆盖,让用户立刻看到反馈又不留下错误内容,同时明确告知「这段可能不完整」。整体设计原则是:任何一段都可以重试,靠序号与版本号收敛,而不是靠「不丢包」。

renderToPipeableStream 与另外两个 API 的差别在「流什么时候能用」。 renderToString 是同步的:它必须等整棵树算完才返回完整字符串,期间不释放任何东西,所以无法配合 Suspense 做流式吐字,适合小页面或非关键场景。renderToNodeStream 返回一个可读流,能边渲染边输出,但它不支持 Suspense,也没有续接脚本的机制,遇到异步数据只能干等。renderToPipeableStream 是当前推荐的方案:它把渲染分成壳与内容两段,先吐出包含 fallback 的页面骨架让浏览器尽早上屏,异步数据就绪后把真实内容连同补丁脚本一起追加到流尾,由 React 在客户端接管替换;onShellReady 适合需要精细控制响应头(状态码、爬虫可见性)的场景,onAllReady 适合爬虫或静态生成,因为要等全部内容才好给完整 HTML。工程取舍很清楚:要首字节快、要 SEO 的页面用 pipeable 并暴露 onShellReady 尽早开流;只需要字符串的小页面用 renderToString 更简单;注意流一旦开始写就不能再改状态码,所以错误边界和状态码决策要在开流前做完。

初始化竞态与事件顺序补发。 桌面 WebView 里,前端脚本开始执行和原生侧准备就绪是两个并行的时序,谁先到不确定,于是常出现「前端订阅事件时,原生已经把事件发完了」的丢消息问题。解法是给事件加一个暂存层:原生侧在启动阶段产生的 ready 事件既推送也写入一个可查询的缓冲(内存队列或状态字段),前端挂载后先主动 get 一次当前状态,再把缓冲里未消费的事件按顺序补发,之后的实时事件才走推送;前端对同一事件做幂等处理(按序号或状态位去重),补发与推送重叠也不会重复生效。command 与 event 的分工是另一条常考题:command 是前端主动发起、有请求响应语义的调用(返回 Promise 或结果值,失败可抛错,适合查询与写操作),event 是后端主动广播、单向无返回值(适合状态变化通知、进度与日志),选错了会导致「用事件做请求响应」这种脆弱结构。判断标准很简单:谁发起、要不要拿到结果——要结果就用 command,只是通知就用 event,跨进程调用的参数与返回值都要能被序列化。

主线程预算:大表格、虚拟滚动与分片 hash 是同一类问题。 浏览器只有一条主线程,任何超过 16ms 的同步任务都会掉帧,所以这几道题共用一个答案:把工作切小、把计算搬走。十万行乘两百列直接渲染 DOM 是不可能的(节点数以百万计),标准方案是 Canvas 绘制可视区加脏区检测与局部重绘:按行列位置映射到像素,只重画滚动或数据变化涉及的矩形区域,把一帧的绘制量控制在预算内;合并单元格对不上通常是因为「视觉合并」和「数据模型」没对齐,正确做法是维护一份合并区间索引(按锚点行列存跨度),绘制时先按锚点合并再裁剪,命中的单元格直接跳过,命中判断用二分而不是逐个比较。虚拟滚动在定高时只是滚动位置的除法,不定高则要用已测量高度的前缀和加二分定位,未测量项先估算、量到真实高度后回写缓存并修正偏移,代价是可能出现滚动抖动和快速滚动白屏,靠提高缓冲区和骨架屏兜。分片上传的 hash 是纯粹的 CPU 密集计算,大文件在 Worker 里分片计算(或抽样计算文件头尾加大小做快速校验),主线程只做索引登记与上传调度,这样滚动和点击不会被打断;从内网迁到公有云要应对的是更高的网络抖动与失败率——分片调小、并发上传分级、断点续传记录已完成分片,以及失败退避重试,这些在内网环境下往往被忽略,一换环境就集中暴露。

BI 仪表板:请求编排与乱序响应收敛。 几十个图表加筛选器如果各自独立发请求,会出现请求风暴、筛选条件不一致、以及旧响应覆盖新响应三类问题。请求侧的做法是分层:以筛选器状态作为一个「查询描述对象」统一生成请求,相同数据源的图表合并成一个批量接口或按数据集分组,对请求做去重(相同参数在飞请求复用 Promise)与取消(参数变化时 abort 掉旧请求),并给筛选加防抖避免连续选择时打满后端。渲染侧的关键是让每个图表独立处理自己的加载与错误状态,别让一个慢接口把整页按住——可以用 Suspense 边界加骨架屏,或者图表各自维护 loading。至于三次修改乱序返回,靠「请求序号 + 结果集比对」解决:每次发请求递增一个版本号,响应回来只接受版本号等于当前最新值的结果,旧的直接丢弃;更严谨的做法是把「当前生效的查询条件」作为响应的一部分回传,前端比对不一致就丢弃,这能覆盖跨组件与服务端缓存带来的隐性乱序。补充一点:筛选器按字段联动时的正确姿势是先算依赖图(哪些图表依赖哪些字段),只有依赖变化的图表才重新请求,而不是全量刷新。

AI 写的代码怎么 review、怎么写不迎合实现的测试。 review AI 产出的代码,重心要往「意图是否被满足」和「边界是否被处理」挪,而不是纠缠格式——格式交给 lint 和格式化工具自动收敛。有效的几条:改动必须能对应到一个明确的输入输出与验收标准,先看测试是否真的断言业务语义;重点关注 AI 最容易漏的地方——错误分支、并发与幂等、空值与越界、资源释放、权限与输入校验;对可疑的小块让其解释「为什么这么写、替代方案是什么」,解释不通的部分人工重写;把「代码里有没有偷偷改掉调用方契约」当成必查项。至于测试迎合代码,本质是同一份上下文里生成实现和断言导致的共谋,破法有三条:断言写到行为层而不是实现细节(不 mock 到函数级、不测内部方法调用次数),否则测试只是把实现抄了一遍;先写失败用例再让 AI 实现(或至少让另一轮会话只看需求写测试);用变异测试或刻意改坏实现来验证测试会不会红——红不了说明测试没在断言真正的东西。最后把高风险路径的用例沉淀成回归集,让它成为后续自动改动的护栏。