Что здесь происходит под капотом? Таймер JavaScript в цикле - PullRequest
3 голосов
/ 20 ноября 2011

Может ли кто-нибудь ясно объяснить, что здесь происходит?

function timerCheck() {
    for(var i=0; i<5; i++) {
        setTimeout(function() {
            console.log("Hello" + i);
        }, 3000);
    }
}

Так что, как некоторые из вас могут знать, вызов этой функции не будет работать должным образом.В итоге произойдет то, что эта функция будет вызываться 5 раз за раз, при этом каждый раз будет установлено значение 5.Через 3 секунды это будет вывод:

Hello5
Hello5
Hello5
Hello5
Hello5

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

Ответы [ 3 ]

2 голосов
/ 20 ноября 2011

Это может помочь вам лучше понять, что происходит:

function timerCheck() {
    for(var i=0; i<5; i++) {
        console.log("Hi" + i);
        setTimeout(function() {
            console.log("Hello" + i);
        }, 3000);
        console.log("Bye" + i);
    }
}

Вы увидите

Hi0
Bye0
Hi1
Bye1
Hi2
Bye2
Hi3
Bye3
Hi4
Bye4

Сразу же выводится на консоль, потому что все пять итераций цикла завершаются очень быстро, и через пять секунд вы увидите:

Hello5
Hello5
Hello5
Hello5
Hello5

потому что все тайм-ауты (которые были установлены приблизительно в одно и то же время) происходят одновременно, и поскольку цикл уже завершен: i == 5.

Это вызвано областью действия i. Переменная i имеет область видимости повсюду после того, как она объявлена ​​в timerCheck(); Внутри вашей анонимной функции в наборе setTimeout нет локального i, нет var i, и i не передается в качестве аргумента функция.

Вы можете легко исправить это с помощью замыкания, которое возвратит функцию с локальной копией i:

function timerCheck() {
    for(var i=0; i<5; i++) {
        setTimeout((function(loc_i) {
            return function() {
                console.log("Hello" + loc_i);
            };
        })(i), 3000);
    }
}

Который выдаст:

Hello0
Hello1
Hello2
Hello3
Hello4

Чтобы понять это:

(function(loc_i) {
    return function() {
        console.log("Hello" + loc_i);
    };
})(i)

Вы должны знать, что функция может быть немедленно выполнена в Javascript. IE. (function(x){ console.log(x); })('Hi'); выводит Hi на консоль. Поэтому приведенная выше внешняя функция просто принимает аргумент (текущее значение i) и сохраняет его в локальной переменной этой функции с именем loc_i. Эта функция немедленно возвращает новую функцию, которая печатает "Hello" + loc_i на консоли. Это функция, которая передается в тайм-аут.

Надеюсь, все это имело смысл, дайте мне знать, если вам все еще что-то неясно.

1 голос
/ 20 ноября 2011

Область действия переменной в JavaScript ограничена функциями.

В вашем примере переменная i объявлена ​​внутри timerCheck.Это означает, что в конце цикла i будет равно 5.

Теперь добавление в вызове setTimeout не меняет того факта, что i ограниченtimerCheck и i был изменен до значения 5 к тому времени, когда код внутри каждого вызова setTimeout был выполнен.

Вы можете создать функцию, которая «захватывает» значение i так что, когда вы вызываете его из цикла, вы получаете новую переменную scope для вызова setTimeout:

function createTimer(j) {
    setTimeout(function() {
        console.log("Hello" + j);
    }, 3000);
}

function timerCheck() {
    for(var i=0; i<5; i++) {
        createTimer(i);
    }
}

Поскольку createTimer принимает параметр j, когда вы передаете i извнутри цикла for в timerCheck до createTimer, j теперь ограничено до createTimer, так что каждый вызов setTimeout имеет свой собственный j.

0 голосов
/ 20 ноября 2011

Это на самом деле дополнение к ответу Эндрюса

Если вы попытаетесь установить переменную, которая задает вывод, это также объясняет область действия.

function test()
{
    for(var i=0; i<5; i++) {
    t = "Hello " + i + "<br/>";
    document.write(t);
        setTimeout(function() {
            document.write(t);
        }, 3000);
    }
}

, как вы можете видеть, записи будут такими, как ожидалось, но в момент запуска setTimeout переменная t будет последним набором.Это будет Hello 4.

, поэтому выход будет

Hello 0
Hello 1
Hello 2
Hello 3
Hello 4

из цикла и

Hello 4
Hello 4
Hello 4
Hello 4
Hello 4

из setTimeout

...