Skip to content

性能优化

一、资源加载优化

1. 图片优化

图片格式选择:

  • WebP:现代浏览器支持,压缩率高
  • AVIF:最新格式,压缩率最高
  • PNG:透明背景
  • JPEG:照片
  • SVG:矢量图,图标

图片优化策略:

html
<!-- 响应式图片 -->
<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.avif" type="image/avif">
  <img src="image.jpg" alt="图片">
</picture>

<!-- 懒加载 -->
<img src="image.jpg" loading="lazy" alt="图片">

<!-- 压缩图片 -->
<!-- 使用工具:TinyPNG、ImageOptim、Squoosh -->

图片懒加载:

javascript
// Intersection Observer API
const images = document.querySelectorAll('img[data-src]');

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
      observer.unobserve(img);
    }
  });
});

images.forEach(img => imageObserver.observe(img));

2. 脚本优化

代码分割:

javascript
// 动态导入
const module = await import('./module.js');

// React 懒加载
const LazyComponent = React.lazy(() => import('./LazyComponent'));

// Vue 懒加载
const AsyncComponent = () => import('./AsyncComponent.vue');

Tree Shaking:

  • 移除未使用的代码
  • 使用 ES6 模块
  • 配置构建工具(Webpack、Rollup)

压缩和混淆:

  • 使用 UglifyJS、Terser
  • 启用 Gzip/Brotli 压缩

异步加载脚本:

html
<!-- defer:DOM 解析完成后执行 -->
<script src="app.js" defer></script>

<!-- async:下载完成后立即执行(不阻塞) -->
<script src="analytics.js" async></script>

<!-- 动态加载 -->
<script>
  const script = document.createElement('script');
  script.src = 'app.js';
  document.body.appendChild(script);
</script>

3. CSS 优化

关键 CSS 内联:

html
<style>
  /* 首屏关键样式 */
  .header { ... }
  .hero { ... }
</style>
<link rel="stylesheet" href="non-critical.css">

CSS 优化策略:

  • 避免使用 @import(阻塞渲染)
  • 压缩 CSS
  • 移除未使用的 CSS
  • 使用 CSS-in-JS 按需加载

CSS 加载优化:

html
<!-- 预加载 -->
<link rel="preload" href="styles.css" as="style">

<!-- 媒体查询 -->
<link rel="stylesheet" href="print.css" media="print">

4. 字体优化

字体加载策略:

html
<!-- 字体预加载 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

<!-- 字体显示策略 -->
@font-face {
  font-family: 'MyFont';
  src: url('font.woff2') format('woff2');
  font-display: swap;  /* swap | block | fallback | optional */
}

<!-- 字体子集化 -->
<!-- 只包含需要的字符 -->

二、渲染优化

1. 减少重排和重绘

批量 DOM 操作:

javascript
// 不推荐:多次重排
element.style.left = '10px';
element.style.top = '10px';
element.style.width = '100px';

// 推荐:一次重排
element.style.cssText = 'left: 10px; top: 10px; width: 100px;';

// 或使用 class
element.className = 'new-style';

// 或使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  fragment.appendChild(li);
}
ul.appendChild(fragment);

使用 transform 代替位置属性:

javascript
// 不推荐:触发重排
element.style.left = '100px';
element.style.top = '100px';

// 推荐:使用 transform(GPU 加速)
element.style.transform = 'translate(100px, 100px)';

避免频繁读取布局属性:

javascript
// 不推荐:强制同步布局
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = elements[i].offsetWidth + 'px';
}

// 推荐:先读取,再写入
const widths = elements.map(el => el.offsetWidth);
elements.forEach((el, i) => {
  el.style.width = widths[i] + 'px';
});

2. 虚拟滚动

长列表优化:

javascript
// 虚拟滚动实现(简化版)
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();
    this.container.addEventListener('scroll', this.handleScroll.bind(this));
  }
  
  render() {
    const endIndex = Math.min(
      this.startIndex + this.visibleCount + 1,
      this.items.length
    );
    
    const visibleItems = this.items.slice(this.startIndex, endIndex);
    
    this.container.innerHTML = visibleItems
      .map((item, index) => `<div>${item}</div>`)
      .join('');
    
    this.container.style.paddingTop = this.startIndex * this.itemHeight + 'px';
  }
  
  handleScroll() {
    const scrollTop = this.container.scrollTop;
    const newStartIndex = Math.floor(scrollTop / this.itemHeight);
    
    if (newStartIndex !== this.startIndex) {
      this.startIndex = newStartIndex;
      this.render();
    }
  }
}

3. 防抖和节流

防抖(Debounce):

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

// 使用
const handleSearch = debounce((query) => {
  search(query);
}, 300);

input.addEventListener('input', (e) => {
  handleSearch(e.target.value);
});

节流(Throttle):

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

// 使用
const handleScroll = throttle(() => {
  updateScrollPosition();
}, 100);

window.addEventListener('scroll', handleScroll);

4. 使用 requestAnimationFrame

优化动画:

javascript
function animate() {
  // 动画逻辑
  element.style.transform = `translateX(${x}px)`;
  
  x += 1;
  if (x < 1000) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

批量 DOM 更新:

javascript
function batchUpdate() {
  // 在下一帧执行,避免频繁重排
  requestAnimationFrame(() => {
    element1.style.width = '100px';
    element2.style.height = '200px';
    element3.style.left = '50px';
  });
}

5. 使用 will-change

提示浏览器优化:

css
.element {
  will-change: transform;
  transition: transform 0.3s;
}

.element:hover {
  transform: translateX(100px);
}

注意事项:

  • 不要过度使用
  • 使用后记得移除
  • 只对需要动画的元素使用
javascript
element.style.willChange = 'transform';
element.addEventListener('transitionend', () => {
  element.style.willChange = 'auto';
});

三、网络优化

1. 缓存策略

浏览器缓存:

http
# 强缓存
Cache-Control: max-age=3600
Expires: Wed, 21 Oct 2025 07:28:00 GMT

# 协商缓存
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

Service Worker 缓存:

javascript
// 注册 Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

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

2. HTTP/2

HTTP/2 优势:

  • 多路复用:一个连接处理多个请求
  • 服务器推送:主动推送资源
  • 头部压缩:减少头部大小
  • 二进制分帧:更高效

3. CDN

使用 CDN 加速:

  • 减少延迟
  • 减轻服务器压力
  • 提高可用性
html
<!-- 使用 CDN -->
<script src="https://cdn.example.com/library.js"></script>
<link rel="stylesheet" href="https://cdn.example.com/style.css">

4. 预加载和预渲染

预加载关键资源:

html
<!-- 预加载 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="critical.js" as="script">
<link rel="preload" href="critical.css" as="style">

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

<!-- 预连接 -->
<link rel="preconnect" href="https://api.example.com">

<!-- 预渲染 -->
<link rel="prerender" href="https://example.com/page">

5. 数据压缩

Gzip / Brotli:

nginx
# Nginx 配置
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_comp_level 6;

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

四、JavaScript 优化

1. 避免内存泄漏

及时清理:

javascript
// 清理定时器
const timer = setInterval(() => {}, 1000);
clearInterval(timer);

// 移除事件监听器
element.addEventListener('click', handleClick);
element.removeEventListener('click', handleClick);

// 清理 DOM 引用
const element = document.getElementById('myElement');
// 使用完后
element = null;

// 清理闭包
function outer() {
  const data = new Array(1000000);
  return function inner() {
    // 使用 data
  };
}

// 使用完后
const inner = outer();
inner = null;

2. 使用 Web Workers

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

javascript
// main.js
const worker = new Worker('worker.js');

worker.postMessage({ data: largeArray });

worker.onmessage = (e) => {
  console.log('结果:', e.data);
};

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

3. 使用 requestIdleCallback

处理非关键任务:

javascript
function processQueue() {
  // 处理队列
}

requestIdleCallback((deadline) => {
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    processQueue();
  }
  
  if (tasks.length > 0) {
    requestIdleCallback(processQueue);
  }
});

4. 代码优化

减少函数调用:

javascript
// 不推荐:多次访问对象属性
for (let i = 0; i < array.length; i++) {
  if (array[i].value > threshold) {
    // ...
  }
}

// 推荐:缓存对象属性
const length = array.length;
for (let i = 0; i < length; i++) {
  const item = array[i];
  if (item.value > threshold) {
    // ...
  }
}

使用合适的算法和数据结构:

javascript
// 不推荐:O(n²) 复杂度
function findDuplicates(array) {
  const duplicates = [];
  for (let i = 0; i < array.length; i++) {
    for (let j = i + 1; j < array.length; j++) {
      if (array[i] === array[j]) {
        duplicates.push(array[i]);
      }
    }
  }
  return duplicates;
}

// 推荐:O(n) 复杂度
function findDuplicates(array) {
  const seen = new Set();
  const duplicates = [];
  for (const item of array) {
    if (seen.has(item)) {
      duplicates.push(item);
    } else {
      seen.add(item);
    }
  }
  return duplicates;
}

五、监控和调试

1. 性能监控

Web Vitals:

javascript
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);

Performance API:

javascript
// 测量执行时间
performance.mark('start');
// 执行代码
performance.mark('end');
performance.measure('duration', 'start', 'end');

const measure = performance.getEntriesByName('duration')[0];
console.log('执行时间:', measure.duration);

Chrome DevTools:

  • Performance 面板:分析性能瓶颈
  • Memory 面板:分析内存使用
  • Network 面板:分析网络请求

2. 性能指标

关键指标:

  • FCP(First Contentful Paint):首次内容绘制
  • LCP(Largest Contentful Paint):最大内容绘制
  • FID(First Input Delay):首次输入延迟
  • CLS(Cumulative Layout Shift):累积布局偏移
  • TTFB(Time to First Byte):首字节时间

六、常见面试题

1. 如何优化首屏加载时间?

  • 代码分割和懒加载
  • 内联关键 CSS
  • 图片优化和懒加载
  • 使用 CDN
  • 启用 Gzip/Brotli
  • 预加载关键资源
  • 减少 JavaScript 执行时间

2. 如何减少重排和重绘?

  • 批量 DOM 操作
  • 使用 transform 代替位置属性
  • 避免频繁读取布局属性
  • 使用 DocumentFragment
  • 使用 visibility 代替 display: none

3. 如何优化长列表性能?

  • 虚拟滚动
  • 分页加载
  • 使用 shouldComponentUpdate(React)
  • 使用 Object.freeze 冻结数据
  • 避免在 render 中创建函数

4. 防抖和节流的区别?

  • 防抖:事件触发后等待 n 秒再执行,期间再次触发则重新计时
  • 节流:固定时间间隔内只执行一次

5. 如何监控页面性能?

  • 使用 Performance API
  • 使用 Web Vitals
  • 使用 Chrome DevTools
  • 使用第三方监控工具(如 Sentry)

基于 VitePress 构建 | Copyright © 2026-present