【JS応用|実務向け】メモリリークを防ぐ!URL.revokeObjectURLを活用したフロントエンドのメモリ管理術

導入:なぜURL.revokeObjectURLが必要なのか

フロントエンド開発において、ローカルの画像プレビューやBlobデータのダウンロード機能を実装する際、`URL.createObjectURL`は非常に便利です。しかし、このメソッドで生成されたURLは、ブラウザが明示的に破棄するまでメモリに残り続けます。SPA(シングルページアプリケーション)のように画面遷移が頻繁な環境では、この「解放忘れ」が蓄積し、深刻なメモリリークを引き起こす原因となります。本記事では、安全にリソースを解放するための作法を解説します。

基礎知識:BlobとObject URLの仕組み

`URL.createObjectURL()`は、`File`オブジェクトや`Blob`オブジェクトを指し示す一時的なURL(例: `blob:https://example.com/uuid`)を生成します。このURLは、ブラウザのメモリ内にあるデータへのポインタのようなものです。重要なのは、このURLはDOMのライフサイクルとは無関係であるという点です。たとえそのURLを表示していた画像要素(imgタグなど)がDOMから削除されても、ブラウザは「まだ他の場所で使われるかもしれない」と判断し、メモリを解放しません。これを手動で明示的に解放するのが`URL.revokeObjectURL()`です。

実装:適切な解放のタイミング

基本的には「生成したURLが不要になったタイミング」で即座に呼び出すのが正解です。特にReactやVueなどのコンポーネント指向フレームワークでは、以下のタイミングが適しています。
・コンポーネントがアンマウントされる直前
・新しいファイルが選択され、古いプレビューが不要になったタイミング

サンプルプログラム:画像プレビューでの実装例

以下は、ファイル選択時にプレビューを表示し、コンポーネント破棄時に正しくメモリを解放する例です。

// Reactの例:useEffectのクリーンアップ関数でメモリを解放する
import { useState, useEffect } from 'react';

function ImagePreview({ file }) {
  const [previewUrl, setPreviewUrl] = useState(null);

  useEffect(() => {
    if (!file) return;

    // オブジェクトURLを生成
    const objectUrl = URL.createObjectURL(file);
    setPreviewUrl(objectUrl);

    // クリーンアップ関数(コンポーネントのアンマウント時に実行される)
    return () => {
      // メモリ解放の実行
      URL.revokeObjectURL(objectUrl);
      console.log('メモリを解放しました');
    };
  }, [file]);

  if (!previewUrl) return null;

  return <img src={previewUrl} alt="プレビュー" />;
}

応用・注意点:現場で陥りやすい罠

1. 早すぎる解放: 非同期処理で画像を読み込んでいる場合、描画が完了する前に`revokeObjectURL`を呼んでしまうと画像が表示されません。必ず描画完了後、または不要になったタイミングを確実に見極めてください。
2. 再レンダリングの罠: React等では、親コンポーネントの再レンダリングによって不要な`createObjectURL`が繰り返されることがあります。`useMemo`や`useEffect`の依存配列を適切に設定し、「必要な時だけ生成する」構造を徹底してください。
3. デバッグ方法: Chrome DevToolsの「Memory」タブからヒープスナップショットを撮影し、`Blob`オブジェクトが急激に増えていないか定期的に監視する習慣をつけると、実装の品質が格段に向上します。

メモリ管理は地味ですが、ユーザー体験(UX)を損なわないための基本技術です。ぜひ今日から実装に取り入れてみてください。

タイトルとURLをコピーしました