且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

是否存在内存泄漏与JavaScript调用一个函数在回调递归?

更新时间:2023-11-24 11:15:34

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



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



在Javsacript中,使用异步回调,启动函数已经返回,因此堆栈在异步回调被调用之前已被清除,是没有积累的堆栈。



在回调完成之前,内存中会有一个函数作用域对象,这很重要,并且是允许内联回调访问其中声明的变量的。父范围。一旦回调完成(不再可达),该范围对象将被垃圾回收。除非你正在做一些不寻常的事情,例如在临时作用域中分配巨字符串或缓冲区,那么该作用域的内存使用不应该是一个问题。



从一个初始函数调用,然后重复调用相同的回调,请记住父函数只执行一次,并且只分配一个scope对象,无论多少次调用回调,因此没有内存的建立每次调用回调。回调本身将在每次被调用时获得一个新的函数作用域,但由于回调本身不会触发任何异步调用,该作用域对象将是临时的,并且一旦回调完成,它就有资格进行垃圾回收工作和返回。



如果你链接/嵌入异步操作内部彼此,那么额外的范围对象将被保留在异步操作的持续时间,但这是如何Javascript工作,是什么提供访问您的父作用域的功能。在实践中,它通常没有被证明是一个内存问题。 Scope对象本身是相对紧凑的对象(一个是为几乎每个函数调用创建的)所以我上面所说的只要你不把巨大的缓冲区或巨型字符串/数组到一个持久的范围,内存使用通常是不相关的






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



有关如何工作的更多信息和JS事件队列上的许多参考文章(在node.js中的工作方式与浏览器中相同),请参阅这篇文章:



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



这是Joyent在 事件驱动的非阻塞I / O模型 上调用node.js的原因之一。 a href =https://nodejs.org/ =nofollow> node.js首页。


Let's say, for example, you are writing a program that waits for a message on a queue, handles it, and then waits for the next message, and this goes on forever. In a language like C or Java it would look something like this:

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

In Javascript (I'm using node.js, btw), because callbacks are used, it normally looks like this:

function processMessage() {
    waitForMessage(function(msg) {
        // handle msg or error here
        processMessage();
    }); 
}

My worry is that you basically have a chain of callbacks that recursively call the original function and the overhead of doing so might slowly eat up memory. I'm guessing this isn't actually a problem since maybe javascript callbacks exist on their own stack independently and are not pushed on to the stack of the original functions? Someone explain javascript callbacks and scoping to me and assure me that the javascript code will not run out of memory when run for an arbitrarily long amount of time while receiving an arbitrarily large number of messages

No, recursive function calls do not cause memory leaks in Javascript.

The only memory used by a function call is a bit of stack space (so the interpreter knows where to go when the function returns) and whatever memory is used by the scope object of the function (e.g. the local variables). That stack memory is completed returned to the system when the function call returns. It does not leak.

In Javsacript, with asynchronous callbacks, the initiating function has already returned and thus the stack has been cleared long before the asynchronous callback has been called so there is no build up of the stack.

There will be a function scope object in memory until the callback is done and that is important and is required to allow the inline callback to have access to the variables declared in its parent scope. As soon as the callback is done (not reachable any more), that scope object will be garbage collected. Unless you are doing something unusual such as allocated giant strings or buffers in that temporary scope, the memory usage of that scope should not be an issue.

As for retrieving many messages from one initial function call and then repeated calls of the same callback, keep in mind that the parent function is just executed once and just one scope object is allocated no matter how many times the callback is called so there is no build up of memory for each time the callback is called. The callback itself will get a new function scope each time it is called, but since the callback itself doesn't trigger any asynchronous calls, that scope object will be temporary and will be eligible for garbage collection as soon as the callback is done with its work and returns.

If you chain/embed asynchronous operations inside each other, then additional scope objects will be retained for the duration of the asynchronous operation, but this is how Javascript works and is what offers the features of having access to your parent scope. In practice, it has not generally proven to be a memory issue. Scope objects by themselves are relatively compact objects (one is created for nearly every function call) so as I said above as long as you don't put giant buffers or giant strings/arrays into a persisting scope, the memory usage is usually not relevant.


Also keep in mind that when you call processMessage() again from within the asynchronous callback that isn't the kind of recursion that you might generally think of because the previous function call to processMessage() has already returned and the stack has completely unwound before the asynchronous event triggered the callback. So, there is no stack build-up in this case. This is because asynchronous operations in Javascript all run through an event queue. When an asynchronous operation is ready to trigger an action, it puts an event in the Javascript event queue. That event is only processed when the current thread of JS operation has finished and completely unwound. Only then does the JS interpeter look in the event queue to see if there is something else to do. As such, the stack is always completely unwound before the next asynchronous operation is triggered.

For more info on how this works and a number of reference articles on the JS event queue (which works the same in node.js that it does in the browser), see this article:

How does JavaScript handle AJAX responses in the background?

This is one of the reasons that Joyent calls node.js an "an event-driven, non-blocking I/O model" right on the node.js home page.