Skip to main content

核心优化手段与场景

面试中除了问指标和工具,最核心的就是考察你在特定场景下的优化手段。以下是前端面试必考的几个经典场景:

1. 首屏加载 / 白屏优化 (如何降低 FCP/LCP)

这是前端优化的重中之重,核心思想是减少关键渲染路径(CRP)上的阻碍降低资源体积

  • 网络与资源层面

    • 资源压缩与合并:开启 Gzip/Brotli 压缩,压缩图片(使用 WebP),CSS/JS 混淆压缩。
    • HTTP 缓存:强缓存(Cache-Control)缓存静态资源,协商缓存(ETag/Last-Modified)验证 html。
    • CDN 加发:将静态资源部署到 CDN,减少物理距离带来的网络延迟。
    • HTTP/2 多路复用:突破 HTTP/1.1 的并发连接限制,减少 TCP 握手开销。
    • 资源提示符:使用 <link rel="preload"> 预加载关键字体/图片,<link rel="prefetch"> 预获取下个页面可能需要的资源,<link rel="dns-prefetch"> 提前解析域名。
  • 代码与架构层面

    • 路由懒加载:使用 React.lazyimport(),按需拆分代码块,避免首屏加载一个巨大的 bundle.js。
    • Tree-Shaking:在构建时移除未使用的死代码(依赖 ESM 的静态分析)。
    • 提取第三方库:将 reactlodash 等不常变动的库打包成单独的 vendor,利用浏览器长效缓存。
    • SSR / SSG (服务端渲染/静态生成):对于白屏极度敏感的 C 端项目,直接由服务端返回渲染好的 HTML,跳过客户端 JS 的下载和执行阶段,首屏极速。
    • 骨架屏 (Skeleton):在数据返回前展示占位结构,缓解用户等待焦虑,同时降低 CLS(布局偏移)。

2. 运行时卡顿优化 (如何降低 FID/INP)

应用加载完后,交互时的卡顿主要是由于主线程被长时间占用频繁操作 DOM 引起的。

  • 拆分长任务 (Long Tasks)
    • 如果有一个计算量巨大的循环,可以利用 setTimeoutrequestAnimationFrameMessageChannel 将其切片(Time Slicing),让出主线程给浏览器执行渲染和响应交互。(React Fiber 架构的底层思想)。
  • 使用 Web Worker
    • 将复杂的计算(如大文件哈希计算、图片像素处理)放到 Worker 线程中,完全不阻塞 UI 主线程。
  • 避免强制同步布局 (Forced Synchronous Layout)
    • 当你在 JS 中修改了元素的样式,紧接着立刻读取元素的几何属性(如 offsetHeightgetBoundingClientRect)时,浏览器为了给你准确的值,会被迫提前进行一次布局计算(Layout)。这就是强制同步布局,极耗性能。
    • 解决:将读取操作和写入操作分离(读写分离)。
  • 减少回流 (Reflow) 与重绘 (Repaint)
    • 尽量使用 CSS3 动画(transformopacity)代替 top/left。因为它们可以通过 GPU 硬件加速,并在合成线程(Compositor Thread)中完成,完全跳过主线程的布局和绘制阶段。
  • 事件委托与防抖/节流
    • 列表项极多时,将事件绑定在父容器上(事件委托)。
    • scrollresizeinput 等高频触发事件使用 debouncethrottle

3. 海量数据渲染优化 (长列表)

当接口一次性返回几万条数据,直接渲染成 DOM 会直接让浏览器卡死。

  • 虚拟列表 (Virtual List)
    • 核心思想:只渲染可视区域及其上下少量缓冲区域的 DOM 节点。当用户滚动时,动态替换这些 DOM 节点的数据并修改 transform: translateY,保持 DOM 总数只有几十个。
    • 常用库:react-windowvue-virtual-scroller
  • 时间分片 (Time Slicing)
    • 如果不希望用虚拟列表(比如需要保持所有 DOM 供浏览器原生搜索 Ctrl+F 查找),可以使用 requestAnimationFrame 将万条数据的渲染分成多次(每次渲染几十条),平滑地分批插入文档中。
  • 分页/无限滚动
    • 业务层面的妥协方案,触底再请求或渲染下一页。

4. 内存优化与防泄漏

  • 及时解绑事件:组件卸载(如 componentWillUnmount)时,一定要 removeEventListener,并清除 setInterval
  • 慎用闭包与全局变量:避免将大对象挂载到 window 或在闭包中长期持有。
  • 使用 WeakMap / WeakSet:对于需要存储 DOM 节点状态的缓存映射,使用弱引用,当 DOM 被移除时,缓存自动被垃圾回收清理。