我可以使用 setTimeout 创建一个廉价的无限循环吗?
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 运行此代码,它会立即打印,这不是我所期望的。
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);
What troubles me with this code is that I'm passing a string on 8000 times. Does this result in any kind of memory problem?
Also, If I run this code with node.js, it prints immediately, which is not what I would expect.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果您担心该字符串会被复制 8,000 次,不用担心,该字符串只有一份副本;传递的是一个引用。
更大的问题是,调用函数时创建的对象(称为“执行上下文”的“变量绑定对象”)是否被保留,因为您正在创建一个闭包,并且它具有对变量对象的引用只要闭包仍然在某处被引用,上下文就会将其保留在内存中。
答案是:是的,但只有在计时器触发之前,因为一旦它不执行任何操作,就不再引用闭包,因此垃圾收集器可以回收它们。所以你不会有 8,000 个未完成的任务,只有一两个。当然,GC何时以及如何运行取决于实现。
奇怪的是,就在今天早些时候,我们有一个关于非常相似主题的另一个问题;另请参阅我的回答。
If you're worried about the string being copied 8,000 times, don't be, there's only one copy of the string; what gets passed around is a reference.
The bigger question is whether the object created when you call a function (called the "variable binding object" of the "execution context") is retained, because you're creating a closure, and which has a reference to the variable object for the context and thus keeps it in memory as long as the closure is still referenced somewhere.
And the answer is: Yes, but only until the timer fires, because once it does nothing is referencing the closure anymore and so the garbage collector can reclaim them both. So you won't have 8,000 of them outstanding, just one or two. Of course, when and how the GC runs is up to the implementation.
Curiously, just earlier today we had another question on a very similar topic; see my answer there as well.
它立即打印,因为程序“立即”执行。在我的 Intel i5 机器上,根据
时间节点 test.js
,整个操作需要 0.07 秒。对于内存问题,以及这是否是一个“廉价的无限循环”,您只需要进行实验和测量即可。
如果您想在节点中创建异步循环,可以使用process.nextTick。它会比
setTimeout(func, 1)
更快。It prints immediately because the program executes "immediately". On my Intel i5 machine, the whole operation takes 0.07s, according to
time node test.js
.For the memory problems, and wether this is a "cheap infinite loop", you'll just have to experiment and measure.
If you want to create an asynchronous loop in node, you could use
process.nextTick
. It will be faster thansetTimeout(func, 1)
.一般来说Javascript不支持尾部调用优化,所以编写递归代码通常存在导致堆栈溢出的风险。如果您像这样使用
setTimeout
,它会有效地重置调用堆栈,因此堆栈溢出不再是问题。但性能将成为问题,因为每次调用
setTimeout
通常需要相当长的时间(大约 10 毫秒),即使您将delay
设置为 0。In general Javascript does not support tail call optimization, so writing recursive code normally runs the risk of causing a stack overflow. If you use
setTimeout
like this, it effectively resets the call stack, so stack overflow is no longer a problem.Performance will be the problem though, as each call to
setTimeout
generally takes a fair bit of time (around 10 ms), even if you setdelay
to 0.“1”是 1 毫秒。它也可能是一个 for 循环。 1 秒是 1000。我最近写了一些类似的东西,检查后端一批进程的进度,并将延迟设置为 500。如果我没记错的话,较旧的浏览器不会看到 1 和大约 15 毫秒之间有任何真正的区别。我认为 V8 实际上可能比这更快。
我认为在最后一次迭代完成之前,任何函数都不会发生垃圾收集,但是这些新一代的 JS JIT 编译器比我更了解的编译器要聪明得多,所以他们可能会发现没有什么超时后真正发生并从内存中提取这些参数。
无论如何,即使为这些参数的每个实例保留内存,也需要超过 8000 次迭代才会导致问题。
防止更多内存密集型参数出现潜在问题的一种方法是传入具有所需参数的对象。那么我相信参数只是对内存中设定位置的引用。
所以像这样:
The '1' is 1 millisecond. It might as well be a for loop. 1 second is 1000. I recently wrote something similar checking on the progress of a batch of processes on the back end and set a delay of 500. Older browsers wouldn't see any real difference between 1 and about 15ms if I remember correctly. I think V8 might actually process faster than that.
I don't think garbage collection will be happening to any of the functions until the last iteration is complete but these newer generations of JS JIT compilers are a lot smarter than the ones I know more about so it's possible they'll see that nothing is really going on after the timeout and pull those params from memory.
Regardless, even if memory is reserved for every instance of those parameters, it would take a lot more than 8000 iterations to cause a problem.
One way to safeguard against potential problems with more memory intensive parameters is if you pass in an object with the params you want. Then I believe the params will just be a reference to a set place in memory.
So something like: