setTimeout в цикле for не печатает последовательные значения

У меня есть этот скрипт:

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

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

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

10 ответов

Решение

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

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

for (var i = 1; i <= 2; ++i)
  doSetTimeout(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 для каждой итерации цикла.

Другая возможность заключается в

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

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

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

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

Решение объявляет одну область действия для каждой итерации, используя выполняемую самофункцию (анонимную или лучше 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);

}

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

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

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

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

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

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

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

ОТВЕТ?

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

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

ПРИМЕЧАНИЕ: продолжительность указана в единицах времени n эпок.

Таким образом, начиная с момента щелчка, анимация начала анимации (КАЖДОЙ анимации) - это произведение каждой единицы на одну секунду, умноженное на количество элементов.

epoc: https://en.wikipedia.org/wiki/Epoch_%28reference_date%29

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

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

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

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

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.

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

Предположим, я хочу 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

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

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

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

Это именно то, что вы хотите, но будьте осторожны с тем, как соотнести переменные PHP и переменные JAVASCRIPT.

Другие вопросы по тегам