使用javascript内存泄漏是否会以递归方式调用回调中的函数?

Mik*_*ike 7 javascript recursion memory-leaks callback node.js

比方说,例如,您正在编写一个等待队列中的消息,处理它,然后等待下一条消息的程序,这将永远持续下去.在像C或Java这样的语言中,它看起来像这样:

void processMessage() {
    while (true) {
        // waitForMessage blocks until the next message is received
       msg = waitForMessage(); 
      // handle msg here
    }
}
Run Code Online (Sandbox Code Playgroud)

在Javascript(我使用node.js,顺便说一句),因为使用了回调,它通常看起来像这样:

function processMessage() {
    waitForMessage(function(msg) {
        // handle msg or error here
        processMessage();
    }); 
}
Run Code Online (Sandbox Code Playgroud)

我担心你基本上有一系列回调函数递归调用原始函数,这样做的开销可能会慢慢耗尽内存.我猜这不是一个问题,因为也许javascript回调独立存在于他们自己的堆栈上并且不会被推到原始函数的堆栈中?有人向我解释了javascript回调和范围,并向我保证,当收到任意大量的消息时,javascript代码在任意长时间运行时不会耗尽内存

jfr*_*d00 5

不,递归函数调用不会导致Javascript中的内存泄漏.

函数调用使用的唯一内存是一些堆栈空间(因此解释器知道函数返回时的去向)以及函数的作用域对象使用的任何内存(例如局部变量).当函数调用返回时,堆栈内存完成返回到系统.它不泄漏.

在JavaScript中,使用异步回调,启动函数已经返回,因此在调用异步回调之前很久就清除了堆栈,因此堆栈没有堆积.

在回调完成之前,内存中将有一个函数作用域对象,这很重要,并且需要允许内联回调访问其父作用域中声明的变量.一旦回调完成(不再可访问),该范围对象将被垃圾收集.除非您正在执行某些异常操作,例如在该临时范围内分配了巨型字符串或缓冲区,否则该范围的内存使用量不应成为问题.

至于从一个初始函数调用中检索许多消息然后重复调用相同的回调,请记住,父函数只执行一次,无论调用回调多少次都只分配一个范围对象,所以没有每次调用回调时都会占用内存.每次调用回调本身都会获得一个新的函数作用域,但由于回调本身不会触发任何异步调用,因此该作用域对象将是临时的,并且一旦回调完成就会有资格进行垃圾回收.工作和回报.

如果在彼此内部链接/嵌入异步操作,则在异步操作期间将保留其他范围对象,但这是Javascript的工作方式,并且提供了访问父范围的功能.实际上,它通常没有被证明是一个记忆问题.范围对象本身是相对紧凑的对象(几乎每个函数调用都创建一个),正如我上面所说,只要你不将巨型缓冲区或巨型字符串/数组放入持久范围内,内存使用通常是不相关的.


还要记住,当你processMessage()从异步回调中再次调用时,这种回调不是你通常会想到的那种递归,因为之前的函数调用processMessage()已经返回并且在异步事件触发回调之前堆栈已完全展开.因此,在这种情况下没有堆栈构建.这是因为Javascript中的异步操作都通过事件队列运行.当异步操作准备好触发操作时,它会将事件放入Javascript事件队列中.该事件仅在JS操作的当前线程完成并完全解除时处理.只有这样,JS interpeter才能查看事件队列中是否还有其他事情要做.因此,在触发下一个异步操作之前,堆栈始终完全展开.

有关其工作原理的更多信息以及JS事件队列上的大量参考文章(在node.js中它与浏览器中的相同),请参阅此文章:

JavaScript如何在后台处理AJAX响应?

这是Joyent 在node.js主页上将node.js称为"事件驱动的非阻塞I/O模型"的原因之一.


Rey*_*les 3

函数不在堆栈上分配。回调函数在使用后将被垃圾收集,除非由于某种原因保留了引用。你的代码应该没问题!