面灵AI→

广州好大夫前端面试

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

《面试题目》

  1. 自我介绍。
  2. 首页白屏优化,使用非核心模块延迟渲染,讲讲它的原理。
  3. 使用 SSE 时,前端如何通过 ReadableStream 消费 SSE 数据流?
  4. 有没有开发过小程序?简单讲讲你做的壁纸分享小程序。
  5. 小程序有没有涉及登录、支付业务场景?
  6. 小程序的登录流程是什么逻辑?
  7. 小程序封装 Request 请求层需要处理哪些逻辑?Token 过期这种边界场景怎么处理?
  8. Token 过期,多个接口同时报 401 未授权,该如何处理?
  9. 如果 Token 只能通过登录获取,登录失效怎么处理?
  10. 使用 PDF.js 做预览时,怎么做体积缩减?
  11. MinIO 对象存储,把图片从 Base64 转成 HTTP URL,会给项目带来什么收益?
  12. 前端大文件断点续传、批量上传如何设计?
  13. 除了非核心组件延迟渲染,白屏问题还有哪些其他优化方案?
  14. 有没有用过原生小程序?
  15. 讲一下虚拟列表。
  16. 了解什么是暂时性死区吗?
  17. JS 事件循环的执行顺序是什么?
  18. 最近在用哪些 AI 辅助开发工具?
  19. AI 生成代码会出现逻辑漏洞、幻觉,如何规避?
  20. 上一份实习为什么离职?
  21. 上一份实习的工作时间是多长?
  22. 如果前端完成进度比后端快,后端暂时没有相应接口返回,你会做什么样的处理来完成前端?
  23. 你的职业规划是怎么样的?

《参考解析》

首页白屏优化:先定位白屏属于哪一类——首字节没回来(服务端或网络慢)、HTML 到了但关键资源被阻塞(JS 包太大、同步脚本、字体阻塞)、还是数据没回来(接口串行、瀑布式请求)。非核心模块延迟渲染的原理是减少首屏关键路径上的工作量:把非首屏组件从同步渲染改成空闲时渲染(requestIdleCallback、IntersectionObserver 可见时再挂载、React.lazy + Suspense 动态 import),主线程就能先让给首屏渲染,对应的 JS 分片也从首包拆了出去。实现要注意:延迟渲染的组件要给同尺寸骨架,避免布局抖动;不要用 setTimeout 硬等,会拖长可交互时间;对 SEO 有要求的页面关键内容仍要服务端渲染。其他常规手段还有路由级与组件级分包、依赖按需引入、图片懒加载与 WebP/AVIF、字体 font-display: swap 加子集化、接口并行与预取、SSR 与 CDN 缓存,以及用 Performance 面板确认真实瓶颈再动手。

用 ReadableStream 消费 SSE:EventSource 只能发 GET、不能自定义请求头(鉴权受限)、不能带请求体、重连行为也不可控,所以工程上常用 fetch 加流式读取。写法是 const res = await fetch(url, { headers: { Accept: 'text/event-stream', Authorization } }),再 res.body.getReader(),用 TextDecoder 解码每次 read() 返回的 Uint8Array,累积进缓冲区后按 \n\n 切分事件块,再逐行解析 event: / data: / id: 字段。两个必须注意的点:一个 chunk 可能把一条消息切开,所以要保留残余缓冲,不能每个 chunk 都当完整事件解析;TextDecoder 要传 { stream: true },否则多字节汉字会被截成乱码。收尾用 AbortController 在组件卸载或用户取消时中止,避免连接泄漏。如果只是单向接收且不需要自定义头,EventSource 更省事。

小程序登录流程与请求层封装:登录链路是 wx.login() 拿临时 code → 传给自家后端 → 后端用 code 加 AppSecret 换 openid 与 session_key → 后端签发自己的登录态返回 → 前端存储并在后续请求里带上。code 只能用一次且短时效,前端不能缓存 code,更不能自己换 openid。请求层要处理的逻辑:baseURL 与超时、统一附加 token 与公共参数、统一错误提示与业务错误码分流、loading 与重复请求合并、响应解包、重试与降级。Token 过期的标准做法是「静默刷新 + 请求排队」:拦截到 401 时如果已经在刷新就先把请求挂进队列,由第一个请求触发刷新,成功后再统一重放;刷新失败才跳登录页。刷新要加锁与次数上限,否则并发 401 会触发多次刷新,把 refresh token 直接刷废。如果登录态只能通过重新登录获取,就用无感登录兜底:重新 wx.login 拿 code 走一遍静默登录,失败或用户拒绝授权再跳登录页并保留原页面路径,登录后回到原来的位置。

大文件断点续传与批量上传:核心是分片、唯一标识加服务端记录进度。前端先算文件指纹(抽样 hash 或用于秒传的 MD5,大文件要用 Web Worker 算,别卡主线程),调「检查」接口拿已上传分片列表,只传缺失分片,最后调「合并」接口。分片要限制并发(3~5 个)、单片失败单独重试、整体支持暂停与恢复;进度按「已上传字节 / 总字节」展示,并把状态持久化到 localStorage 或 IndexedDB,刷新后还能继续。批量上传是并发池加队列:控制同时在传的文件数,小文件走一次性表单上传、大文件走分片,失败的进重试队列并提供「重试全部 / 跳过」入口。注意 File 对象在页面刷新后会失效,续传需要用户重新选择文件。

PDF.js 体积缩减与虚拟列表:PDF.js 的体积主要来自主库、worker 以及字体与 cmap 资源。做法是按需加载——主包改用动态 import,只在用户打开预览时才下载;worker 单独打包走独立 chunk 或 CDN,不要打进主包;只有非拉丁文本才需要的 cmap 与标准字体按需拉取,不在首屏预载;主库与 worker 版本必须对齐,否则直接报错。虚拟列表的原理是只渲染可视区域内的节点:容器固定高度、内部占位撑起总高度,监听滚动算出可视起始索引,只挂载 [start - buffer, end + buffer] 区间内的项,用 translateY 或绝对定位做偏移。高度不确定时用「估算高度 + 动态测量回填 + 修正滚动偏移」的方案。要点是 key 稳定、缓冲区够大(避免快速滚动白屏)、滚动事件要节流。

暂时性死区与事件循环:暂时性死区指 let / const 声明的变量在进入作用域到初始化完成之间不可访问,读取会抛 ReferenceError,而 var 此时只是 undefined;typeof 对处于 TDZ 的变量同样会抛错,这也是它和「未声明的变量」的区别。事件循环的顺序是:同步代码在调用栈里执行完 → 清空微任务队列(Promise.then、queueMicrotask、MutationObserver)→ 取下一个宏任务(script、setTimeout、setInterval、I/O、UI 渲染)。所以同一轮里 Promise.then 一定先于 setTimeout(fn, 0)。浏览器在宏任务结束且微任务清空后才决定渲染,长任务会阻塞渲染,优化手段是时间切片(requestIdleCallback、scheduler.yield)把长任务拆开。

与 AI 编码工具协作时的质量把控:把生成结果当成「一位很快但会自信犯错的同事提交的改动」来对待。可落地的几条:只让它做边界清晰的小改动,一次一个目标,避免大范围重写;生成后自己读懂再合,看不懂就要求它解释或拆小;用类型检查、lint、单测当客观闸门——幻觉最典型的表现就是调了不存在的 API、参数顺序错、边界没处理;涉及鉴权、支付、删除数据、并发与安全的代码逐行审;让它给边界清单和测试用例,但测试要自己跑;上下文里给足项目约定(目录结构、既有封装、代码风格),否则它会重新发明一套。另外不要把不该外传的业务代码与密钥贴进提示词,也不要随手引入它推荐但没评估过的依赖。

二面里的稳定性与协作问题:离职原因按「事实 — 决策 — 与岗位的连接」讲,不抱怨前东家,主动说明到岗时间与稳定性。前端进度快于后端接口时,正经做法有几种:契约先行,先和后端约定字段与示例响应(OpenAPI 或 Mock),前端按 Mock 开发,联调时只切 baseURL;接口没就绪就用本地 Mock 或代理转发到测试环境,绝不把假数据写进业务代码;字段没定就先做与数据无关的部分(布局、交互、状态机),把数据依赖收敛到一个 adapter 层,接口一好就接上;确实要临时兜底就走明确的开关,别静默降级,并记一条待办。职业规划贴着岗位与业务讲:短期把业务与技术栈吃透、能独立负责模块,中期在性能与工程化方向深入,长期做能带小项目的骨干——避免「几年转管理」这类与岗位无关的空头承诺。