JavaScript性能调优实战案例
目录
- [代码分割(Code Splitting)](#代码分割(Code Splitting))
- [懒加载(Lazy Loading)](#懒加载(Lazy Loading))
- 内存泄漏排查
- [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
✅ 使用缓存策略减少重复计算
❌ 避免过早优化,先测量后优化