JavaScript性能调优实战案例

JavaScript性能调优实战案例

目录

  1. [代码分割(Code Splitting)](#代码分割(Code Splitting))
  2. [懒加载(Lazy Loading)](#懒加载(Lazy Loading))
  3. 内存泄漏排查
  4. [Chrome DevTools高级调试技巧](#Chrome DevTools高级调试技巧)

代码分割(Code Splitting)

1.1 概述

代码分割是一种将代码拆分成多个chunks的技术,可以按需加载,减少初始加载体积,提升首屏加载速度。

1.2 Webpack代码分割配置

动态导入(Dynamic Import)
javascript 复制代码
// 方式一:使用import()语法
// 原始代码
import { heavyFunction } from './heavyModule';

// 优化后:动态导入
document.getElementById('loadBtn').addEventListener('click', async () => {
  const module = await import('./heavyModule.js');
  module.heavyFunction();
});
路由级别的代码分割(React示例)
javascript 复制代码
// App.js - 使用React.lazy和Suspense
import React, { Suspense, lazy } from 'react';

// 动态导入路由组件
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  );
}
Webpack配置
javascript 复制代码
// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // 对所有类型的chunk进行分割
      minSize: 30000, // 模块最小30KB才分割
      maxSize: 0, // 无最大限制
      minChunks: 1, // 模块至少被引用1次
      maxAsyncRequests: 5, // 异步请求最大数
      maxInitialRequests: 3, // 入口点最大并行请求数
      automaticNameDelimiter: '~', // chunk名称分隔符
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/, // 匹配node_modules
          priority: -10, // 优先级
          reuseExistingChunk: true, // 复用已存在的chunk
        },
        default: {
          minChunks: 2, // 模块至少被引用2次
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js',
  },
};

1.3 Vite代码分割

javascript 复制代码
// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom'],
          'ui-vendor': ['antd', 'lodash'],
          'utils': ['axios', 'dayjs'],
        },
      },
    },
    chunkSizeWarningLimit: 1000,
  },
};

懒加载(Lazy Loading)

2.1 图片懒加载

原生Intersection Observer API
html 复制代码
<!DOCTYPE html>
<html>
<head>
  <style>
    .lazy-image {
      opacity: 0;
      transition: opacity 0.3s;
      min-height: 200px;
      background-color: #f0f0f0;
    }
    .lazy-image.loaded {
      opacity: 1;
    }
  </style>
</head>
<body>
  <img class="lazy-image" data-src="image1.jpg" alt="图片1">
  <img class="lazy-image" data-src="image2.jpg" alt="图片2">
  
  <script>
    class LazyImageLoader {
      constructor(options = {}) {
        this.options = {
          rootMargin: '100px',
          threshold: 0.01,
          ...options
        };
        this.observer = null;
        this.init();
      }

      init() {
        if ('IntersectionObserver' in window) {
          this.observer = new IntersectionObserver(
            this.observerCallback.bind(this),
            this.options
          );
          this.observeElements();
        } else {
          this.fallbackLazyLoad();
        }
      }

      observerCallback(entries) {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            const img = entry.target;
            this.loadImage(img);
            this.observer.unobserve(img);
          }
        });
      }

      loadImage(img) {
        const src = img.dataset.src;
        if (!src) return;

        img.src = src;
        img.onload = () => img.classList.add('loaded');
        img.onerror = () => {
          img.src = 'placeholder.jpg';
          img.classList.add('loaded');
        };
      }

      observeElements() {
        const images = document.querySelectorAll('.lazy-image');
        images.forEach(img => this.observer.observe(img));
      }

      fallbackLazyLoad() {
        const images = document.querySelectorAll('.lazy-image');
        images.forEach(img => this.loadImage(img));
      }
    }

    // 使用
    new LazyImageLoader();
  </script>
</body>
</html>
React图片懒加载组件
javascript 复制代码
// LazyImage.js
import React, { useState, useRef, useEffect } from 'react';

function LazyImage({ src, alt, className, placeholder }) {
  const [isLoaded, setIsLoaded] = useState(false);
  const [isInView, setIsInView] = useState(false);
  const imgRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsInView(true);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  const handleLoad = () => {
    setIsLoaded(true);
  };

  return (
    <div ref={imgRef} className={className}>
      {isInView ? (
        <>
          <img
            src={src}
            alt={alt}
            onLoad={handleLoad}
            style={{ 
              opacity: isLoaded ? 1 : 0,
              transition: 'opacity 0.3s ease-in-out'
            }}
          />
          {!isLoaded && (
            <div 
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                height: '100%',
                backgroundColor: '#f0f0f0',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center'
              }}
            >
              {placeholder || '加载中...'}
            </div>
          )}
        </>
      ) : (
        <div style={{ 
          height: '200px', 
          backgroundColor: '#f0f0f0' 
        }}>
          {placeholder || '等待加载...'}
        </div>
      )}
    </div>
  );
}

export default LazyImage;

2.2 组件懒加载

React组件懒加载
javascript 复制代码
// 使用React.lazy
import React, { Suspense } from 'react';

// 预加载组件
const preloadComponent = (componentImport) => {
  componentImport();
};

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

function Dashboard() {
  return (
    <div>
      <Suspense fallback={<LoadingSkeleton />}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}

// 预加载触发器
function PreloadButton({ onMouseEnter }) {
  return (
    <button
      onMouseEnter={() => {
        // 鼠标悬停时预加载
        import('./HeavyComponent');
      }}
    >
      查看详情
    </button>
  );
}

2.3 数据懒加载

javascript 复制代码
// 无限滚动加载
class InfiniteScrollLoader {
  constructor(options) {
    this.loading = false;
    this.page = 1;
    this.hasMore = true;
    this.onLoad = options.onLoad;
    this.init();
  }

  init() {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && !this.loading && this.hasMore) {
          this.loadMore();
        }
      },
      { threshold: 0.1 }
    );

    const sentinel = document.getElementById('scroll-sentinel');
    if (sentinel) observer.observe(sentinel);
  }

  async loadMore() {
    this.loading = true;
    try {
      const data = await this.onLoad(this.page);
      if (data.length === 0) {
        this.hasMore = false;
      } else {
        this.page++;
        this.renderItems(data);
      }
    } catch (error) {
      console.error('加载失败:', error);
    } finally {
      this.loading = false;
    }
  }

  renderItems(items) {
    // 渲染逻辑
  }
}

// 使用
const loader = new InfiniteScrollLoader({
  onLoad: async (page) => {
    const response = await fetch(`/api/items?page=${page}`);
    return response.json();
  }
});

内存泄漏排查

3.1 常见内存泄漏场景

3.1.1 未清理的事件监听器
javascript 复制代码
// ❌ 错误示例 - 内存泄漏
class Component {
  constructor() {
    this.handleClick = this.handleClick.bind(this);
    document.addEventListener('click', this.handleClick);
  }

  handleClick() {
    console.log('clicked');
  }
}

// ✅ 正确示例 - 清理监听器
class Component {
  constructor() {
    this.handleClick = this.handleClick.bind(this);
    document.addEventListener('click', this.handleClick);
  }

  handleClick() {
    console.log('clicked');
  }

  destroy() {
    document.removeEventListener('click', this.handleClick);
  }
}

// React中使用useEffect清理
function MyComponent() {
  useEffect(() => {
    const handleClick = () => console.log('clicked');
    document.addEventListener('click', handleClick);

    // 清理函数
    return () => {
      document.removeEventListener('click', handleClick);
    };
  }, []);

  return <div>Hello</div>;
}
3.1.2 定时器未清除
javascript 复制代码
// ❌ 错误示例
class TimerComponent {
  start() {
    this.timer = setInterval(() => {
      this.update();
    }, 1000);
  }
}

// ✅ 正确示例
class TimerComponent {
  start() {
    this.timer = setInterval(() => {
      this.update();
    }, 1000);
  }

  stop() {
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
  }
}

// React中
function Timer() {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('tick');
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return <div>Timer</div>;
}
3.1.3 闭包导致的内存泄漏
javascript 复制代码
// ❌ 潜在问题
function createHandler() {
  const largeData = new Array(1000000).fill('data');
  
  return function() {
    console.log('handled');
    // 这里访问largeData会导致其无法被GC回收
  };
}

// ✅ 解决方案 - 释放不需要的引用
function createHandler() {
  const largeData = new Array(1000000).fill('data');
  
  // 处理完数据后立即释放
  const result = processData(largeData);
  largeData.length = 0; // 清空数组
  
  return function() {
    console.log('handled', result);
  };
}

3.2 使用Chrome DevTools检测内存泄漏

内存快照对比
javascript 复制代码
// 用于测试内存泄漏的代码
let leakingObjects = [];

function createLeak() {
  for (let i = 0; i < 10000; i++) {
    leakingObjects.push({
      id: i,
      data: new Array(100).fill('x'),
      // 添加到全局window对象
      window[`temp${i}`] = `data${i}`
    });
  }
}

function cleanLeak() {
  leakingObjects = [];
  // 清理window上的属性
  for (let key in window) {
    if (key.startsWith('temp')) {
      delete window[key];
    }
  }
}

// 使用步骤:
// 1. 打开Chrome DevTools -> Memory
// 2. 拍摄初始快照
// 3. 执行 createLeak()
// 4. 拍摄第二个快照
// 5. 对比两个快照,查看对象增长
// 6. 执行 cleanLeak()
// 7. 拍摄第三个快照,确认清理效果
堆快照分析
javascript 复制代码
// 测试DOM节点泄漏
let detachedNodes = [];

function createDetachedNodes() {
  for (let i = 0; i < 1000; i++) {
    const div = document.createElement('div');
    div.innerHTML = `Node ${i}`;
    // 创建但不挂载到DOM
    detachedNodes.push(div);
  }
}

// 检测方法:
// 1. Memory -> Take Heap Snapshot
// 2. 搜索 "Detached DOM tree"
// 3. 查看保留树(retainers)找到引用来源

3.3 内存泄漏检测工具封装

javascript 复制代码
class MemoryMonitor {
  constructor() {
    this.initialSize = 0;
    this.snapshots = [];
  }

  async takeSnapshot(name) {
    if (!window.performance || !window.performance.memory) {
      console.warn('性能API不可用');
      return null;
    }

    const snapshot = {
      name,
      timestamp: Date.now(),
      usedJSHeapSize: window.performance.memory.usedJSHeapSize,
      totalJSHeapSize: window.performance.memory.totalJSHeapSize,
      jsHeapSizeLimit: window.performance.memory.jsHeapSizeLimit
    };

    this.snapshots.push(snapshot);
    console.log(`📊 ${name}:`, this.formatMemory(snapshot.usedJSHeapSize));
    
    return snapshot;
  }

  formatMemory(bytes) {
    return (bytes / 1024 / 1024).toFixed(2) + ' MB';
  }

  compareSnapshots(name1, name2) {
    const snap1 = this.snapshots.find(s => s.name === name1);
    const snap2 = this.snapshots.find(s => s.name === name2);

    if (!snap1 || !snap2) {
      console.error('快照不存在');
      return;
    }

    const diff = snap2.usedJSHeapSize - snap1.usedJSHeapSize;
    const sign = diff > 0 ? '+' : '';
    
    console.log(`📈 内存变化: ${sign}${this.formatMemory(diff)}`);
    
    if (diff > 0) {
      console.warn('⚠️ 内存增长,可能存在泄漏');
    }
  }

  printAllSnapshots() {
    console.table(this.snapshots.map(s => ({
      名称: s.name,
      时间: new Date(s.timestamp).toLocaleTimeString(),
      已使用: this.formatMemory(s.usedJSHeapSize),
      总量: this.formatMemory(s.totalJSHeapSize),
      限制: this.formatMemory(s.jsHeapSizeLimit)
    })));
  }
}

// 使用示例
const monitor = new MemoryMonitor();

async function testMemoryLeak() {
  await monitor.takeSnapshot('初始状态');
  
  // 执行可能泄漏的操作
  const objects = [];
  for (let i = 0; i < 10000; i++) {
    objects.push({ id: i, data: new Array(100).fill('x') });
  }
  
  await monitor.takeSnapshot('操作后');
  monitor.compareSnapshots('初始状态', '操作后');
  monitor.printAllSnapshots();
}

Chrome DevTools高级调试技巧

4.1 Performance面板

性能分析实战
javascript 复制代码
// 测试代码 - 执行耗时操作
function heavyComputation() {
  const result = [];
  for (let i = 0; i < 100000; i++) {
    result.push(i * i);
  }
  return result;
}

function inefficientSort(arr) {
  // 低效的排序算法 - 冒泡排序
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  return arr;
}

// 使用性能标记
performance.mark('start');

const data = heavyComputation();
inefficientSort(data);

performance.mark('end');
performance.measure('总耗时', 'start', 'end');

const measure = performance.getEntriesByName('总耗时')[0];
console.log(`执行时间: ${measure.duration.toFixed(2)}ms`);
性能分析步骤
复制代码
1. 打开Chrome DevTools -> Performance
2. 点击Record按钮(或按Ctrl+E)
3. 执行需要分析的代码
4. 停止录制
5. 分析结果:
   - Main线程: 查看JavaScript执行情况
   - Frames: 查看帧率,目标60fps(16.6ms/frame)
   - Network: 查看资源加载
   - Flame Chart: 火焰图,函数调用栈

4.2 Sources面板高级调试

条件断点
javascript 复制代码
// 在循环中设置条件断点
function processUsers(users) {
  for (let i = 0; i < users.length; i++) {
    const user = users[i];
    console.log(`Processing user ${i}: ${user.name}`);
    
    // 设置条件断点: i === 100
    // 右键行号 -> Add conditional breakpoint -> 输入条件
  }
}
Logpoint(日志点)
javascript 复制代码
function calculateTotal(items) {
  let total = 0;
  items.forEach((item, index) => {
    total += item.price;
    
    // 设置Logpoint: "Item ${index}: ${item.price}, Total: ${total}"
    // 右键行号 -> Add logpoint
    // 不需要中断,只在Console输出日志
  });
  return total;
}
DOM断点
javascript 复制代码
// HTML结构
<div id="container">
  <button id="addBtn">添加元素</button>
  <div id="list"></div>
</div>

<script>
const list = document.getElementById('list');

document.getElementById('addBtn').addEventListener('click', () => {
  const item = document.createElement('div');
  item.textContent = 'New Item';
  list.appendChild(item);
});
</script>

// 使用DOM断点:
// 1. Elements面板右键#list元素
// 2. Break on -> Subtree modifications
// 3. 点击按钮,自动触发断点
黑盒脚本(Blackboxing)
javascript 复制代码
// 对于第三方库代码,不想单步调试时可以黑盒化
// Sources面板 -> 右键脚本文件 -> Blackbox script
// 之后调试时将跳过该文件的内部细节

4.3 Network面板优化

资源加载分析
javascript 复制代码
// 模拟API请求
async function fetchUserData() {
  // 添加性能标记
  const startMark = 'fetch-start';
  const endMark = 'fetch-end';
  
  performance.mark(startMark);
  
  const response = await fetch('/api/users', {
    headers: { 'Content-Type': 'application/json' }
  });
  
  performance.mark(endMark);
  performance.measure('fetch-user', startMark, endMark);
  
  const measure = performance.getEntriesByName('fetch-user')[0];
  console.log(`API请求耗时: ${measure.duration}ms`);
  
  return response.json();
}
缓存策略测试
javascript 复制代码
// fetch API缓存控制
fetch('/api/data', {
  cache: 'no-cache', // 'default', 'no-store', 'reload', 'no-cache', 'force-cache', 'only-if-cached'
  headers: {
    'Cache-Control': 'max-age=3600',
    'ETag': 'some-etag-value'
  }
});

// Service Worker缓存测试
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then(registration => {
        console.log('SW注册成功:', registration);
      })
      .catch(error => {
        console.log('SW注册失败:', error);
      });
  });
}

4.4 Console高级技巧

console.table数据可视化
javascript 复制代码
const users = [
  { id: 1, name: 'Alice', age: 25, role: 'Admin' },
  { id: 2, name: 'Bob', age: 30, role: 'User' },
  { id: 3, name: 'Charlie', age: 35, role: 'Admin' }
];

// 表格化显示
console.table(users);

// 只显示特定列
console.table(users, ['name', 'age']);
console.time性能测量
javascript 复制代码
// 测量代码执行时间
console.time('array-sort');

const arr = Array.from({ length: 10000 }, () => Math.random());
arr.sort((a, b) => a - b);

console.timeEnd('array-sort');
// 输出: array-sort: 5.234ms
console.group分组输出
javascript 复制代码
function analyzeData(data) {
  console.group('数据分析');
  
  console.log('数据总数:', data.length);
  
  console.group('前5项数据');
  console.table(data.slice(0, 5));
  console.groupEnd();
  
  console.group('统计信息');
  const avg = data.reduce((sum, val) => sum + val, 0) / data.length;
  console.log('平均值:', avg);
  console.log('最大值:', Math.max(...data));
  console.log('最小值:', Math.min(...data));
  console.groupEnd();
  
  console.groupEnd();
}

4.5 Lighthouse性能评分

javascript 复制代码
// 自动化Lighthouse测试
// 安装: npm install -g lighthouse
// 运行: lighthouse https://example.com --view

// 使用Node.js API
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');

async function runLighthouse(url) {
  const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
  
  const options = {
    logLevel: 'info',
    output: 'json',
    onlyCategories: ['performance'],
    port: chrome.port
  };
  
  const runnerResult = await lighthouse(url, options);
  
  await chrome.kill();
  
  const score = runnerResult.lhr.categories.performance.score * 100;
  console.log(`性能得分: ${score}`);
  
  // 详细报告
  console.log('FCP:', runnerResult.lhr.audits['first-contentful-paint'].displayValue);
  console.log('LCP:', runnerResult.lhr.audits['largest-contentful-paint'].displayValue);
  console.log('CLS:', runnerResult.lhr.audits['cumulative-layout-shift'].displayValue);
  
  return runnerResult;
}

// runLighthouse('https://example.com');

4.6 调试React应用

React DevTools
javascript 复制代码
// React Profiler使用
import { Profiler } from 'react';

function onRenderCallback(
  id,              // 组件的标识
  phase,           // 'mount' 或 'update'
  actualDuration,  // 渲染耗时
  baseDuration,    // 不使用memoization的基础耗时
  startTime,       // 开始时间
  commitTime       // 提交时间
) {
  console.log(`${id} ${phase}:`, actualDuration.toFixed(2) + 'ms');
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MyComponent />
    </Profiler>
  );
}
性能优化检查清单
javascript 复制代码
// React性能优化要点

// 1. 使用React.memo避免不必要的重渲染
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  return <div>{/* 复杂渲染 */}</div>;
});

// 2. 使用useMemo缓存计算结果
function Component({ items }) {
  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => a.value - b.value);
  }, [items]);
  
  return <List items={sortedItems} />;
}

// 3. 使用useCallback缓存回调函数
function Parent() {
  const handleClick = useCallback((id) => {
    console.log('Clicked:', id);
  }, []);
  
  return <Child onClick={handleClick} />;
}

// 4. 虚拟列表优化长列表
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={400}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>{items[index].name}</div>
      )}
    </FixedSizeList>
  );
}

综合实战案例

案例: 电商商品列表页性能优化

javascript 复制代码
// goods-list.js - 优化前
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function GoodsList() {
  const [goods, setGoods] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    axios.get('/api/goods')
      .then(res => {
        setGoods(res.data);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>加载中...</div>;

  return (
    <div>
      {goods.map(item => (
        <GoodCard 
          key={item.id}
          title={item.title}
          price={item.price}
          image={item.image}
          description={item.description}
        />
      ))}
    </div>
  );
}

// GoodCard.js - 优化前
function GoodCard({ title, price, image, description }) {
  return (
    <div className="card">
      <img src={image} alt={title} />
      <h3>{title}</h3>
      <p>¥{price}</p>
      <p>{description}</p>
    </div>
  );
}
javascript 复制代码
// goods-list-optimized.js - 优化后
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { FixedSizeList as List } from 'react-window';
import LazyImage from './LazyImage';

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

function GoodsList() {
  const [goods, setGoods] = useState([]);
  const [loading, setLoading] = useState(false);
  const [page, setPage] = useState(1);
  const [hasMore, setHasMore] = useState(true);

  // 使用useCallback缓存fetch函数
  const fetchGoods = useCallback(async (pageNum = 1) => {
    setLoading(true);
    try {
      const res = await axios.get('/api/goods', { 
        params: { page: pageNum, size: 20 } 
      });
      
      if (pageNum === 1) {
        setGoods(res.data.list);
      } else {
        setGoods(prev => [...prev, ...res.data.list]);
      }
      
      setHasMore(res.data.hasMore);
    } catch (error) {
      console.error('加载失败:', error);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    fetchGoods(1);
  }, [fetchGoods]);

  // 无限滚动加载
  const loadMore = useCallback(() => {
    if (!loading && hasMore) {
      const nextPage = page + 1;
      setPage(nextPage);
      fetchGoods(nextPage);
    }
  }, [loading, hasMore, page, fetchGoods]);

  // 使用useMemo优化商品列表
  const memoizedGoods = useMemo(() => goods, [goods]);

  // 行渲染函数
  const Row = useCallback(({ index, style }) => (
    <div style={style}>
      <GoodsCard item={memoizedGoods[index]} />
    </div>
  ), [memoizedGoods]);

  if (loading && page === 1) return <LoadingSkeleton />;

  return (
    <Suspense fallback={<div>加载组件中...</div>}>
      <List
        height={800}
        itemCount={memoizedGoods.length}
        itemSize={300}
        width="100%"
        onItemsRendered={({ visibleStopIndex }) => {
          // 接近底部时加载更多
          if (visibleStopIndex >= memoizedGoods.length - 5) {
            loadMore();
          }
        }}
      >
        {Row}
      </List>
      {loading && <div>加载更多...</div>}
    </Suspense>
  );
}

// goods-card-optimized.js
import React, { memo } from 'react';
import LazyImage from './LazyImage';

// 使用React.memo避免不必要的重渲染
const GoodsCard = memo(function GoodsCard({ item }) {
  return (
    <div className="card">
      {/* 使用懒加载图片 */}
      <LazyImage 
        src={item.image} 
        alt={item.title}
        placeholder="加载中..."
      />
      <h3>{item.title}</h3>
      <p>¥{item.price}</p>
      <p>{item.description}</p>
    </div>
  );
});

export default GoodsCard;

性能对比测试

javascript 复制代码
// performance-test.js
import { performance } from 'perf_hooks';

class PerformanceTester {
  constructor(name) {
    this.name = name;
  }

  async test(fn, iterations = 100) {
    const results = [];
    
    for (let i = 0; i < iterations; i++) {
      const start = performance.now();
      await fn();
      const end = performance.now();
      results.push(end - start);
    }

    const avg = results.reduce((a, b) => a + b, 0) / results.length;
    const min = Math.min(...results);
    const max = Math.max(...results);
    
    console.log(`\n📊 ${this.name} 性能测试结果:`);
    console.log(`   平均耗时: ${avg.toFixed(2)}ms`);
    console.log(`   最小耗时: ${min.toFixed(2)}ms`);
    console.log(`   最大耗时: ${max.toFixed(2)}ms`);
    
    return { avg, min, max };
  }
}

// 使用示例
const tester = new PerformanceTester('商品列表渲染');

// 测试优化前
async function testBefore() {
  // 模拟渲染1000个商品
  const goods = Array.from({ length: 1000 }, (_, i) => ({
    id: i,
    title: `商品${i}`,
    price: i * 10,
    image: `image${i}.jpg`
  }));
  return goods;
}

// 测试优化后
async function testAfter() {
  // 模拟虚拟列表渲染
  const goods = Array.from({ length: 1000 }, (_, i) => ({
    id: i,
    title: `商品${i}`,
    price: i * 10,
    image: `image${i}.jpg`
  }));
  return goods.slice(0, 20); // 只渲染可见部分
}

// 执行测试
(async () => {
  await tester.test(testBefore);
  await tester.test(testAfter);
})();

最佳实践总结

代码分割

✅ 使用动态import()进行路由级别的代码分割

✅ 配置splitChunks优化第三方库打包

✅ 使用React.lazy()和Suspense实现组件懒加载

❌ 避免过度分割,过多的chunk会影响性能

懒加载

✅ 图片使用Intersection Observer实现懒加载

✅ 长列表使用虚拟滚动技术

✅ 非关键功能延迟加载

❌ 不要懒加载首屏关键资源

内存管理

✅ 及时清理事件监听器和定时器

✅ 避免不必要的闭包引用

✅ 使用Chrome DevTools定期检查内存使用

❌ 不要在全局对象上积累大量数据

性能优化

✅ 使用Performance API监测关键指标

✅ 优化主线程任务,保持60fps

✅ 使用缓存策略减少重复计算

❌ 避免过早优化,先测量后优化


参考资料

相关推荐
Moment2 小时前
前端工程化 + AI 赋能,从需求到运维一条龙怎么搭 ❓❓❓
前端·javascript·面试
Joker Zxc3 小时前
【前端基础(Javascript部分)】6、用JavaScript的递归函数和for循环,计算斐波那契数列的第 n 项值
开发语言·前端·javascript
Highcharts.js3 小时前
React 图表如何实现下钻(Drilldown)效果
开发语言·前端·javascript·react.js·前端框架·数据可视化·highcharts
chushiyunen3 小时前
python中的魔术方法(双下划线)
前端·javascript·python
终端鹿3 小时前
Vue2 迁移 Vue3 避坑指南
前端·javascript·vue.js
进击的尘埃4 小时前
Signals 跨框架收敛:TC39 提案、Solid、Angular、Preact 的实现差异与调度策略对比
javascript
进击的尘埃4 小时前
从多仓到 Monorepo 的渐进式迁移:Git 历史保留、依赖收敛与缓存调优
javascript
SuperEugene4 小时前
TypeScript+Vue 实战:告别 any 滥用,统一接口 / Props / 表单类型,实现类型安全|编码语法规范篇
开发语言·前端·javascript·vue.js·安全·typescript
gis开发6 小时前
cesium 中添加鹰眼效果
前端·javascript