EventSource 中的事件侦听器是否会自动进行垃圾收集?

发布于 2025-01-13 08:37:04 字数 1678 浏览 3 评论 0原文

我在 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);
}

也就是说,新消息仍然会导致 messagereply 处理程序运行。

但是 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

愁以何悠 2025-01-20 08:37:04

回答问题标题

是和否(来自评论)。当无法在任何地方访问事件源时,可以对其进行垃圾回收1。当并且如果它被垃圾收集时,任何声明为 addEventListener 的内联参数的匿名事件处理函数都将被垃圾收集。无法收集仍可访问的更全局定义的函数。

  • 当无法在代码中访问 JavaScript 值时,它们就符合垃圾回收的条件。

  • events 值的事件侦听器函数记录在对象的事件侦听器映射中:如果在代码中无法访问该对象,则也无法访问事件侦听器。 如果到达它们的唯一方法是通过事件处理程序映射(它们是匿名的),则可以进行垃圾收集

每次调用建立SSE 时,它都会返回一个新的 EventSource 对象 - 作为对象“值”,该对象与上次调用时返回的对象不同。

当传递给 checkStatus 时,events 的值保存在传递给 setTimeout 的函数的闭包中。因此,当超时到期并执行计时器功能时,将“达到”该值。

接下来发生两种情况之一

  1. events 中的EventSource 对象仍然打开。再次调用 checkStatus 会创建新的超时。这会为新的计时器回调函数创建一个新的闭包,但具有相同的 events 参数值。因此,仍然可以达到 events 的(未更改的)值,并且无法进行垃圾收集。

  2. events 中的事件源已关闭。在这种情况下,checkStatus 调用 builtSSE(以重新打开源)并从计时器调用返回,而不将其参数存储在任何地方或将其保留在可以执行的闭包范围内稍后。此时,已关闭的 events 对象就可以进行垃圾回收,因为无法访问该对象。

    builtSSE 继续创建一个保存在名为 events 的变量中的新值,并用它调用 checkStatus,但它不一样值作为调用堆栈中其上方的 checkStatus 调用中超出范围的值。

但是,由于 sseReplysseRefresh 未嵌套在 estabishSSE 中,因此它们不会仅仅因为 事件 就被垃圾回收> 对象值已被GC。


1 例外:blob 或文件对象参数传递给 createObjectURL 保存在内存中(当网络流量检索其数据时),必须通过调用 URL.revokeObjectURL 显式释放。


编辑答案

问题编辑后更新

但是事件不再可以从程序内的任何位置访问。

这是一个无效的假设,可能会导致以下问题:

  1. checkStatus 的 events 参数位于 checkStatus< 返回的计时器回调函数的范围内/code> 因为回调函数嵌套在 checkStatus 中。


  2. 回调函数无法被垃圾回收,因为 setTimeout 的[本机代码]实现必须持有对其的引用。

  3. 如果 events 的值未处于关闭状态,计时器回调将通过使用现有 checkStatus 重新调用来创建新的计时器回调函数>events 对象值,为新的 setTimeout 调用生成新的回调函数。

  4. 重复步骤 1。

如果直接将 checkStatus 用作计时器回调,而不对其自身进行延迟递归调用(例如通过 setTimeout 传递参数),则可能会更直接,也不会那么混乱。 code>:

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

如果实现上述更改,externalSS 还需要将 checkStatus 传递给 setTimeout 作为其第一个参数,而不先调用它:

function establishSSE() {
  const events = new EventSource("/sse/" + sseID);

  events.addEventListener("reply", sseReply);
  events.addEventListener("refresh", sseRefresh);

  setTimeout(checkStatus, 500, events);
}

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 of events is held in a closure of the function passed to setTimeout. Hence the value will be "reached" when the timeout expires and the timer function executed.

One of two cases occur next

  1. The EventSource object in events is still open. A new timeout is created by calling checkStatus again. This creates a new closure for a new timer callback function, but with the same value of the events argument. Hence the (unchanged) value of events can still be reached and can't be garbage collected.

  2. The event source in events has been closed. In this case checkStatus calls establishSSE (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 closed events 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 called events, and calls checkStatus with it, but it's not the same value as the one going out of scope in the checkStatus call above it in the call stack.

However, since sseReply and sseRefresh are not nested within estabishSSE, they won't be garbage collected just because an event 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 calling URL.revokeObjectURL.


Answer to Edits

Updated after question edit

Butevents is no longer accessible from anywhere within the program.

This is an invalid assumption which may have lead to the question:

  1. The events argument of checkStatus is in scope of the timer callback function returned by checkStatus because the callback function is nested within checkStatus.

  2. The callback function is prevented from being garbage collected because the [native code] implementation of setTimeout necessarily holds a reference to it.

  3. 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 existing events object value, to generate a new callback function for a new setTimeout call.

  4. 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 via 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);
}

If implementing the change above, establishSS would also need to pass checkStatus to setTimeout as its first argument without calling it first:

function establishSSE() {
  const events = new EventSource("/sse/" + sseID);

  events.addEventListener("reply", sseReply);
  events.addEventListener("refresh", sseRefresh);

  setTimeout(checkStatus, 500, events);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文