Skip to content

前端性能优化

目录

加载优化

资源压缩

javascript
// webpack.config.js
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  plugins: [
    new CompressionPlugin({
      test: /\.(js|css|html|svg)$/,
      algorithm: 'gzip',
      threshold: 10240,
      minRatio: 0.8
    })
  ]
};

// 图片压缩
const imagemin = require('imagemin');
const imageminMozjpeg = require('imagemin-mozjpeg');
const imageminPngquant = require('imagemin-pngquant');

(async () => {
  await imagemin(['images/*'], {
    destination: 'compressed_images',
    plugins: [
      imageminMozjpeg({ quality: 75 }),
      imageminPngquant({ quality: [0.6, 0.8] })
    ]
  });
})();

说明

  • 压缩 JavaScript、CSS、HTML 文件
  • 压缩图片资源
  • 使用 Gzip/Brotli 压缩
  • 设置合适的压缩率

代码分割

javascript
// 路由懒加载
const About = lazy(() => import('./About'));

const App = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  );
};

// 组件懒加载
const HeavyComponent = lazy(() => import('./HeavyComponent'));

const Parent = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
};

说明

  • 按路由分割代码
  • 按组件分割代码
  • 使用动态导入
  • 添加加载状态

懒加载

javascript
// 图片懒加载
const LazyImage = ({ src, alt }) => {
  const [isLoaded, setIsLoaded] = useState(false);

  return (
    <img
      src={src}
      alt={alt}
      loading="lazy"
      onLoad={() => setIsLoaded(true)}
      style={{ opacity: isLoaded ? 1 : 0 }}
    />
  );
};

// 列表懒加载
const InfiniteList = () => {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);

  const loadMore = useCallback(() => {
    fetchItems(page).then(newItems => {
      setItems(prev => [...prev, ...newItems]);
      setPage(prev => prev + 1);
    });
  }, [page]);

  return (
    <div>
      {items.map(item => (
        <div key={item.id}>{item.text}</div>
      ))}
      <button onClick={loadMore}>Load More</button>
    </div>
  );
};

说明

  • 图片懒加载
  • 列表懒加载
  • 组件懒加载
  • 资源懒加载

渲染优化

重排重绘

javascript
// 批量更新
const BatchUpdate = () => {
  const [items, setItems] = useState([]);

  const addItems = () => {
    // 使用 requestAnimationFrame 批量更新
    requestAnimationFrame(() => {
      const newItems = Array.from({ length: 1000 }, (_, i) => ({
        id: i,
        text: `Item ${i}`
      }));
      setItems(newItems);
    });
  };

  return (
    <div>
      <button onClick={addItems}>Add Items</button>
      <div style={{ height: '400px', overflow: 'auto' }}>
        {items.map(item => (
          <div key={item.id}>{item.text}</div>
        ))}
      </div>
    </div>
  );
};

// 使用 transform 代替 top/left
const AnimatedBox = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const move = () => {
    setPosition({ x: 100, y: 100 });
  };

  return (
    <div
      style={{
        transform: `translate(${position.x}px, ${position.y}px)`,
        transition: 'transform 0.3s'
      }}
    >
      Animated Box
    </div>
  );
};

说明

  • 避免频繁重排
  • 使用 transform 代替位置属性
  • 批量更新 DOM
  • 使用 requestAnimationFrame

虚拟列表

javascript
const VirtualList = ({ items, itemHeight, windowHeight }) => {
  const [scrollTop, setScrollTop] = useState(0);

  const visibleItems = items.slice(
    Math.floor(scrollTop / itemHeight),
    Math.ceil((scrollTop + windowHeight) / itemHeight)
  );

  const totalHeight = items.length * itemHeight;
  const offsetY = Math.floor(scrollTop / itemHeight) * itemHeight;

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

说明

  • 只渲染可见区域
  • 计算滚动位置
  • 使用 transform 定位
  • 保持滚动条高度

防抖节流

javascript
// 防抖
const debounce = (fn, delay) => {
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};

// 节流
const throttle = (fn, delay) => {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last > delay) {
      fn.apply(this, args);
      last = now;
    }
  };
};

// 使用示例
const SearchInput = () => {
  const [value, setValue] = useState('');

  const handleSearch = debounce(value => {
    // 执行搜索
    console.log('Searching:', value);
  }, 300);

  return (
    <input
      value={value}
      onChange={e => {
        setValue(e.target.value);
        handleSearch(e.target.value);
      }}
    />
  );
};

说明

  • 防抖用于输入框
  • 节流用于滚动事件
  • 避免频繁触发
  • 提高性能

缓存优化

浏览器缓存

javascript
// 设置缓存头
app.use(express.static('public', {
  maxAge: '1d',
  etag: true,
  lastModified: true
}));

// 使用 localStorage
const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = value => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
};

说明

  • 使用 HTTP 缓存
  • 使用 localStorage
  • 使用 sessionStorage
  • 使用 Cookie

Service Worker

javascript
// 注册 Service Worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js').then(registration => {
      console.log('SW registered:', registration);
    }).catch(error => {
      console.log('SW registration failed:', error);
    });
  });
}

// sw.js
const CACHE_NAME = 'v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

说明

  • 离线缓存
  • 资源预缓存
  • 请求拦截
  • 缓存策略

IndexedDB

javascript
// 打开数据库
const openDB = () => {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('MyDB', 1);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve(request.result);

    request.onupgradeneeded = event => {
      const db = event.target.result;
      db.createObjectStore('users', { keyPath: 'id' });
    };
  });
};

// 存储数据
const storeData = async (data) => {
  const db = await openDB();
  const transaction = db.transaction('users', 'readwrite');
  const store = transaction.objectStore('users');

  return new Promise((resolve, reject) => {
    const request = store.add(data);
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
};

说明

  • 大容量存储
  • 异步操作
  • 事务支持
  • 索引支持

网络优化

HTTP/2

nginx
# nginx.conf
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }
}

说明

  • 多路复用
  • 头部压缩
  • 服务器推送
  • 二进制协议

CDN

html
<!-- 使用 CDN -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>

<!-- 配置 CDN -->
<script>
  window.__CDN__ = {
    baseUrl: 'https://cdn.example.com',
    version: '1.0.0'
  };
</script>

说明

  • 静态资源加速
  • 全球分发
  • 负载均衡
  • 缓存优化

预加载

html
<!-- 预加载资源 -->
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="main.js" as="script">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

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

说明

  • 预加载关键资源
  • 预连接域名
  • DNS 预解析
  • 资源优先级

监控分析

性能指标

javascript
// 使用 Performance API
const measurePerformance = () => {
  const performance = window.performance;
  const timing = performance.timing;

  const metrics = {
    // 页面加载时间
    loadTime: timing.loadEventEnd - timing.navigationStart,
    // DOM 解析时间
    domReadyTime: timing.domComplete - timing.domLoading,
    // 首次内容绘制
    fcp: performance.getEntriesByName('first-contentful-paint')[0]?.startTime,
    // 最大内容绘制
    lcp: performance.getEntriesByName('largest-contentful-paint')[0]?.startTime
  };

  return metrics;
};

// 使用 Web Vitals
import { getCLS, getFID, getLCP } from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getLCP(console.log);

说明

  • 核心 Web 指标
  • 性能时间线
  • 资源加载时间
  • 用户交互时间

错误监控

javascript
// 全局错误处理
window.onerror = (message, source, lineno, colno, error) => {
  console.error('Global error:', { message, source, lineno, colno, error });
  // 上报错误
};

// Promise 错误处理
window.addEventListener('unhandledrejection', event => {
  console.error('Unhandled promise rejection:', event.reason);
  // 上报错误
});

// 自定义错误边界
class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    // 上报错误
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

说明

  • 全局错误捕获
  • Promise 错误处理
  • React 错误边界
  • 错误上报

用户行为

javascript
// 用户行为跟踪
const trackUserBehavior = () => {
  // 页面访问
  const pageView = {
    url: window.location.href,
    title: document.title,
    timestamp: Date.now()
  };

  // 点击事件
  document.addEventListener('click', event => {
    const click = {
      target: event.target.tagName,
      text: event.target.textContent,
      timestamp: Date.now()
    };
    // 上报点击
  });

  // 滚动事件
  let scrollTimeout;
  window.addEventListener('scroll', () => {
    clearTimeout(scrollTimeout);
    scrollTimeout = setTimeout(() => {
      const scroll = {
        position: window.scrollY,
        timestamp: Date.now()
      };
      // 上报滚动
    }, 100);
  });
};

说明

  • 页面访问统计
  • 用户点击跟踪
  • 滚动行为分析
  • 性能数据收集

最佳实践

1. 加载优化

  • 压缩资源文件
  • 使用代码分割
  • 实现懒加载
  • 优化图片加载

2. 渲染优化

  • 减少重排重绘
  • 使用虚拟列表
  • 实现防抖节流
  • 优化动画性能

3. 缓存优化

  • 合理使用缓存
  • 实现离线功能
  • 优化存储策略
  • 清理过期缓存

4. 网络优化

  • 使用 HTTP/2
  • 配置 CDN
  • 实现预加载
  • 优化请求策略

学习资源

在线教程

视频课程

  • 慕课网
  • 极客时间
  • B站技术区
  • YouTube 技术频道

工具推荐

启航团队技术文档