クロージャとメモリリークについてのコピペ

前に見た時に理解できずにいた2chスレを備忘録としてコピペ。
時間あったら整形する。

+ JavaScript の質問用スレッド vol.52 +

670 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 11:42:28 ID:???
>>666 自分でそういう関数を1行で定義して使うのはいいんじゃねの?
>>669 setAttributeダサイ!
>>667 ホレ。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html><head><title>???</title>
<script type="text/javascript">
function test() {
  var elt = document.getElementById('i0');
  elt.src = 't2.png';
  elt.onclick = function() { location.href = 't2.html'; }
}
</script>
</head><body>
<div><img id="i0" src="t1.png"></div>
<div><button onclick="test()">Test</button></div>
</body></html>

674 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 12:27:22 ID:???
>>670
またもろにメモリーリークパターンか。ほんと使えんやっちゃな。おまえかなり高齢だろ?

676 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 12:33:45 ID:???
>>674
どこでどういう風にメモリリークするのか解説してよ
初心者向けスレなんだから

677 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 12:46:57 ID:???
ホレ氏は素人だから、メモリリークが起こっただけで低脳呼ばわりするのは可哀想だが…
674に代わって解説すると、

function test() {
  var elt = document.getElementById('i0');
  elt.src = 't2.png';
  elt.onclick = function() { location.href = 't2.html'; }
}

上のコードでtestの中のスコープがeltへの参照を保持していて、
eltのonclickには、クロージャへの参照を保持している。
そしてクロージャの特性上、クロージャがtestスコープへの参照を保持する訳で、
ここで循環参照が発生してメモリリークが発生している、
これは、まっとうなJavaScriptプログラマなら必ず避けなければいけないコードの典型的な例。

正直ホレ氏のコードは問題が多くて初心者向きではないと思うので、それを踏まえて利用してください。

679 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 13:34:56 ID:???
>>677
問題になるのは循環参照を持った大きいデータが次々にゴミになる場合。
ここでは本来クロージャは不要だから処理系がまともなら何も作らない。
馬鹿な処理系が毎回クロージャ作るとしてもeltという変数1個。
で、次のように変えて

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html><head><title>???</title>
<script type="text/javascript">
function test() {
  var elt = document.getElementById('i0');
  elt.src = 't2.png';
  elt.onclick = function() { location.href = 't2.html'; }
}
</script>
</head><body>
<div><img id="i0" src="t1.png"></div>
<div><button onclick="setInterval(test,100)">Test</button></div>
</body></html>

すごい勢いでtest()を呼んでみたけどメモリ使用量は増えてるかどうか
分からない程度の変動(UNIX/Gecko, WIN/IE6)。他の人も実験してみそ。
少なくとも人間がボタン押す程度で問題になるもんじゃないだろ。
で、アンタの正しいコードを貼ってくれよ。

681 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 13:38:27 ID:???
で、アンタの正しいコードを貼ってくれよ。せいぜいvar elt
の宣言を外側に出す程度?そうやってグローバル汚染するのと
どっちがいいかという程度の選択だろ。

682 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 13:40:21 ID:???
>>679
> ここでは本来クロージャは不要だから処理系がまともなら何も作らない。
Firefoxのシェアを馬鹿にするな

ちょっと変更してみた

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html><head><title>???</title>
<script type="text/javascript">
function test() {
  var dummy = [];
  for(var i=0;i<100000;i++) { dummy[i] = i; }
  var elt = document.getElementById('i0');
  elt.src = 't2.png';
  elt.onclick = function() { location.href = 't2.html'; }
}
</script>
</head><body>
<div><img id="i0" src="t1.png"></div>
<div><button onclick="setInterval(test,1000)">Test</button></div>
</body></html>

物凄い勢いでリークしていた

684 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 13:42:11 ID:???
>>679
メモリリークってのは、何度も呼び出したからリークするんじゃなくて、
ブラウザを閉じるまで別のページに行ってもメモリが解放されない問題なんだよ
intervalで呼び出すことになんら意味は無いよ
本当にホレ氏は素人なんだな

それと、
> で、アンタの正しいコードを貼ってくれよ。
かっこ悪すぎるw

685 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 13:45:12 ID:???
>684が正解
手っ取り早くメモリリークを起こすコードを書くと、

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html><head><title>???</title>
<script type="text/javascript">
function test() {
  var dummy = [];
  for(var i=0;i<1000000;i++) { dummy[i] = i; }
  var elt = document.getElementById('i0');
  elt.src = 't2.png';
  elt.onclick = function() { location.href = 't2.html'; }
}
</script>
</head><body>
<div><img id="i0" src="t1.png"></div>
<div><button onclick="test()">Test</button></div>
</body></html>

これを表示してtestを押し、別のページに移動→メモリ開放されてない
確認して無いけどたぶん動くはず。仕事中にて失礼

686 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 13:50:41 ID:???
わざわざ巨大な環境作って持たせたり何万回も呼べば
そりゃそうなるけど。みんなそんな何日もブラウザ
あげっぱなしなもんなのかね?

689 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 14:00:04 ID:???
>>686
256Mのノートパソコンで見ている人がメモリリークの問題のあるページを
2〜3経由するだけで、「メモリが不足しています」と表示されるんじゃないかな

690 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 14:01:57 ID:???
>>689
分かりました、謙虚になります ^_^;;
それで謙虚に質問なんですが、この問題の場合リークのない
コードって皆さんどういう風に書いてますか?マジに質問です。
勉強しますので。

691 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 14:07:53 ID:???
関係ないが勉強になった。偉いぞおまいら。

692 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 14:16:28 ID:???
クロージャ周りのメモリリークIE7/Fx2では修正されてるけどね。
今回の場合はIE6でも最後eltにnull代入すればリークしないし。

function test() {
  var elt = document.getElementById('i0');
  elt.src = 't2.png';
  elt.onclick = function() { location.href = 't2.html'; }
  elt = null;
}

695 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 14:31:48 ID:???
>>692
なるほど! eltにnullを代入すると確かに大丈夫になります。
なんですが、一般にはクロージャに取り込む変数に全部null
を入れるわけに行かないですよね。結局どういう指針で
コーディングすればいいんでしょう。 >>694 が間違ってる
のならどう違うのか知りたいですけど…

693 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 14:16:39 ID:???
>>690
あなたが誰か知らないけれど、
リークの仕組みを理解していれば回避は結構簡単です。

リークが発生するのはDOMの循環リンク管理とScriptの循環リンク管理を
ブラウザで別システムで扱ってしまっているため、システムを超えて参照すると
それがリークとして残ってしまうことが原因です。

一番簡単なのが使い終わったイベントハンドラをスクリプトで外すことです。
ただ、removeEventListener は昔のFirefoxで正常に動かない問題があったりするので
確実な方法ではありません(ちなみに循環参照のメモリリークはFirefox1.5でも発生します)

確実なのはクロージャを使わず、クロージャを実行する関数を別に作って
それに実行させることです。
その場合はDOMに割り当てられるのは関数のみになるので循環参照は起こりません。

クロージャが1個でも循環参照していると、そのスコープ全てがリークするので
メモリリークはそこそこ気を遣う価値のある問題だと思います。
最も今回の発端になったコードくらいだったらリークしてもいいじゃん、と思うけどw

694 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 14:25:41 ID:???
>>693
ありがとうございます。「クロージャを実行する関数を別に」
ってたとえばこういうことですよね。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html><head><title>???</title>
<script type="text/javascript">
function handle() { location.href = 't2.html'; }
function test() {
  var elt = document.getElementById('i0');
  elt.src = 't2.png'; elt.onclick = handle;
}
function test1() {
  for(var i = 0; i < 10000; ++i) test();
}
</script>
</head><body>
<div><img id="i0" src="t1.png"></div>
<div><button onclick="test1()">Test</button></div>
</body></html>

でもこれだとGeckoですごい勢いでメモリ消費してるよう
なんですけど…何か間違ってます?他の人がどう書いてるのか
教えて欲しいのでよろしくお願いします…

698 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 15:10:02 ID:???

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html><head><title>???</title>
<script type="text/javascript">
function test() {
  var elt = document.getElementById('i0');
  elt.src = 't2.png'; elt = null;
}
function test1() {
  for(var i = 0; i < 10000; ++i) test();
}
</script>
</head><body>
<div><img id="i0" src="t1.png"></div>
<div><button onclick="test1()">Test</button></div>
</body></html>

Firefox1.0だとこれでメモリリークが起こる。
もうわけわからんが、クロージャの問題でないことは間違いないな。

702 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 15:25:47 ID:???
>>694
そのやり方でもメモリリークは防げるが、クロージャ使ったときのように
test内の変数をイベントハンドラ内で使うことができなくなる。
多分>>693が言いたかったのはこんな感じ。

function createLeakFreeClosure(closure) {
  var count = createLeakFreeClosure.count++;
  createLeakFreeClosure[count] = closure;
  closure = null;
  return function() { return createLeakFreeClosure[count].apply(this, arguments); };
}
createLeakFreeClosure.count = 0;

function test() {
  var elt = document.getElementById('i0');
  elt.src = 't2.png';
  elt.onclick = createLeakFreeClosure(function() { location.href = 't2.html'; });
}

>>695
大雑把に言えばDOM周りのオブジェクトへの参照を切っとけばOK。
例えば>>685のdummyはDOMとは関係ないJavaScriptの配列なので
nullを代入して参照を切る必要はない。ただ、参照を切るやり方だと
クロージャ内でeltを使いたいとかいったときに困るから、
そうした場合は↑の方法のほうがいいと思う。

それからFirefox/Geckoでリークしてるという人、何を根拠にそう判断した?
Mozillaの中の人の非公式な弁明は見たか?
ttp://www.d-toybox.com/studio/weblog/show.php?mode=single&id=2004120603

704 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 15:31:05 ID:???
>>702
> それからFirefox/Geckoでリークしてるという人、何を根拠にそう判断した?

"calling addEventListener with a closure holding a content node leaks the document"
https://bugzilla.mozilla.org/show_bug.cgi?id=241518

1.5には反映されて無い

705 名前:702[sage] 投稿日:2006/11/24(金) 15:39:05 ID:???
>>704
すまんね、702の最後は主に>>694の「Geckoですごい勢いで
メモリ消費してるようなんですけど」と>>698に向けた言葉だった。
確かにFirefox 1.5でもクロージャを直接イベントハンドラ
設定したときのメモリリークは起きる。Firefox 2では修正されてるけど。

707 名前:Name_Not_Found[sage] 投稿日:2006/11/24(金) 16:00:11 ID:???
>>705
はい、おっしゃる意味は分かりますけど、実行によってプロセスの
使用する記憶領域(実記憶、仮想記憶)が「ものすごく増えたまま」
になるのはGCが効果をもたらしてない可能性が高いという意味で
「怪しい」んじゃないですかね。厳密には分からないというのは
そうですけど…

まあ、回避方法としてはJavaScript側のどこかにクロージャを
持たせてそれを環境を持たない関数で呼び出すということですね。

ところで >>702 だと大量にクロージャ生成してそれ使ったとき
全部配列に並べて持ったままになると思うけどそれはそういう
ものだということ?

718 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 06:54:19 ID:???
>>702 ってクロージャリークを回避してるの?
うへー、なんでそれで回避になるのかわかんねw
function内で変数つくって function作った時点でクロージャなるんじゃないんだ?

719 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 08:00:36 ID:???
変数作らなくてもargumentsとかは存在してるからなー。

720 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 09:17:06 ID:???
>>718
elt.onclickに関連付けられている関数自体がtestスコープに参照を持ってないのに注目
この場合、ブラウザの実装として、内部で関数呼び出しを単純なハッシュテーブルのインデックスで扱うので
DOMシステムとスクリプトシステムをまたいだ循環参照にならない。

循環参照をJavaScriptの中に閉じ込めてしまっているのがポイント

721 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 11:17:48 ID:???
>>720
elt.onclickに関連づけられる関数(クロージャ)はargumentsへの
参照を持っていて、argumentsは引数への参照を持っていて、引数
は外側のクロージャを持っていて、外側のクロージャはtestスコープ
の参照を持っているけど、それでいいの?

722 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 11:30:27 ID:???
>>721
なんかよくわからん文章だな。解読するとこんな感じか?

elt.onclickに関連づけられる関数(クロージャ)
【=createLeakFreeClosureのreturn文で返されるクロージャ】は
【createLeakFreeClosureの】argumentsへの参照を持っていて、
【createLeakFreeClosureの】argumentsは【createLeakFreeClosureの】
引数【であるclosure】への参照を持っていて、引数【closure】
は外側のクロージャ【=test内でcreateLeakFreeClosureの
呼び出し時に引数として渡されたクロージャ】を持っていて、
外側のクロージャ【=test内で作られたクロージャ】は
testスコープの参照を持っているけど、それでいいの?

だとしたら「引数は外側のクロージャを持っていて」が間違い。
createLeakFreeClosure内でclosureにnullを代入して参照を切ってるだろ。

723 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 11:31:19 ID:???
>>721
> elt.onclickに関連づけられる関数(クロージャ)はargumentsへの
> 参照を持っていて
持っていないんじゃない?
自分自身のargumentsの参照を持つという意味なら当然持ってるけど、
そのargumentsは外側のクロージャを参照していないし。

725 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 12:35:39 ID:???
>>723-724
なるほど。勝ち負けは別にどうでもいいんだけど、まとめなら洩れも
欲しい。結局どう書けばいいのかまだ分からん。>>702 みたいに使ってる
ものもいないものもクロージャ全部抱えておいても、別のページに
移動すれば解放されるからそれでいいってこと?そして >>698 みたいに
null入れるだけでもメモリ圧迫されるのは処理系のバグなの?

726 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 12:43:41 ID:???
>>720
createLeakFreeClosureは渡されるクロージャーへの参照を持っているので
createLeakFreeClosureを通して参照関係は残ってるんじゃないの?

727 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 12:45:09 ID:???
で、たとえば最初のコードでeltにnull入れて消せばいいんでしょうか。
それでいいならこの場合一番ラクちんだと思うけど。真面目に質問なので
教えてください。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html><head><title>???</title>
<script type="text/javascript">
function test() {
  var elt = document.getElementById('i0');
  elt.src = 't2.png';
  elt.onclick = function() { location.href = 't2.html'; }
  elt = null;
}
</script>
</head><body>
<div><img id="i0" src="t1.png"></div>
<div><button onclick="test()">Test</button></div>
</body></html>

728 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 12:46:37 ID:???
>>725
そもそも循環参照リークだって処理系のバグみたいなもの。

729 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 12:48:11 ID:???
スクリプト言語でこんな心配しなきゃならんのか

730 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 12:53:17 ID:???
>>720
解説ありがとう。
うー、でもまだイマイチわからない・・・。
> http://www.microsoft.com/japan/msdn/ie/general/ie_leak_patterns.aspx
ここに

実際 >>702 試したらそのように動いたのは面白かった。

>>727
実際試してみればわかるけど、最初のコードで言えばそれでもいいんじゃね。
ってか2行くらいだったらそもそも elt を作らなくてもいいし、それでもいいかも。

731 名前:730[sage] 投稿日:2006/11/25(土) 12:57:43 ID:???
間違って途中で送ってもうたw
> http://www.microsoft.com/japan/msdn/ie/general/ie_leak_patterns.aspx
ここに
> 要素がクロージャーを参照し、クロージャは親関数パラメータへのスコープ参照を保持します。ここでリークします。
ってあるんだけど、
>>726 もいってるように、>>702 で動くのがちょっと不思議。
もう 1時間くらい悩んでくる ノシ

732 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 13:13:37 ID:???
>>726
createLeakFreeClosureはいわばグローバル変数であり、
test内で作られるクロージャもcreateLeakFreeClosure内で作られる
クロージャもcreateLeakFreeClosureへの参照は持っていない。
クロージャがもつのは親関数の引数及び変数への参照ね。

733 名前:730[sage] 投稿日:2006/11/25(土) 14:55:28 ID:???
えーと、
・elt.onclick が呼び出そうとする関数が、クロージャの関数そのもの。
→ リークする

・elt.onclick が呼び出そうとする関数が、普通の関数の場合
→ リークしない

・elt.onclick が呼び出そうとする関数が、普通の関数で、
  クロージャの関数を別の(グローバルな)場所に退避させておき、その関数の中からそれを直接呼び出す場合
→ リークしない

ですかね?
>>702 はそれを動的に作っている感じか。

最初のコードの場合、↓みたいに簡単に変更すれば、クロージャも利きつつ、リークもしなくなるっぽい。(IE6)

                                                                  • -
function test() {
  var dummy = [];
  for(var i=0;i<200000;i++) { dummy[i] = i; }

  var elt = document.getElementById('i0');
  elt.src = 't2.png';
  elt.onclick = test.run;
  test.func = function () { alert(elt && elt.src); alert(dummy.length); };
}
test.run = function () { test.func && test.func(); };
                                                                  • -

// test.func → test.run.func に変更するとリークしたりして (そりゃそうだわ)、いろいろ試してて勉強になった。

734 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 15:15:09 ID:???
>>733
なるほど分かりやすいまとめですね。外側で1個関数
作らないといけないのがあんまり嬉しくない感じは
するけどそれはもうしょうがないのかな。

744 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 17:31:45 ID:???
elt=nullで防げるメカニズムがよくわからん。

745 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 17:37:04 ID:???
>>744
あの場合の循環参照は DOMノードのonclickが関数クロージャを差し、関数
クロージャが変数eltへの参照を保持し、eltにはDOMノードが入っている、
というもの。事後にでも変数eltをnullにすればもはやDOMノードを指さなく
なるので循環参照でなくなる。

749 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 18:41:29 ID:???
だって後からnull入れたって変数elt自体は参照できるじゃん。
ただ参照したときの値がnullだっていうだけで。

752 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 19:14:15 ID:???
>>749
循環参照はされなくなるっ初夜

753 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 19:20:27 ID:???

  • DOMノードはonclickを持ってる
  • onclick は function() {}
  • function(){} は「test() 実行時に確保された変数領域」を参照している(*)
  • 「test() 実行時の変数領域」には var elt があり、DOMを指している

DOMノードを開放しようとしたとき、

  • onclickの開放が必要
  • onclickの開放には「test()実行時に確保された変数領域」の開放が必要
  • var eltの開放が必要
  • そのときeltがDOMノードを指してると循環でヴァー

なので、どこでもいつでもいいからeltがDOMを指してるのをやめればいいのか。

で、(*)のfunctionと変数領域の組がクロージャなんだと理解したんだけどあってる?

757 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 21:00:13 ID:???
>>753
だいたい合ってんじゃないかなー。
仕組みについては、だいたい >>693, >>720, あとは >>730 のMSへのリンクに書いてる感じだと思う。
後半の循環の仕組みは自分ははっきりとはわからないけど、↑の解説みてて受けた印象はそれとはちょっと違ったかも。

しかし、クロージャ、昨日までは、クロージャのなかで使ってる変数だけ選りすぐって都合よく参照するもんだと思ってた。
親関数内で function 作ったら、その親関数内部の変数まるごと参照しちゃうんだねえ・・・。

758 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 21:28:14 ID:???
>>730
>ってか2行くらいだったらそもそも elt を作らなくてもいいし、それでもいいかも。

この問題>>730がいうように、domをスクリプトの変数に保持する必要がなければ敢えて変数保持するなってことで解決したりしないの?

function test(){
  $('i0').src='t2.png';
  $('i0').onclik=function(){location.href = 't2.html';};
}

762 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 22:25:08 ID:???
>>758
いいだろうけど2回getElementById()なりを呼ぶことになるわけで…

763 名前:Name_Not_Found[sage] 投稿日:2006/11/25(土) 22:37:34 ID:???
>>753
クロージャとかの理解はいいと思うけど、そうやって再帰的に
たどりながら領域解放するという理解は違っているよ。だって
それじゃよそからも指されているところを解放してしまって破滅
するじゃん。Wikipediaでまず「ガベージコレクション」を読んで
それから「参照カウント」を読むことをすすめる。

771 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 02:52:45 ID:???
>>757
おれもれも。なんでfunction(){}は上の何も見てないのに〜とか混乱してた。

>>763
あー、参照カウント側から攻めたらわけわからんようになりそうだったので。
「Aの開放=Aへの参照が0になるためには」と読みかえればいいだけかな。

>>768
ついでにやっとクロージャも理解できた。

>>770
3年で陳腐化する知識を拒んでたらどうにも…

772 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 05:22:40 ID:???
>>768
そうか?俺vol3〜40前後の間にお世話になって、このスレの後半からまた見だしたものだが、
そん時同じくクロージャのことでメモリリークするからって>>702チックな書き方のコード見たことあるぞい。
記憶違いだったかな。

>>762

function test(){
  with($('i0')){
    src='t2.png';
    onclik=function(){location.href = 't2.html';};
}}

773 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 05:59:57 ID:???
>>1 のカコスレ全集で簡単に検索してみたけど、
メモリリークについては、今回ほどまとまって議論?はされてないような気がするね。
リークすんだろボケ! 的なのは何回もあったがw
>>702 みたいなコードまではでてないような?

>>772 のコード
おお! まじで!
・・・って思ったけど、めちゃめちゃリークされるやん、このウスラトンカチw

774 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 06:34:30 ID:???
しかし、Ajax

var req = new XMLHttpRequest();
req.onreadystatechange = function() {
  if (req.readyState != 4 || req.status != 200) return;
  // なんたらかんたら
};

ってコードがよくサンプルで乗ってるけど、
これも地味にメモリリークしちゃうのな。怖いねえ・・・。

775 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 06:53:45 ID:???
あれ。試してないけどprototype通してるからとかじゃなくて?
with(document.getElementById('i0')){
ならどうよ。>>730の論理はそういうことだと思われ。

776 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 06:57:35 ID:???
>>775
や、それでいかんねん。やってみ?

777 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 07:19:26 ID:???
>>774
グローバルスコープの変数なら違うだろ。曲解すんな。

778 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 07:32:08 ID:???
>>772

var src = "global";
function test() {
  with ($('i0')) {
    src = 't2.png';
    onclick = function() { alert(src); };
  }
}

とすると「global」ではなく「.../t2.png」と表示される。
これはクロージャが要素i0への参照を持っているから。

function test() {
  var handler = function() { location.href = 't2.html'; };
  with($('i0')){
    src = 't2.png';
    onclick = handler;
  }
}

ならリークしない。

779 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 07:46:08 ID:???
フォロー?クス。
要は、クロージャ―をDOMに直張りすんなってこと?

780 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 08:03:42 ID:???
>>777
グローバルの話なんかしてないしw
まあ>>774の省略コードじゃそうとられるかもしれんがw function 内に書いてるんだよ。

783 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 09:26:31 ID:???
>>777
ていうか、グローバルな変数に req を設定すれば、今度は Fx がリークする・・・、
っていうか、Fx だとページ移動しても元のページのJSの実行結果もキャッシュするみたいで、
つまりグローバルな変数の中身を保持し続けるから、更新しない限りページ移動してもメモリ減らないぞ。

まあ、走らせた後 req を null にすればいいんだけどさ。

787 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 12:06:33 ID:???
原理はよくわかってないんだけど、
>>702のコードを使用すればIE6とFirefox1.5でメモリリークの問題が起こらない
という認識に間違いはないよね?

788 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 13:23:17 ID:???
>>787
クロージャとDOMでのリークについてはそれでいいかと。
あと >>702 のコード、2つに分けたほうがいいかもしれない。 (addEventListener に対応するため)

789 名前:Name_Not_Found[sage] 投稿日:2006/11/26(日) 13:44:46 ID:???
>>783
FefoxやOperaが戻る進むの高速化のためにページの内容
(ソースではなく)をキャッシュしてるのとメモリリークを混同するなよ。

というかLeak Monitorで見る限り、Firefox 1.5はloadやunloadといった
特定のイベント以外は、普通の関数かクロージャかに関係なく
removeEventListenerしないとリークするような気がする。
詳しくは試してないし勘違いかもしれないけど。

834 名前:Name_Not_Found[sage] 投稿日:2006/11/28(火) 10:37:21 ID:???
イベントのatachとdetachについです。

javascriptの本とかweb上にあるサンプルを見ると
addEventListenerやatachEventを使った後に
removeEventListenerやdetachEventを使っていないのをよく見るのですが、
どういうときにremoveやdetachを行って、どういうときは行わないで良いのですか?

今までaddEventListenerなどで登録したままで終わりだったんですけど、
それだとメモリーリークが発生する恐れがあると言われたので。

基本的にdetachするのが当たり前ですかね?

839 名前:Name_Not_Found[sage] 投稿日:2006/11/28(火) 12:12:25 ID:???
>>834
メモリリークについては丁度このスレで話されてたとこだから、
>>670 あたりから読みつつ、「循環参照 メモリリーク javascript」 でぐぐっとけばいいと思うよ。

840 名前:834[sage] 投稿日:2006/11/28(火) 13:24:46 ID:???
>>839
そうです。この話を追っていて、そういえばって思い出し、
http://www.microsoft.com/japan/msdn/ie/general/ie_leak_patterns.aspx
のページを見たんだけど、
「じゃあ何で本やサンプルではそういうコーディングになってなく、説明もないのかな?」
と思ったわけです。

過去ログでもメモリリークの話は出てるけど、detachとかと絡めた話にはなってないなぁと。

841 名前:Name_Not_Found[sage] 投稿日:2006/11/28(火) 13:36:01 ID:???
>>840
本の著者がそういう知識を持ってないからじゃないのかな

日本語でまともな本は皆無

842 名前:Name_Not_Found[sage] 投稿日:2006/11/28(火) 14:26:46 ID:???
>>840
実用上問題視されないから。

最近は、同一ページで作業する時間が長いアプリケーションとか増えてきたから、そういうアプリが気にする様な話だ。

845 名前:Name_Not_Found[sage] 投稿日:2006/11/28(火) 15:44:11 ID:???
>>841
オライリーjavascript&DHTMLクックブックも同じ感じだったんですよね。
情報が古いから仕方ない?

>>842
ってことはページが切り替われば問題なくなるということですか?
unloadする時に全部detachした方が良いと言うのを最近の本で見たのですけど、
どうなのでしょう?

846 名前:Name_Not_Found[sage] 投稿日:2006/11/28(火) 15:59:00 ID:???
>>845
ほんとにこのスレの流れ読んだのかいなw

・循環参照にならないように気をつける (ならないのなら以下の対策はいらない)
・別の関数をつかう (クロージャは使えない)
・>>702 みたいにクッションを置く
・unload時に remove する

選択肢はこんなか?