2012-01-12 37 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似乎沒有辦法阻止定時器完成後就像這樣! – 2012-01-12 21:18:36

+1

這不完全是需要特殊處理的問題 - 這是設計。如果你打算在用戶離開頁面之前停止一個定時器,那麼保存來自'setTimeout'的返回值而不是將其拋出,所以你可以使用'clearTimeout'來停止它。 – 2012-01-12 21:23:03

+0

你真的不會在真實代碼中使用任何看起來像這樣的東西嗎?內聯函數聲明,容器對象以及從一個範圍泄漏到另一個範圍的整個組合,這些都使得代碼難以閱讀。在少數情況下,一些包裝可以解決一些問題,但這似乎並不是其中的一種。當然 – aaaaaaaaaaaa 2012-01-12 21:39:49

回答

7

obj不會垃圾收集,因爲您傳遞給setTimeout的封閉必須保留以便執行。並且它反過來保留對obj的引用,因爲它捕獲了myRef

如果您將該閉包傳遞給任何其他保留它的函數(例如在數組中),情況也是如此。

現在沒有辦法刪除計時器,沒有可怕的黑客攻擊。但是這很自然:它是一個物體的工作,需要自行清理。這個對象的目的是無限地觸發一次超時,這樣對象顯然不打算在自身之後清理,這可能是合適的。如果沒有至少使用一些內存,你不能指望永遠發生。


可怕的黑客:因爲計時器的ID只是整數,你可以從,比如說環,1 1000000000,並在每個整數調用clearTimeout。顯然這會殺死其他正在運行的計時器!

+1

+1 – zvolkov 2012-01-27 16:09:45

+0

你怎麼知道它是防止垃圾收集的setTimeout()?它可能是console.log(),如果你刪除了console.log(),它可能會垃圾收集。 – James 2014-09-03 18:49:06

0

垃圾收集器不會清除定時器功能,因爲setTimeout()實施中的某些內容會保留對其的引用,直到您致電clearTimeout()

如果你沒有清除它並將引用放到「setTimeout()」返回的值上,那麼你引入了「內存泄漏」(因爲定時器函數不能被刪除)。

1

迴應K2xL的評論。

對您的功能進行微調,它的行爲與您的建議相同。如果obj被賦予了新的價值if會失敗,傳播將停止,並且一大堆可以被垃圾收集:

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() 
})() 

注意,您可以使用您的標記想要的任何物體,這很容易被文檔元素。即使使用相同ID的新元素在其位置豎立的傳播仍然會停止當元素從文檔中刪除作爲新的元素不等於舊:

(function(){ 
    var marker=document.getElementById("marker") 
    function iterator(){ 
     if(document.getElementById("marker")===marker){ 
      setTimeout(iterator,1000) 
      console.log("time down!") 
     } 
    } 
    iterator() 
})() 
1
  • 列表項

當然定時器仍然火災;你用myRef.f遞歸地在嵌套函數中調用它。

你的猜測是,該窗口包含到OBJ參考。然而,這是真的,這並不是爲什麼遞歸調用setTimeout,也不能做什麼來取消它。

有幾種方式來提供定時結算。一種方法是在開始時傳遞一個條件函數。

要停止計時,只要調用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);