我是 javascript 新手,但有一些 C++ 背景,因此以下内容让我感到困惑:我从浏览器创建一些 WebSocket 连接,如下所示:
function ready() {
var connection = new WebSocket('ws://localhost:3000');
connection.onmessage = function(msg) {
console.log(msg.data);
};
}
document.addEventListener("DOMContentLoaded", ready);
connection
变量是本地变量,因此对 WebSocket
的引用当ready()
时对象超出范围退出。我的期望是连接应该随着回调一起消失,但不知何故我仍然能够得到 connection.onmessage
回调。我的理解是,这与 javascript 作用域无关,而是如何WebSocket
的问题。连接是在内部组织的。是这样吗?您能解释一下这种行为吗?我相信 javascript 中的每个异步 api 都是如此,另一个例子:
function get(url) {
var req = new XMLHttpRequest();
req.open("GET", url);
req.onload = function() {
console.log(req.response);
}
req.send();
}
get("file.json");
因此,回调似乎与它们所放置的对象无关。提前致谢!
最佳答案
Javascript 是一种垃圾收集语言。这意味着 Javascript 中变量的对象或内容在超出函数范围时不会自动释放。相反,该对象会一直存在,直到没有更多事件代码可以访问它为止。
因此,在您的函数中,您在 connection
对象上设置了一个事件处理程序。只要某处存在仍然引用该连接对象的事件代码,Javascript 就不会对该对象进行垃圾回收。
在这种特殊情况下,connection
对象表示与另一台主机的实时 TCP 连接,并且该 TCP 连接背后有 Javascript 代码和 native 代码,它们仍然具有对连接对象的引用,并且仍然可以其上发生火灾事件。 Javascript 引擎知道此代码仍然具有对此对象的引用,因此它不会对其进行垃圾收集。而且,只要有新数据包到达 webSocket 连接,它就会调用您的 onMessage 处理程序。
使该连接对象符合垃圾回收条件的方法是关闭连接。这将导致 webSocket 代码及其背后的 native 代码清除它对连接对象的任何引用,然后它将有资格进行垃圾回收。
在 Javascript 中,我们永远不应该认为函数局部变量的生命周期与函数返回的时间严格相关。您很快就会发现(特别是由于 Javascript 中许多操作的异步性质)函数中的局部变量在函数返回后很长时间内仍保持事件状态,这非常非常有用。
我使用的心理模型(可能是也可能不是它的实现方式)帮助我理解这一切,那就是永远不要将函数中的局部变量视为在堆栈帧上分配。相反,将它们视为在堆上动态分配的。只要需要,它们就可以在该堆上生存,包括函数返回后很长时间。当没有代码再使用它们或可以再使用它们时,垃圾收集器将释放它们。
由于异步回调(如您的示例中所示),此类回调很容易导致局部变量在包含函数返回后很长时间内保持事件状态。
the connection variable is local so the reference to WebSocket object goes out of scope when ready() exits
当包含函数返回时,函数作用域中的内容不会自动释放。它们不像 C/C++ 那样分配在堆栈上。它们会被垃圾收集,并在不再使用时被释放。
My expectation is that the connection should die along with the callback on it, but somehow I am still able to get connection.onmessage callbacks
见上文。 Javascript 中的局部变量在其宿主函数返回后可以长期存在。
My understanding that it's not related to javascript scoping but a matter of how WebSocket connections are organized internally.
这并不是特定于 webSocket 的。如上所述,这是由于 Javascript 垃圾收集造成的。管理(仍然事件的)webSocket 连接的代码具有对 connection
对象的实时引用,从而防止它被垃圾收集。要终止该实时引用,您需要关闭 webSocket 连接,然后管理 webSocket 连接的代码将清除它对连接对象的引用,然后它就可以进行垃圾回收。
关于javascript 异步回调生命周期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54171052/