Источник: http://bonsaiden.github.io/JavaScript-Garden/ru/#other.timeouts
setTimeout
и setInterval
Поскольку JavaScript поддерживает асинхронность, есть возможность запланировать выполнение функции, используя функции setTimeout
и setInterval
.
function foo() {}
var id = setTimeout(foo, 1000); // возвращает число > 0
Функция setTimeout
возвращает идентификатор таймаута и планирует вызвать foo
через, примерно, тысячу миллисекунд. Функция foo
при этом будет вызвана ровно один раз.
В зависимости от разрешения таймера в используемом для запуска кода движке JavaScript, а также с учётом того, что JavaScript является однопоточным языком и посторонний код может заблокировать выполнение потока, нет никакой гарантии, что переданный код будет выполнен ровно через указанное в вызове setTimeout
время.
Замечание: Поскольку setTimeout
принимает объект функции в качестве первого параметра, часто совершается ошибка в использовании setTimeout(foo(), 1000)
, при котором будет использоваться возвращённое значение от вызова функции foo
, а не вызываться сама функция foo
. В большинстве случаев ошибка пройдёт незамеченной, а в случае если функция возвращает undefined
, setTimeout
вообще не породит никакой ошибки.
Переданная первым параметром функция будет вызвана как глобальный объект — это значит, что оператор this
в вызываемой функции будет ссылаться на этот самый объект.
function Foo() {
this.value = 42;
this.method = function() {
// this ссылается на глобальный объект
console.log(this.value); // выведет в лог undefined
};
setTimeout(this.method, 500);
}
new Foo();
setInterval
setTimeout
вызывает функцию единожды; setInterval
— как и предполагает название — вызывает функцию каждые X
миллисекунд. И его использование не рекомендуется.
В то время, когда исполняющийся код будет блокироваться во время вызова с таймаутом, setInterval
будет продолжать планировать последующие вызовы переданной функции. Это может (особенно в случае небольших интервалов) повлечь за собой выстраивание вызовов функций в очередь.
function foo(){
// что-то, что выполняется одну секунду
}
setInterval(foo, 100);
В приведённом коде foo
выполнится один раз и заблокирует этим главный поток на одну секунду.
Пока foo
блокирует код, setInterval
продолжает планировать последующие её вызовы. Теперь, когда первая foo
закончила выполнение, в очереди будут уже десять ожидающих выполнения вызовов foo
.
Функцию setInterval
лучше использовать, если вы стопроцентно уверены, что код внутри неё будет исполняться как минимум в три раза быстрее переданного ей интервала.
Самый простой и контролируемый способ — использовать setTimeout
внутри самой функции.
function foo(){
// что-то, выполняющееся одну секунду
setTimeout(foo, 100);
}
foo();
Такой способ не только инкапсулирует вызов setTimeout
, но и предотвращает от очередей блокирующих вызовов и при этом обеспечивает дополнительный контроль. Сама функция foo
теперь принимает решение, хочет ли она запускаться ещё раз или нет.
Удаление таймаутов и интервалов работает через передачу соответствующего идентификатора либо в функцию clearTimeout
, либо в функцию clearInterval
— в зависимости от того, какая функция set...
использовалась для его получения.
var id = setTimeout(foo, 1000);
clearTimeout(id);
Очистка всех таймаутов
Из-за того, что встроенного метода для удаления всех таймаутов и/или интервалов не существует, для достижения этой цели приходится использовать брутфорс.
// удаляем "все" таймауты
for(var i = 1; i < 1000; i++) {
clearTimeout(i);
}
Вполне могут остаться таймауты, которые не будут захвачены этим произвольным числом; так что всё же рекомендуется следить за идентификаторами всех создающихся таймаутов, за счёт чего их можно будет удалять индивидуально.
eval
setTimeout
и setInterval
могут принимать строку в качестве первого параметра. Эту возможность не следует использовать никогда, поскольку изнутри при этом производится скрытый вызов eval
.
function foo() {
// будет вызвана
}
function bar() {
function foo() {
// никогда не будет вызвана
}
setTimeout('foo()', 1000);
}
bar();
Поскольку eval
в этом случае не вызывается напрямую, переданная в setTimeout
строка будет выполнена в глобальной области видимости; так что локальная переменная foo
из области видимости bar
не будет выполнена.
По этим же причинам рекомендуется не использовать строку для передачи аргументов в функцию, которая должна быть вызвана из одной из двух функций, работающих с таймаутами.
function foo(a, b, c) {}
// НИКОГДА не делайте такого
setTimeout('foo(1,2, 3)', 1000)
// Вместо этого используйте анонимную функцию
setTimeout(function() {
foo(1, 2, 3);
}, 1000)
Замечание: При том, что синтаксис setTimeout(foo, 1000, 1, 2, 3)
разрешено использовать, это крайне не рекомендуется, поскольку может привести к сложно распознаваемым ошибкам при работе с методами.
Никогда не используйте строки как параметры setTimeout
или setInterval
. Это явный признак действительно плохого кода. Если вызываемой функции необходимо передавать аргументы, лучше передавать анонимную функцию, которая самостоятельно будет отвечать за сам вызов.
Кроме того, избегайте использования setInterval
в случаях, когда его планировщик может блокировать выполнение JavaScript.