ブログ記事

JavaScript パフォーマンス最適化の実践テクニック 2025年版

2025年最新のJavaScriptパフォーマンス最適化テクニックを実例とともに解説。メモリ管理、非同期処理、バンドル最適化まで網羅的にカバーします。

7分で読めます
R
Rina
Daily Hack 編集長
プログラミング
JavaScript パフォーマンス フロントエンド 最適化
JavaScript パフォーマンス最適化の実践テクニック 2025年版のヒーロー画像

この記事で学べること

  • 2025 年最新の JavaScriptパフォーマンス最適化手法
  • 実際のコード例による具体的な改善方法
  • メモリリークの防止とデバッグ技術
  • 非同期処理の効率的な実装

はじめに

JavaScriptのパフォーマンス最適化は、ユーザー体験向上において極めて重要な要素です。2025 年現在、モダンブラウザの進化とともに新しい最適化手法が登場しています。

1. メモリ管理の最適化

WeakMapとWeakSetの活用

// ❌ 従来の方法(メモリリークの可能性)
const cache = new Map();

function processData(element) {
  if (cache.has(element)) {
    return cache.get(element);
  }
  
  const result = heavyComputation(element);
  cache.set(element, result);
  return result;
}

// ✅ 改善された方法
const cache = new WeakMap();

function processData(element) {
  if (cache.has(element)) {
    return cache.get(element);
  }
  
  const result = heavyComputation(element);
  cache.set(element, result);
  return result;
}

WeakMapの利点

WeakMap を使用することで、キーとなるオブジェクトが削除されると自動的にキャッシュからも削除され、メモリリークを防げます。

メモリ使用量の監視

// メモリ使用量の監視
function monitorMemory() {
  if ('memory' in performance) {
    const memory = performance.memory;
    console.log({
      used: `${Math.round(memory.usedJSHeapSize / 1048576)} MB`,
      total: `${Math.round(memory.totalJSHeapSize / 1048576)} MB`,
      limit: `${Math.round(memory.jsHeapSizeLimit / 1048576)} MB`
    });
  }
}

// 定期的な監視
setInterval(monitorMemory, 5000);

2. 非同期処理の最適化

Promise.allSettledの活用

// ❌ 順次処理(遅い)
async function fetchDataSequentially(urls) {
  const results = [];
  for (const url of urls) {
    try {
      const response = await fetch(url);
      results.push(await response.json());
    } catch (error) {
      results.push({ error: error.message });
    }
  }
  return results;
}

// ✅ 並列処理(高速)
async function fetchDataConcurrently(urls) {
  const promises = urls.map(async (url) => {
    try {
      const response = await fetch(url);
      return await response.json();
    } catch (error) {
      return { error: error.message };
    }
  });
  
  return await Promise.allSettled(promises);
}

AbortControllerによるリクエスト制御

class APIManager {
  constructor() {
    this.controllers = new Map();
  }
  
  async fetchWithTimeout(url, timeout = 5000) {
    const controller = new AbortController();
    const id = Date.now();
    
    this.controllers.set(id, controller);
    
    // タイムアウト設定
    const timeoutId = setTimeout(() => {
      controller.abort();
    }, timeout);
    
    try {
      const response = await fetch(url, {
        signal: controller.signal
      });
      
      clearTimeout(timeoutId);
      this.controllers.delete(id);
      
      return await response.json();
    } catch (error) {
      clearTimeout(timeoutId);
      this.controllers.delete(id);
      
      if (error.name === 'AbortError') {
        throw new Error('Request timeout');
      }
      throw error;
    }
  }
  
  cancelAll() {
    this.controllers.forEach(controller => controller.abort());
    this.controllers.clear();
  }
}

3. DOM操作の最適化

DocumentFragmentの活用

// ❌ 非効率なDOM操作
function addItemsInefficient(items) {
  const list = document.getElementById('list');
  
  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item.name;
    list.appendChild(li); // 毎回リフローが発生
  });
}

// ✅ 効率的なDOM操作
function addItemsEfficient(items) {
  const list = document.getElementById('list');
  const fragment = document.createDocumentFragment();
  
  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item.name;
    fragment.appendChild(li);
  });
  
  list.appendChild(fragment); // 一度だけリフロー
}

Intersection Observerによる遅延読み込み

class LazyLoader {
  constructor() {
    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      {
        rootMargin: '50px',
        threshold: 0.1
      }
    );
  }
  
  observe(element) {
    this.observer.observe(element);
  }
  
  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        this.loadContent(entry.target);
        this.observer.unobserve(entry.target);
      }
    });
  }
  
  async loadContent(element) {
    const src = element.dataset.src;
    if (src) {
      try {
        element.src = src;
        element.classList.add('loaded');
      } catch (error) {
        console.error('Failed to load:', src, error);
      }
    }
  }
}

// 使用例
const lazyLoader = new LazyLoader();
document.querySelectorAll('img[data-src]').forEach(img => {
  lazyLoader.observe(img);
});

4. バンドル最適化

Tree Shakingの効果的な活用

// ❌ 全体をインポート
import * as lodash from 'lodash';

function processArray(arr) {
  return lodash.uniq(lodash.flatten(arr));
}

// ✅ 必要な関数のみインポート
import { uniq, flatten } from 'lodash';

function processArray(arr) {
  return uniq(flatten(arr));
}

// ✅ さらに最適化(個別インポート)
import uniq from 'lodash/uniq';
import flatten from 'lodash/flatten';

function processArray(arr) {
  return uniq(flatten(arr));
}

動的インポートによるコード分割

// ルートベースのコード分割
class Router {
  async loadRoute(routeName) {
    try {
      const module = await import(`./routes/${routeName}.js`);
      return module.default;
    } catch (error) {
      console.error(`Failed to load route: ${routeName}`, error);
      return null;
    }
  }
}

// 機能ベースのコード分割
async function loadChartLibrary() {
  const { Chart } = await import('chart.js');
  return Chart;
}

// 条件付きロード
async function loadPolyfills() {
  if (!window.IntersectionObserver) {
    await import('intersection-observer');
  }
  
  if (!window.fetch) {
    await import('whatwg-fetch');
  }
}

5. パフォーマンス測定

Web Vitalsの監視

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

class PerformanceMonitor {
  constructor() {
    this.metrics = {};
    this.initializeMetrics();
  }
  
  initializeMetrics() {
    getCLS(this.handleMetric.bind(this, 'CLS'));
    getFID(this.handleMetric.bind(this, 'FID'));
    getFCP(this.handleMetric.bind(this, 'FCP'));
    getLCP(this.handleMetric.bind(this, 'LCP'));
    getTTFB(this.handleMetric.bind(this, 'TTFB'));
  }
  
  handleMetric(name, metric) {
    this.metrics[name] = metric;
    this.sendToAnalytics(name, metric);
  }
  
  sendToAnalytics(name, metric) {
    // アナリティクスサービスに送信
    console.log(`${name}:`, metric.value);
    
    // 閾値チェック
    const thresholds = {
      CLS: 0.1,
      FID: 100,
      FCP: 1800,
      LCP: 2500,
      TTFB: 800
    };
    
    if (metric.value > thresholds[name]) {
      console.warn(`${name} threshold exceeded:`, metric.value);
    }
  }
}

const monitor = new PerformanceMonitor();

カスタムパフォーマンス測定

class CustomPerformanceTracker {
  constructor() {
    this.marks = new Map();
    this.measures = new Map();
  }
  
  mark(name) {
    const timestamp = performance.now();
    this.marks.set(name, timestamp);
    performance.mark(name);
  }
  
  measure(name, startMark, endMark = null) {
    if (!endMark) {
      endMark = `${name}-end`;
      this.mark(endMark);
    }
    
    const measure = performance.measure(name, startMark, endMark);
    this.measures.set(name, measure.duration);
    
    return measure.duration;
  }
  
  getMetrics() {
    return {
      marks: Object.fromEntries(this.marks),
      measures: Object.fromEntries(this.measures)
    };
  }
  
  // 関数の実行時間を測定するデコレータ
  timeFunction(fn, name) {
    return (...args) => {
      const startMark = `${name}-start`;
      this.mark(startMark);
      
      const result = fn.apply(this, args);
      
      if (result instanceof Promise) {
        return result.finally(() => {
          this.measure(name, startMark);
        });
      } else {
        this.measure(name, startMark);
        return result;
      }
    };
  }
}

// 使用例
const tracker = new CustomPerformanceTracker();

const optimizedFunction = tracker.timeFunction(
  function heavyComputation(data) {
    // 重い処理
    return data.map(item => item * 2);
  },
  'heavyComputation'
);

6. 実践的な最適化例

仮想スクロールの実装

class VirtualScroller {
  constructor(container, itemHeight, renderItem) {
    this.container = container;
    this.itemHeight = itemHeight;
    this.renderItem = renderItem;
    this.data = [];
    this.visibleStart = 0;
    this.visibleEnd = 0;
    this.scrollTop = 0;
    
    this.setupContainer();
    this.bindEvents();
  }
  
  setupContainer() {
    this.container.style.overflow = 'auto';
    this.container.style.position = 'relative';
    
    this.viewport = document.createElement('div');
    this.viewport.style.position = 'absolute';
    this.viewport.style.top = '0';
    this.viewport.style.left = '0';
    this.viewport.style.right = '0';
    
    this.container.appendChild(this.viewport);
  }
  
  bindEvents() {
    this.container.addEventListener('scroll', 
      this.throttle(this.handleScroll.bind(this), 16)
    );
  }
  
  setData(data) {
    this.data = data;
    this.container.style.height = `${data.length * this.itemHeight}px`;
    this.updateVisibleItems();
  }
  
  handleScroll() {
    this.scrollTop = this.container.scrollTop;
    this.updateVisibleItems();
  }
  
  updateVisibleItems() {
    const containerHeight = this.container.clientHeight;
    const buffer = 5; // バッファアイテム数
    
    this.visibleStart = Math.max(0, 
      Math.floor(this.scrollTop / this.itemHeight) - buffer
    );
    this.visibleEnd = Math.min(this.data.length - 1,
      Math.ceil((this.scrollTop + containerHeight) / this.itemHeight) + buffer
    );
    
    this.renderVisibleItems();
  }
  
  renderVisibleItems() {
    this.viewport.innerHTML = '';
    this.viewport.style.transform = `translateY(${this.visibleStart * this.itemHeight}px)`;
    
    for (let i = this.visibleStart; i <= this.visibleEnd; i++) {
      const item = this.renderItem(this.data[i], i);
      item.style.height = `${this.itemHeight}px`;
      this.viewport.appendChild(item);
    }
  }
  
  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);
      }
    };
  }
}

パフォーマンス改善の効果

これらの最適化技術を適用することで、以下の改善が期待できます:

  • ページ読み込み時間の 30-50%短縮
  • メモリ使用量の 20-40%削減
  • ユーザーインタラクションの応答性向上

まとめ

JavaScriptのパフォーマンス最適化は継続的なプロセスです。2025 年の最新技術を活用し、適切な測定と改善を繰り返すことで、優れたユーザー体験を提供できます。

重要なポイント:

  • メモリ管理の徹底
  • 非同期処理の効率化
  • DOM 操作の最適化
  • バンドルサイズの削減
  • 継続的なパフォーマンス監視

これらの技術を実践し、ユーザーにとって快適な Web アプリケーションを構築しましょう。

Rinaのプロフィール画像

Rina

Daily Hack 編集長

フルスタックエンジニアとして10年以上の経験を持つ。 大手IT企業やスタートアップでの開発経験を活かし、 実践的で即効性のある技術情報を日々発信中。 特にWeb開発、クラウド技術、AI活用に精通。

この記事は役に立ちましたか?

あなたのフィードバックが記事の改善に役立ちます

この記事は役に立ちましたか?

Daily Hackでは、開発者の皆様に役立つ情報を毎日発信しています。