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);
}
Это потому что!
- Обратные вызовы функции тайм-аута работают хорошо после завершения цикла. На самом деле, с течением времени, даже если для каждой итерации было установлено значение setTimeout(.., 0), все эти обратные вызовы функций по-прежнему выполнялись бы строго после завершения цикла, поэтому 3 было отражено!
- все две из этих функций, хотя они определяются отдельно в каждой итерации цикла, закрываются в одной общей глобальной области действия, в которой фактически есть только один 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.