setTimeout, похоже, меняет мои переменные! Зачем? - PullRequest
7 голосов
/ 03 марта 2012

Я буду быстрым и прыгну прямо к делу. Код прокомментирован, чтобы вы знали мои намерения. По сути, я создаю небольшую игру на основе HTML5, и вместо сохранения чего-либо на сервере или в cookie-файле я просто предоставлю игроку код уровня. Когда игрок вводит код (в виде простого хэша) в поле ввода текста и нажимает кнопку, чтобы загрузить этот уровень, вызывается функция «l». Эта функция сначала извлекает записи игрока, затем перебирает список хэшей и сравнивает их. Когда совпадение увлекается, этот определенный уровень должен быть загружен, но были ошибки. Я сделал немного отладки и обнаружил, что значение итератора ("i") изменилось внутри setTimeout! Я хочу сделать паузу в 1 секунду, потому что немедленная загрузка уровня была бы слишком быстрой и выглядела бы плохо.

levelCodes = //Just a set of "hashes" that the player can enter to load a certain level. For now, only "code" matters.
[
    {"code": "#tc454", "l": 0},
    {"code": "#tc723", "l": 1},
]

var l = function() //This function is called when a button is pressed on the page
{
    var toLoad = document.getElementById("lc").value; //This can be "#tc723", for example

    for (i = 0; i < levelCodes.length; i++) //levelCodes.length == 2, so this should run 2 times, and in the last time i should be 1
        if (levelCodes[i].code == toLoad) //If I put "#tc723" this will be true when i == 1, and this happens
        {
            console.log(i); //This says 1
            setTimeout(function(){console.log(i)}, 1000); //This one says 2!
        }
}

Ответы [ 4 ]

6 голосов
/ 03 марта 2012

Другие уже написали причину поведения, которое вы получаете. Теперь решение: измените строку setTimeout на:

(function(i) {
    setTimeout(function(){console.log(i)}, 1000);
})(i);

Это работает, потому что фиксирует текущее значение переменной i в еще одном замыкании, а переменная в этом замыкании не изменяется.

4 голосов
/ 03 марта 2012

ECMAscript использует технику lexical closures, которая является не чем иным, как внутренним хранилищем всех родительских объектов контекста / записей лексической среды (ES3 / ES5).

Одним словом, ваша анонимная функция используется setTimeout закрывается над этой переменной i, поэтому, пока этот тайм-аут "ждет", ваш цикл продолжается.setTimeout работает, конечно, асинхронно, и это, в свою очередь, означает, что в то время, когда цикл завершил работу, значение, если i, конечно, равно 2.

Теперь вспомните о закрытии, нашей анонимной функции в setTimeout все еще содержит ссылку на i, когда он, наконец, срабатывает (после 1000 мс).Таким образом, он правильно показывает вам значение 2.

Если вы хотите показывать числа для каждой итерации после 1000 мс, вам нужно вызвать другой контекст.Это может выглядеть как

setTimeout((function( local ) {
    return function() {
        console.log( local );
    };
}( i )), 1000);
3 голосов
/ 03 марта 2012

Цикл for непрерывно увеличивается i до тех пор, пока не будет выполнено условие цикла, даже если код в цикле for не выполняется, когда код в setTimeout выполняет его, он показывает значение current i - 2.

1 голос
/ 03 марта 2012

К тому времени, когда обратный вызов setTimeout получит выполнение переменной i будет иметь значение 2, поскольку вы не выходите из цикла, а i продолжает считать, пока он не станет равным levelCodes. legnth (то есть 2). В основном вам нужно добавить return или break после вызова setTimeout. Кроме того, вы не объявляете переменную i , поэтому она будет привязана к глобальному пространству имен, что плохо и может привести к очень неясным ошибкам. Например, значение i можно изменить в других функциях, поэтому обратный вызов setTimeout увидит другое значение. Вам нужно добавить var i; в начале функции l .

...