前書き
メモリ リークの問題について話す前に、JavaScript のガベージ コレクション メカニズムを見てみましょう。JavaScript には、使用されなくなった変数を見つけて、それらが占有しているメモリを解放する自動ガベージ コレクション メカニズムがあります。これを行うために、ガベージ コレクターは一定の間隔 (またはコード実行中にスケジュールされた収集時間) で動作します。一般的に使用される方法には、クリア マーキングと参照カウントの 2 つがあります。
1. マーク スイープ
JavaScript で最も一般的に使用されるガベージ コレクション方法は、マーク アンド スイープです。ガベージ コレクターは、実行時にメモリに格納されているすべての変数にマークを付けます (任意のマーク方法を使用できます)。次に、環境内の変数と環境内の変数によって参照される変数のタグを削除します。これ以降にマークされた変数は、環境内の変数がこれらの変数にアクセスできなくなるため、削除される変数とみなされます。最後に、ガベージ コレクターはメモリのクリーンアップ作業を完了し、マークされた値を破棄し、それらが占有しているメモリ領域を再利用します。
2. 参照カウント
参照カウントの意味は、各値が参照された回数を追跡することです。変数が宣言され、その変数に参照型の値が割り当てられている場合、この値への参照の数は 1 です。同じ値が別の変数に代入されている場合、その値の参照カウントは 1 増加します。逆に、この値への参照を含む変数が別の値を取得すると、この値への参照の数は 1 つ減ります。この値への参照の数が 0 になると、この値にアクセスする方法がなくなったことを意味するため、この値が占有しているメモリ領域を再利用できます。こうすることで、次回ガベージ コレクターが実行されるときに、参照がゼロの値によって占められていたメモリが解放されます。
Netscape Navigator 3.0 は、参照カウント戦略を使用した最初のブラウザでしたが、すぐに深刻な問題に遭遇しました。次の例を参照してください:
function problem(){ var objectA = new Object(); var objectB = new Object(); objectA.someOtherObject = objectB; objectB.anotherObject = objectA; }
説明: objectA と objectB は、それぞれの属性、つまり、両方のオブジェクトの参照カウントは 2 です。マーク アンド スイープ戦略を使用する実装では、関数の実行後に両方のオブジェクトがスコープを離れるため、この相互参照は問題になりません。ただし、参照カウント戦略を使用した実装では、objectA と objectB の参照数が 0 になることはないため、関数の実行後も存在し続けます。この関数が複数回呼び出された場合、大量のメモリはリサイクルされません。
このため、Netscape は Navigator 4.0 で参照カウント方式を放棄しましたが、参照カウントによって引き起こされる問題はこれで終わりではありませんでした。 IE 9 より前の一部のオブジェクトはネイティブ JavaScript オブジェクトではありませんでした。たとえば、BOM および DOM 内のオブジェクトは、C++ を使用して COM (コンポーネント オブジェクト モデル) オブジェクトの形式で実装され、COM オブジェクトのガベージ コレクション メカニズムでは参照カウント方式が使用されます。したがって、IE の JavaScript エンジンはマーク アンド スイープ戦略を使用して実装されていますが、JavaScript によってアクセスされる COM オブジェクトは依然として参照カウント戦略に基づいています。言い換えれば、COM オブジェクトが IE に関与する場合は常に、循環参照の問題が発生します。
例:
var element = document.getElementById("some_element"); var myObject = new Object(); myObject.element = element; element.someObject = myObject;
DOM 要素 (element) とネイティブ JavaScript オブジェクト (myObject) の間に循環参照が作成されます。その中で、変数 myObject には要素オブジェクトを指す element という名前のプロパティがあり、変数 element には myObject を指す someObject という名前のプロパティもあります。この循環参照のため、例の DOM がページから削除されても、リサイクルされることはありません。
解決策: 変数を null に設定して、変数と以前に参照していた値の間の接続を切断します。
myObject.element = null; element.someObject = null;
上記の内容を読んだ上で、本題についてお話しさせていただきます。
クロージャーによってメモリ リークが発生することはありません
IE9 より前のバージョンでは、JScript オブジェクトと COM オブジェクトに対して異なるガベージ コレクションが使用されるためです。したがって、これらのバージョンの IE では、クロージャによっていくつかの特別な問題が発生します。具体的には、HTML 要素がクロージャのスコープ チェーンに格納されている場合、その要素は破棄できないことを意味します。例を参照してください。
function assignHandler(){ var element = document.getElementById("someElement"); element.onclick = function(){ alert(element.id); }; }
上記のコードは、要素要素のイベント ハンドラとして機能するクロージャを作成します。このクロージャーは循環参照を作成します。無名関数は assignHandler() のアクティブなオブジェクトへの参照を保存するため、要素への参照の数を減らすことができなくなります。匿名関数が存在する限り、要素の参照番号は少なくとも 1 であるため、要素が占有するメモリは決してリサイクルされません
解決策は序文で述べたように、element.id のコピーを変数に保存することで、削除 クロージャ内の変数への循環参照も、要素変数を null に設定します。
function assignHandler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ alert(id); }; element = null; }
概要: クロージャはメモリ リークを引き起こしませんが、IE9 より前のバージョンでは JScript オブジェクトと COM オブジェクトに対して異なるガベージ コレクションが使用されるため、メモリをリサイクルできません。これは IE の問題であるため、クロージャとメモリ リークは問題ではありません。 。