性能、渲染与大数据真题
从 Web Vitals、构建与运行时优化,到移动端 1px、虚拟列表、Worker 和大数据传输,整理可验证的面试回答。
性能、渲染与大数据真题
1. 性能题先给指标和场景
不要直接罗列“压缩、懒加载、缓存”。先确认用户路径(首屏、输入、滚动、提交)、设备/网络和数据规模,再选择指标:
| 目标 | 优先指标 | 说明 |
|---|---|---|
| 首屏主要内容出现 | LCP、FCP、TTFB | LCP 在真实用户中通常看 P75;还要保留候选元素和归因 |
| 点击/输入响应 | INP、长任务、事件耗时 | INP 已取代 FID,覆盖整个页面生命周期;TBT 主要是实验室指标 |
| 页面不跳动 | CLS | 预留图片/广告/字体空间,排除用户主动触发的偏移 |
| 资源是否命中 | Resource Timing、缓存头 | 区分浏览器缓存、Service Worker、CDN 和回源 |
| 发布是否退化 | RUM 分位数、错误率、构建体积 | 按路由、版本、设备和网络切分,保留回滚阈值 |
“LCP 小于 2.5 秒”只是常见分段参考,不是脱离页面和样本的承诺。测量条件、样本量、P75/P95 和优化前后基线必须一起记录。
2. 编译构建时如何优化?
2.1 体积和请求
- 用 bundle analyzer 或 source map 追溯大模块、重复依赖和意外引入,而不是凭感觉删除包。
- 用路由/功能动态导入做 Code Splitting;共享依赖按更新频率与缓存收益拆分。
- 生产启用压缩、Tree Shaking 和合适的
sideEffects声明;动态 CommonJS、全局注册和错误副作用标记会破坏摇树。 - 图片使用合适格式、尺寸和
srcset;关键字体/图片才preload,下一步资源才prefetch。 - 静态资源使用内容 hash 和长期缓存,HTML/接口使用短缓存或协商缓存;发布要保留旧资源以支持回滚。
HTTP/2 多路复用减少了“合并所有文件”的必要性,但不意味着请求数量、压缩、缓存粒度和解析成本可以忽略。每次改动都要用 Network 与构建报告对照。
2.2 开发和 CI 构建
记录冷启动、增量构建、CI 构建、压缩和类型检查分别耗时;确认文件系统缓存、loader 范围和插件耗时。构建变快不能以跳过类型检查、测试、source map 或安全扫描为代价。详见 构建体积与性能治理。
3. 运行时卡顿怎么拆?
浏览器主线程大致经历 JavaScript、样式计算、布局、绘制和合成。排查卡顿时先用 Performance 录制定位长任务,再判断是哪一层:
| 现象 | 常见原因 | 可验证改动 |
|---|---|---|
| 输入延迟高 | 同步计算、过大的状态更新、事件处理过长 | 分片/Worker、缩小更新边界、记录 Event Timing/INP |
| 滚动掉帧 | 滚动回调读写布局、过多 DOM、图片解码 | IntersectionObserver、虚拟列表、分帧和占位尺寸 |
| 页面跳动 | 图片/字体/异步内容未预留空间 | width/height 或 aspect-ratio、字体策略、CLS 归因 |
| 内存持续上涨 | detached DOM、监听器、闭包、缓存无上限 | Heap Snapshot、释放监听器/对象 URL、LRU 上限 |
| 动画耗时 | 频繁触发布局/绘制、图层过多 | 优先 transform/opacity,核对图层和 GPU 内存 |
requestAnimationFrame 只适合把视觉更新对齐到下一帧;requestIdleCallback 可能长期得不到执行;Worker 能移出计算但不能直接操作 DOM,消息序列化/Transferable 也有成本。不要把任何一个 API 当万能解法。
4. 后端一次返回大量数据,如何减轻渲染负担?
按数据量和交互要求分层回答:
- 减少传输:分页、游标、按字段查询(REST 参数或 GraphQL)、压缩和服务端聚合;避免把用户永远看不到的字段发到浏览器。
- 控制请求:无限滚动用
IntersectionObserver触发下一页,设置并发上限、取消旧请求和去重;失败要能重试或回到已确认页。 - 控制 DOM:虚拟列表只保留可视窗口及缓冲区,维护占位高度和滚动位置;动态高度、键盘焦点、跳转定位和可访问性必须纳入设计。
- 控制计算:解析、排序、聚合等重任务放到 Worker;主线程分批提交更新,并在任务之间让出帧时间。
- 控制缓存:对稳定查询使用带版本/权限的缓存;IndexedDB 适合离线结构化数据,不能无限增长。
- 服务端流式化:SSE/WebSocket 或 chunked 响应可降低首个结果等待,但仍需定义顺序、断线、取消、超时和最终一致性。
虚拟列表解决的是 DOM 数量,不会自动解决数据传输、排序和组件重复渲染;先分页/裁剪数据,才有机会让列表真正可用。
5. 移动端 1px 线怎么回答?
设备像素比(DPR)使 CSS 像素和物理像素不总是一一对应。常见方案及代价:
/* 伪元素缩放:不改变布局占位,适合单条边框 */
.hairline {
position: relative;
}
.hairline::after {
content: '';
position: absolute;
inset: auto 0 0;
height: 1px;
background: currentColor;
transform: scaleY(0.5);
transform-origin: 0 100%;
}
transform: scale可绘制半物理像素,但要确认 transform origin、圆角和命中区域。box-shadow或渐变能做视觉细线,浏览器差异和颜色控制要实测。- 依赖 rem/viewport 的方案会改变全局尺寸,老项目迁移成本高,不应为一条线改动整套布局。
- 现代浏览器可直接使用合适的边框渲染,但仍要按目标设备截图验证;不要保证所有屏幕都能稳定呈现物理 1px。
6. 主题切换有哪些实现?
优先用 CSS 自定义属性,让布局和颜色切换不依赖重新生成大量样式:
:root {
color-scheme: light;
--surface: #ffffff;
--text: #202124;
}
[data-theme='dark'] {
color-scheme: dark;
--surface: #202124;
--text: #f1f3f4;
}
body { background: var(--surface); color: var(--text); }
脚本只切换 data-theme 并持久化用户偏好;初始化时在首屏 HTML 或极早脚本读取偏好,避免闪烁。主题数量固定、需要编译时消除分支时可用 Sass 等预处理器;主题包很大时可动态加载 CSS,但要处理旧样式替换、加载失败和无 JS 降级。CSS-in-JS、Tailwind 令牌也只是实现方式,关键是令牌契约、可访问对比度和发布兼容。
7. 高频追问
Q: React.memo、useMemo、useCallback 都用了,为什么仍然卡?
A: 先看 Performance 和 React Profiler:可能是状态放得太高、Context value 每次变化、子组件 key 不稳定、昂贵计算在 memo 之前执行,或单次更新本身就超过帧预算。稳定引用只是跳过部分渲染的条件,不是性能保证。回到 React 与 Vue 运行时优化 对照更新边界。
Q: preload 越多越好吗?
A: 不是。preload 会提高资源优先级并占用连接/带宽;只预加载当前路径确定会影响 LCP 的资源,并设置正确的 as、跨源属性和失败降级。下一步可能用到的资源用 prefetch,低概率资源不要抢首屏。
Q: Worker 一定能让页面更快吗?
A: 只有当计算足够重且消息传输成本可接受时才可能收益。先量化主线程阻塞,再比较结构化克隆和 Transferable、Worker 启动、内存和取消成本;DOM 操作仍需回主线程。
Q: 如何证明优化有效?
A: 固定版本、设备、网络、数据和操作脚本,做对照或灰度,观察 P75/P95、错误率和业务完成率,并保留停止条件与回滚路径。没有现场数据时只能称为实验结果或验证计划。
8. 来源与索引
本篇融合 高频真题解析与9月考点预测中.pdf、高频真题解析与9月考点预测下.pdf 和三份性能 PDF 中的技术问题。旧资料的 FID 统一指向现代 INP;演示中的性能百分比、课程名称和推广话术未纳入。