2012-01-12 14 views
3

次のコードを検討してください(これはChromeのデベロッパーコンソールに入れて確認できます)。Javascript/ECMAScriptガベージコレクション

var obj = { 
    f: function() { 
     var myRef = this; 
     val = setTimeout(function() { 
      console.log("time down!"); 
      myRef.f(); 
     }, 1000); 
    } 
}; 

私はその後、

obj.f(); 

はタイマーを開始するために実行した場合、私は "ダウンタイム!" 秒ごとに見ることができます私はその後、

obj = null; 

を実行する場合

タイマーがまだ起動します。

なぜガーベジコレクションはタイマーをクリアしていないのですか?怖いのは、のタイマーを今すぐ削除する方法がないようです。 - 私は正しいですか?

私の推測では、技術的にはwindowにはまだオブジェクトへの参照が保持されていますが、結果としてオブジェクトはメモリ内にとどまっています。私は別のECMAベースの言語(Actionscript)でこの問題を経験し、それを扱うためのライブラリを構築しましたが、Javascriptは別のアプローチを取ると考えていました。

+0

それは答えをチェックアウトする価値があるかもしれません:http://stackoverflow.com/questions/858619/viewing-all-the-timouts-intervals-in-javascriptそれはあるようですが停止する方法はありませんこのようにしたらタイマー! –

+1

これは特別な処理が必要な問題ではありません。これは設計によるものです。ユーザーがページから移動する前にタイマーを停止させる場合は、 'setTimeout'の戻り値を放棄するのではなく、' clearTimeout'を使ってその値を止めることができます。 –

+0

実際のコードでは、このようなことは実際には起こりません。このインライン関数宣言、コンテナオブジェクト、およびあるスコープから別のスコープへの漏洩のこのような組み合わせはすべて、コードを読みにくくします。少し包装がいくつかの問題を解決できるいくつかの状況がありますが、これはそれらの1つのようではありません。 – aaaaaaaaaaaa

答えて

7

objsetTimeoutに渡すクロージャは、実行するために保持する必要があるため、ガベージコレクトされません。そして、それはmyRefをキャプチャするので、順番にobjへの参照を保持します。

このクロージャーを(配列などで)保持している他の関数に渡した場合も同じです。

タイマーを今すぐ削除する方法はありません。恐ろしいハックはありません。しかし、これはかなり自然なことです。それは、それ自体の後でクリーンアップするオブジェクトの仕事です。このオブジェクトの目的は無制限にタイムアウトを発生させることです。そのため、オブジェクトは決して自分自身で決して決してクリーンアップしないように意図しています。そうしている間は、少なくともいくつかのメモリを使い切ることなく、何かが永遠に起こることは期待できません。


恐ろしいハック:タイマーIDは整数だけなので、あなたが1000000000から1、たとえば、からのループができ、各整数にclearTimeoutを呼び出します。明らかに、これは他のランニングタイマーを殺すでしょう!

+1

+1脚注 – zvolkov

+0

ガベージコレクションを妨げているsetTimeout()であることをどのように知っていますか? console.log()であり、console.log()を削除するとガベージコレクトが発生する可能性があります。 – James

0

を呼び出すまでsetTimeout()の実装で何かが参照を保持しているため、ガベージコレクタはタイマ関数をクリアしません。

「setTimeout()」によって返された値をクリアして参照を削除すると、「メモリリーク」が発生しています(タイマー機能を削除できないという点)。

1

K2xLさんのコメントに対する応答です。

あなたの機能のマイナーな調整で、あなたの提案どおりに動作します。objifが失敗する新しい値を指定すると、伝播は停止し、全体の多くは、ごみを収集することができます:私は少しフラットな構造を好む

var obj = { 
    f: function() { 
     var myRef = this; 
     if(myRef===obj){ 
      val = setTimeout(function() { 
       console.log("time down!"); 
       myRef.f(); 
      }, 1000); 
     } 
    } 
}; 

、あなたはオブジェクトコンテナをスキップして頼ることができます

(function(){ 
    var marker={} 
    window.obj=marker 
    function iterator(){ 
     if(window.obj===marker){ 
      setTimeout(iterator,1000) 
      console.log("time down!") 
     } 
    } 
    iterator() 
})() 

マーカーに必要な任意のオブジェクトを使用できますが、これは簡単にドキュメント要素になる可能性があることに注意してください。

(function(){ 
    var marker=document.getElementById("marker") 
    function iterator(){ 
     if(document.getElementById("marker")===marker){ 
      setTimeout(iterator,1000) 
      console.log("time down!") 
     } 
    } 
    iterator() 
})() 
1
  • リスト項目:同じIDを持つ新しい要素がその場所に建立された場合でも、新たな要素が古いものと等しくないとして伝播は、まだ要素が文書から削除されたときに停止します

もちろん、タイマーはまだ起動します。 myRef.fでネストした関数の中で再帰的に呼び出すことになります。

あなたの推測では、ウィンドウにobjへの参照が保持されていたということでした。しかし、それはsetTimeoutが再帰的に呼び出される理由ではなく、キャンセルするために何ができるのでもない。

タイマーをクリアする方法はいくつかあります。 1つの方法は、最初に条件関数を渡すことです。

タイマーを停止するには、単にclearTimeoutを呼び出してから、再帰的にsetTimeoutを呼び出しないでください。基本的な例:

(識別子valはグローバルオブジェクトのプロパティとして作成され、常にVARを使用しています。!)

ことからのステップアップを移動
 
var obj = { 
    f : function (i) { 
     // (GS) `this` is the base of `f` (aka obj). 
     var myRef = this; 
     var timer = setTimeout(function() { 
      if(i == 0) { 
       clearTimeout(timer); 
       return; 
      } 
      console.log(i, "time down!"); 
      myRef.f(--i); 
     }, 1000); 
    } 
}; 

obj.f(4); 

、isDone方法はレフリーとより高機能のチェックを提供することができます返されたsetTimeoutはsetIntervalに変更できます。

 
var obj = { 
    f : function (i, isDone, animEndHandler) { 
     var timer = setInterval(function() { 
      console.log(i, "time down!"); 
      if(isDone(--i)) { 
       animEndHandler({toString: function(){return"blast off!"}, i: i}); 
       clearInterval(timer); 
      } 
     }, 1000); 
    } 
}; 

function isDone(i) { 
    return i == 0; 
} 

function animEndHandler(ev) { 
    console.log(""+ev); 
} 
obj.f(3, isDone, animEndHandler); 
関連する問題