setTimeout и setInterval¶
Поскольку JavaScript поддерживает асинхронность, есть возможность запланировать выполнение функции, используя функции setTimeout
и setInterval
.
Замечание: Таймауты не являются частью стандарта ECMAScript, они были разработаны как раздел спецификации DOM.
function foo() {}
var id = setTimeout(foo, 1000) // возвращает число > 0
Функция setTimeout
возвращает идентификатор таймаута и планирует вызвать foo
через, примерно, тысячу миллисекунд. Функция foo
при этом будет вызвана ровно один раз.
В зависимости от разрешения таймера в используемом для запуска кода движке JavaScript, а также с учётом того, что JavaScript является однопоточным языком и посторонний код может заблокировать выполнение потока, нет никакой гарантии, что переданный код будет выполнен ровно через указанное в вызове setTimeout
время.
Переданная первым параметром функция будет вызвана как глобальный объект — это значит, что оператор this
в вызываемой функции будет ссылаться на этот самый объект.
function Foo() {
this.value = 42
this.method = function () {
// this ссылается на глобальный объект
console.log(this.value) // выведет в лог undefined
}
setTimeout(this.method, 500)
}
new Foo()
Замечание: Поскольку
setTimeout
принимает объект функции в качестве первого параметра, часто совершается ошибка в использованииsetTimeout(foo(), 1000)
, при котором будет использоваться возвращённое значение от вызова функцииfoo
, а не вызываться сама функцияfoo
. В большинстве случаев ошибка пройдёт незамеченной, а в случае если функция возвращаетundefined
,setTimeout
вообще не породит никакой ошибки.
Поочерёдные вызовы с использованием setInterval
¶
setTimeout
вызывает функцию единожды; setInterval
— как и предполагает название — вызывает функцию каждые X
миллисекунд. И его использование не рекомендуется.
В то время, когда исполняющийся код будет блокироваться во время вызова с таймаутом, setInterval
будет продолжать планировать последующие вызовы переданной функции. Это может (особенно в случае небольших интервалов) повлечь за собой выстраивание вызовов функций в очередь.
function foo() {
// что-то, что выполняется одну секунду
}
setInterval(foo, 100)
В приведённом коде foo
выполнится один раз и заблокирует этим главный поток на одну секунду.
Пока foo
блокирует код, setInterval
продолжает планировать последующие её вызовы. Теперь, когда первая foo
закончила выполнение, в очереди будут уже десять ожидающих выполнения вызовов foo
.
Разбираемся с потенциальной блокировкой кода¶
Самый простой и контролируемый способ — использовать 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
.
Замечание: Поскольку функции работы с таймаутами не определены в стандарте ECMAScript, точная внутренняя механика их работы может различаться от движка к движку. Известно, что Microsoft JScript использует конструктор
Function
вместо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.