Skip to content

前端性能优化

一、性能指标

1.1 Core Web Vitals

指标全称说明良好值
LCPLargest Contentful Paint最大内容绘制< 2.5s
FIDFirst Input Delay首次输入延迟< 100ms
CLSCumulative Layout Shift累积布局偏移< 0.1
INPInteraction to Next Paint交互到下一帧绘制< 200ms

1.2 其他重要指标

指标说明
FCP首次内容绘制
TTI可交互时间
TBT总阻塞时间
TTFB首字节时间

1.3 性能监测

javascript
// Performance API
const [entry] = performance.getEntriesByType('navigation');
console.log('DNS 解析:', entry.domainLookupEnd - entry.domainLookupStart);
console.log('TCP 连接:', entry.connectEnd - entry.connectStart);
console.log('请求响应:', entry.responseEnd - entry.requestStart);
console.log('DOM 解析:', entry.domContentLoadedEventEnd - entry.responseEnd);

// Web Vitals 库
import { getLCP, getFID, getCLS } from 'web-vitals';
getLCP(console.log);
getFID(console.log);
getCLS(console.log);

// PerformanceObserver
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime);
  }
});
observer.observe({ entryTypes: ['largest-contentful-paint'] });

二、加载性能优化

2.1 资源压缩

javascript
// Webpack 配置
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin(),      // JS 压缩
      new CssMinimizerPlugin() // CSS 压缩
    ]
  }
};

// 启用 Gzip/Brotli
// Nginx 配置
gzip on;
gzip_types text/plain text/css application/json application/javascript;
brotli on;
brotli_types text/plain text/css application/json application/javascript;

2.2 代码分割

javascript
// 动态导入
const module = await import('./heavy-module.js');

// React 懒加载
const LazyComponent = React.lazy(() => import('./LazyComponent'));

// Webpack SplitChunks
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        chunks: 'all'
      }
    }
  }
}

2.3 Tree Shaking

javascript
// package.json
{
  "sideEffects": false  // 标记无副作用
}

// 使用 ES Modules
import { debounce } from 'lodash-es';  // ✅
// import _ from 'lodash';              // ❌ 全量引入

2.4 资源预加载

html
<!-- 预连接 -->
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">

<!-- 预加载关键资源 -->
<link rel="preload" href="/fonts/font.woff2" as="font" crossorigin>
<link rel="preload" href="/critical.css" as="style">

<!-- 预获取下一页资源 -->
<link rel="prefetch" href="/next-page.js">

<!-- 模块预加载 -->
<link rel="modulepreload" href="/module.js">

2.5 图片优化

html
<!-- 响应式图片 -->
<img
  src="small.jpg"
  srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1200w"
  sizes="(max-width: 600px) 100vw, 50vw"
  loading="lazy"
  decoding="async"
  alt="description"
>

<!-- 现代格式 -->
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="description">
</picture>
css
/* 骨架屏占位 */
.image-placeholder {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

三、运行时性能优化

3.1 长任务拆分

javascript
// ❌ 长任务阻塞主线程
function processLargeArray(array) {
  array.forEach(item => heavyProcess(item));
}

// ✅ 使用 requestIdleCallback 拆分
function processWithIdle(array) {
  let index = 0;

  function process(deadline) {
    while (index < array.length && deadline.timeRemaining() > 0) {
      heavyProcess(array[index++]);
    }
    if (index < array.length) {
      requestIdleCallback(process);
    }
  }

  requestIdleCallback(process);
}

// ✅ 使用 scheduler.yield() (实验性)
async function processWithYield(array) {
  for (const item of array) {
    heavyProcess(item);
    if (navigator.scheduling?.isInputPending()) {
      await scheduler.yield();
    }
  }
}

3.2 虚拟列表

javascript
// 只渲染可见区域
function VirtualList({ items, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);

  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(
    startIndex + Math.ceil(containerHeight / itemHeight) + 1,
    items.length
  );

  const visibleItems = items.slice(startIndex, endIndex);
  const offsetY = startIndex * itemHeight;

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

3.3 防抖与节流

javascript
// 防抖:延迟执行,重置计时
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 节流:固定频率执行
function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// 使用
window.addEventListener('scroll', throttle(handleScroll, 100));
input.addEventListener('input', debounce(search, 300));

3.4 Web Worker

javascript
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeData });
worker.onmessage = (e) => console.log(e.data);

// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};

四、渲染性能优化

4.1 避免强制同步布局

javascript
// ❌ 读写交错
elements.forEach(el => {
  el.style.width = container.offsetWidth + 'px';  // 每次都触发重排
});

// ✅ 先读后写
const width = container.offsetWidth;
elements.forEach(el => {
  el.style.width = width + 'px';
});

4.2 使用 CSS contain

css
.card {
  contain: layout paint;  /* 隔离重排重绘范围 */
}

.offscreen {
  content-visibility: auto;  /* 跳过屏幕外内容渲染 */
}

4.3 优化动画

css
/* 只改变 transform 和 opacity */
.animated {
  transform: translateX(100px);
  opacity: 0.5;
  will-change: transform, opacity;
}

五、网络优化

5.1 使用 HTTP/2

# 优势
- 多路复用
- 头部压缩
- 服务器推送

5.2 CDN 加速

javascript
// 静态资源使用 CDN
const cdnUrl = 'https://cdn.example.com';
const imageSrc = `${cdnUrl}/images/hero.webp`;

5.3 Service Worker 缓存

javascript
// 缓存优先策略
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then(cached => cached || fetch(event.request))
  );
});

六、高频面试题

Q1: 如何优化 LCP?

  1. 优化服务器响应时间 (TTFB)
  2. 预加载关键资源
  3. 优化图片 (WebP/AVIF, 响应式, lazy loading)
  4. 移除阻塞渲染的资源

Q2: 如何减少 CLS?

  1. 为图片/视频设置明确尺寸
  2. 避免在现有内容上方插入内容
  3. 使用 transform 做动画
  4. 预留广告位空间

Q3: 如何优化首屏加载?

  1. 代码分割 + 懒加载
  2. 关键 CSS 内联
  3. 预加载关键资源
  4. SSR/SSG
  5. 骨架屏

Q4: 列表渲染卡顿如何优化?

  1. 虚拟列表
  2. 分页加载
  3. requestIdleCallback 拆分任务
  4. Web Worker 处理数据

前端面试知识库