关于jquery:JavaScript回调函数中的变量范围

scope of variables in JavaScript callback functions

我希望下面的代码警告"0"和"1",但它会两次警告"2"。 我不明白原因。 不知道这是不是jQuery的问题。 另外,如果这些帖子的标题和标签不准确,请帮我编辑标题和标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
    <head>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
        <script type="text/javascript">
            $(function() {
                for (var i=0; i<2; i++) {
                    $.get('http://www.google.com/', function() {
                        alert(i);
                    });
                }
            });
       
    </head>
    <body>
    </body>
</html>


您将在所有回调中共享单个i变量。

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

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

例如:

1
2
3
4
5
6
7
8
9
function sendRequest(i) {
    $.get('http://www.google.com/', function() {
        alert(i);
    });
}

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

这样,每个回调都将有一个单独的闭包,其中包含一个单独的i参数。


替代SLaks的回答

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


另一种解决方案是使用你的回调并逐字地使它成为一个命名函数。

我为什么要这样做?
如果一个函数正在做一个变量需要占用新范围的东西,那么匿名函数很可能需要突破一个新函数。这还将确保不必通过复制变量或包装回调来为代码引入额外的复杂性。你的代码将保持简单和自我描述。

例:

1
2
3
4
5
6
7
8
9
10
11
function getGoogleAndAlertIfSuccess(attemptNumber) {
    $.get('http://www.google.com/', function() {
        alert(attemptNumber);
    });
}

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

这里发生的是你的AJAX请求$.get在循环完成后完成。因此,i最终成为迭代完成时设置的最终变量,为2.这只是一个奇怪的JavaScript陷阱,与jQuery无关。

您可以做的一件事是异步排队这些调用,以便迭代停止,直到当前的AJAX请求完成。如果您不想这样做,可以在每次迭代中捕获function闭包中的变量i

像这样的东西:

1
2
3
4
5
6
for ( var i = 0; i < 2; i++ )
    (function(iter){
        $.get('http://www.google.com/', function(){
            alert( iter );
        });
    })(i); // Capture i

看来你已经在循环中创建了一个闭包Mozilla Developers Reference有一个很好的部分。