JavaScriptメモリ管理完全ガイド2025 - メモリリークの検出から最適化まで
JavaScriptのメモリ管理の仕組みを完全解説。ガベージコレクション、メモリリークの検出方法、Chrome DevToolsでのプロファイリング、実践的な最適化テクニックを詳しく紹介します。
2025年最新のJavaScriptパフォーマンス最適化テクニックを実例とともに解説。メモリ管理、非同期処理、バンドル最適化まで網羅的にカバーします。
JavaScriptのパフォーマンス最適化は、ユーザー体験向上において極めて重要な要素です。2025 年現在、モダンブラウザの進化とともに新しい最適化手法が登場しています。
// ❌ 従来の方法(メモリリークの可能性)
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 を使用することで、キーとなるオブジェクトが削除されると自動的にキャッシュからも削除され、メモリリークを防げます。
// メモリ使用量の監視
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);
// ❌ 順次処理(遅い)
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);
}
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();
}
}
// ❌ 非効率な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); // 一度だけリフロー
}
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);
});
// ❌ 全体をインポート
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');
}
}
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'
);
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);
}
};
}
}
これらの最適化技術を適用することで、以下の改善が期待できます:
JavaScriptのパフォーマンス最適化は継続的なプロセスです。2025 年の最新技術を活用し、適切な測定と改善を繰り返すことで、優れたユーザー体験を提供できます。
重要なポイント:
これらの技術を実践し、ユーザーにとって快適な Web アプリケーションを構築しましょう。