メモリリークの防止 | メモリ管理とガベージコレクション | JavaScript 超完全入門 基本から発展までのすべて

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
~
ad. 価格範囲を指定して商品を探せます

メモリリークとは?

メモリリークとは、プログラムで不要になったデータやオブジェクトが、メモリ上に残り続けてしまう状態のことです。

JavaScriptにはガベージコレクションという仕組みがあり、不要になったメモリは自動的に解放されます。しかし、どこかから参照され続けているデータは「まだ必要」と判断されるため、メモリに残り続けることがあります。

メモリリークが増えると、ページの動作が重くなったり、長時間利用するWebアプリでパフォーマンスが低下したりする原因になります。

メモリリークが発生するケース

JavaScriptでメモリリークが発生しやすい代表的なケースには、次のようなものがあります。

  • グローバル変数の過剰な使用: グローバルスコープにデータを保持し続けると、不要になっても解放されにくくなります。
  • 未削除のイベントリスナー: 不要になった要素や処理にイベントリスナーが残ると、参照が残り続けることがあります。
  • クロージャによる参照の保持: クロージャが大きなデータを参照し続けると、メモリが解放されない場合があります。
  • 不要なDOM要素への参照: DOMから削除した要素をJavaScript側の変数が参照し続けると、解放されにくくなります。
  • タイマーの解除忘れ: setInterval()などを止めずに残すと、処理や参照が残り続けることがあります。

メモリリークの防止策

メモリリークを防ぐには、不要になった参照を残さないことが重要です。代表的な防止策を見ていきましょう。

グローバル変数の適切な管理

グローバル変数はどこからでも参照できるため便利ですが、必要以上に使うとメモリ上にデータが残りやすくなります。


// メモリに残りやすい例
const globalData = [];

function addData(item) {
    globalData.push(item);
}

// 必要な範囲だけでデータを使う例
function addData(item) {
    const localData = [];
    localData.push(item);

    return localData;
}

この例では、必要なデータを関数内で扱うようにしています。

すべてのグローバル変数が悪いわけではありませんが、長期間保持する必要がないデータは、できるだけスコープを限定しましょう。

不要なイベントリスナーの削除

イベントリスナーが不要になった場合は、removeEventListener()で削除できます。


const button = document.getElementById("myButton");

function handleClick() {
    console.log("クリックされました");
}

button.addEventListener("click", handleClick);

// 不要になったらイベントリスナーを削除
button.removeEventListener("click", handleClick);
button.remove();

イベントリスナーを削除するには、追加時と同じ関数を指定する必要があります。

そのため、匿名関数を直接渡すと削除しにくくなる場合があります。

AbortControllerを使ったイベントリスナーの管理

現在のJavaScriptでは、AbortControllerを使ってイベントリスナーをまとめて解除する方法も利用できます。


const controller = new AbortController();
const button = document.getElementById("myButton");

button.addEventListener("click", () => {
    console.log("クリックされました");
}, {
    signal: controller.signal
});

// 不要になったらまとめて解除
controller.abort();

複数のイベントリスナーをまとめて管理したい場合に便利です。

クロージャの参照に注意する

クロージャは外側の変数を保持できる便利な仕組みですが、大きなデータを参照し続けるとメモリリークの原因になることがあります。


function createHandler() {
    const largeArray = new Array(100000).fill("data");

    return function () {
        console.log(largeArray[0]);
    };
}

const handler = createHandler();
handler();

この例では、返された関数がlargeArrayを参照し続けます。

クロージャ自体は問題ではありませんが、不要になったデータを長く保持しないように設計することが重要です。

不要なDOM要素への参照を残さない

DOM要素を画面から削除しても、JavaScriptの変数がその要素を参照し続けていると、メモリに残ることがあります。


let element = document.createElement("div");

document.body.appendChild(element);

// DOMから削除
element.remove();

// 参照も不要なら解除
element = null;

通常はDOMから削除すれば十分な場合もありますが、大きな要素や長時間動作するアプリでは、不要な参照を残さないように注意しましょう。

タイマーを解除する

setInterval()やsetTimeout()も、不要になったら解除することが大切です。


const timerId = setInterval(() => {
    console.log("定期的に実行");
}, 1000);

// 不要になったら停止
clearInterval(timerId);

特にSPAや長時間開いたまま使う画面では、タイマーの解除忘れがメモリリークや不要な処理の原因になります。

メモリリーク防止のチェックリスト

防止策 具体例
グローバル変数を増やしすぎない 必要なデータはできるだけローカルスコープで扱う。
イベントリスナーを解除する 不要になったらremoveEventListener()やAbortControllerを使う。
クロージャの参照に注意する 大きなデータを不要に保持し続けない。
DOM要素への参照を残さない 削除したDOM要素を変数で持ち続けない。
タイマーを停止する setInterval()は不要になったらclearInterval()で解除する。

初心者が覚えておきたいポイント

  • JavaScriptにはガベージコレクションがあるため、通常は手動でメモリ解放する必要はありません。
  • ただし、参照が残っているデータは不要になっても解放されません。
  • イベントリスナー、タイマー、DOM参照はメモリリークの原因になりやすいです。
  • SPAのように長時間動作するアプリでは、特にメモリ管理が重要です。
  • Chrome DevToolsなどの開発者ツールを使うと、メモリ使用量を確認できます。

まとめ

  • メモリリークとは、不要になったデータがメモリに残り続ける状態である。
  • JavaScriptにはガベージコレクションがあるが、参照が残っているデータは解放されない。
  • グローバル変数、イベントリスナー、クロージャ、DOM参照、タイマーはメモリリークの原因になりやすい。
  • 不要なイベントリスナーやタイマーは適切に解除することが重要である。
  • DOM要素を削除した後も、JavaScript側の参照が残っていないか注意する。
  • 長時間動作するWebアプリでは、メモリリーク対策がパフォーマンス維持につながる。