使用javascript的无限计时器循环(无设置间隔)?
(一位朋友)让我建立一个计时器(无限计时器,每秒写一行),但没有使用javascript的无限计时器循环(无设置间隔)?,javascript,performance,memory-leaks,Javascript,Performance,Memory Leaks,(一位朋友)让我建立一个计时器(无限计时器,每秒写一行),但没有setInterval 我希望: var i = 0; function k(myId, cb) { setTimeout(function () { console.log(myId); cb(); }, 1000); } function go() { i++;
setInterval
我希望:
var i = 0;
function k(myId, cb)
{
setTimeout(function ()
{
console.log(myId);
cb();
}, 1000);
}
function go()
{
i++;
k(i, go);
}
go();
它正在发挥作用
问题是我担心会有记忆压力。它实际上创建了一个递归,过了一段时间(一周或几周),这个过程将消耗大量内存。(堆栈永远不会释放)
如何更改代码以减少内存消耗?这不会造成内存泄漏 事实上,这是一个非常常用的概念。通常以以下形式出现:
setTimeout(function next() {
// Do something...
// Have the function set another timeout to call itself later.
setTimeout(next, 10);
}, 10);
如果您想经常检查某些内容(此处为每10毫秒一次),最好使用此模式而不是setInterval
,因为它可以提高页面性能。例如,如果您的函数执行时间超过10毫秒,并且您使用setInterval(f,10)
,那么它将继续被调用。但是,如果您使用上面的setTimeout
模式,它将至少确保处理器在每次调用之间获得10毫秒的中断,无论函数执行多长时间
有关此模式的更多信息,请参阅。它不是递归
它可能看起来像递归,但setTimeout不会创建递归
setTimeout的工作方式是它立即返回。因此,对k
的调用立即结束,堆栈被解除分配
当超时实际发生并且对go
的调用再次发生时,它不是从上一次对k
的调用开始的,而是从全局范围开始的*
*注意:这里我没有使用ECMAScript规范中定义的范围的严格含义。我的意思是,对k
的调用将如同在普通的
标记中编写一样:也就是说,在任何其他函数调用之外
关于你对关闭的关注
在您的特定情况下,k
函数创建的闭包中实际包含的内容很少。唯一重要的结束是对参数cb
和myId
的引用。即使如此,它也只持续大约一秒钟:
#1 function k(myId, cb) {
#2 setTimeout(function(){
#3 console.log(myId); // there is a closure here to myId
#4 cb(); // and another one for cb
#5
/* But at this point in the function, setTimeout ends
* and as the function returns, there are no remaining
* references to either "cb" or "myId" accessible
* anywhere else. Which means that the GC can immediately
* free them (though in reality the GC may run a bit later)
*/
#6 }, 1000); // So one second is roughly the longest the closure lasts
}
可能更简单
我应该注意到你的代码相当复杂。如果您只是这样编写,它可以编写得更简单,并且完全不使用闭包(减去全局变量i):
// Simpler, does exactly the same thing:
var i = 0;
function go () {
console.log(i);
i++;
setTimeout(go, 1000); // callback
}
go();
这行是假的:
它实际上创建了一个递归,过了一段时间(一周或几周),这个过程将消耗大量内存。(堆栈永远不会释放)
它不会创建递归,因为函数完全退出,然后再次调用
递归堆栈彼此重叠
function a() {a()}; // function calls itself until a stack overflow.
堆栈看起来像这样
a()
a()
a()
a() ... until a crash.
使用setTimeout,您可以执行一个函数。该函数设置了一个事件,以便该函数再次运行——但这里有一个重要的区别:该函数完全退出并消失[1]。然后它被再次调用
从执行角度看,这与这样做没有太大区别:
function a() {console.log("I am called");}
a(); // Call the function;
a(); // Call the function again
a(); // Call the function again
setTimeout
只要你愿意,浏览器就有机会“呼吸”。有机会更新屏幕,处理其他事件。使用正确的术语,它不会阻止浏览器。“范围”属于函数的词法上下文,而不是调用堆栈。更好的措辞可能是,“当超时实际发生并且对go
的调用再次发生时,它以一个新的空堆栈开始。它不会继续添加到前一个堆栈。”“但是从全局范围”-这不太正确。上次对k
的调用的结束情况如何?传递给setTimeout()
的匿名函数使用了k()
中的一个参数,因此,即使k()
已经完成了执行,它的一些部分仍然会挂起至少一段时间…@nnnnnn你能详细说明一下吗?@Lee:阐明了我的意思。我没有使用你的措辞,因为虽然措辞在技术上是错误的,但我发现更多的人通过这种方式更快地理解这个概念。@RoyiNamir:阅读我的答案:。它是关于XMLHttpRequest而不是setTimeout的,但是有一个很好的ASCII艺术图,解释了事件循环在浏览器中的工作原理,也适用于setTimeout。“我担心会有内存压力”-你担心可能会有,或者你已经测试过了,结果有?你提到要运行一周-你真的这么做了并且有问题吗?正如下面的答案中提到的,这不是递归。另外,为什么要用go()
和k()
和匿名函数设置三重函数?您可以在go()
中执行console.log()
,然后执行setTimeout(go,1000)
。请注意,OPssetTimeout
调用中的函数表达式创建了外部函数执行上下文的闭包,并显式地创建了cb
,因此形成了一个作用域堆栈。它可能会被优化,因为唯一需要的封闭是直接的外部和全局环境,但这取决于实现,可能不可靠。无论如何,你是对的,它不应该产生任何问题。回答得好。我喜欢a();a();a()
example@RoyiNamir-传递给setTimeout
的匿名函数、外部k
函数和全局对象之间有一个闭包。但是当匿名函数被调用并完成执行时,它就(或应该)可用于垃圾收集。新的setTimeout
中的新函数形成了一个新的闭包,但它不涉及上一个闭包,它是一个全新的执行上下文。@RobG你能加入吗?@RobG--完全同意更接近的问题。然而,出于简单的考虑,我在回答中忽略了它。