JavaScript回调函数中变量的作用域

22
我希望代码下方会弹出“0”和“1”,但它两次弹出了“2”,我不明白原因。不知道是不是jQuery的问题。还请帮我编辑一下这篇文章的标题和标签,如果它们不准确的话。
<html>
    <head>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
        <script type="text/javascript">
            $(function() {
                for (var i=0; i<2; i++) {
                    $.get('http://www.google.com/', function() {
                        alert(i);
                    });
                }
            });
        </script>
    </head>
    <body>
    </body>
</html>

2
@chaos:我猜应该是在“不要使用正则表达式处理HTML”这句话下面吧。 ;) - Tomalak
2
JavaScript 闭包入门(针对新手)第五个例子 - Felix Kling
1
很难只选一个作为最接近的:https://dev59.com/lUrSa4cB1Zd3GeqPVVA8https://dev59.com/cHRB5IYBdhLWcg3wXmRIhttps://dev59.com/_UnSa4cB1Zd3GeqPRtgshttps://dev59.com/ZHM_5IYBdhLWcg3wjj0phttps://dev59.com/SHI_5IYBdhLWcg3wDOdChttps://dev59.com/v3RB5IYBdhLWcg3wAjNHhttps://dev59.com/VEfSa4cB1Zd3GeqPAM_jhttp://stackoverflow.com/questions/1579978/https://dev59.com/7UnSa4cB1Zd3GeqPM1Frhttp://stackoverflow.com/questions/2808471/ - Christian C. Salvadó
1
公平地说,闭包循环问题可以说是JavaScript中的语言设计缺陷(以及其他提供闭包但仍使用C风格每个函数作用域的语言)。 - bobince
5个回答

39

你正在将单个i变量在所有回调函数之间共享。

由于Javascript闭包通过引用捕获变量,所以回调函数总是使用i的当前值。因此,在循环执行后,当jQuery调用回调函数时,i始终是2

您需要将i作为参数引用到一个单独的函数中。

例如:

function sendRequest(i) {
    $.get('http://www.google.com/', function() {
        alert(i);
    });
}

for (var i = 0; i < 2; i++) {
    sendRequest(i);
}

这样做,每个回调都会有一个独立的闭包和一个单独的 i 参数。


13

SLaks的答案的替代方案

$(function() {
    for (var i=0; i<2; i++) {
        $.get('http://www.google.com/', function(i) {
            return function() { alert(i); }
        }(i));
    }
});

1
@RTF 不,这是同一件事情,只是表达方式不同。此外,像这样的东西很可能是你性能下降的最后一个地方,所以使用它是因为你更喜欢它,而不是因为你认为它可能更快。 (说实话,它并不完全相同。它会在每个循环迭代中创建一个额外的函数对象。测量性能差异并找出是否应该关注它。) - Tomalak

1
这里发生的情况是您的 AJAX 请求 $.get 在循环完成后才完成。因此,当迭代完成时,i 最终变量被设置为 2。这只是一个奇怪的 JavaScript 陷阱,与 jQuery 无关。
您可以做的一件事是异步排队这些调用,以便迭代在当前 AJAX 请求完成之前停止。如果您不想这样做,您可以在每次迭代中捕获变量 i 在一个 function 闭包中。
像这样:
for ( var i = 0; i < 2; i++ )
    (function(iter){
        $.get('http://www.google.com/', function(){
            alert( iter );
        });
    })(i); // Capture i

1

另一个解决方案是将回调函数直接定义为命名函数。

为什么要这样做?
如果一个函数正在执行某些操作,需要让变量获得新的作用域,那么将匿名函数拆分成新的函数可能更合适。这也可以确保您的代码不会因为复制变量或包装回调而引入额外的复杂性。您的代码将保持简单和自我描述。

示例:

function getGoogleAndAlertIfSuccess(attemptNumber) {
    $.get('http://www.google.com/', function() {
        alert(attemptNumber);
    });
}

function testGoogle() {
    for (var i=0; i<2; i++) {
        getGoogleAndAlertIfSuccess(i);
    }
}

0

看起来你在循环内创建了一个闭包。Mozilla开发者参考文档有一个好的章节关于这个问题。


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