ちょこちょこと紹介されているので知っている人も多いと思うが、IE には DOM ノードに絡んだメモリリークの問題がある。これに関しては Microsoft 自身の記事である「Understanding and Solving Internet Explorer Leak Patterns」に詳しいが、簡単にいえば DOM ノードオブジェクトに関する循環参照を作ると、IE を終了させるまでそのオブジェクトが解放されないというものだ。記事によればメモリリークには以下のようなパターンがあるという。
ある DOM ノードオブジェクトのプロパティをたどっていくと自分自身に行き着く場合。以下のようなパターンが考えられる。
element.property == element
element1.property1 == element2, element2.property2 == element1
クロージャはそのクロージャが定義されたスコープへの参照を持っている。下の例でいうならば、element.onclick
にセットされているクロージャは、addClickAction
関数の引数である element
への参照を持っているということだ。また、element
のほうはというと onclick イベントにクロージャが結び付けられているため、element →クロージャ→ element という循環参照ができてしまっている。
function addClickAction(element)
{
element.onclick = function () { doSomething; };
}
これは循環参照とは異なる問題だが、IE では文書ツリーに属していない要素 (createElement()
で作成したあとどこにも挿入されていない要素) に対してイベントハンドラを結びつけることもメモリリークを招くらしい。もっともこの場合は先ほどと異なり、リークするメモリはほんの少し (たった数バイト) だそうだ。解決策としては要素を文書ツリーに追加してからイベントハンドラを結びつけることが挙げられる。
そして実際の対策であるが、1 は DOM ノードオブジェクトに直接 DOM ノードオブジェクトを結び付けないようにすればいいし、3 はよほど大量の作業をしない限りは影響が出ないだろうからいいとして (なくすにこしたことはないが) 、問題は 2 だ。JavaScript ではクロージャを使うことで柔軟な表現が可能になるので、それが使えないとなると困ってしまう。
そこで他の人がどうしているかを見ると、prototype.js ではイベントを結びつけるたびにその内容を記録し、文書が破棄されるとき (unload イベント発生時) に removeEventListener
または detachEvent
でそれらを解除するという方法をとっている。また、「Leak Free Javascript Closures」(「Collection & Copy」経由) では、クロージャを直接要素に結び付けるのでなく、そのクロージャを実行する関数を作りそれを要素に結びつけるようにしている。クロージャを実行する関数は実際のクロージャを取得するためのハッシュキーしか持っていないので参照が途切れるというわけだ。
なお、一見メモリリークを引き起こしそうだが実はそうでないパターンもある。以下の例がそうだ。
function addActions()
{
var links = document.getElementsByTagName("a");
for (var i = 0; i < links.length; i++)
links[i].onclick = function () { alert("Hello!"); };
}
クロージャが a 要素全体への参照を持っているように思えるが、links
の内容は動的 (あとから a 要素が追加 / 削除された場合、それは links
にも反映される) であり、links
が直接特定の a 要素への参照を持っているわけではないので循環参照にはならないと考えられる。
セコメントをする