というか、これを書くための前置きとして書いたものだったり。
もう話題としては「かなり」古いんだけど、IE6以前のJavascriptにはメモリリークの問題があって
[IE] Javascript で動的にイベントを登録するとメモリリークが発生 (support.microsoft.com)
Internet Explorer リーク パターンを理解して解決する (msdn.microsoft.com)
この辺を参照かな。
IE6とかさっさと滅べばいいのに。
(A) たとえば、このコードはメモリリークを起こします。
function foo(element) { element.onmousedown = function(){ alert('test'); }; }
(B) これも起こします。
function foo() { var element = document.getElementById('foo'); element.onmousedown = function(){ alert('test'); }; }
(C) でもこれは起こしません。
function foo() { document.getElementById('foo').onmousedown = function(){ alert('test'); }; }なぜでしょう?
これは、Javasciprtのガベージコレクションは「マーク&スウィープ方式」が使われているのですが、DOMオブジェクトだけは内部で「参照カウンタ方式」を使っているために起きている問題です。DOMオブジェクトがJavaSciprtオブジェクトを参照していて、そのJavascriptオブジェクトでもDOMオブジェクトを参照していたりすると、循環参照になってメモリから解放されずに残ってしまうわけです。
つまり上の例(A)(B)だと、DOMオブジェクト「element」が onmousemoveハンドラでクロージャfunction(){ … }を参照していて、クロージャでも内部でelementを参照しているため、メモリリークが発生してしまいます。
一見、このクロージャはelementオブジェクトを参照してないように見えますが、javascriptのクロージャとローカル変数で書いたように、クロージャ内側でelementを使っていようがいまいが、同じスコープで宣言されている全てのローカル変数をクロージャ内に保持してしまう特性上、elementもきっちり参照しています。
(C)の例では、DOMオブジェクトをローカル変数として定義していないのでクロージャ内でid='foo'のDOMオブジェクトは参照されていません。したがってメモリリークは発生しません。
function foo() { var element = document.getElementById('foo'); element.onmousedown = function(){ alert('test'); }; element = null; }このようにelementをnullで消しておけば、参照が切れてメモリリークを防げます。
elementを戻り値として返したい場合などは、try{ … } finally { … }を使うとスマートに書けたりします。
function createButton() { element = document.createElement('foo'); element.innerHTML = 'click here'; element.onclick = function() { alert('click!'); }; try { return element; } finally { element = null; } } document.getElementById('hoge').appendChild( createButton() );
PerlやPythonから入ってる人は、なぜ(A)(B)が循環参照になるのか理解しづらいかもなので、書いてみました。
なんか他にも書いておきたいことあったけど、長くなりそうだからもういいや。
0 件のコメント:
コメントを投稿