网页卡顿排查

使用 Chrome Performance 和 Memory 面板定位性能问题

问题

网页出现卡顿,如何使用 Chrome DevTools 定位问题原因?

解答

1. Performance 面板排查

打开 DevTools → Performance → 点击录制 → 操作页面 → 停止录制

// 模拟长任务导致的卡顿
function heavyTask() {
  const start = Date.now();
  // 同步阻塞主线程 500ms
  while (Date.now() - start < 500) {
    Math.random();
  }
}

// 点击时触发卡顿
button.addEventListener('click', heavyTask);

Performance 面板关注点:

  • Main 线程火焰图:红色三角标记表示长任务(>50ms)
  • FPS 行:绿色条越低表示帧率越差
  • Frames 行:红色帧表示掉帧
  • Summary 面板:查看 Scripting/Rendering/Painting 耗时占比

优化长任务:

// 使用 requestIdleCallback 拆分任务
function splitTask(tasks) {
  function runTask(deadline) {
    while (tasks.length > 0 && deadline.timeRemaining() > 0) {
      const task = tasks.shift();
      task();
    }
    if (tasks.length > 0) {
      requestIdleCallback(runTask);
    }
  }
  requestIdleCallback(runTask);
}

// 或使用 setTimeout 分片
async function processInChunks(items, chunkSize = 100) {
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize);
    chunk.forEach(process);
    // 让出主线程
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

2. Memory 面板排查

用于检测内存泄漏导致的卡顿。

// 常见内存泄漏:未清理的事件监听
class Component {
  constructor() {
    this.data = new Array(10000).fill('leak');
    // 问题:组件销毁后监听器仍存在
    window.addEventListener('lypu7', this.onResize);
  }
  
  onResize = () => {
    console.log(this.data.length);
  };
  
  // 修复:提供销毁方法
  destroy() {
    window.removeEventListener('lypu7', this.onResize);
    this.data = null;
  }
}

Memory 面板操作步骤:

  1. Heap snapshot:拍摄内存快照

    • 操作前拍一次,操作后拍一次
    • 对比两次快照,查看 Delta 列增长的对象
  2. Allocation instrumentation:追踪内存分配

    • 蓝色条表示新分配且未释放的内存
    • 点击蓝色条查看具体对象和调用栈
// 常见内存泄漏:闭包引用
function createLeak() {
  const largeData = new Array(100000).fill('x');
  
  // 问题:返回的函数持有 largeData 引用
  return function() {
    console.log(largeData.length);
  };
}

const leaks = [];
setInterval(() => {
  leaks.push(createLeak()); // 不断累积
}, 1000);

3. 快速排查流程

// 1. 检测长任务
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 50) {
      console.warn('Long Task:', entry.duration, 'ms');
    }
  }
});
observer.observe({ entryTypes: ['longtask'] });

// 2. 监控帧率
let lastTime = performance.now();
let frames = 0;

function checkFPS() {
  frames++;
  const now = performance.now();
  if (now - lastTime >= 1000) {
    console.log('FPS:', frames);
    frames = 0;
    lastTime = now;
  }
  requestAnimationFrame(checkFPS);
}
checkFPS();

// 3. 检测内存增长
setInterval(() => {
  if (performance.memory) {
    const used = performance.memory.usedJSHeapSize / 1024 / 1024;
    console.log('Memory:', used.toFixed(2), 'MB');
  }
}, 5000);

关键点

  • Performance 面板:看 Main 线程火焰图,红色三角是长任务,需要拆分或异步化
  • Memory 面板:对比多次 Heap snapshot,关注 Delta 增长的对象
  • 常见卡顿原因:同步长任务、频繁重排重绘、内存泄漏、大量 DOM 操作
  • 长任务优化:用 requestIdleCallbacksetTimeout 或 Web Worker 拆分
  • 内存泄漏排查:检查未清理的事件监听、定时器、闭包引用、脱离 DOM 的节点