忘れられたタイマーとコールバックはJavaScriptのメモリリークを引き起こす?原因と対策を解説
忘れられたタイマーとコールバックが引き起こすメモリリーク
JavaScriptには、setTimeout()とsetInterval()という2つの代表的なタイマーイベントがあります。setTimeout()は指定したミリ秒後に一度だけ関数を実行するのに対し、setInterval()は一定の時間間隔ごとに関数を繰り返し実行します。
オブジェクトがタイマーのコールバックに紐付けられていると、タイムアウトが完了するまでそのオブジェクトは解放されません。特にsetInterval()のようにタイマーが自分自身を再設定しながら永久に動き続けるケースでは、ガベージコレクター(GC)がメモリを回収できなくなり、リークが発生します。この「忘れられたタイマー」は、JavaScriptにおける最も頻繁なメモリリークの原因のひとつです。
問題のあるコード例
次の例では、タイマーコールバックに紐付いたオブジェクト(tiedObject)は、タイムアウトが完了するまで解放されません。その間もタイマーは自分自身を再設定して永久に実行され続けるため、元のオブジェクトへの外部参照がなくなった後も、そのメモリ領域は決して回収されません。
<html>
<body>
<script>
for (var i = 0; i < 100000; i++) {
var tiedObject = {
callAgain: function() {
var text = this;
var value = setTimeout(function() {
text.callAgain();
}, 100000);
}
}
tiedObject.callAgain();
tiedObject = null;
}
</script>
</body>
</html>
メモリリークを防ぐための対策
- タイマー内での不要な参照を避ける:
setInterval()やsetTimeout()のコールバックからオブジェクトを参照している限り、そのオブジェクトはガベージコレクションの対象になりません。コールバックには必要なデータだけを渡すよう心がけましょう。 - 不要になったタイマーは明示的に解除する:役目を終えたタイマーは、
clearTimeout()やclearInterval()を呼び出して必ず停止させてください。
適切な解除方法の例
var timerId = setInterval(function() {
// 定期的に実行したい処理
}, 1000);
// 不要になった時点で必ず解除する
clearInterval(timerId);
モダンブラウザでの状況
IEなどの古いブラウザを除けば、Chromeをはじめとする近年のブラウザでは、この種のメモリリークは大幅に改善されています。また、jQueryなどの主要ライブラリも内部でイベントハンドラやタイマーの後始末を適切に行う仕組みを備えているため、ライブラリを活用することでもリークのリスクを抑えられます。
とはいえ、長時間稼働するシングルページアプリケーション(SPA)などでは、依然としてタイマーの管理が重要です。「使わなくなったら必ず解除する」ことを習慣づけることが、健全なメモリ管理への第一歩となります。
-
JavaScriptのコールバック関数とは?基本の仕組みと実装例をわかりやすく解説
JavaScriptでは、関数もオブジェクトとして扱われます。そのため、関数を別の関数に引数として渡すことが可能です。このように渡された関数は、受け取った側の関数の中で呼び出すことができ、これをコールバック関数(callback function)と呼びます。 コールバックは、非同期処理やイベントハンドリングなど、現代のJavaScript開発において欠かせない重要な概念です。ここでは、基本的なコールバックの仕組みをサンプルコードとともに解説します。 コールバック関数のサンプルコード 以下は、2つの数値を加算し、その結果をコールバック関数で処理するシンプルな例です。 <!DOCTYPE
-
href属性が「#」のリンクでJavaScriptのクリック処理を無効化する方法
リンクの中には、href=# のようにページ内遷移やダミーリンクとして設定されているものがあります。こうした「#」で始まるリンクに対してクリックイベントを発火させたくない場合、jQueryのセレクタと preventDefault() を組み合わせることで簡単に実現できます。 基本的な考え方 ポイントは、セレクタ a:not([href=#]) を使って「href属性が # 以外のリンク」だけを対象にすることです。これにより、# のリンクはクリックイベントの処理対象から除外され、何も起こらなくなります。また、対象となったリンクには event.preventDefault() を呼び出すことで