JavaScript 性能优化实战技巧

JavaScript 性能优化实战技巧

性能优化是前端开发中的重要话题。本文将分享一些实用的 JavaScript 性能优化技巧,帮助你构建更快、更流畅的 Web 应用。

🎯 性能优化的重要性

为什么性能很重要?

  • 📱 用户体验: 快速响应提升用户满意度
  • 💰 商业价值: 性能直接影响转化率和收入
  • 🔍 SEO 排名: 搜索引擎偏爱快速加载的网站
  • 🌍 可访问性: 在低端设备和慢网络下也能正常使用

性能指标

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

🚀 代码层面优化

1. 避免不必要的重复计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ❌ 不好的做法
function processItems(items) {
  for (let i = 0; i < items.length; i++) {
    // 每次循环都会计算 items.length
    processItem(items[i]);
  }
}

// ✅ 优化后
function processItems(items) {
  const length = items.length; // 缓存长度
  for (let i = 0; i < length; i++) {
    processItem(items[i]);
  }
}

// ✅ 更好的做法
function processItems(items) {
  items.forEach(processItem); // 使用原生方法
}

2. 使用适当的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ❌ 使用数组进行频繁查找
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  // ... 更多用户
];

function findUser(id) {
  return users.find(user => user.id === id); // O(n) 时间复杂度
}

// ✅ 使用 Map 进行快速查找
const usersMap = new Map([
  [1, { id: 1, name: 'Alice' }],
  [2, { id: 2, name: 'Bob' }],
  // ... 更多用户
]);

function findUser(id) {
  return usersMap.get(id); // O(1) 时间复杂度
}

3. 函数防抖和节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 防抖:延迟执行,如果在延迟期间再次触发,则重新计时
function debounce(func, delay) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

// 节流:限制执行频率
function throttle(func, limit) {
  let inThrottle;
  return function (...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// 使用示例
const searchInput = document.getElementById('search');
const debouncedSearch = debounce(performSearch, 300);
const throttledScroll = throttle(handleScroll, 100);

searchInput.addEventListener('input', debouncedSearch);
window.addEventListener('scroll', throttledScroll);

🎨 DOM 操作优化

1. 批量 DOM 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ❌ 多次 DOM 操作
function addItems(items) {
  const container = document.getElementById('container');
  items.forEach(item => {
    const div = document.createElement('div');
    div.textContent = item.text;
    container.appendChild(div); // 每次都触发重排
  });
}

// ✅ 使用 DocumentFragment
function addItems(items) {
  const container = document.getElementById('container');
  const fragment = document.createDocumentFragment();
  
  items.forEach(item => {
    const div = document.createElement('div');
    div.textContent = item.text;
    fragment.appendChild(div);
  });
  
  container.appendChild(fragment); // 只触发一次重排
}

// ✅ 使用 innerHTML (适用于简单内容)
function addItems(items) {
  const container = document.getElementById('container');
  const html = items.map(item => `<div>${item.text}</div>`).join('');
  container.innerHTML = html;
}

2. 虚拟滚动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class VirtualScroller {
  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.bindEvents();
  }
  
  render() {
    const endIndex = Math.min(
      this.startIndex + this.visibleCount,
      this.items.length
    );
    
    const visibleItems = this.items.slice(this.startIndex, endIndex);
    
    this.container.innerHTML = visibleItems
      .map((item, index) => `
        <div style="
          position: absolute;
          top: ${(this.startIndex + index) * this.itemHeight}px;
          height: ${this.itemHeight}px;
        ">
          ${item.content}
        </div>
      `)
      .join('');
      
    // 设置容器总高度
    this.container.style.height = `${this.items.length * this.itemHeight}px`;
  }
  
  bindEvents() {
    this.container.addEventListener('scroll', () => {
      const newStartIndex = Math.floor(this.container.scrollTop / this.itemHeight);
      if (newStartIndex !== this.startIndex) {
        this.startIndex = newStartIndex;
        this.render();
      }
    });
  }
}

⚡ 异步操作优化

1. 使用 Web Workers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// main.js
function processLargeDataset(data) {
  return new Promise((resolve, reject) => {
    const worker = new Worker('worker.js');
    
    worker.postMessage(data);
    
    worker.onmessage = (e) => {
      resolve(e.data);
      worker.terminate();
    };
    
    worker.onerror = (error) => {
      reject(error);
      worker.terminate();
    };
  });
}

// worker.js
self.onmessage = function(e) {
  const data = e.data;
  
  // 执行耗时计算
  const result = heavyComputation(data);
  
  self.postMessage(result);
};

function heavyComputation(data) {
  // 复杂的数据处理逻辑
  return data.map(item => ({
    ...item,
    processed: true,
    timestamp: Date.now()
  }));
}

2. 请求优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 请求缓存
class RequestCache {
  constructor() {
    this.cache = new Map();
  }
  
  async get(url, options = {}) {
    const cacheKey = `${url}${JSON.stringify(options)}`;
    
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    
    const promise = fetch(url, options).then(res => res.json());
    this.cache.set(cacheKey, promise);
    
    try {
      const result = await promise;
      return result;
    } catch (error) {
      this.cache.delete(cacheKey); // 失败时清除缓存
      throw error;
    }
  }
}

// 请求合并
class RequestBatcher {
  constructor() {
    this.pending = new Map();
  }
  
  async batchRequest(ids) {
    const cacheKey = ids.sort().join(',');
    
    if (this.pending.has(cacheKey)) {
      return this.pending.get(cacheKey);
    }
    
    const promise = this.fetchMultiple(ids);
    this.pending.set(cacheKey, promise);
    
    try {
      const result = await promise;
      return result;
    } finally {
      this.pending.delete(cacheKey);
    }
  }
  
  async fetchMultiple(ids) {
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ids })
    });
    return response.json();
  }
}

🧠 内存管理

1. 避免内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// ❌ 可能导致内存泄漏
class Component {
  constructor() {
    this.data = new Array(1000000).fill(0);
    
    // 忘记清理事件监听器
    window.addEventListener('resize', this.handleResize);
    
    // 忘记清理定时器
    this.timer = setInterval(this.update, 1000);
  }
  
  handleResize() {
    // 处理窗口大小变化
  }
  
  update() {
    // 更新组件
  }
}

// ✅ 正确的内存管理
class Component {
  constructor() {
    this.data = new Array(1000000).fill(0);
    
    // 绑定 this 并保存引用以便清理
    this.handleResize = this.handleResize.bind(this);
    window.addEventListener('resize', this.handleResize);
    
    this.timer = setInterval(() => this.update(), 1000);
  }
  
  destroy() {
    // 清理事件监听器
    window.removeEventListener('resize', this.handleResize);
    
    // 清理定时器
    clearInterval(this.timer);
    
    // 清理大对象
    this.data = null;
  }
  
  handleResize() {
    // 处理窗口大小变化
  }
  
  update() {
    // 更新组件
  }
}

2. 使用 WeakMap 和 WeakSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 使用 WeakMap 存储私有数据
const privateData = new WeakMap();

class User {
  constructor(name, email) {
    privateData.set(this, { name, email });
  }
  
  getName() {
    return privateData.get(this).name;
  }
  
  getEmail() {
    return privateData.get(this).email;
  }
}

// 使用 WeakSet 跟踪对象状态
const processedItems = new WeakSet();

function processItem(item) {
  if (processedItems.has(item)) {
    return; // 已处理过,跳过
  }
  
  // 处理逻辑
  doSomethingWith(item);
  
  processedItems.add(item);
}

📊 性能监控

1. Performance API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 测量函数执行时间
function measurePerformance(name, fn) {
  return function (...args) {
    const start = performance.now();
    const result = fn.apply(this, args);
    const end = performance.now();
    
    console.log(`${name} 执行时间: ${end - start}ms`);
    return result;
  };
}

// 使用示例
const optimizedFunction = measurePerformance('数据处理', processData);

// 监控页面性能
function monitorPagePerformance() {
  window.addEventListener('load', () => {
    setTimeout(() => {
      const navigation = performance.getEntriesByType('navigation')[0];
      const paint = performance.getEntriesByType('paint');
      
      console.log('页面加载时间:', navigation.loadEventEnd - navigation.fetchStart);
      console.log('DOM 解析时间:', navigation.domContentLoadedEventEnd - navigation.domLoading);
      
      paint.forEach(entry => {
        console.log(`${entry.name}:`, entry.startTime);
      });
    }, 0);
  });
}

2. 自定义性能监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class PerformanceMonitor {
  constructor() {
    this.metrics = new Map();
  }
  
  startTimer(name) {
    this.metrics.set(name, performance.now());
  }
  
  endTimer(name) {
    const startTime = this.metrics.get(name);
    if (startTime) {
      const duration = performance.now() - startTime;
      console.log(`${name}: ${duration.toFixed(2)}ms`);
      this.metrics.delete(name);
      return duration;
    }
  }
  
  measureAsync(name, asyncFn) {
    return async (...args) => {
      this.startTimer(name);
      try {
        const result = await asyncFn(...args);
        return result;
      } finally {
        this.endTimer(name);
      }
    };
  }
}

// 使用示例
const monitor = new PerformanceMonitor();

const fetchData = monitor.measureAsync('API请求', async (url) => {
  const response = await fetch(url);
  return response.json();
});

🎯 实际应用案例

大列表渲染优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class OptimizedList {
  constructor(container, data) {
    this.container = container;
    this.data = data;
    this.itemHeight = 50;
    this.visibleCount = Math.ceil(container.clientHeight / this.itemHeight);
    this.buffer = 5; // 缓冲区
    
    this.init();
  }
  
  init() {
    this.createScrollContainer();
    this.bindEvents();
    this.render();
  }
  
  createScrollContainer() {
    this.scrollContainer = document.createElement('div');
    this.scrollContainer.style.height = `${this.data.length * this.itemHeight}px`;
    this.scrollContainer.style.position = 'relative';
    this.container.appendChild(this.scrollContainer);
  }
  
  render() {
    const scrollTop = this.container.scrollTop;
    const startIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.buffer);
    const endIndex = Math.min(
      this.data.length,
      startIndex + this.visibleCount + this.buffer * 2
    );
    
    // 清空现有内容
    this.scrollContainer.innerHTML = '';
    
    // 渲染可见项目
    for (let i = startIndex; i < endIndex; i++) {
      const item = this.createItem(this.data[i], i);
      this.scrollContainer.appendChild(item);
    }
  }
  
  createItem(data, index) {
    const item = document.createElement('div');
    item.style.position = 'absolute';
    item.style.top = `${index * this.itemHeight}px`;
    item.style.height = `${this.itemHeight}px`;
    item.style.width = '100%';
    item.textContent = data.name;
    return item;
  }
  
  bindEvents() {
    let ticking = false;
    
    this.container.addEventListener('scroll', () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          this.render();
          ticking = false;
        });
        ticking = true;
      }
    });
  }
}

📝 性能优化清单

代码优化

  • ✅ 避免不必要的重复计算
  • ✅ 使用适当的数据结构
  • ✅ 实现防抖和节流
  • ✅ 优化循环和条件判断

DOM 优化

  • ✅ 批量 DOM 操作
  • ✅ 使用 DocumentFragment
  • ✅ 实现虚拟滚动
  • ✅ 避免强制同步布局

异步优化

  • ✅ 使用 Web Workers 处理重计算
  • ✅ 实现请求缓存和合并
  • ✅ 优化 Promise 链
  • ✅ 合理使用 async/await

内存管理

  • ✅ 及时清理事件监听器
  • ✅ 清理定时器和动画
  • ✅ 使用 WeakMap/WeakSet
  • ✅ 避免闭包陷阱

🚀 总结

JavaScript 性能优化是一个持续的过程,需要:

  1. 测量优先: 先测量再优化,避免过早优化
  2. 渐进改进: 逐步优化,不要一次性重写
  3. 监控反馈: 持续监控性能指标
  4. 用户体验: 始终以用户体验为中心

记住:过早的优化是万恶之源,但合理的性能优化能显著提升用户体验!


下一篇预告: 《Node.js 服务端性能优化实战》

有任何问题欢迎在评论区讨论!⚡