というか、これを書くための前置きとして書いたものだったり。
もう話題としては「かなり」古いんだけど、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 件のコメント:
コメントを投稿