我的javascript范围有什么问题?

What is wrong with my javascript scope?

本问题已经有最佳答案,请猛点这里访问。

以下内容每次都会提醒2

1
2
3
4
5
6
7
8
9
10
function timer() {
    for (var i = 0; i < 3; ++i) {
        var j = i;
        setTimeout(function () {
            alert(j);
        }, 1000);
    }
}

timer();

难道var j = i;不应该把j设定在设定的单独范围内吗?

如果我这样做:

1
2
3
4
5
6
7
8
9
10
11
function timer() {
    for (var i = 0; i < 3; ++i) {
        (function (j) {
            setTimeout(function () {
                alert(j);
            }, 1000);
        })(i);
    }
}

timer();

它会像应该的那样提醒012

我有什么东西不见了吗?


javascript具有功能范围。这意味着

1
2
3
for(...) {
    var j = i;
}

等于

1
2
3
4
var j;
for(...) {
    j = i;
}

事实上,这就是JavaScript编译器实际处理此代码的方式。当然,这会导致你的小"诡计"失败,因为j会在setTimeout中的函数被调用之前递增,也就是说,j现在与i没有什么不同,它只是一个具有相同作用域的别名。

如果Javascript有块范围,那么您的技巧就可以了,因为j在每次迭代中都是一个新的变量。

您需要做的是创建一个新的作用域:

1
2
3
4
5
6
for(var i = ...) {
    (function (j) {
        // you can safely use j here now
        setTimeout(...);
    })(i);
}


IIFE是一个功能工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
function timer() {
    for (var i = 0; i < 3; ++i) {
        setTimeout(createTimerCallback(i), 1000);
    }
}

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

timer();

也就是说,这是javascript标记中最常见的问题之一。见:

  • 循环内的javascript闭包——简单的实践示例
  • javascript臭名昭著的循环问题?


另一种方法是使用(通常被滥用)关键字with

1
2
3
4
5
6
7
8
9
10
11
function timer() {
    for (var i = 0; i < 3; ++i) {
        with({j: i}) {
            setTimeout(function () {
                alert(j);
            }, 1000);
        }
    }
}

timer();

它创建了一个与函数类似的新范围,但没有笨拙的语法。我在这里第一次看到:javascript的"with"语句有合法的用途吗?