SetTimeout in for-loop не печатает последовательные значения

У меня есть этот script:

for (var i = 1; i <= 2; i++) {
    setTimeout(function() { alert(i) }, 100);
}

Но 3 предупреждается обоим раза, а не 1, а затем 2.

Есть ли способ передать i, не записывая функцию в виде строки?

Ответы

Ответ 1

Вы должны организовать отдельную копию "i" для каждой функции тайм-аута.

function doSetTimeout(i) {
  setTimeout(function() { alert(i); }, 100);
}

for (var i = 1; i <= 2; ++i)
  doSetTimeout(i);

Если вы не сделаете что-то подобное (и есть другие варианты этой же идеи), то каждая из функций-обработчиков таймера будет иметь одну и ту же переменную "i". Когда цикл закончится, какое значение у "i"? Это 3! Используя посредническую функцию, создается копия значения переменной. Поскольку обработчик тайм-аута создается в контексте этой копии, у него есть свой собственный "i" для использования.

редактировать - со временем было несколько комментариев, в которых была очевидна некоторая путаница в связи с тем фактом, что установка нескольких тайм-аутов приводит к тому, что обработчики запускаются одновременно. Важно понимать, что процесс установки таймера - вызовы setTimeout() - практически не занимают время. То есть, говоря системе "Пожалуйста, вызовите эту функцию через 1000 миллисекунд", вы вернетесь почти немедленно, так как процесс установки запроса на тайм-аут в очереди таймера очень быстрый.

Таким образом, если выполняется последовательность запросов времени ожидания, как это имеет место в коде в OP и в моем ответе, и значение задержки времени одинаково для каждого, то после того, как это количество времени истекло, все обработчики таймера будет вызываться один за другим в быстрой последовательности.

Если вам нужно, чтобы обработчики вызывались с интервалами, вы можете либо использовать setInterval(), который вызывается точно так же, как setTimeout() но который будет срабатывать более одного раза после повторных задержек запрошенной суммы, или вместо этого вы можете установить время ожидания и умножьте значение времени на ваш счетчик итераций. То есть, чтобы изменить мой пример кода:

function doScaledTimeout(i) {
  setTimeout(function() {
    alert(i);
  }, i * 5000);
}

(При тайм-ауте в 100 миллисекунд эффект не будет очень очевидным, поэтому я увеличил число до 5000.) Значение i умножается на значение базовой задержки, поэтому вызов 5 раз в цикле приведет к задержкам 5 секунд, 10 секунд, 15 секунд, 20 секунд и 25 секунд.

Обновить

Здесь, в 2018 году, существует более простая альтернатива. С новой возможностью объявлять переменные в областях, более узких, чем функции, оригинальный код будет работать, если так изменить:

for (let i = 1; i <= 2; i++) {
    setTimeout(function() { alert(i) }, 100);
}

Объявление let, в отличие от var, само по себе приведет к тому, что для каждой итерации цикла будет свой i.

Ответ 2

Вы можете использовать функциональное выражение с немедленным вызовом (IIFE), чтобы создать замыкание вокруг setTimeout:

for (var i = 1; i <= 3; i++) {
    (function(index) {
        setTimeout(function() { alert(index); }, i * 1000);
    })(i);
}

Ответ 3

Аргумент функции setTimeout закрывает переменную цикла. Цикл заканчивается до первого таймаута и отображает текущее значение i, которое равно 3.

Поскольку переменные JavaScript имеют только область функций, решение должно передать переменную цикла функции, задающей тайм-аут. Вы можете объявить и вызвать такую ​​функцию следующим образом:

for (var i = 1; i <= 2; i++) {
    (function (x) {
        setTimeout(function () { alert(x); }, 100);
    })(i);
}

Ответ 4

Это потому, что !

  1. Функции обратного вызова функции тайм-аута работают хорошо после завершения цикла. Фактически, поскольку таймеры идут, даже если на каждой итерации было установлено значение Timeout (.., 0), все эти обратные вызовы функций будут выполняться строго после завершения цикла, поэтому 3 отразилось!
  2. все две из этих функций, хотя они определены отдельно в каждой итерации цикла, закрываются по одной и той же общей глобальной области, которая на самом деле имеет только один я в ней.

Решение, объявляющее единую область для каждой итерации, используя выполненную самофункцию (анонимный или лучше IIFE) и имеющий копию i в нем, например:

for (var i = 1; i <= 2; i++) {

     (function(){

         var j = i;
         setTimeout(function() { console.log(j) }, 100);

     })();

}

чище было бы

for (var i = 1; i <= 2; i++) {

     (function(i){ 

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

     })(i);

}

Использование iFE (самозапускаемой функции) внутри каждой итерации создало новую область для каждой итерации, которая дала нам функцию тайм-аута, которая вызывает обратную функцию для закрытия новой области для каждой итерации, которая имела переменную с правильной переменной, итерации в нем для доступа.

Ответ 5

Вы можете использовать дополнительные аргументы для setTimeout для передачи параметров функции обратного вызова.

for (var i = 1; i <= 2; i++) {
    setTimeout(function(j) { alert(j) }, 100, i);
}

Примечание. Это не работает в IE9 и ниже браузеров.

Ответ 6

ANSWER

Я использую его для анимации для добавления предметов в корзину. Значок корзины плавает в области корзины с кнопки "добавить" продукта при нажатии:

function addCartItem(opts) {
    for (var i=0; i<opts.qty; i++) {
        setTimeout(function() {
            console.log('ADDED ONE!');
        }, 1000*i);
    }
};

ПРИМЕЧАНИЕ. Продолжительность в единицах времени n epocs.

Итак, начиная с момента клика, epoc анимации (из каждой анимации) является произведением каждого односекундного блока, умноженного на количество элементов.

epoc: https://en.wikipedia.org/wiki/Epoch_ (reference_date)

Надеюсь, это поможет!

Ответ 7

Вы можете использовать метод bind

for (var i = 1, j = 1; i <= 3; i++, j++) {
    setTimeout(function() {
        alert(this);
    }.bind(i), j * 100);
}

Ответ 8

Ну, другое рабочее решение, основанное на ответе Коди, но немного более общее может быть примерно так:

function timedAlert(msg, timing){
    setTimeout(function(){
        alert(msg);    
    }, timing);
}

function yourFunction(time, counter){
    for (var i = 1; i <= counter; i++) {
        var msg = i, timing = i * time * 1000; //this is in seconds
        timedAlert (msg, timing);
    };
}

yourFunction(timeInSeconds, counter); // well here are the values of your choice.

Ответ 9

У меня была такая же проблема, как только я решил это.

Предположим, что я хочу 12 задержек с интервалом в 2 секунды

    function animate(i){
         myVar=setTimeout(function(){
            alert(i);
            if(i==12){
              clearTimeout(myVar);
              return;
            }
           animate(i+1)
         },2000)
    }

    var i=1; //i is the start point 1 to 12 that is
    animate(i); //1,2,3,4..12 will be alerted with 2 sec delay

Ответ 10

настоящее решение здесь, но вам нужно быть знакомым с языком программирования PHP. вы должны смешивать заказы PHP и JAVASCRIPT, чтобы достичь своей цели.

обратите внимание на это:

<?php 
for($i=1;$i<=3;$i++){
echo "<script language='javascript' >
setTimeout(function(){alert('".$i."');},3000);  
</script>";
}
?> 

Он точно делает то, что вы хотите, но будьте осторожны с тем, как сделать рационализацию между Переменные PHP и JAVASCRIPT.