DOM オブジェクトとメモリリーク
2005-12-04


IE でのメモリリーク

ちょこちょこと紹介されているので知っている人も多いと思うが、IE には DOM ノードに絡んだメモリリークの問題がある。これに関しては Microsoft 自身の記事である「Understanding and Solving Internet Explorer Leak Patterns」に詳しいが、簡単にいえば DOM ノードオブジェクトに関する循環参照を作ると、IE を終了させるまでそのオブジェクトが解放されないというものだ。記事によればメモリリークには以下のようなパターンがあるという。

1. 単純な循環参照

ある DOM ノードオブジェクトのプロパティをたどっていくと自分自身に行き着く場合。以下のようなパターンが考えられる。

element.property == element
element1.property1 == element2, element2.property2 == element1

2. クロージャにまつわる循環参照

クロージャはそのクロージャが定義されたスコープへの参照を持っている。下の例でいうならば、element.onclick にセットされているクロージャは、addClickAction 関数の引数である element への参照を持っているということだ。また、element のほうはというと onclick イベントにクロージャが結び付けられているため、element →クロージャ→ element という循環参照ができてしまっている。

function addClickAction(element)
{
  element.onclick = function () { doSomething; };
}

3. 文書ツリーに属していない要素

これは循環参照とは異なる問題だが、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 要素への参照を持っているわけではないので循環参照にはならないと考えられる。

メモリリークが起こるわけ


続きを読む

[JavaScript]
[Web 関連技術]

コメント(全0件)
コメントをする


記事を書く
powered by ASAHIネット