前端开发面试题:性能优化(23 题)

技术 · 2025-06-26
前端开发面试题:性能优化(23 题)

2026-06-26T15:43:59.png

P-OPT1:<script> 放在 header 和 body 底部的区别?

位置区别
header 中浏览器遇到 <script>同步加载并执行,阻塞后面 DOM 解析(脚本可能 document.write 改变 DOM),白屏时间长。
body 底部DOM 解析完成后才加载脚本,页面元素先显示出来,用户感知更快。

进阶方案

<!-- defer:并行下载,DOMContentLoaded 前执行 -->
<script src="app.js" defer></script>

<!-- async:并行下载,下载完立即执行(不保证顺序) -->
<script src="analytics.js" async></script>

<!-- type="module":默认 defer 行为 -->
<script type="module" src="app.js"></script>

推荐

  • 第三方库用 defer(依赖顺序可控);
  • 独立统计脚本用 async(执行越早越好);
  • 应用入口用 defer

P-OPT2:前端性能优化指标有哪些?怎么检测?

核心指标(Core Web Vitals)

指标说明优秀阈值
FCP(First Contentful Paint)首次内容渲染< 1.8s
LCP(Largest Contentful Paint)最大内容渲染< 2.5s
FID(First Input Delay)首次输入延迟< 100ms
INP(Interaction to Next Paint)交互到下一帧< 200ms
CLS(Cumulative Layout Shift)累积布局偏移< 0.1
TTI(Time to Interactive)可交互时间-
TBT(Total Blocking Time)总阻塞时间< 200ms

检测工具

  • Lighthouse:综合性能评分;
  • WebPageTest:多地点、多浏览器、多网络环境测试;
  • Chrome DevTools Performance:录制运行时性能;
  • PerformanceObserver API:业务中上报性能数据。
// 上报 LCP / FID / CLS
new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime);
    sendMetric({ name: entry.name, value: entry.startTime });
  }
}).observe({ type: 'largest-contentful-paint', buffered: true });

P-OPT3:SPA 首屏加载速度慢怎么解决?

与 V14 互补,从性能指标角度补充

  1. 骨架屏 / Loading:转移注意力,减少感知等待时间;
  2. 路由级代码分割:按路由懒加载,初始包体积减少 50%+;
  3. CDN 加速:静态资源走 CDN,启用 HTTP/2;
  4. 资源预加载<link rel="preload" href="critical.css">
  5. 预获取数据:路由切换前预取下一页数据;
  6. SSR / SSG:首屏 HTML 服务端生成,TTI 提升明显;
  7. 开启 gzip / brotli 压缩:减少 70%+ 体积;
  8. Web Vitals 监控:上线后持续优化。

P-OPT4:用 CSS 提升页面性能

  1. 减少 CSS 体积

    • 压缩合并(CSSNano);
    • 移除未使用样式(PurgeCSS / UnCSS);
    • CSS-in-JS 会增加运行时开销,需谨慎。
  2. 关键 CSS 内联:首屏所需 CSS 放在 <style> 中,其余异步加载。
  3. 避免昂贵选择器:避免深层后代选择器(如 .a .b .c .d),浏览器从右到左匹配。
  4. 避免 @import:串行加载。
  5. CSS Containmentcontain: layout / paint / size 限制重绘范围。
  6. will-change:提示浏览器提前优化,但不能滥用(会消耗 GPU 内存)。
  7. GPU 加速属性transform / opacity 触发 GPU 合成,跳过重绘重排。

P-OPT5:站点内的图片性能优化

  1. 选择合适格式

    • WebP:体积比 JPEG/PNG 小 25-35%;
    • AVIF:体积更小,压缩比优于 WebP;
    • SVG:适合图标、插画。
  2. 按需加载

    • 原生 loading="lazy"
    • 滚动到视口附近才加载;
    • 占位图 + 模糊到清晰过渡。
  3. 响应式图片

    <img srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
         sizes="(max-width: 600px) 480px, 800px"
         src="medium.jpg" />
  4. CDN + 图片处理:使用 ?x-oss-process=image/resize,w_800 等 CDN 参数。
  5. 压缩与裁剪:去除 EXIF、合理质量(75-85% 肉眼难辨)。
  6. 雪碧图:HTTP/2 下作用减弱。
  7. 避免 Layout Shift:永远设置 width / height 属性。

P-OPT6:虚拟 DOM 一定更快吗?

不一定

虚拟 DOM 适用场景:DOM 操作复杂、组件数量中等(几十到几百)。

更快的场景

  • 直接 DOM 操作:极简单页面,框架开销大于收益;
  • 细粒度响应式(Vue / SolidJS):精确更新,零 VNode 开销;
  • WebGL / Canvas:不走 DOM。

虚拟 DOM 的优势不是“更快”,而是“提供合理性能 + 开发体验”

  • 避免手动管理 DOM 状态;
  • 提供跨平台能力(React Native、SSR);
  • 批量更新减少 DOM 操作次数。

本质:是用 JS 计算换 DOM 操作,开发效率提升远大于性能损失。


P-OPT7:有些框架不用虚拟 DOM,性能也不错是为什么?

原因

  1. 编译期优化:Svelte / SolidJS 在编译阶段将组件编译为命令式 DOM 操作代码,无需运行时 VNode 比较。
  2. 细粒度响应式:Vue / SolidJS 的响应式系统能精确追踪到组件内具体属性变化,只更新必要的 DOM 节点。
  3. 跳过 VNode 层:直接调用 DOM API,省去 diff 计算与对象创建开销。
  4. 举例

    • SolidJS:类似 React 的 JSX,但编译产物是“订阅-更新”函数;
    • Svelte:编译为高度优化的 vanilla JS;
    • Vue 3 + Vapor:Vue 3 的无 VNode 模式。

总结没有 VNode 也能做到高效,关键是“精准更新”。


P-OPT8:几百个函数需要执行,怎么优化页面?

方案

  1. Web Worker:把 CPU 密集任务放到后台线程,避免主线程卡顿。

    const worker = new Worker('worker.js');
    worker.postMessage(data);
    worker.onmessage = e => console.log(e.data);
  2. 任务切片 + 调度器

    function chunk(tasks: Function[], chunkSize = 50) {
      let i = 0;
      function next() {
        const slice = tasks.slice(i, i + chunkSize);
        i += chunkSize;
        slice.forEach(fn => fn());
        if (i < tasks.length) requestIdleCallback(next);
      }
      next();
    }
  3. requestIdleCallback / requestAnimationFrame:在浏览器空闲时执行。
  4. 防抖节流:高频触发合并处理。
  5. WebAssembly:计算密集型(图形、加密)场景。

P-OPT9:png8、png16、png32 的区别,png 的特点

类型颜色数透明度适用
PNG8256 色不支持简单图标、表情
PNG24约 1600 万色不支持复杂图片、照片
PNG32约 1600 万色8 位 alpha需透明背景的复杂图片

PNG 特点

  • 无损压缩,质量高;
  • 支持 alpha 透明;
  • 文件体积通常比 JPEG 大,不适合照片(应选 JPEG / WebP);
  • 适合 UI 切图、Logo、图标。

P-OPT10:页面加载过程中,JS 文件一定会阻塞 DOM 解析吗?

不一定

加载方式阻塞情况
普通 <script src="...">阻塞:加载并同步执行
<script defer>不阻塞(并行下载,DOMContentLoaded 前执行)
<script async>不阻塞(并行下载,下载完立即执行)
<script type="module">默认 defer 行为
动态创建 <script>不阻塞 DOMContentLoaded

注意

  • defer / async 脚本仍会阻塞 load 事件(执行完成后才触发);
  • defer 脚本按顺序执行,async 不保证顺序。

P-OPT11:React.memo 和 useMemo 的用法与区别

维度React.memouseMemo
类型组件级Hook(值级)
作用浅比较 props,相同则跳过渲染缓存计算结果
返回记忆化组件记忆化值
场景子组件不需随父组件重渲染复杂计算 / 引用稳定性
// React.memo
const Child = React.memo(function Child({ value }) {
  console.log('Child render');
  return <div>{value}</div>;
});

// useMemo
const sorted = useMemo(() => list.sort((a, b) => a.id - b.id), [list]);

// useCallback(缓存函数)
const onClick = useCallback(() => doSomething(id), [id]);

注意:React.memo 浅比较对象 props 失效时需自定义比较函数(第二个参数)。


P-OPT12:页面加载白屏时间长的原因与优化

原因排查

  1. HTML 体积过大:服务器响应慢;
  2. 关键 CSS / JS 阻塞渲染:未使用 defer / async;
  3. 第三方脚本拖累:广告 SDK、统计代码;
  4. 首屏 JS 体积大:未做代码分割;
  5. API 响应慢:数据返回后再渲染页面;
  6. 大量同步计算:主线程阻塞。

优化方案

  • 启用 CDN + gzip;
  • 内联关键 CSS;
  • 路由懒加载;
  • SSR / 骨架屏;
  • 异步数据预取;
  • 移除未使用的第三方依赖。

P-OPT13:100000 条数据的列表如何展示?

核心:虚拟列表(Virtual List)

原理:只渲染视口内可见的列表项(约 10-30 个),滚动时动态替换 DOM。

主流库

  • React:react-windowreact-virtuosoreact-virtualized
  • Vue:vue-virtual-scroller
  • 通用:自实现基于 IntersectionObserver

简化实现思路

function VirtualList({ items, itemHeight, height }) {
  const [scrollTop, setScrollTop] = useState(0);
  const startIdx = Math.floor(scrollTop / itemHeight);
  const visibleCount = Math.ceil(height / itemHeight);
  const visibleItems = items.slice(startIdx, startIdx + visibleCount);
  const offsetY = startIdx * itemHeight;

  return (
    <div style={{ height, overflow: 'auto' }} onScroll={e => setScrollTop(e.target.scrollTop)}>
      <div style={{ height: items.length * itemHeight, position: 'relative' }}>
        <div style={{ transform: `translateY(${offsetY}px)` }}>
          {visibleItems.map(item => <Row key={item.id} {...item} />)}
        </div>
      </div>
    </div>
  );
}

进阶:动态高度、分片加载、滚动到顶部触发加载更多。


P-OPT14:DNS 预解析是什么?怎么实现?

作用:在用户点击链接前,浏览器提前解析域名到 IP,减少 DNS 查询延迟(50-200ms)。

<!-- 开启 DNS 预解析 -->
<link rel="dns-prefetch" href="//cdn.example.com">

<!-- 预连接(DNS + TCP + TLS,比预解析更彻底) -->
<link rel="preconnect" href="https://api.example.com">

<!-- 预加载(提前加载关键资源) -->
<link rel="preload" href="critical.css" as="style">

<!-- 预获取(下一页资源,优先级低) -->
<link rel="prefetch" href="/next-page.js">

Chrome 默认开启:TLS 握手中的 hostname 也会被预解析。


P-OPT15:React 中可以做哪些性能优化?

详见 Q10,补充:

  1. 生产构建:生产环境 React 会移除 PropTypes / DevTools;
  2. 使用 Profiler:定位瓶颈组件;
  3. 不可变数据:避免 setState 误判;
  4. 键列表正确使用 key:避免 DOM 重建;
  5. React DevTools:可视化分析组件渲染次数;
  6. React.lazy + Suspense:按需加载;
  7. 避免 inline 对象 / 函数:每次渲染都是新引用,会破坏 memo。

P-OPT16:浏览器为什么限制并发数?

原因

  1. 保护服务器:避免恶意页面同时打开上百个连接压垮源站;
  2. 保护客户端:每个 TCP 连接占用内存(接收/发送缓冲区)、端口;
  3. HTTP/1.1 队头阻塞:同域 6 个并发(Chrome / Firefox),其他浏览器 2-8 个不等;
  4. 历史包袱:RFC 2616 建议客户端不超过 2 个并发连接。

不同浏览器并发数(HTTP/1.1 同域)

浏览器并发数
Chrome6
Firefox6
Safari6
IE 118
IE 8-106-8

HTTP/2 解决:多路复用,单连接可承载无数请求。


P-OPT17:Performance API 是什么?怎么确定页面可用性时间?

Performance API:浏览器提供的性能监控接口。

关键时间点

const t = performance.timing; // 已废弃,改用 PerformanceNavigationTiming
const nav = performance.getEntriesByType('navigation')[0];

const metrics = {
  // DNS 查询
  dns: nav.domainLookupEnd - nav.domainLookupStart,
  // TCP 连接
  tcp: nav.connectEnd - nav.connectStart,
  // 首字节时间
  ttfb: nav.responseStart - nav.requestStart,
  // 响应下载
  download: nav.responseEnd - nav.responseStart,
  // DOM 解析
  domParse: nav.domInteractive - nav.responseEnd,
  // 资源加载
  resourceLoad: nav.loadEventStart - nav.domContentLoadedEventEnd,
  // 首屏可交互
  fmp: nav.domContentLoadedEventEnd - nav.startTime
};

可用性时间domInteractive(DOM 可交互)或 domContentLoaded(DOM + 同步脚本完成)。


P-OPT18:window.requestAnimationFrame 是什么?

概念:浏览器在下一次重绘前调用指定回调函数,通常以 60fps 频率(16.7ms)。

function animate() {
  el.style.transform = `translateX(${x++}px)`;
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

优势

  • 与显示器刷新率同步;
  • 标签页隐藏时自动暂停,节省资源;
  • setTimeout(fn, 16) 更精准;

典型场景

  • 动画 / 滚动联动;
  • Canvas 绘制;
  • 复杂 UI 渐进展示。

配套 API

  • requestIdleCallback:浏览器空闲时调用,适合非关键任务;
  • cancelAnimationFrame:取消回调。

P-OPT19:CSS 加载会造成阻塞吗?

会,但有条件

加载方式阻塞
<link rel="stylesheet">阻塞渲染(但不阻塞 DOM 解析)
内联 <style>阻塞渲染(取决于大小)
media="print" / media="(max-width: 0)"不阻塞(媒体查询不匹配)
rel="preload" as="style"不阻塞(需配合 onload 应用)

阻塞原因:CSS 可能改变布局 / 样式,浏览器需等待 CSSOM 构建完成才能渲染。

优化

<!-- 关键 CSS 内联 -->
<style>/* critical CSS */</style>

<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">

P-OPT20:什么是内存泄漏?什么原因导致?

定义:程序中不再使用的内存未被释放,长期累积导致应用卡顿甚至崩溃。

常见原因

  1. 未清理的定时器 / 事件监听setInterval / addEventListener 未在 unmount 时清理;
  2. 闭包持有 DOM 引用:变量无意中保存了已移除 DOM;
  3. 意外的全局变量:未用 var/let/const 直接赋值;
  4. 游离的 DOM 引用:Map / WeakMap 中 key 是 DOM 节点,被移除后未清理;
  5. 循环引用:老 IE 问题,现代浏览器 GC 优化后较少;
  6. 未释放的 WebSocket / EventSource

排查工具

  • Chrome DevTools Memory 面板;
  • performance.memory(Chrome 扩展 API);
  • 堆快照对比。
// 正确写法
onMounted(() => {
  const timer = setInterval(tick, 1000);
  const handler = () => console.log('resize');
  window.addEventListener('resize', handler);

  onBeforeUnmount(() => {
    clearInterval(timer);
    window.removeEventListener('resize', handler);
  });
});

P-OPT21:用 Webpack 优化前端性能

  1. 缩小打包体积

    • Tree Shaking(mode: 'production' 默认开启);
    • Scope Hoisting(optimization.concatenateModules);
    • 按需引入(babel-plugin-import);
    • 图片压缩(image-webpack-loader);
    • Gzip / Brotli 压缩。
  2. 拆包策略

    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendor' },
        common: { minChunks: 2, name: 'common' }
      }
    }
  3. 代码分割import() 动态导入;
  4. 持久化缓存cache: { type: 'filesystem' }
  5. 多线程thread-loaderterser-webpack-plugin(默认多核);
  6. DLL / Module Federation:共享依赖;
  7. SourceMap 区分环境:开发用 eval-cheap-module,生产去掉;
  8. Bundle 分析webpack-bundle-analyzer

P-OPT22:常规的前端性能优化手段

系统化总结(八层优化):

层级手段
网络层CDN、HTTP/2、DNS 预解析、预连接、强缓存
资源层压缩、合并、雪碧图、WebP、字体子集化
加载层懒加载、预加载、代码分割、Tree Shaking
渲染层SSR、SSG、骨架屏、避免重排重排
运行时Web Worker、防抖节流、虚拟列表
代码层算法优化、避免全局查找、缓存计算结果
框架层React.memo、Vue v-once、组件细分
监控层Lighthouse、Web Vitals、APM

原则:先测量、再优化、最后监控。


P-OPT23:什么是 CSS Sprites?

概念:将多个小图标合并为一张大图,通过 background-position 定位显示。

.icon { background: url('sprites.png') no-repeat; }
.icon-home { background-position: 0 0; width: 16px; height: 16px; }
.icon-user { background-position: -16px 0; width: 16px; height: 16px; }

优点

  • 减少 HTTP 请求数(一次下载多图);
  • 利于缓存(合并图一次缓存,多次复用)。

缺点

  • 维护成本高(新增图标需重新拼图);
  • 不支持 Retina 高清屏(需提供 2x 图);
  • HTTP/2 下意义不大(多路复用已减少请求成本)。

现代方案

  • SVG Sprite:多个 SVG 合并为一个 <symbol><use href="#icon-home" /> 引用;
  • 字体图标:Iconfont / Iconify;
  • CDN 图标服务:按需加载 SVG。

本文作者:小码哥

本文链接:https://wesee.club/archives/1190/

版权声明:自由转载-非商用-非衍生-保持署名(cc 创意共享 3.0 许可证

Theme Jasmine by Kent Liao

粤ICP备2023052298号-1