我在Stackoverflow上看到了答案,其中提到如果DOM元素超出范围,它的事件侦听器将自动被垃圾收集。对于非DOM元素(如EventSource),是否也是情况?
下面是我的意思的一个例子:
function checkStatus(events) {
return function() {
if (events.readyState === EventSource.CLOSED) {
establishSSE(); // Is this fine?
} else {
setTimeout(checkStatus(events), 500);
}
}
}
function establishSSE() {
const events = new EventSource("/sse/" + sseID);
events.addEventListener("reply", sseReply);
events.addEventListener("refresh", sseRefresh);
setTimeout(checkStatus(events), 500);
}
establishSSE();
(我知道我可以使用onerror
事件。这只是一个例子)
在这种情况下,当establishSSE
在第4行重新运行时,以前的事件处理程序会被垃圾收集吗?
编辑:
我不明白这些值怎么会被垃圾收集。例如,以下工作:
function sseSubscribe() {
let events = new EventSource("/v1/sse/mySSEID");
events.addEventListener("message", sseHeartbeat);
events.addEventListener("reply", sseReply);
}
也就是说,新消息仍然会导致message
和reply
处理程序运行。
但是events
不再可以从程序中的任何位置访问。那么,它不应该被垃圾收集吗?似乎EventSource
从未被垃圾回收(除非连接可能断开?)
编辑#2:
似乎甚至如果事件处理程序是匿名的,就像这样:
function sseSubscribe() {
let events = new EventSource("/v1/sse/SSEID");
events.addEventListener("message", function() {
console.log("Heartbeat");
});
}
sseSubscribe();
这个仍然对我有效。也就是说,Heartbeat
每30秒登录一次控制台(我的服务器发送它们的间隔)。尽管events
立即超出范围,并且在程序中的其他任何地方都无法访问,但情况依然如此。
问题标题的答案
是和否(来自评论)。事件源值在任何地方都无法访问时,可以进行垃圾收集1。当它被垃圾收集时,任何被声明为addEventListener
的内联参数的匿名事件处理程序函数都将被垃圾收集。无法收集更全局定义的、仍然可以访问的函数。
-
当JavaScript值在代码中无法访问时,它们就有资格进行垃圾收集。
-
events
值的事件监听器函数记录在对象的事件监听器映射中:如果在代码中无法访问对象,则也无法访问事件监听器如果访问它们的唯一方法是通过事件处理程序映射(它们是匿名的),则可以进行垃圾收集。
每次调用establishSSE
时,它都返回一个新的EventSource对象,该对象作为对象";值";,与上次调用时返回的不同。
当传递给checkStatus
时,events
的值保持在传递给setTimeout
的函数的闭包中。因此,该值将是"0";达到";当超时到期并且定时器功能被执行时。
下一次出现两种情况之一
-
events
中的EventSource对象仍然处于打开状态。通过再次调用checkStatus
来创建新的超时。这为新的计时器回调函数创建了一个新的闭包,但具有相同的events
参数值。因此,events
的(未改变的)值仍然可以达到,并且不能被垃圾回收。 -
events
中的事件源已关闭。在这种情况下,checkStatus
调用establishSSE
(以重新打开源)并从计时器调用返回,而不将其参数存储在任何位置,也不将其保留在以后可以执行的闭包范围内。此时,由于无法访问已关闭的events
对象,因此该对象有资格进行垃圾收集。establishSSE
继续创建一个保存在名为events
的变量中的新值,并用它调用checkStatus
,但它与调用堆栈中它上面的checkStatus
调用中超出范围的值不同。
但是,由于sseReply
和sseRefresh
没有嵌套在estabishSSE
中,因此它们不会因为event
对象值为gc’ed而被垃圾收集。
1有一个例外:传递给createObjectURL
的blob或文件对象参数保存在内存中(而网络流量检索其数据),必须通过调用URL.revokeObjectURL
显式释放。
编辑答案
问题编辑后更新
但是
events
不再可以从程序中的任何位置访问。
这是一个无效的假设,可能会导致以下问题:
-
checkStatus
的events
参数在checkStatus
返回的计时器回调函数的范围内,因为回调函数嵌套在checkStatus
中。 -
由于
setTimeout
的[本机代码]实现必然包含对回调函数的引用,因此可以防止回调函数被垃圾回收 -
如果
events
的值不处于关闭状态,则计时器回调通过使用现有的events
对象值对checkStatus
进行新的调用来创建新的计时器回调函数,以为新的setTimeout
调用生成新的回调函数。 -
从步骤1开始重复。
如果直接将checkStatus
用作计时器回调,而不对其本身进行延迟递归调用,例如通过setTimeout
:传递参数,则可能会更直接,也不会那么令人困惑
function checkStatus(events) {
if (events.readyState === EventSource.CLOSED) {
establishSSE(); // create a new EventSource object
return; // the actual argument object value is now inaccessible
// and becomes eligble for garbage collection.
}
setTimeout( checkStatus, 500, events);
}
如果实现上述更改,establishSS
还需要将checkStatus
传递给setTimeout
作为其第一个参数,而不首先调用它:
function establishSSE() {
const events = new EventSource("/sse/" + sseID);
events.addEventListener("reply", sseReply);
events.addEventListener("refresh", sseRefresh);
setTimeout(checkStatus, 500, events);
}