EventSource 中的事件侦听器是否会自动进行垃圾收集?
我在 Stackoverflow 上看到过答案,提到如果 DOM 元素超出范围,它的事件监听器将被自动垃圾收集。非 DOM 元素也是如此,例如 事件源?
下面是我的意思的一个示例:(
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
事件。这只是一个示例)
在这种情况下,当 externalSSE
时,之前的事件处理程序会被垃圾回收吗? code> 在第 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 秒(我的服务器发送它们的时间间隔)就会记录到控制台。尽管事件
立即超出范围并且在程序中的其他任何地方都无法访问,但仍然如此。
I've seen answers on Stackoverflow mentioning how if a DOM element goes out of scope, its event listeners are automatically garbage collected. Is that also the case with non-DOM elements, such as EventSource?
Here's an example of what I mean:
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();
(I know I can use the onerror
event. This is just an example)
In this case, would the previous event handlers be garbage collected when establishSSE
was re-ran on line 4?
Edit:
I don't understand how these values can be garbage collected. For example, the following works:
function sseSubscribe() {
let events = new EventSource("/v1/sse/mySSEID");
events.addEventListener("message", sseHeartbeat);
events.addEventListener("reply", sseReply);
}
That is, new messages still cause the message
and reply
handlers to run.
But events
is no longer accessible from anywhere within the program. So, shouldn't it have been garbage collected? It seems like EventSource
is never garbage collected (unless maybe the connection is broken?)
Edit #2:
It seems that even if the event handlers are anonymous, like so:
function sseSubscribe() {
let events = new EventSource("/v1/sse/SSEID");
events.addEventListener("message", function() {
console.log("Heartbeat");
});
}
sseSubscribe();
This still works on my end. That is, Heartbeat
is being logged into the console every 30 seconds (the interval my server sends them). This is despite events
immediately going out of scope and being inaccessible anywhere else in the program.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
回答问题标题
是和否(来自评论)。当无法在任何地方访问事件源值时,可以对其进行垃圾回收1。当并且如果它被垃圾收集时,任何声明为
addEventListener
的内联参数的匿名事件处理函数都将被垃圾收集。无法收集仍可访问的更全局定义的函数。当无法在代码中访问 JavaScript 值时,它们就符合垃圾回收的条件。
events
值的事件侦听器函数记录在对象的事件侦听器映射中:如果在代码中无法访问该对象,则也无法访问事件侦听器。 如果到达它们的唯一方法是通过事件处理程序映射(它们是匿名的),则可以进行垃圾收集。每次调用建立SSE 时,它都会返回一个新的 EventSource 对象 - 作为对象“值”,该对象与上次调用时返回的对象不同。
当传递给
checkStatus
时,events
的值保存在传递给setTimeout
的函数的闭包中。因此,当超时到期并执行计时器功能时,将“达到”该值。接下来发生两种情况之一
events
中的EventSource 对象仍然打开。再次调用checkStatus
会创建新的超时。这会为新的计时器回调函数创建一个新的闭包,但具有相同的events
参数值。因此,仍然可以达到events
的(未更改的)值,并且无法进行垃圾收集。events
中的事件源已关闭。在这种情况下,checkStatus
调用builtSSE
(以重新打开源)并从计时器调用返回,而不将其参数存储在任何地方或将其保留在可以执行的闭包范围内稍后。此时,已关闭的events
对象就可以进行垃圾回收,因为无法访问该对象。builtSSE
继续创建一个保存在名为events
的变量中的新值,并用它调用checkStatus
,但它不一样值作为调用堆栈中其上方的checkStatus
调用中超出范围的值。但是,由于
sseReply
和sseRefresh
未嵌套在estabishSSE
中,因此它们不会仅仅因为事件
就被垃圾回收> 对象值已被GC。1 例外:blob 或文件对象参数传递给
createObjectURL
保存在内存中(当网络流量检索其数据时),必须通过调用URL.revokeObjectURL
显式释放。编辑答案
问题编辑后更新
这是一个无效的假设,可能会导致以下问题:
checkStatus 的
events
参数位于checkStatus< 返回的计时器回调函数的范围内/code> 因为回调函数嵌套在
checkStatus
中。回调函数无法被垃圾回收,因为
setTimeout
的[本机代码]实现必须持有对其的引用。如果
events
的值未处于关闭状态,计时器回调将通过使用现有checkStatus
重新调用来创建新的计时器回调函数>events 对象值,为新的setTimeout
调用生成新的回调函数。重复步骤 1。
如果直接将
checkStatus
用作计时器回调,而不对其自身进行延迟递归调用(例如通过setTimeout
传递参数),则可能会更直接,也不会那么混乱。 code>:如果实现上述更改,
externalSS
还需要将checkStatus
传递给setTimeout
作为其第一个参数,而不先调用它:Answer to question title
Yes and no (from comment). The event source value can be garbage collected when it can't be accessed anywhere1. When and if it is garbage collected, any anonymous event handler functions declared as inline arguments to
addEventListener
will be garbage collected. Functions defined more globally that can still be reached can't be collected.JavaScript values become eligible for garbage collection when they can't be reached in code.
The event listener functions of an
events
value are recorded in a map of event listeners of the object: if the object can't be reached in code, the event listeners can't be reached either. If the only way or reaching them was through the event handler map (they were anonymous) the can be garbage collected.Each time
establishSSE
is called it returns a new EventSource object - which, as an object "value", is different than the one returned the previous time it was called.When passed to
checkStatus
, the value ofevents
is held in a closure of the function passed tosetTimeout
. Hence the value will be "reached" when the timeout expires and the timer function executed.One of two cases occur next
The EventSource object in
events
is still open. A new timeout is created by callingcheckStatus
again. This creates a new closure for a new timer callback function, but with the same value of theevents
argument. Hence the (unchanged) value ofevents
can still be reached and can't be garbage collected.The event source in
events
has been closed. In this casecheckStatus
callsestablishSSE
(to reopen the source) and returns from the timer call without storing its argument anywhere or retaining it in the scope of a closure that can be executed at a later time. The closedevents
object becomes eligible for garbage collection at this point because it can't be reached.establishSSE
goes on to create a new value held in a variable calledevents
, and callscheckStatus
with it, but it's not the same value as the one going out of scope in thecheckStatus
call above it in the call stack.However, since
sseReply
andsseRefresh
are not nested withinestabishSSE
, they won't be garbage collected just because anevent
object value was gc'ed.1 With an exception: blob or file object arguments passed to
createObjectURL
are held in memory (while network traffic retrieves their data) and must be explicitly released by callingURL.revokeObjectURL
.Answer to Edits
Updated after question edit
This is an invalid assumption which may have lead to the question:
The
events
argument ofcheckStatus
is in scope of the timer callback function returned bycheckStatus
because the callback function is nested withincheckStatus
.The callback function is prevented from being garbage collected because the [native code] implementation of
setTimeout
necessarily holds a reference to it.If the value of
events
is not in a closed state, the timer callback creates a new timer callback function by making a fresh call tocheckStatus
using the existingevents
object value, to generate a new callback function for a newsetTimeout
call.Repeat from step 1.
It might be more straight forward and less confusing if
checkStatus
were used as the timer callback directly without making a delayed recursive call to itself, say by passing arguments viasetTimeout
:If implementing the change above,
establishSS
would also need to passcheckStatus
tosetTimeout
as its first argument without calling it first: