setInterval
Для многократного запуска кода через равные промежутки времени предназначена функция setInterval
. Тем не менее она обладает рядом минусов, в основном это разное поведение в различных браузерах.
Первое отличие состоит в разности момента установки таймера для следующего запуска. Создадим небольшой тест: будем отмерять количество времени, прошедшее с начала выполнения предыдущего запуска и с его окончания.
var d1 = new Date(), d2 = new Date();
setInterval(function() {
var d = new Date();
document.body.innerHTML += (d - d1) + ' ' + (d - d2) + '<br>';
// Ставим метку в начале функции
d1 = new Date();
while (new Date() - d1 < 200); // ничего не делаем 200 миллисекунд
// И в конце функции
d2 = new Date();
}, 1000);
Вывод будет информативным, начиная со второй строчки.
В Firefox, Opera, Safari и Chrome ситуация будет схожая: первое число будет примерно равно 1000
, второе — на 200
меньше. Различие будут только в разбросе значений. Самый маленький разброс в Chrome и Opera.
1000 800
1003 803
1004 804
1018 808
987 787
1003 803
1000 800
Чуть больше разброс в Safari.
1035 835
1259 1059
841 641
1091 891
1048 848
1005 805
998 798
И самый большой — в Firefox.
988 788
985 785
1007 807
971 771
984 784
990 790
974 774
1010 810
1004 804
В Internet Explorer ситуация более стабильная, но противоположная: 1000
равно второе число, а первое на 200
больше.
1203 1000
1203 1000
1203 1000
1203 1000
1204 1000
1203 1000
1203 1000
Другими словами IE устанавливает таймер на следующий запуск после выполнения callback-функции, а остальные браузеры — до. Поэтому, например, анимация, реализованная с помощью setInterval
в Internet Explorer всегда будет медленнее, чем в других браузерах.
Ещё одно отличие менее заметное и более трудновоспроизводимое, но иногда способное доставить много хлопот, — это устойчивость к изменению системного времени. Если запустить следующий тест
setInterval(function() {
document.body.innerHTML = Math.random();
}, 500);
И после запуска перевести системное время на минуту назад, то в браузерах Firefox и Safari смена чисел приостановится, а через минуту запустится вновь. Конечно, ручной перевод системного времени крайне редкая ситуация, но на многихсистемах настроена автоматическая синхронизация времени с серверами в интернете, поэтому в некоторых ситуациях нельзя сбрасывать со счетов этот фактор.
Ещё один маленький минус функции setInterval
— чтобы была возможность остановить её действие, необходимо где-то запоминать её идентификатор, что не всегда удобно.
setInterval
Чтобы избавиться от перечисленных недостатков setInterval
можно использовать многократный setTimeout
, для это есть очень удобный паттерн.
(function() {
// Выполняем периодические действия
setTimeout(arguments.callee, 500);
})();
Здесь мы создаём безымянную функцию, тут же её вызываем, и в последующем передаём её же функции setTimeout
. При этом типичная асинхронная функция, выполняющая какие-то периодические действия, выглядит следующим образом.
/**
* @param {Function} callback Фукнция, вызываемая по окончании работы.
*/
function foo(callback) {
// Инициализируем переменные
(function() {
// Выполняем действия
if (/* больше ничего делать не надо */) {
callback();
} else {
setTimeout(arguments.callee, 500);
}
})();
}
Обратите внимание, в отличие от setInterval
, в данном случае первая итерация выполняется сразу же, без задержки.
Напишем, например, функцию, посимвольно выводящую строку в нужный DOM-элемент, и вызывающую callback-функцию по окончанию работы.
function typeString(elId, str, callback) {
var i = 1, el = document.getElementById(elId);
(function() {
el.innerHTML = str.substr(0, i++);
if (i > str.length) {
callback();
} else {
setTimeout(arguments.callee, 300);
}
})();
}
typeString('t', 'Hello, World!', function() {
alert('Напечатали');
});