变量全局作用域理解问题

5

我的问题实际上是理解性的,我有一个工作解决方案,只是不理解它的工作原理。

好的,所以我要做的是在循环中添加setTimeout,并通过它传递一个正在变化的值。例如:

for (i=0;i<11;i++)
{
     setTimeout("alert(i)",1000);
}

如果我理解正确,这个不起作用是因为Javascript不像PHP一样将i的值传递给函数,而是传递i的引用 - 这个引用并不是静态的,而是随着计数器的变化而不断改变。

我找到了一个解决方案,方法如下:

for (i=0;i<11;i++)
{
    setTimeout(function(x){return function(){alert(x)};}(i),1000);
}

我不太明白这实际上是做什么。它似乎将“alert”函数传回调用函数,但我无法理解其含义。
我可以使用此解决方案并将其适应其他情境,但我真的想了解我的所有代码,而不只是使用我在某处找到的东西,并对其工作感到满意。此外,我正在寻找一个更精简的版本以达到同样的目的。
谢谢,马尔科。

3
+1表示同意,原文的意思是“我真的很想理解我的所有代码,而不仅仅是使用找到的东西并且高兴它能正常工作”。 - Nick Craver
3个回答

4

这个是做什么的:

function(x){return function(){alert(x)};}(i)

它需要一个函数吗:

function(x){ ...code... }

并且立即执行它,将 i(来自 for 循环)作为唯一参数传递进去(这就是末尾的 (i) 的作用)。这会返回另一个函数:

function(){ alert(x); }

被传递给setTimeout()的函数it是在计时器结束时调用的,它使用的不是正在改变的循环中的变量i的引用,而是使用创建新函数时传入的副本。


感谢尼克抽出时间向我解释这个问题 - 也感谢你的“+1”。事实上,除了 Google 的部分,我的整个页面(airports.palzkill.de)都是自己制作的。 - Marco P.

2
你之所以调用一个返回函数的函数,是因为你需要一种方式让传递给setTimeout()的函数能够引用到当前i的值。
由于代码将等待1000ms才运行,for循环在运行前就已经完成了,i的值将会是11。
但是由于函数具有自己的变量作用域,你可以将i的值立即传递给被调用的函数,使其被本地变量x引用,当setTimeout()最终调用它时,返回的函数可以引用该变量。
for (i=0; i<11; i++) {
    setTimeout(function(x){
                 // CONTINUE HERE:
                 // x is a local variable to the function being executed
                 //    which references the current value of i

                 // A function is being returned to the setTimeout that
                 //    references the local x variable
                 return function(){ alert(x); };

               }(i) // START HERE:
                    // The "outer" function is executed immediately, passing the
                    //   current value of "i" as the argument.
     ,1000);
}

所以你最终会得到类似于以下内容的等效物:
setTimeout( function(){ alert(x); }, 1000); //...where x === 0
setTimeout( function(){ alert(x); }, 1000); //...where x === 1
setTimeout( function(){ alert(x); }, 1000); //...where x === 2
setTimeout( function(){ alert(x); }, 1000); //...where x === 3
// etc.

Patrick,非常感谢。这可能是缺失的链接。 - Marco P.

0
Patrick和Nick在理解整个问题方面帮了我很大的忙,因此我想为所有遇到同样问题的人总结一下:
setTimeout(以及其他一些延迟函数,如事件监听器)似乎将回调作为字符串存储,然后使用某种内部eval对该字符串进行解释,从而将其解释为代码。
这会导致循环和延迟函数出现问题,因为它们对变量的引用是指向该循环的最终结果,或者可能是一个甚至不是全局的变量。
据我所知,通过在函数中嵌套函数的解决方案可以解决此问题,通过将字符串作为函数结果返回,该函数结果包含值而不是对变量的引用(alert("1")而不是alert(i))。
关于使代码更短的问题,我的简单思维想到了一个简单的解决方案。由于期望回调是一个字符串,为什么不将变量的值写入该字符串中,然后将其返回:
for (i=0;i<11;i++)
{
     setTimeout("alert("+i+")",1000);
}

客观地说,这可能不是最好的解决方案,但相比其他解决方案需要编写更少的代码和更少的脑力资源来理解它是如何运作的,我现在可以使用它。
再次感谢Patrick、Nick和那位撤回答案的未知人士花时间帮助我!

Marco - 你说的部分正确。setTimeout可以接受一个字符串作为参数,然后尝试进行eval操作。如果全局命名空间中存在方法(如alert),它将被触发。但是,setTimeout也可以接受函数对象作为参数。这可以通过传递匿名函数(你示例代码中返回的内容)或传递对函数的变量引用来实现。如果接收到函数,则无论其是否起源于私有作用域,它都将在全局命名空间中调用。在js中,你可以像这样传递函数。 - user113716

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接