Конспект JS-course

setTimeout и setInterval

Источник: http://learn.javascript.ru/settimeout-setinterval

Почти все реализации JavaScript имеют внутренний таймер-планировщик, который позволяет задавать вызов функции через заданный период времени.

В частности, эта возможность поддерживается в браузерах и в сервере Node.JS.

setTimeout

Синтаксис:

var timerId = setTimeout(func/code, delay[, arg1, arg2...])

Параметры:

  • func/code
    • Функция или строка кода для исполнения.
    • Строка поддерживается для совместимости, использовать её не рекомендуется.
  • delay
    • Задержка в милисекундах, 1000 милисекунд равны 1 секунде.
  • arg1, arg2…
    • Аргументы, которые нужно передать функции. Не поддерживаются в IE9-.
    • Исполнение функции произойдёт спустя время, указанное в параметре delay.

Например, следующий код вызовет alert('Привет') через одну секунду:

function func() {
  alert('Привет');
}
setTimeout(func, 1000);

Если первый аргумент является строкой, то интерпретатор создаёт анонимную функцию из этой строки.

То есть такая запись работает точно так же:

setTimeout("alert('Привет')", 1000);

Использование строк не рекомендуется, так как они могут вызвать проблемы при минимизации кода, и, вообще, сама возможность использовать строку сохраняется лишь для совместимости.

Вместо них используйте анонимные функции:

setTimeout(function() { alert('Привет') }, 1000);

Параметры для функции и контекст

Во всех современных браузерах, с учетом IE10, setTimeout позволяет указать параметры функции.

Пример ниже выведет "Привет, я Вася" везде, кроме IE9-:

function sayHi(who) {
  alert("Привет, я " + who);
}

setTimeout(sayHi, 1000, "Вася");

…Однако, в большинстве случаев нам нужна поддержка старого IE, а он не позволяет указывать аргументы. Поэтому, для того, чтобы их передать, оборачивают вызов в анонимную функцию:

function sayHi(who) {
  alert("Привет, я " + who);
}

setTimeout(function() { sayHi('Вася') }, 1000);

Вызов через setTimeout не передаёт контекст this.

В частности, вызов метода объекта через setTimeout сработает в глобальном контексте. Это может привести к некорректным результатам.

Например, вызовем user.sayHi() через одну секунду:

function User(id) {
  this.id = id;

  this.sayHi = function() {
    alert(this.id);
  };
}

var user = new User(12345);

setTimeout(user.sayHi, 1000); // ожидается 12345, но выведет "undefined"

Так как setTimeout запустит функцию user.sayHi в глобальном контексте, она не будет иметь доступ к объекту через this.

Иначе говоря, эти два вызова setTimeout делают одно и то же:

// (1) одна строка
setTimeout(user.sayHi, 1000);

// (2) то же самое в две строки
var func = user.sayHi;
setTimeout(func, 1000);

К счастью, эта проблема также легко решается созданием промежуточной функции:

function User(id) {
  this.id = id;

  this.sayHi = function() {
    alert(this.id);
  };
}

var user = new User(12345);

setTimeout(function() {
  user.sayHi();
}, 1000);

Функция-обёртка используется, чтобы кросс-браузерно передать аргументы и сохранить контекст выполнения.

Отмена исполнения

Функция setTimeout возвращает идентификатор timerId, который можно использовать для отмены действия.

Синтаксис:

clearTimeout(timerId)

В следующем примере мы ставим таймаут, а затем удаляем (передумали). В результате ничего не происходит.

var timerId = setTimeout(function() { alert(1) }, 1000);

clearTimeout(timerId);

setInterval

Метод setInterval имеет синтаксис, аналогичный setTimeout.

var timerId = setInterval(func/code, delay[, arg1, arg2...])

Смысл аргументов — тот же самый. Но, в отличие от setTimeout, он запускает выполнение функции не один раз, а регулярно повторяет её через указанный интервал времени. Остановить исполнение можно вызовом:

clearInterval(timerId)

Следующий пример при запуске станет выводить сообщение каждые две секунды, пока вы не нажмете на кнопку «Стоп»:

<input type="button" onclick="clearInterval(timer)" value="Стоп">

<script>
  var i = 1;
  var timer = setInterval(function() { alert(i++) }, 2000);
</script>

Очередь и наложение вызовов в setInterval

Вызов setInterval(функция, задержка) ставит функцию на исполнение через указанный интервал времени. Но здесь есть тонкость.

На самом деле пауза между вызовами меньше, чем указанный интервал.

Для примера, возьмем setInterval(function() { func(i++) }, 100). Она выполняет func каждые 100 мс, каждый раз увеличивая значение счетчика.

На картинке ниже, красный блок - это время исполнения func. Время между блоком — это время между запусками функции, и оно меньше, чем установленная задержка!

То есть, браузер инициирует запуск функции аккуратно каждые 100мс, без учета времени выполнения самой функции.

Бывает, что исполнение функции занимает больше времени, чем задержка. Например, функция сложная, а задержка маленькая. Или функция содержит операторы alert/confirm/prompt, которые блокируют поток выполнения. В этом случае начинаются интересные вещи.

Если запуск функции невозможен, потому что браузер занят — она становится в очередь и выполнится, как только браузер освободится.

Изображение ниже иллюстрирует происходящее для функции, которая долго исполняется.

Вызов функции, инициированный setInterval, добавляется в очередь и незамедлительно происходит, когда это становится возможным:

Второй запуск функции происходит сразу же после окончания первого:

Больше одного раза в очередь выполнение не ставится.

Если выполнение функции занимает больше времени, чем несколько запланированных исполнений, то в очереди она всё равно будет стоять один раз. Так что «накопления» запусков не происходит.

На изображении ниже setInterval пытается выполнить функцию в 200 мс и ставит вызов в очередь. В 300 мс и 400 мс таймер пробуждается снова, но ничего не просходит.

Вызов setInterval(функция, задержка) не гарантирует реальной задержки между исполнениями.

Бывают случаи, когда реальная задержка больше или меньше заданной. Вообще, не факт, что будет хоть какая-то задержка.

Повторение вложенным setTimeout

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

Ниже — пример, который выдает alert с интервалами 2 секунды между ними.

<input type="button" onclick="clearTimeout(timer)" value="Стоп">

<script>
  var i = 1;

  var timer = setTimeout(function run() {
    alert(i++);
    timer = setTimeout(run, 2000);
  }, 2000);

</script>

На временной линии выполнения будут фиксированные задержки между запусками. Иллюстрация для задержки 100мс:

Минимальная задержка таймера

У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4мс в современных браузерах. В более старых она может быть больше и достигать 15мс.

По стандарту, минимальная задержка составляет 4мс. Так что нет разницы между setTimeout(..,1) и setTimeout(..,4).

В поведении setTimeout и setInterval с нулевой задержкой есть браузерные особенности.

  1. В Opera, setTimeout(.., 0) — то же самое, что setTimeout(.., 4). Оно выполняется реже, чем setTimeout(.. ,2). Это особенность данного браузера.
  2. В Internet Explorer, нулевая задержка setInterval(.., 0) не сработает. Это касается именно setInterval, т.е. setTimeout(.., 0) работает нормально.

Реальная частота срабатывания

Срабатывание может быть и гораздо реже В ряде случаев задержка может быть не 4мс, а 30мс или даже 1000мс.

Большинство браузеров (десктопных в первую очередь) продолжают выполнять setTimeout/setInterval, даже если вкладка неактивна. При этом ряд из них (Chrome, FF, IE10) снижают минимальную частоту таймера, до 1 раза в секунду. Получается, что в «фоновой» вкладке будет срабатывать таймер, но редко.

При работе от батареи, в ноутбуке — браузеры тоже могут снижать частоту, чтобы реже выполнять код и экономить заряд батареи. Особенно этим известен IE. Снижение может достигать нескольких раз, в зависимости от настроек. При слишком большой загрузке процессора JavaScript может не успевать обрабатывать таймеры вовремя. При этом некоторые запуски setInterval будут пропущены.

Вывод: на частоту 4мс стоит ориентироваться, но не стоит рассчитывать.

Вывод интервалов в консоль Код, который считает интервалы времени между вызовами, выглядит примерно так:

var timeMark = new Date;
setTimeout(function go() {
  var diff = new Date - timeMark;

  // вывести очередную задержку в консоль вместо страницы
  console.log(diff);

  // запомним время в самом конце,
  // чтобы измерить задержку именно между вызовами
  timeMark = new Date;

  setTimeout(go, 100);
}, 100);

Трюк setTimeout(func, 0)

Этот трюк достоин войти в анналы JavaScript-хаков.

Функцию оборачивают в setTimeout(func, 0), если хотят запустить ее после окончания текущего скрипта.

Дело в том, что setTimeout никогда не выполняет функцию сразу. Он лишь планирует ее выполнение. Но интерпретатор JavaScript начнёт выполнять запланированные функции лишь после выполнения текущего скрипта.

По стандарту, setTimeout в любом случае не может выполнить функцию с задержкой 0. Как мы говорили раньше, обычно задержка составит 4мс. Но главное здесь именно то, что выполнение в любом случае будет после выполнения текущего кода.

Например:

var result;

function showResult() {
  alert(result);
}

setTimeout(showResult, 0);

result = 2*2;

// выведет 4

Итого

Методы setInterval(func, delay) и setTimeout(func, delay) позволяют запускать func регулярно/один раз через delay миллисекунд.

Оба метода возвращают идентификатор таймера. Его используют для остановки выполнения вызовом clearInterval/clearTimeout.

| | setInterval | setTimeout | || ----------- | ---------- | | Тайминг | Идет вызов строго по таймеру. Если интерпретатор занят — один вызов становится в очередь. Время выполнения функции не учитывается, поэтому промежуток времени от окончания одного запуска до начала другого может быть различным. | Рекурсивный вызов setTimeout используется вместо setInterval там, где нужна фиксированная пауза между выполнениями. | | Задержка | Минимальная задержка: 4мс. | Минимальная задержка: 4мс. | | Браузерные особенности | В IE не работает задержка 0. | В Opera нулевая задержка эквивалентна 4мс, остальные задержки обрабатываются точно, в том числе нестандартные 1мс, 2мс и 3мс. |