BemoDB 2.0

Back

性能优化的整体框架#

性能优化不是孤立的技巧堆砌,而是一个系统化的工程。我们需要先建立完整的认知框架:

优化的三个层次:

  1. 度量层:如何衡量性能(Core Web Vitals、性能指标)
  2. 分析层:如何定位问题(DevTools、Performance API)
  3. 优化层:如何解决问题(渲染优化、资源优化、代码优化)

本文将按照”先理解原理 → 再学会度量 → 最后掌握优化”的顺序展开。

一、浏览器渲染原理#

1.1 关键渲染路径#

理解浏览器如何渲染页面是优化的基础:

HTML → DOM Tree
CSS  → CSSOM Tree

  Render Tree → Layout → Paint → Composite

JavaScript
plaintext

完整的渲染流程:

JavaScript → Style → Layout (布局) → Paint (绘制) → Composite (合成)
plaintext

性能关键点:

  • Layout(布局):计算元素的几何属性(位置、尺寸),开销大
  • Paint(绘制):填充像素,将元素绘制成位图,开销中等
  • Composite(合成):将多个图层合成最终图像,开销小,可由 GPU 加速

1.2 回流与重绘#

回流 (Reflow):元素的几何属性变化,触发 Layout → Paint → Composite,开销最大。

触发回流的操作:

  • 修改 widthheightpaddingmarginborder
  • 修改 positiontopleft
  • 添加/删除 DOM 节点
  • 修改 font-sizefont-family
  • 窗口 resize
  • 读取布局属性:offsetWidthscrollTopclientHeightgetComputedStyle()

重绘 (Repaint):元素的视觉样式变化但几何属性不变,跳过 Layout,只触发 Paint → Composite,开销较小。

触发重绘的操作:

  • 修改 colorbackground-colorvisibilitybox-shadow

优化原则: 避免回流 > 减少重绘 > 只触发合成

1.3 图层与合成#

浏览器将页面分成多个图层(Layer),类似 Photoshop 的图层概念。每个图层独立绘制,最后由 GPU 合成。

触发图层提升的条件:

  • 3D 变换:transform: translate3d(), rotate3d()
  • will-change 属性
  • <video><canvas><iframe> 元素
  • position: fixed
  • opacitytransform 动画
  • filter 属性

transform 和 opacity 的性能秘密:

当元素在独立图层上使用 transformopacity 时,渲染路径缩短为:

JavaScript → Style → Composite
plaintext

完全跳过 Layout 和 Paint,由 GPU 直接处理图层变换,轻松达到 60fps。

二、性能度量体系#

2.1 Core Web Vitals(核心 Web 指标)#

Google 提出的三个关键用户体验指标:

LCP (Largest Contentful Paint) - 最大内容绘制

  • 含义:视口内最大内容元素的渲染时间
  • 目标:≤ 2.5 秒
  • 优化方向:优化资源加载、减少渲染阻塞

INP (Interaction to Next Paint) - 交互响应

  • 含义:用户交互到下一帧绘制的时间
  • 目标:≤ 200 毫秒
  • 优化方向:拆分长任务、优化 JavaScript 执行

CLS (Cumulative Layout Shift) - 累积布局偏移

  • 含义:页面生命周期内所有意外布局偏移的累积分数
  • 目标:≤ 0.1
  • 优化方向:为图片/视频设置尺寸、避免动态插入内容

2.2 其他重要指标#

FCP (First Contentful Paint):首次内容绘制,目标 ≤ 1.8 秒

TTI (Time to Interactive):页面完全可交互时间,目标 ≤ 3.8 秒

TBT (Total Blocking Time):总阻塞时间,目标 ≤ 200 毫秒

TTFB (Time to First Byte):首字节时间,目标 ≤ 800 毫秒

2.3 性能分析工具#

Lighthouse - 自动化审计

快速生成性能报告,适合发现问题:

  1. 打开 DevTools → Lighthouse 面板
  2. 选择 Performance + Mobile
  3. 点击 Analyze page load

报告包含:性能总分、核心指标、优化建议(Opportunities)

Performance 面板 - 深入分析

记录页面运行时的详细信息,适合定位问题:

  1. 打开 Performance 面板
  2. 点击 Record 或 Start profiling and reload page
  3. 执行操作后点击 Stop

分析火焰图:

  • 红色长条:长任务(>50ms),导致卡顿
  • 黄色:JavaScript 执行
  • 紫色:Layout 和 Rendering
  • 绿色:Paint
  • Timings 泳道:查看 LCP 标记
  • Experience 泳道:查看 Layout Shift

Performance API - 真实用户监控

// 获取导航时间
const navTiming = performance.getEntriesByType('navigation')[0];
console.log('TTFB:', navTiming.responseStart - navTiming.requestStart);

// 自定义性能标记
performance.mark('task-start');
// 执行任务
performance.mark('task-end');
performance.measure('task-duration', 'task-start', 'task-end');

// 监听 LCP
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('LCP:', entry.renderTime || entry.loadTime);
  }
});
observer.observe({ entryTypes: ['largest-contentful-paint'] });
javascript

三、渲染性能优化#

3.1 动画优化:优先使用 transform 和 opacity#

最佳实践:

/* ✓ 推荐:只触发合成 */
.element {
  transform: translateX(100px);
  opacity: 0.5;
}

/* ✗ 避免:触发回流和重绘 */
.element {
  left: 100px;
  width: 200px;
}
css

动画性能对比:

  • transform / opacity:60fps,GPU 加速
  • left / top / width / height:可能掉帧,CPU 密集

3.2 will-change:提前优化#

will-change 提示浏览器元素即将变化,提前创建图层。

.animating-element {
  will-change: transform, opacity;
}
css

使用原则:

  1. 按需使用:动画开始前添加,结束后移除
element.addEventListener('mouseenter', () => {
  element.style.willChange = 'transform';
});

element.addEventListener('animationend', () => {
  element.style.willChange = 'auto';
});
javascript
  1. 不要滥用:每个图层消耗内存,过多图层反而降低性能
  2. 使用 Layers 面板:检查图层是否合理

3.3 避免回流:批量操作与读写分离#

问题:布局抖动(Layout Thrashing)

// ✗ 错误:每次循环都强制回流
elements.forEach(el => {
  const height = el.offsetHeight; // 读:强制回流
  el.style.height = height + 10 + 'px'; // 写:标记需要回流
});
javascript

解决:读写分离

// ✓ 正确:先读后写
const heights = elements.map(el => el.offsetHeight);
elements.forEach((el, i) => {
  el.style.height = heights[i] + 10 + 'px';
});
javascript

批量 DOM 操作:使用 DocumentFragment

// ✗ 错误:每次 appendChild 都可能触发回流
for (let i = 0; i < 100; i++) {
  const el = document.createElement('p');
  container.appendChild(el);
}

// ✓ 正确:只触发一次回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const el = document.createElement('p');
  fragment.appendChild(el);
}
container.appendChild(fragment);
javascript

批量样式修改:使用 classList

// ✗ 避免:多次修改 style
element.style.width = '200px';
element.style.height = '200px';
element.style.border = '2px solid blue';

// ✓ 推荐:一次性切换 class
element.classList.add('large-box');
javascript

3.4 CSS Containment#

使用 contain 属性隔离元素,限制浏览器的样式、布局和绘制计算范围。

.widget {
  contain: layout style paint;
}
css

四、资源加载优化#

4.1 关键渲染路径优化#

内联关键 CSS

将首屏 CSS 内联到 HTML,避免阻塞渲染:

<head>
  <style>
    /* 关键 CSS */
    .header { background: #fff; height: 60px; }
  </style>
  <!-- 非关键 CSS 延迟加载 -->
  <link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
</head>
html

延迟非关键 JavaScript

<!-- 关键 JS:正常加载 -->
<script src="critical.js"></script>

<!-- 非关键 JS:延迟加载 -->
<script src="analytics.js" defer></script>
<script src="widgets.js" async></script>
html

4.2 预加载技术#

根据资源的重要性和使用时机选择合适的预加载策略:

<!-- preload:当前页面必需,高优先级 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

<!-- prefetch:未来页面可能需要,低优先级 -->
<link rel="prefetch" href="/next-page.js">

<!-- preconnect:提前建立连接(DNS + TCP + TLS) -->
<link rel="preconnect" href="https://cdn.example.com">

<!-- dns-prefetch:仅 DNS 预解析 -->
<link rel="dns-prefetch" href="https://api.example.com">

<!-- modulepreload:预加载 ES 模块 -->
<link rel="modulepreload" href="/app.js">
html

资源优先级调整:

<img src="hero.jpg" fetchpriority="high">
<script src="analytics.js" fetchpriority="low"></script>
html

4.3 图片优化#

现代图片格式

<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="描述" loading="lazy">
</picture>
html

响应式图片

<img
  srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
  sizes="(max-width: 600px) 480px, (max-width: 900px) 800px, 1200px"
  src="medium.jpg"
  alt="描述"
  loading="lazy">
html

优化策略:

  • 使用 WebP/AVIF 格式,减小 30-50% 体积
  • 使用 loading="lazy" 懒加载非首屏图片
  • 为图片设置明确的 widthheight,避免 CLS
  • 使用 CDN 进行图片优化和压缩

4.4 字体优化#

@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2');
  font-display: swap; /* 立即显示后备字体 */
}
css
<!-- 预加载关键字体 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
html

font-display 策略:

  • swap:立即显示后备字体,加载完成后切换(推荐)
  • optional:短时间内未加载完成则放弃
  • fallback:短暂阻塞后显示后备字体
  • block:阻塞渲染直到字体加载

字体子集化:只包含需要的字符,减小文件大小

4.5 缓存策略#

HTTP 缓存

# 静态资源:强缓存
Cache-Control: public, max-age=31536000, immutable

# HTML:协商缓存
Cache-Control: no-cache
ETag: "abc123"
plaintext

Service Worker 缓存

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

4.6 网络优化#

HTTP/2 优势:

  • 多路复用:单连接并行传输
  • 头部压缩:减少请求开销
  • 服务器推送:主动推送资源

资源压缩:

# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript;

# Brotli 压缩(更高效)
brotli on;
brotli_types text/plain text/css application/json application/javascript;
nginx

五、JavaScript 执行优化#

5.1 代码分割与懒加载#

动态导入

// 按需加载模块
button.addEventListener('click', async () => {
  const module = await import('./heavy-module.js');
  module.doSomething();
});
javascript

Tree Shaking

// package.json
{
  "sideEffects": false
}
json
// 使用 ES6 模块
import { specificFunction } from 'library'; // ✓
const library = require('library'); // ✗
javascript

5.2 长任务拆分#

任何执行时间超过 50ms 的任务都会阻塞主线程,导致页面无法响应。

使用 setTimeout 拆分

function processLargeArray(array) {
  let index = 0;
  const chunkSize = 100;

  function processChunk() {
    const end = Math.min(index + chunkSize, array.length);
    for (let i = index; i < end; i++) {
      processItem(array[i]);
    }
    index = end;

    if (index < array.length) {
      setTimeout(processChunk, 0);
    }
  }

  processChunk();
}
javascript

使用 requestIdleCallback

requestIdleCallback(deadline => {
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    const task = tasks.shift();
    task();
  }
});
javascript

5.3 Web Workers#

将计算密集型任务移到 Worker 线程:

// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeDataSet });
worker.onmessage = (e) => {
  console.log('结果:', e.data);
};

// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};
javascript

5.4 高频事件优化#

防抖(Debounce):延迟执行,重复触发则重新计时

function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

input.addEventListener('input', debounce(handleInput, 300));
javascript

节流(Throttle):固定间隔执行

function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

window.addEventListener('scroll', throttle(handleScroll, 100));
javascript

requestAnimationFrame:动画优化

function animate() {
  element.style.transform = `translateX(${x}px)`;
  if (shouldContinue) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);
javascript

5.5 虚拟滚动#

长列表优化的核心技术,只渲染可视区域:

class VirtualScroll {
  constructor(container, items, itemHeight) {
    this.container = container;
    this.items = items;
    this.itemHeight = itemHeight;
    this.visibleCount = Math.ceil(container.clientHeight / itemHeight);
    this.startIndex = 0;

    this.render();
    container.addEventListener('scroll', () => this.onScroll());
  }

  onScroll() {
    this.startIndex = Math.floor(this.container.scrollTop / this.itemHeight);
    this.render();
  }

  render() {
    const endIndex = this.startIndex + this.visibleCount;
    const visibleItems = this.items.slice(this.startIndex, endIndex);

    this.container.innerHTML = visibleItems.map((item, i) =>
      `<div style="position: absolute; top: ${(this.startIndex + i) * this.itemHeight}px">
        ${item}
      </div>`
    ).join('');

    this.container.style.height = this.items.length * this.itemHeight + 'px';
  }
}
javascript

六、内存优化#

6.1 常见内存泄漏场景#

// 1. 未清理的定时器
const timer = setInterval(() => {}, 1000);
clearInterval(timer); // 记得清理

// 2. 未解绑的事件监听器
element.addEventListener('click', handler);
element.removeEventListener('click', handler); // 记得解绑

// 3. 闭包引用大对象
function createClosure() {
  const largeData = new Array(1000000);
  return function() {
    console.log(largeData[0]); // largeData 无法被回收
  };
}

// 4. 分离的 DOM 引用
let detachedElement = document.getElementById('element');
element.remove();
detachedElement = null; // 手动释放引用
javascript

6.2 垃圾回收机制#

  • 标记清除:现代浏览器主要使用,标记可达对象,清除不可达对象
  • 引用计数:旧版 IE 使用,存在循环引用问题

七、移动端优化#

7.1 触摸事件优化#

// 使用 passive 优化滚动性能
element.addEventListener('touchstart', handler, { passive: true });
javascript

7.2 避免 300ms 点击延迟#

<meta name="viewport" content="width=device-width">
html
html {
  touch-action: manipulation;
}
css

八、构建优化#

8.1 Bundle 分析#

# Webpack
npm install --save-dev webpack-bundle-analyzer

# Vite
npm install --save-dev rollup-plugin-visualizer
bash

8.2 代码分割策略#

  • 路由级别分割:每个路由单独打包
  • 组件级别分割:大组件按需加载
  • 第三方库分割:vendor bundle 单独缓存

九、实战优化场景#

9.1 优化首屏加载#

  1. 内联关键 CSS,延迟非关键 CSS
  2. 使用 preload 预加载关键资源
  3. 图片使用 WebP/AVIF + 懒加载
  4. 代码分割,只加载首屏必需代码
  5. 启用 Gzip/Brotli 压缩
  6. 使用 CDN 加速静态资源
  7. 服务端渲染(SSR)或静态生成(SSG)

9.2 优化卡顿页面#

  1. 使用 Performance 面板定位长任务
  2. 拆分长任务(setTimeout / requestIdleCallback)
  3. 使用 Web Workers 处理计算密集任务
  4. 动画使用 transform 和 opacity
  5. 高频事件使用防抖/节流
  6. 长列表使用虚拟滚动

9.3 优化无限滚动列表#

  1. 实现虚拟滚动,只渲染可见元素
  2. 使用 IntersectionObserver 检测可见性
  3. 图片懒加载
  4. 使用 requestAnimationFrame 优化滚动
  5. 防抖滚动事件处理

9.4 优化大量 DOM 操作#

  1. 使用 DocumentFragment 批量操作
  2. 读写分离,避免布局抖动
  3. 使用 classList 而非直接修改 style
  4. 隐藏元素后操作,完成后再显示
  5. 考虑使用虚拟 DOM 库(React、Vue)

总结#

性能优化是一个系统工程,需要:

  1. 建立度量体系:关注 Core Web Vitals,使用 Lighthouse 和 Performance 面板
  2. 理解渲染原理:掌握回流、重绘、合成的区别,优先使用 transform 和 opacity
  3. 优化关键路径:减少阻塞资源,优化首屏加载
  4. 优化 JavaScript:拆分长任务,使用 Web Workers,代码分割
  5. 持续监控:使用 Performance API 收集真实用户数据

过早优化是万恶之源。先度量,找到瓶颈,再针对性优化。

浏览器性能优化完全指南
https://astro-pure.js.org/handbook/browser/性能优化
Author Bolaxious
Published at March 9, 2026
Comment seems to stuck. Try to refresh?✨