var recurse = function(steps, data, delay) {
if(steps == 0) {
console.log(data.length)
} else {
setTimeout(function(){
recurse(steps - 1, data, delay);
}, delay);
}
};
var myData = "abc";
recurse(8000, myData, 1);
这段代码让我感到困扰的是,我传递了8000次字符串。这会导致任何类型的内存问题吗?
此外,如果我使用node.js运行此代码,它会立即打印,这不是我所期望的。
答案 0 :(得分:4)
如果您担心字符串被复制8000次,请不要,只有一个字符串副本;传递的是一个参考。
更大的问题是,当你调用一个函数时创建的对象(称为“执行上下文”的“变量绑定对象”)是否被保留,因为你正在创建一个闭包,并且它具有对变量的引用上下文的对象,因此只要闭包仍在某处被引用,它就会将其保存在内存中。
答案是:是的,但只有在计时器触发之前,因为一旦它什么都不做就会再引用关闭,所以垃圾收集器可以回收它们。所以你不会有8,000个未完成的,只有一两个。当然,GC的运行时间和方式取决于实施。
奇怪的是,就在今天早些时候,我们在一个非常相似的主题上有another question;在那里也看到my answer。
答案 1 :(得分:2)
它立即打印,因为程序“立即”执行。根据{{1}},在我的Intel i5机器上,整个操作需要0.07秒。
对于记忆问题,这是一个“廉价的无限循环”,你只需要进行实验和测量。
如果要在节点中创建异步循环,可以使用time node test.js
。它会比process.nextTick
快。
答案 2 :(得分:2)
通常是Javascript does not support tail call optimization,因此编写递归代码通常会冒着导致堆栈溢出的风险。如果像这样使用setTimeout
,它会有效地重置调用堆栈,因此堆栈溢出不再是问题。
性能将成为问题,因为每次调用setTimeout
通常需要相当长的时间(大约10毫秒),即使您将delay
设置为0。
答案 3 :(得分:2)
'1'是1毫秒。它也可能是一个for循环。 1秒是1000.我最近在后端写了类似的检查一批进程的进度并设置了500的延迟。如果我没记错的话,旧的浏览器在1到15ms之间看不到任何真正的区别。我认为V8实际上可能比这更快。
在最后一次迭代完成之前,我认为垃圾收集不会发生在任何函数上,但是这些新一代的JS JIT编译器比我知道的更多更聪明,所以他们可能会看到超时后没有任何事情发生,并从内存中拉出这些参数。
无论如何,即使为这些参数的每个实例保留了内存,也需要花费8000多次迭代才能导致问题。
如果您使用所需的参数传入对象,则可以使用更多内存密集型参数来防范潜在问题。然后我相信这些参数只是对记忆中一个固定位置的引用。
类似于:
var recurseParams ={ steps:8000, data:"abc", delay:100 } //outside of the function
//define the function
recurse(recurseParams);
//Then inside the function reference like this:
recurseParams.steps--