广州好大夫前端面试
- 轮次
- 一面二面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 首页白屏优化,使用非核心模块延迟渲染,讲讲它的原理。
- 使用 SSE 时,前端如何通过 ReadableStream 消费 SSE 数据流?
- 有没有开发过小程序?简单讲讲你做的壁纸分享小程序。
- 小程序有没有涉及登录、支付业务场景?
- 小程序的登录流程是什么逻辑?
- 小程序封装 Request 请求层需要处理哪些逻辑?Token 过期这种边界场景怎么处理?
- Token 过期,多个接口同时报 401 未授权,该如何处理?
- 如果 Token 只能通过登录获取,登录失效怎么处理?
- 使用 PDF.js 做预览时,怎么做体积缩减?
- MinIO 对象存储,把图片从 Base64 转成 HTTP URL,会给项目带来什么收益?
- 前端大文件断点续传、批量上传如何设计?
- 除了非核心组件延迟渲染,白屏问题还有哪些其他优化方案?
- 有没有用过原生小程序?
- 讲一下虚拟列表。
- 了解什么是暂时性死区吗?
- JS 事件循环的执行顺序是什么?
- 最近在用哪些 AI 辅助开发工具?
- AI 生成代码会出现逻辑漏洞、幻觉,如何规避?
- 上一份实习为什么离职?
- 上一份实习的工作时间是多长?
- 如果前端完成进度比后端快,后端暂时没有相应接口返回,你会做什么样的处理来完成前端?
- 你的职业规划是怎么样的?
《参考解析》
首页白屏优化:先定位白屏属于哪一类——首字节没回来(服务端或网络慢)、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 层,接口一好就接上;确实要临时兜底就走明确的开关,别静默降级,并记一条待办。职业规划贴着岗位与业务讲:短期把业务与技术栈吃透、能独立负责模块,中期在性能与工程化方向深入,长期做能带小项目的骨干——避免「几年转管理」这类与岗位无关的空头承诺。