Skip to content
On this page

JavaScript 性能优化

JavaScript性能优化是构建高效Web应用的关键。通过优化代码,可以提升用户体验,减少资源消耗,提高应用响应速度。

性能分析工具

浏览器开发者工具

javascript
// 使用Performance API测量执行时间
console.time('operation');
// 执行操作
for (let i = 0; i < 1000000; i++) {
  // 某些操作
}
console.timeEnd('operation');

// 使用Performance API进行更精确的测量
const start = performance.now();
// 执行操作
const end = performance.now();
console.log(`操作耗时: ${end - start} 毫秒`);

使用console.profile进行性能分析

javascript
// 开始性能分析
console.profile('MyProfile');

// 执行需要分析的代码
function intensiveOperation() {
  // 一些密集操作
  const arr = [];
  for (let i = 0; i < 100000; i++) {
    arr.push(i * 2);
  }
  return arr;
}

intensiveOperation();

// 结束性能分析
console.profileEnd('MyProfile');

内存优化

避免内存泄漏

javascript
// 1. 及时清理事件监听器
class Component {
  constructor(element) {
    this.element = element;
    this.handleClick = this.handleClick.bind(this);
    this.element.addEventListener('click', this.handleClick);
  }
  
  handleClick() {
    console.log('Clicked!');
  }
  
  destroy() {
    // 移除事件监听器
    this.element.removeEventListener('click', this.handleClick);
    // 清理引用
    this.element = null;
    this.handleClick = null;
  }
}

// 2. 避免循环引用
// 不好的做法
const parent = {};
const child = { parent: parent };
parent.child = child; // 循环引用

// 好的做法
const parent = {};
const child = { parentId: parent.id }; // 使用ID引用而不是对象引用

// 3. 清理定时器
class Timer {
  constructor() {
    this.intervalId = setInterval(() => {
      console.log('tick');
    }, 1000);
  }
  
  stop() {
    clearInterval(this.intervalId);
    this.intervalId = null;
  }
}

优化数组操作

javascript
// 1. 避免在循环中创建数组
// 不好的做法
function badExample(items) {
  const results = [];
  for (let i = 0; i < items.length; i++) {
    results.push(items[i] * 2);
  }
  return results;
}

// 好的做法 - 使用map
function goodExample(items) {
  return items.map(item => item * 2);
}

// 2. 预分配数组大小(当大小已知时)
// 不好的做法
function createArray(n) {
  const arr = [];
  for (let i = 0; i < n; i++) {
    arr.push(null);
  }
  return arr;
}

// 好的做法
function createArray(n) {
  return new Array(n).fill(null);
}

// 3. 使用适当的数组方法
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 对于查找,使用find而不是filter(当只需要第一个匹配项时)
const firstEven = numbers.find(n => n % 2 === 0); // 返回5
// 而不是
// const firstEven = numbers.filter(n => n % 2 === 0)[0]; // 创建了整个过滤数组

// 对于存在性检查,使用some而不是filter
const hasEven = numbers.some(n => n % 2 === 0); // 返回true/false

算法优化

选择合适的数据结构

javascript
// 1. 使用Map而不是Object进行键值对存储(当键不是字符串时)
// 不好的做法
const obj = {};
obj[someObject] = 'value'; // 对象会被转换为字符串'[object Object]'

// 好的做法
const map = new Map();
map.set(someObject, 'value');

// 2. 使用Set进行唯一值存储
// 不好的做法
function removeDuplicates(arr) {
  const unique = [];
  for (const item of arr) {
    if (unique.indexOf(item) === -1) {
      unique.push(item);
    }
  }
  return unique;
}

// 好的做法
function removeDuplicates(arr) {
  return [...new Set(arr)];
}

// 3. 使用Object的键进行快速查找
const validStatuses = ['active', 'inactive', 'pending'];
const status = 'active';

// 不好的做法 - O(n)时间复杂度
if (validStatuses.includes(status)) {
  // 处理
}

// 好的做法 - O(1)时间复杂度
const validStatusesSet = new Set(validStatuses);
if (validStatusesSet.has(status)) {
  // 处理
}

// 或者使用对象
const validStatusesObj = { active: true, inactive: true, pending: true };
if (validStatusesObj[status]) {
  // 处理
}

优化循环

javascript
// 1. 缓存数组长度
const items = new Array(1000000).fill(1);

// 不好的做法
for (let i = 0; i < items.length; i++) {
  // 每次都要访问items.length
}

// 好的做法
for (let i = 0, len = items.length; i < len; i++) {
  // 缓存了长度
}

// 2. 使用for...of而不是forEach(在某些情况下)
// forEach有函数调用开销
for (const item of items) {
  // 处理item
}

// 3. 避免不必要的计算
// 不好的做法
for (let i = 0; i < items.length; i++) {
  const processed = expensiveOperation(items[i]);
  if (processed > threshold) {
    break; // 但仍然计算了expensiveOperation
  }
}

// 好的做法
for (let i = 0; i < items.length; i++) {
  if (someQuickCheck(items[i])) {
    const processed = expensiveOperation(items[i]);
    if (processed > threshold) {
      break;
    }
  }
}

函数优化

避免重复计算

javascript
// 1. 使用缓存/记忆化
function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const expensiveCalculation = memoize((n) => {
  // 模拟耗时计算
  let result = 0;
  for (let i = 0; i < n; i++) {
    result += i * i;
  }
  return result;
});

// 2. 避免在函数内部创建函数(在循环中)
// 不好的做法
function createHandlers(items) {
  return items.map(item => {
    return function() {
      // 每次都创建新函数
      console.log(item.value);
    };
  });
}

// 好的做法
function createHandlers(items) {
  const handler = function(item) {
    return function() {
      console.log(item.value);
    };
  };
  
  return items.map(item => handler(item));
}

// 3. 使用箭头函数时注意this绑定
class Calculator {
  constructor() {
    this.result = 0;
  }
  
  // 不好的做法 - this指向问题
  processArrayBad(arr) {
    arr.forEach(function(item) {
      this.result += item; // this不指向Calculator实例
    });
  }
  
  // 好的做法
  processArrayGood(arr) {
    arr.forEach(item => {
      this.result += item; // 箭头函数继承this
    });
  }
  
  // 或者预先绑定
  processArrayAlsoGood(arr) {
    const adder = function(item) {
      this.result += item;
    }.bind(this);
    
    arr.forEach(adder);
  }
}

DOM 操作优化

减少重排和重绘

javascript
// 1. 批量DOM操作
const container = document.getElementById('container');

// 不好的做法 - 多次重排重绘
for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.textContent = `Item ${i}`;
  div.style.width = '100px';
  div.style.height = '50px';
  container.appendChild(div);
  // 每次appendChild都会可能触发重排
}

// 好的做法 - 使用文档片段
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const div = document.createElement('div');
  div.textContent = `Item ${i}`;
  div.style.width = '100px';
  div.style.height = '50px';
  fragment.appendChild(div);
}
container.appendChild(fragment); // 只触发一次重排

// 2. 避免强制同步布局
const element = document.getElementById('myElement');

// 不好的做法 - 强制同步布局
element.style.left = '10px';
console.log(element.offsetLeft); // 强制浏览器立即计算布局
element.style.top = '10px';
console.log(element.offsetTop);  // 再次强制布局计算

// 好的做法 - 批量读写操作
element.style.left = '10px';
element.style.top = '10px';
const left = element.offsetLeft;
const top = element.offsetTop;

// 3. 使用CSS类而不是直接操作样式
// 不好的做法
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.fontSize = '16px';

// 好的做法
element.className = 'highlighted';

// CSS:
// .highlighted {
//   color: red;
//   background-color: blue;
//   font-size: 16px;
// }

事件优化

javascript
// 1. 使用事件委托
// 不好的做法 - 为每个元素添加事件监听器
document.querySelectorAll('.button').forEach(button => {
  button.addEventListener('click', handleClick);
});

// 好的做法 - 事件委托
document.addEventListener('click', function(event) {
  if (event.target.matches('.button')) {
    handleClick(event);
  }
});

// 2. 使用被动事件监听器
element.addEventListener('touchstart', handleTouch, { passive: true });

// 3. 限制事件处理器的执行频率
function throttle(func, limit) {
  let inThrottle;
  return function() {
    const args = arguments;
    const context = this;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  }
}

// 用于滚动事件
const throttledScroll = throttle(function() {
  console.log('处理滚动事件');
}, 100);

window.addEventListener('scroll', throttledScroll);

// 防抖用于搜索输入
function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  }
}

const debouncedSearch = debounce(function(event) {
  performSearch(event.target.value);
}, 300);

document.getElementById('search').addEventListener('input', debouncedSearch);

异步操作优化

Promise 优化

javascript
// 1. 避免Promise链过长
// 不好的做法
fetchUser()
  .then(user => fetchUserPosts(user.id))
  .then(posts => fetchUserComments(posts))
  .then(comments => processComments(comments))
  .then(result => displayResult(result))
  .catch(error => console.error(error));

// 好的做法 - 使用async/await
async function loadUserData() {
  try {
    const user = await fetchUser();
    const posts = await fetchUserPosts(user.id);
    const comments = await fetchUserComments(posts);
    const result = await processComments(comments);
    return displayResult(result);
  } catch (error) {
    console.error(error);
  }
}

// 2. 并行执行独立的异步操作
// 不好的做法 - 串行执行
async function badParallel() {
  const user = await fetchUser(1);
  const posts = await fetchUser(2);
  const comments = await fetchUser(3);
  return { user, posts, comments };
}

// 好的做法 - 并行执行
async function goodParallel() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(1),
    fetchUser(2),
    fetchUser(3)
  ]);
  return { user, posts, comments };
}

// 3. 处理可能部分失败的并行操作
async function handlePartialFailure() {
  const results = await Promise.allSettled([
    fetchUser(1),
    fetchUser(2),
    fetchUser(3)
  ]);
  
  const data = {};
  results.forEach((result, index) => {
    const keys = ['user', 'posts', 'comments'];
    if (result.status === 'fulfilled') {
      data[keys[index]] = result.value;
    } else {
      console.error(`${keys[index]} 获取失败:`, result.reason);
      data[keys[index]] = null;
    }
  });
  
  return data;
}

代码分割和懒加载

动态导入优化

javascript
// 1. 按需加载功能模块
async function loadChartingLibrary() {
  // 只在需要图表功能时才加载
  const { Chart } = await import('chart.js');
  return new Chart();
}

// 2. 路由级别的代码分割
async function loadRoute(routeName) {
  const routes = {
    'dashboard': () => import('./Dashboard.js'),
    'profile': () => import('./Profile.js'),
    'settings': () => import('./Settings.js')
  };
  
  if (routes[routeName]) {
    const module = await routes[routeName]();
    return module.default;
  }
  throw new Error(`Unknown route: ${routeName}`);
}

// 3. 预加载重要模块
const essentialModules = Promise.all([
  import('./router.js'),
  import('./auth.js'),
  import('./api.js')
]);

// 4. 条件加载
async function initializeFeature() {
  if (window.location.pathname === '/admin') {
    const adminModule = await import('./admin-panel.js');
    adminModule.init();
  }
}

优化技巧和模式

对象优化

javascript
// 1. 避免在循环中创建对象
// 不好的做法
function processItems(items) {
  return items.map(item => {
    return {
      id: item.id,
      name: item.name,
      processed: true,
      timestamp: Date.now()
    };
  });
}

// 好的做法 - 重用对象结构
function processItems(items) {
  const template = {
    id: null,
    name: null,
    processed: true,
    timestamp: Date.now()
  };
  
  return items.map(item => {
    return {
      ...template,
      id: item.id,
      name: item.name
    };
  });
}

// 2. 使用对象池减少垃圾回收
class ObjectPool {
  constructor(createFn, resetFn) {
    this.createFn = createFn;
    this.resetFn = resetFn;
    this.pool = [];
  }
  
  acquire() {
    return this.pool.length > 0 ? this.pool.pop() : this.createFn();
  }
  
  release(obj) {
    this.resetFn(obj);
    this.pool.push(obj);
  }
}

// 使用示例
const vectorPool = new ObjectPool(
  () => ({ x: 0, y: 0 }),
  (obj) => { obj.x = 0; obj.y = 0; }
);

function useVector() {
  const vector = vectorPool.acquire();
  vector.x = 10;
  vector.y = 20;
  // 使用vector...
  vectorPool.release(vector);
}

字符串优化

javascript
// 1. 使用模板字符串而不是字符串连接
const name = 'John';
const age = 30;

// 不好的做法
const message = 'Hello, my name is ' + name + ' and I am ' + age + ' years old.';

// 好的做法
const message = `Hello, my name is ${name} and I am ${age} years old.`;

// 2. 大量字符串拼接使用数组
// 不好的做法
let result = '';
for (let i = 0; i < 10000; i++) {
  result += 'item' + i + ',';
}

// 好的做法
const items = [];
for (let i = 0; i < 10000; i++) {
  items.push('item' + i);
}
const result = items.join(',');

// 3. 使用String.fromCharCode()处理字符码
const codes = [72, 101, 108, 108, 111]; // 'Hello'的字符码
const str = String.fromCharCode(...codes);

性能监控

性能测量

javascript
// 1. 使用Performance API
function measureFunction(fn, ...args) {
  const start = performance.now();
  const result = fn.apply(this, args);
  const end = performance.now();
  console.log(`${fn.name} 执行时间: ${end - start} 毫秒`);
  return result;
}

// 2. 监控内存使用
function getMemoryInfo() {
  if (performance.memory) {
    return {
      used: performance.memory.usedJSHeapSize,
      total: performance.memory.totalJSHeapSize,
      limit: performance.memory.jsHeapSizeLimit
    };
  }
  return null;
}

// 3. 监控长任务
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 50) { // 超过50ms的任务
      console.warn('长任务检测到:', entry);
    }
  }
});

observer.observe({ entryTypes: ['longtask'] });

// 4. 自定义性能标记
performance.mark('start-operation');
// 执行操作
performance.mark('end-operation');
performance.measure('operation', 'start-operation', 'end-operation');

const measures = performance.getEntriesByName('operation');
console.log('操作耗时:', measures[0].duration);

最佳实践总结

性能优化检查清单

javascript
// 1. 代码层面
// - 使用合适的数据结构
// - 避免不必要的计算
// - 使用缓存机制
// - 优化循环和条件判断

// 2. DOM操作层面
// - 减少DOM查询
// - 批量DOM操作
// - 使用事件委托
// - 避免强制同步布局

// 3. 异步操作层面
// - 合理使用Promise
// - 并行执行独立操作
// - 使用防抖和节流

// 4. 内存管理层面
// - 及时清理事件监听器
// - 避免内存泄漏
// - 使用对象池

// 5. 网络层面
// - 代码分割和懒加载
// - 使用CDN
// - 启用压缩

// 示例:优化的数据处理类
class OptimizedDataProcessor {
  constructor() {
    this.cache = new Map();
    this.pool = [];
  }
  
  process(data) {
    const cacheKey = JSON.stringify(data);
    
    // 检查缓存
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    
    // 从池中获取处理对象
    let processor = this.pool.pop() || this.createProcessor();
    
    // 处理数据
    const result = this.performProcessing(processor, data);
    
    // 存入缓存
    this.cache.set(cacheKey, result);
    
    // 将处理器放回池中
    this.resetProcessor(processor);
    this.pool.push(processor);
    
    return result;
  }
  
  createProcessor() {
    return {
      tempArray: new Array(100),
      tempObj: {}
    };
  }
  
  resetProcessor(processor) {
    processor.tempArray.fill(0);
    Object.keys(processor.tempObj).forEach(key => delete processor.tempObj[key]);
  }
  
  performProcessing(processor, data) {
    // 实际的数据处理逻辑
    return data.map(item => item * 2);
  }
}

JavaScript性能优化是一个持续的过程,需要结合实际应用场景选择合适的优化策略。始终记住"过早优化是万恶之源",应该先测量性能瓶颈,再针对性地进行优化。