14.3 性能分析与内存泄漏排查
性能分析基础
性能分析的核心指标
- CPU使用率:JavaScript主线程的CPU占用情况
- 内存消耗:堆内存、栈内存的使用情况
- 渲染性能:布局、绘制、合成等浏览器渲染流水线指标
- 网络请求:资源加载时间和顺序
浏览器性能工具
// 使用 console.time 和 console.timeEnd 进行简单性能测量
console.time('array initialize');
const largeArray = new Array(1000000).fill().map((_, i) => i);
console.timeEnd('array initialize');
// 使用 performance.now() 获取高精度时间戳
const start = performance.now();
expensiveOperation();
const end = performance.now();
console.log(`Operation took ${end - start} milliseconds`);
Chrome DevTools 性能分析
CPU 性能分析
-
录制性能分析
- 打开 Performance 面板
- 点击 Record 按钮
- 执行需要分析的操作
- 停止录制查看结果
-
关键指标解读
- Main:主线程活动火焰图
- FPS:帧率图表
- CPU:CPU使用率
- Network:网络请求瀑布图
内存分析
-
堆内存快照
- 打开 Memory 面板
- 选择 "Heap snapshot"
- 点击 "Take snapshot"
- 分析内存分配情况
-
内存分配时间线
- 选择 "Allocation instrumentation on timeline"
- 开始录制并执行操作
- 查看内存分配的时间分布
内存泄漏排查
常见内存泄漏模式
- 意外的全局变量
function leak() {
leakedVar = 'This leaks to global scope'; // 缺少 var/let/const
}
- 闭包持有引用
function createClosure() {
const largeData = new Array(1000000).fill('data');
return function() {
// 持有 largeData 引用
console.log('Closure created');
};
}
- DOM 引用未清理
const elements = [];
function addElement() {
const div = document.createElement('div');
document.body.appendChild(div);
elements.push(div); // 即使从DOM移除,数组仍持有引用
}
- 定时器未清除
function startTimer() {
setInterval(() => {
// 即使不再需要,定时器仍会保持回调引用
}, 1000);
}
内存泄漏诊断工具
-
Chrome DevTools Memory 面板
- 比较多个堆快照
- 查看对象保留树(Retaining Tree)
-
Node.js 内存分析
node --inspect your-script.js
然后使用 Chrome DevTools 连接分析
- process.memoryUsage()
setInterval(() => {
const usage = process.memoryUsage();
console.log(`RSS: ${usage.rss}, Heap: ${usage.heapUsed}/${usage.heapTotal}`);
}, 1000);
性能优化实战
1. 函数优化
// 优化前:每次调用都创建新函数
function processItems(items) {
return items.map(item => {
return {
...item,
processed: expensiveCalculation(item.value)
};
});
}
// 优化后:预计算可缓存结果
const cache = new Map();
function processItemsOptimized(items) {
return items.map(item => {
if (!cache.has(item.value)) {
cache.set(item.value, expensiveCalculation(item.value));
}
return {
...item,
processed: cache.get(item.value)
};
});
}
2. DOM 操作优化
// 优化前:频繁单独操作DOM
function renderList(items) {
const container = document.getElementById('list');
container.innerHTML = '';
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item.name;
container.appendChild(div);
});
}
// 优化后:使用文档片段批量操作
function renderListOptimized(items) {
const container = document.getElementById('list');
const fragment = document.createDocumentFragment();
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item.name;
fragment.appendChild(div);
});
container.innerHTML = '';
container.appendChild(fragment);
}
3. 事件处理优化
// 优化前:为每个元素添加监听器
function addListeners() {
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('click', handleClick);
});
}
// 优化后:事件委托
function addListenersOptimized() {
document.getElementById('container').addEventListener('click', event => {
if (event.target.classList.contains('item')) {
handleClick(event);
}
});
}
Node.js 性能分析
使用 Clinic.js 工具套件
npm install -g clinic
clinic doctor -- node your-server.js
# 进行负载测试后生成报告
使用 v8-profiler
const profiler = require('v8-profiler-next');
const fs = require('fs');
// 开始CPU分析
profiler.startProfiling('CPU profile');
setTimeout(() => {
const profile = profiler.stopProfiling('CPU profile');
profile.export()
.pipe(fs.createWriteStream('cpuprofile.cpuprofile'))
.on('finish', () => profile.delete());
}, 5000);
内存泄漏排查实战
案例1:缓存无限增长
// 问题代码
const cache = new Map();
function getData(key) {
if (!cache.has(key)) {
const data = fetchData(key); // 假设是异步获取数据
cache.set(key, data);
}
return cache.get(key);
}
// 解决方案:添加缓存清理策略
function createLRUCache(maxSize) {
const cache = new Map();
return {
get(key) {
if (cache.has(key)) {
const value = cache.get(key);
cache.delete(key);
cache.set(key, value);
return value;
}
return null;
},
set(key, value) {
if (cache.size >= maxSize) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(key, value);
}
};
}
案例2:未清理的订阅
// 问题代码
class EventEmitter {
constructor() {
this.listeners = [];
}
subscribe(fn) {
this.listeners.push(fn);
}
emit(data) {
this.listeners.forEach(fn => fn(data));
}
}
// 解决方案:提供取消订阅方法
class SafeEventEmitter {
constructor() {
this.listeners = new Set();
}
subscribe(fn) {
this.listeners.add(fn);
return () => this.listeners.delete(fn);
}
emit(data) {
this.listeners.forEach(fn => fn(data));
}
}
性能监控与告警
浏览器 Performance Observer API
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.duration);
}
});
// 观察长任务(>50ms的任务)
observer.observe({ entryTypes: ['longtask'] });
// 观察布局偏移(CLS)
observer.observe({ type: 'layout-shift', buffered: true });
Node.js 性能监控
const { performance, PerformanceObserver } = require('perf_hooks');
const obs = new PerformanceObserver((items) => {
const entry = items.getEntries()[0];
console.log(`HTTP ${entry.name} took ${entry.duration}ms`);
performance.clearMarks();
});
obs.observe({ entryTypes: ['measure'] });
// 监控HTTP请求耗时
app.use((req, res, next) => {
performance.mark('start');
res.on('finish', () => {
performance.mark('end');
performance.measure(`${req.method} ${req.url}`, 'start', 'end');
});
next();
});
最佳实践总结
-
性能分析
- 先测量,再优化
- 关注关键路径而非微观优化
- 使用合适的工具进行多维度分析
-
内存泄漏预防
- 避免不必要的全局变量
- 及时清理事件监听器和定时器
- 注意闭包中的大对象引用
- 使用弱引用(WeakMap/WeakSet)管理缓存
-
持续监控
- 在生产环境实施性能监控
- 设置合理的性能预算
- 定期进行负载测试和内存分析
-
优化策略
- 减少主线程阻塞操作
- 合理使用缓存
- 批量DOM操作
- 使用Web Worker处理CPU密集型任务
通过系统性地应用这些技术和工具,可以有效地诊断和解决JavaScript应用中的性能问题和内存泄漏,确保应用的稳定性和响应速度。
#前端开发
分享于 2025-03-25