JavaScript中的递归闭包
假设我有JavaScript中的递归闭包,javascript,closures,Javascript,Closures,假设我有 function animate(param) { // ... if (param < 10) setTimeout(function () { animate(param + 1) }, 100); } animate(0); 函数动画(参数) { // ... 如果(参数
function animate(param)
{
// ...
if (param < 10)
setTimeout(function () { animate(param + 1) }, 100);
}
animate(0);
函数动画(参数)
{
// ...
如果(参数<10)
setTimeout(函数(){animate(param+1)},100);
}
动画(0);
这是否意味着函数本地数据的每个实例都将保存在内存中,直到animate完成,即直到param达到10
如果实例保存在内存中是真的,那么有更好的方法吗?我知道,将文本代码传递给
setTimeout()
解决了这个问题,但在我的例子中,函数参数中有一些对象不能很容易地表示为字符串。只有与最近调用animate
相关的上下文才会被保留(理论上,这完全取决于垃圾收集器)。当animate
创建匿名函数时,匿名函数将获得对调用animate
的上下文的引用,因此该上下文将保留在内存中。当超时发生时,计时器代码对匿名函数的引用被释放,这将释放该函数对上下文的引用。同时创建了一个新上下文,但新上下文不引用旧上下文,因此不会将旧上下文保留在内存中
关键是上下文与函数的绑定发生在函数创建时,而不是调用时。如何
function animate(param)
{
//....
if(param < 10)
animateTimeout(param);
}
function animateTimeout(param)
{
setTimout(function() { animate(param + 1) }, 100 );
}
函数动画(参数)
{
//....
如果(参数<10)
animateTimeout(参数);
}
函数animateTimeout(参数)
{
setTimout(function(){animate(param+1)},100);
}
这样您就不会将本地数据存储在//。。。在等待超时时
不确定你是否认为这里的问题比实际问题更多。您的代码不是递归的,因为它将导致10个深闭包链,因为一旦进行第二次动画调用,闭包1就会退出。每个闭包仅在一个setTimeout的生存期内存在。是的,在您的示例中,每次执行animate函数时,都会创建一个具有自己闭包范围的新函数 为了避免出现多个闭包,您可以尝试以下方法:
function animate (param) {
function doIt () {
param++;
if (param < 10) {
setTimeout(doIt, 100);
}
};
setTimeout(doIt, 100);
}
function myFunc() {
var bigarray = new Array(10000).join('foobar');
myFunc();
}
myFunc();
函数动画(参数){
函数doIt(){
param++;
如果(参数<10){
setTimeout(doIt,100);
}
};
setTimeout(doIt,100);
}
否,在任何给定时间点,最多有两个函数本地数据实例保存在内存中。以下是事件的顺序:
动画(0)
param==0
的闭包,它现在阻止释放此变量动画(1)
param==1
的新闭包,它现在阻止释放此变量animate()
调用中的局部变量设置动画(2)
实际上,这里没有创建递归函数。通过调用
setTimeout
它不再调用自己了。这里创建的唯一闭包是setTimeout
的匿名函数,一旦执行该函数,垃圾收集器将识别到对前一个实例的引用可以被清除。这可能不会立即发生,但您肯定无法使用它创建堆栈溢出。让我们来看看这个例子:
function myFunc() {
var bigarray = new Array(10000).join('foobar');
setTimeout(myFunc, 200);
}
myFunc();
现在从浏览器查看内存使用情况。它会不断生长,但过一段时间(对我来说是20-40秒),它会再次被完全清洗干净。另一方面,如果我们创建这样一个真正的递归:
function animate (param) {
function doIt () {
param++;
if (param < 10) {
setTimeout(doIt, 100);
}
};
setTimeout(doIt, 100);
}
function myFunc() {
var bigarray = new Array(10000).join('foobar');
myFunc();
}
myFunc();
我们的内存使用会增加,浏览器会锁定,我们最终会造成堆栈溢出。Javascript没有实现尾部递归,因此在所有情况下都会导致溢出
更新
看起来我在第一个例子中错了。只有在未调用函数上下文(例如使用匿名函数)时,该行为才是正确的。如果我们再这样写
function myFunc() {
var bigarray = new Array(10000).join('foobar');
setTimeout(function() {
myFunc();
}, 200);
}
myFunc();
浏览器内存似乎不再被释放。永远成长。这可能是因为任何内部closured都保留了对bigarray
的引用。但是无论如何。那么AnimateMout的实例将被困在内存中,不是吗?不是,一旦setTimeout中的代码被执行,第一个实例就可以被垃圾收集,因为一旦setTimeout中的代码被执行,并且在第一次调用animate时没有返回任何内容来引用局部变量。哦,对不起,在回答animateTimeout时-是-但是只进行1次迭代。我假设在您的评论部分中有更多本地数据,您希望避免存储这些数据。如果你担心的是10个深层次的问题,那么忽略这段代码,因为这根本不会发生。但在OP的原始代码中,与早期调用相关的上下文在与它们相关的计时器触发后不再被引用,因此它们可以自由地被GC调用。例如,计时器第二次启动时,第一次调用的上下文不再被任何东西引用。@T.J.Crowder,这是我对这个问题的理解,这实际上是可取的。是的,这也是我的理解。我不明白为什么它需要重铸(不是重铸的方式有什么问题。或者只是…setTimeout(animate,100,param+1)
–它使用了零个额外的闭包。看起来像步骤5。一旦外部上下文中有任何变量,它的激活对象就会被清空,因此,它的激活对象被填满。@jAndy:不,不是。此时可以释放内存,但这并不意味着它会被释放,确切的时间仍然取决于垃圾收集器和内存分配算法。很好的答案,我想写一些类似的东西,但现在不需要了:-)我认为外部闭包(animate