renovations

This commit is contained in:
Ilya Kantor 2015-01-14 10:23:45 +03:00
parent c7d4c7e3ff
commit e1948130f6
170 changed files with 1496 additions and 1161 deletions

View file

@ -0,0 +1,17 @@
```js
//+ run
function printNumbersInterval() {
var i = 1;
var timerId = setInterval(function() {
console.log(i);
if (i == 20) clearInterval(timerId);
i++;
}, 100);
}
// вызов
printNumbersInterval();
```

View file

@ -0,0 +1,21 @@
# Вывод чисел каждые 100мс
[importance 5]
Напишите функцию `printNumbersInterval()`, которая последовательно выводит в консоль числа от 1 до 20, с интервалом между числами 100мс. То есть, весь вывод должен занимать 2000мс, в течение которых каждые 100мс в консоли появляется очередное число.
Нажмите на кнопку, открыв консоль, для демонстрации:
<script>
function printNumbersInterval() {
var i = 1;
var timerId = setInterval(function() {
console.log(i);
if (i == 20) clearInterval(timerId);
i++;
}, 100);
}
</script>
<button onclick="printNumbersInterval()">printNumbersInterval()</button>
</script>
P.S. Функция должна использовать `setInterval`.

View file

@ -0,0 +1,17 @@
```js
//+ run
function printNumbersTimeout20_100() {
var i = 1;
var timerId = setTimeout(function go() {
console.log(i);
if (i < 20) setTimeout(go, 100);
i++;
}, 100);
}
// вызов
printNumbersTimeout20_100();
```

View file

@ -0,0 +1,5 @@
# Вывод чисел каждые 100мс, через setTimeout
[importance 5]
Сделайте то же самое, что в задаче [](/task/output-numbers-100ms), но с использованием рекурсивного `setTimeout` вместо `setInterval`.

View file

@ -0,0 +1,5 @@
**Нужно выбрать вариант 2, который гарантирует браузеру свободное время между выполнениями `highlight`.**
Первый вариант может загрузить процессор на 100%, если `highlight` занимает время, близкое к 10мс или, тем более, большее чем 10мс, т.к. таймер не учитывает время выполнения функции.
Что интересно, в обоих случаях браузер не будет выводить предупреждение о том, что скрипт занимает много времени. Но от 100% загрузки процессора возможны притормаживания других операций. В общем, это совсем не то, что мы хотим, поэтому вариант 2.

View file

@ -0,0 +1,34 @@
# Для подсветки setInterval или setTimeout?
[importance 5]
Стоит задача: реализовать подсветку синтаксиса в длинном коде при помощи JavaScript, для онлайн-редактора кода. Это требует сложных вычислений, особенно загружает процессор генерация дополнительных элементов страницы, визуально осуществляющих подсветку.
Поэтому решаем обрабатывать не весь код сразу, что привело бы к зависанию скрипта, а разбить работу на части: подсвечивать по 20 строк раз в 10мс.
Как мы знаем, есть два варианта реализации такой подсветки:
<ol>
<li>Через `setInterval`, с остановкой по окончании работы:
```js
timer = setInterval(function() {
if (есть еще что подсветить) highlight();
else clearInterval(timer);
}, 10);
```
</li>
<li>Через рекурсивный `setTimeout`:
```js
setTimeout(function go() {
highlight();
if (есть еще что подсветить) setTimeout(go, 10);
}, 10);
```
</li>
</ol>
Какой из них стоит использовать? Почему?

View file

@ -0,0 +1,8 @@
Ответы:
<ul>
<li>`alert` выведет `100000000`.</li>
<li>**3**, срабатывание будет после окончания работы `hardWork`.</li>
</ul>
Так будет потому, что вызов планируется на `100мс` от времени вызова `setTimeout`, но функция выполняется больше, чем `100мс`, поэтому к моменту ее окончания время уже подошло и отложенный вызов выполняется тут же.

View file

@ -0,0 +1,31 @@
# Что выведет setTimeout?
[importance 5]
В коде ниже запланирован запуск `setTimeout`, а затем запущена тяжёлая функция `f`, выполнение которой занимает более долгое время, чем интервал до срабатывания таймера.
Когда сработает `setTimeout`? Выберите нужный вариант:
<ol>
<li>До выполнения `f`.</li>
<li>Во время выполнения `f`.</li>
<li>Сразу же по окончании `f`.</li>
<li>Через 100мс после окончания `f`.</li>
</ol>
Что выведет `alert` в коде ниже?
```js
setTimeout(function() {
alert(i);
}, 100);
var i;
function hardWork() {
// время выполнения этого кода >100мс, сам код неважен
for(i=0; i<1e8; i++) hardWork[i%2] = i;
}
hardWork();
```

View file

@ -0,0 +1,37 @@
Вызов `alert(i)` в `setTimeout` введет `100000001`.
Можете проверить это запуском:
```js
//+ run
var timer = setInterval(function() {
i++;
}, 10);
setTimeout(function() {
clearInterval(timer);
*!*
alert(i); // (*)
*/!*
}, 50);
var i;
function f() {
// точное время выполнения не играет роли
// здесь оно заведомо больше 100мс
for(i=0; i<1e8; i++) f[i%2] = i;
}
f();
```
Правильный вариант срабатывания: **3** (сразу же по окончании `f` один раз).
Планирование `setInterval` будет вызывать функцию каждые `10мс` после текущего времени. Но так как интерпретатор занят долгой функцией, то до конца ее работы никакого вызова не происходит.
За время выполнения `f` может пройти время, на которое запланированы несколько вызовов `setInterval`, но в этом случае остается только один, т.е. накопления вызовов не происходит. Такова логика работы `setInterval`.
После окончания текущего скрипта интерпретатор обращается к очереди запланированных вызовов, видит в ней `setInterval` и выполняет. А затем тут же выполняется `setTimeout`, очередь которого тут же подошла.
Итого, как раз и видим, что `setInterval` выполнился ровно 1 раз по окончании работы функции. Такое поведение кросс-браузерно.

View file

@ -0,0 +1,44 @@
# Что выведет после setInterval?
[importance 5]
В коде ниже запускается `setInterval` каждые 10мс, и через 50мс запланирована его отмена.
После этого запущена тяжёлая функция `f`, выполнение которой (мы точно знаем) потребует более 100мс.
Сработает ли `setInterval`, как и когда?
Варианты:
<ol>
<li>Да, несколько раз, *в процессе* выполнения `f`.</li>
<li>Да, несколько раз, *сразу после* выполнения `f`.</li>
<li>Да, один раз, *сразу после* выполнения `f`.</li>
<li>Нет, не сработает.</li>
<li>Может быть по-разному, как повезёт.</li>
</ol>
Что выведет `alert` в строке `(*)`?
```js
var i;
var timer = setInterval(function() { // планируем setInterval каждые 10мс
i++;
}, 10);
setTimeout(function() { // через 50мс - отмена setInterval
clearInterval(timer);
*!*
alert(i); // (*)
*/!*
}, 50);
// и запускаем тяжёлую функцию
function f() {
// точное время выполнения не играет роли
// здесь оно заведомо больше 100мс
for(i=0; i<1e8; i++) f[i%2] = i;
}
f();
```

View file

@ -0,0 +1,55 @@
Задача -- с небольшим "нюансом".
Есть браузеры, в которых на время работы JavaScript таймер "застывает", например таков IE. В них количество шагов будет почти одинаковым, +-1.
В других браузерах (Chrome) первый бегун будет быстрее.
Создадим реальные объекты `Runner` и запустим их для проверки:
```js
//+ run
function Runner() {
this.steps = 0;
this.step = function() {
this.doSomethingHeavy();
this.steps++;
};
function fib(n) {
return n <= 1 ? n : fib(n-1) + fib(n-2);
}
this.doSomethingHeavy = function() {
for(var i=0; i<25; i++) {
this[i] = fib(i);
}
};
}
var runner1 = new Runner();
var runner2 = new Runner();
// запускаем бегунов
var t1 = setInterval(function() {
runner1.step();
}, 15);
var t2 = setTimeout(function go() {
runner2.step();
t2 = setTimeout(go, 15);
}, 15);
// кто сделает больше шагов?
setTimeout(function() {
clearInterval(t1);
clearTimeout(t2);
alert(runner1.steps);
alert(runner2.steps);
}, 5000);
```
Если бы в шаге `step()` не было вызова `doSomethingHeavy()`, то есть он бы не требовал времени, то количество шагов было бы почти равным.
Но так как у нас шаг, всё же, что-то делает, и функция `doSomethingHeavy()` специально написана таким образом, что она требует (небольшого) времени, то первый бегун успеет сделать больше шагов. Ведь в `setTimeout` пауза `15` мс будет *между* шагами, а `setInterval` шагает равномерно, каждые `15` мс. Получается чаще.

View file

@ -0,0 +1,35 @@
# Кто быстрее?
[importance 5]
Есть два бегуна:
```js
var runner1 = new Runner();
var runner2 = new Runner();
```
У каждого есть метод `step()`, который делает шаг, увеличивая свойство `steps`.
Конкретный код метода `step()` не имеет значения, важно лишь что шаг делается не мгновенно, он требует небольшого времени.
Если запустить первого бегуна через `setInterval`, а второго -- через вложенный `setTimeout` -- какой сделает больше шагов за 5 секунд?
```js
// первый?
setInterval(function() {
runner1.step();
}, 15);
// или второй?
setTimeout(function go() {
runner2.step();
setTimeout(go, 15);
}, 15);
setTimeout(function() {
alert(runner1.steps);
alert(runner2.steps);
}, 5000);
```

View file

@ -0,0 +1,12 @@
function delay(f, ms) {
return function() {
var savedThis = this;
var savedArgs = arguments;
setTimeout(function() {
f.apply(savedThis, savedArgs);
}, ms);
};
}

View file

@ -0,0 +1,45 @@
describe("delay", function() {
before(function() {
this.clock = sinon.useFakeTimers();
});
after(function() {
this.clock.restore();
});
it("вызывает функцию через указанный таймаут", function() {
var start = Date.now();
function f(x) {
assert.equal(Date.now() - start, 1000);
}
f = sinon.spy(f);
var f1000 = delay(f, 1000);
f1000("test");
this.clock.tick(2000);
assert(f.calledOnce, 'calledOnce check fails');
});
it("передаёт аргументы и контекст", function() {
var start = Date.now();
var user = {
sayHi: function(phrase, who) {
assert.equal(this, user);
assert.equal(phrase, "Привет");
assert.equal(who, "Вася");
assert.equal(Date.now() - start, 1500);
}
};
user.sayHi = sinon.spy(user.sayHi);
var spy = user.sayHi;
user.sayHi = delay(user.sayHi, 1500);
user.sayHi("Привет", "Вася");
this.clock.tick(2000);
assert(spy.calledOnce, 'проверка calledOnce не сработала');
});
});

View file

@ -0,0 +1,46 @@
```js
//+ run
function delay(f, ms) {
*!*
return function() {
var savedThis = this;
var savedArgs = arguments;
setTimeout(function() {
f.apply(savedThis, savedArgs);
}, ms);
};
*/!*
}
function f(x) {
alert(x);
}
var f1000 = delay(f, 1000);
var f1500 = delay(f, 1500);
f1000("тест"); // выведет "тест" через 1000 миллисекунд
f1500("тест2"); // выведет "тест2" через 1500 миллисекунд
```
Обратим внимание на то, как работает обёртка:
```js
return function() {
var savedThis = this;
var savedArgs = arguments;
setTimeout(function() {
f.apply(savedThis , savedArgs);
}, ms);
};
```
Именно обёртка возвращается декоратором `delay` и будет вызвана. Чтобы передать аргумент и контекст функции, вызываемой через `ms` миллисекунд, они копируются в локальные переменные `savedThis` и `savedArgs`.
Это один из самых простых, и в то же время удобных способов передать что-либо в функцию, вызываемую через `setTimeout`.

View file

@ -0,0 +1,23 @@
# Функция-задержка
[importance 5]
**Напишите функцию `delay(f, ms)`, которая возвращает обёртку вокруг `f`, задерживающую вызов на `ms` миллисекунд.**
Например:
```js
function f(x) {
alert(x);
}
var f1000 = delay(f, 1000);
var f1500 = delay(f, 1500);
f1000("тест"); // выведет "тест" через 1000 миллисекунд
f1500("тест2"); // выведет "тест2" через 1500 миллисекунд
```
Иначе говоря, `f1000` -- это "задержанный на 1000мс" вызов `f`.
В примере выше у функции только один аргумент, но `delay` должна быть универсальной: передавать любое количество аргументов и контекст `this`.

View file

@ -0,0 +1,17 @@
function debounce(f, ms) {
var state = null;
var COOLDOWN = 1;
return function() {
if (state) return;
f.apply(this, arguments);
state = COOLDOWN;
setTimeout(function() { state = null }, ms);
}
}

View file

@ -0,0 +1,38 @@
describe("debounce", function() {
before(function() {
this.clock = sinon.useFakeTimers();
});
after(function() {
this.clock.restore();
});
it("вызывает функцию не чаще чем раз в ms миллисекунд", function() {
var log = '';
function f(a) { log += a; }
f = debounce(f, 1000);
f(1); // выполнится сразу же
f(2); // игнор
setTimeout(function() { f(3) }, 100); // игнор (рановато)
setTimeout(function() { f(4) }, 1100); // выполнится (таймаут прошёл)
setTimeout(function() { f(5) }, 1500); // игнор
this.clock.tick(5000);
assert.equal(log, "14");
});
it("сохраняет контекст вызова", function() {
var obj = {
f: function() {
assert.equal(this, obj);
}
};
obj.f = debounce(obj.f, 1000);
obj.f("test");
});
});

View file

@ -0,0 +1,38 @@
```js
//+ run
function debounce(f, ms) {
var state = null;
var COOLDOWN = 1;
return function() {
if (state) return;
f.apply(this, arguments);
state = COOLDOWN;
setTimeout(function() { state = null }, ms);
}
}
function f(x) { alert(x) }
var f = debounce(f, 1000);
f(1); // 1, выполнится сразу же
f(2); // игнор
setTimeout( function() { f(3) }, 100); // игнор (прошло только 100мс)
setTimeout( function() { f(4) }, 1100); // 4, выполнится
setTimeout( function() { f(5) }, 1500); // игнор
```
Вызов `debounce` возвращает функцию-обёртку. Все необходимые данные для неё хранятся в замыкании.
При вызове ставится таймер и состояние `state` меняется на константу `COOLDOWN` ("в процессе охлаждения").
Последующие вызовы игнорируются, пока таймер не обнулит состояние.

View file

@ -0,0 +1,23 @@
# Вызов не чаще чем в N миллисекунд
[importance 5]
**Напишите функцию `debounce(f, ms)`, которая возвращает обёртку, которая передаёт вызов `f` не чаще, чем раз в `ms` миллисекунд.**
"Лишние" вызовы игнорируются. Все аргументы и контекст -- передаются.
Например:
```js
function f() { ... }
var f = debounce(f, 1000);
f(1); // выполнится сразу же
f(2); // игнор
setTimeout( function() { f(3) }, 100); // игнор (прошло только 100мс)
setTimeout( function() { f(4) }, 1100); // выполнится
setTimeout( function() { f(5) }, 1500); // игнор
```

View file

@ -0,0 +1,29 @@
function throttle(func, ms) {
var isThrottled = false,
savedArgs,
savedThis;
function wrapper() {
if (isThrottled) {
savedArgs = arguments;
savedThis = this;
return;
}
func.apply(this, arguments);
isThrottled = true;
setTimeout(function() {
isThrottled = false;
if (savedArgs) {
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}
return wrapper;
}

View file

@ -0,0 +1,44 @@
describe("throttle(f, 1000)", function() {
var f1000;
var log = "";
function f(a) { log += a; }
before(function() {
f1000 = throttle(f, 1000);
this.clock = sinon.useFakeTimers();
});
it("первый вызов срабатывает тут же", function() {
f1000(1); // такой вызов должен сработать тут же
assert.equal(log, "1");
});
it("тормозит второе срабатывание до 1000мс", function() {
f1000(2); // (тормозим, не прошло 1000мс)
f1000(3); // (тормозим, не прошло 1000мс)
// через 1000 мс запланирован вызов с последним аргументом
assert.equal(log, "1"); // пока что сработал только первый вызов
this.clock.tick(1000); // прошло 1000мс времени
assert.equal(log, "13"); // log==13, т.к. сработал вызов f1000(3)
});
it("тормозит третье срабатывание до 1000мс после второго", function() {
this.clock.tick(100);
f1000(4); // (тормозим, с последнего вызова прошло 100мс - менее 1000мс)
this.clock.tick(100);
f1000(5); // (тормозим, с последнего вызова прошло 200мс - менее 1000мс)
this.clock.tick(700);
f1000(6); // (тормозим, с последнего вызова прошло 900мс - менее 1000мс)
this.clock.tick(100); // сработал вызов с 6
assert.equal(log, "136");
});
after(function() {
this.clock.restore();
});
});

View file

@ -0,0 +1,42 @@
```js
function throttle(func, ms) {
var isThrottled = false,
savedArgs,
savedThis;
function wrapper() {
if (isThrottled) { // (2)
savedArgs = arguments;
savedThis = this;
return;
}
func.apply(this, arguments); // (1)
isThrottled = true;
setTimeout(function() {
isThrottled = false; // (3)
if (savedArgs) {
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}
return wrapper;
}
```
Шаги работы этой функции:
<ol>
<li>Декоратор `throttle` возвращает функцию-обёртку `wrapper`, которая при первом вызове запускает `func` и переходит в состояние "паузы" (`isThrottled = true`).</li>
<li>В этом состоянии все новые вызовы запоминаются в замыкании через `savedArgs/savedThis`. Обратим внимание, что и контекст вызова и аргументы для нас одинаково важны и запоминаются одновременно. Только зная и то и другое, можно воспроизвести вызов правильно.</li>
<li>Далее, когда пройдёт таймаут `ms` миллисекунд -- пауза будет снята, а `wrapper` -- запущен с последними аргументами и контекстом (если во время паузы были вызовы).</li>
</ol>
Шаг `(3)` запускает именно не саму функцию, а снова `wrapper`, так как необходимо не только выполнить `func`, но и снова поставить выполнение на паузу. Получается последовательность "вызов - пауза.. вызов - пауза .. вызов - пауза ...", каждое выполнение в обязательном порядке сопровождается паузой после него. Это удобно описывается рекурсией.

View file

@ -0,0 +1,50 @@
# Тормозилка
[importance 5]
Напишите функцию `throttle(f, ms)` -- "тормозилку", которая возвращает обёртку, передающую вызов `f` не чаще, чем раз в `ms` миллисекунд.
**У этой функции должно быть важное существенное отличие от `debounce`:** если игнорируемый вызов оказался последним, т.е. после него до окончания задержки ничего нет -- то он выполнится.
Чтобы лучше понять, откуда взялось это требование, и как `throttle` должна работать -- разберём реальное применение, на которое и ориентирована эта задача.
**Например, нужно обрабатывать передвижения мыши.**
В JavaScript это делается функцией, которая будет запускаться при каждом микро-передвижении мыши и получать координаты курсора. По мере того, как мышь двигается, эта функция может запускаться очень часто, может быть 100 раз в секунду (каждые 10мс).
**Функция обработки передвижения должна обновлять некую информацию на странице.**
При этом обновление -- слишком "тяжёлый" процесс, чтобы делать его при каждом микро-передвижении. Имеет смысл делать его раз в 100мс, не чаще.
Пусть функция, которая осуществляет это обновление по передвижению, называется `onmousemove`.
Вызов `throttle(onmousemove, 100)`, по сути, предназначен для того, чтобы "притормаживать" обработку `onmousemove`. Технически, он должен возвращать обёртку, которая передаёт все вызовы `onmousemove`, но не чаще чем раз в 100мс.
**При этом промежуточные движения можно игнорировать, но мышь в конце концов где-то остановится. И это последнее, итоговое положение мыши обязательно нужно обработать!**
Визуально это даст следующую картину обработки перемещений мыши:
<ol>
<li>Первое обновление произойдёт сразу (это важно, посетитель тут же видит реакцию на своё действие).</li>
<li>Дальше может быть много вызовов (микро-передвижений) с разными координатами, но пока не пройдёт 100мс -- ничего не будет.</li>
<li>По истечении 100мс -- опять обновление, с последними координатами. Промежуточные микро-передвижения игнорированы.</li>
<li>В конце концов мышь где-то остановится, обновление по окончании очередной паузы 100мс (иначе мы не знаем, последнее оно или нет) сработает с последними координатами.</li>
</ol>
Ещё раз заметим -- задача из реальной жизни, и в ней принципиально важно, что *последнее* передвижение обрабатывается. Пользователь должен увидеть, где остановил мышь.
Пример использования:
```js
var f = function(a) { console.log(a) };
// затормозить функцию до одного раза в 1000 мс
var f1000 = throttle(f, 1000);
f1000(1); // выведет 1
f1000(2); // (тормозим, не прошло 1000мс)
f1000(3); // (тормозим, не прошло 1000мс)
// когда пройдёт 1000мс...
// выведет 3, промежуточное значение 2 игнорируется
```

View file

@ -0,0 +1,318 @@
# setTimeout и setInterval
Почти все реализации JavaScript имеют внутренний таймер-планировщик, который позволяет задавать вызов функции через заданный период времени.
В частности, эта возможность поддерживается в браузерах и в сервере Node.JS.
[cut]
## setTimeout
Синтаксис:
```js
var timerId = setTimeout(func/code, delay[, arg1, arg2...])
```
Параметры:
<dl>
<dt>`func/code`</dt>
<dd>Функция или строка кода для исполнения.
Строка поддерживается для совместимости, использовать её не рекомендуется.</dd>
<dt>`delay`</dt>
<dd>Задержка в милисекундах, 1000 милисекунд равны 1 секунде.</dd>
<dt>`arg1`, `arg2`...</dt>
<dd>Аргументы, которые нужно передать функции. Не поддерживаются в IE9-.</dd>
</dl>
Исполнение функции произойдёт спустя время, указанное в параметре `delay`.
Например, следующий код вызовет `func()` через одну секунду:
```js
//+ run
function func() {
alert('Привет');
}
*!*
setTimeout(func, 1000);
*/!*
```
С передачей аргументов (не сработает в IE9-):
```js
//+ run
function func(phrase, who) {
alert(phrase + ', ' + who);
}
*!*
setTimeout(func, 1000, "Привет", "Вася"); // Привет, Вася
*/!*
```
Если первый аргумент является строкой, то интерпретатор создаёт анонимную функцию из этой строки.
То есть такая запись тоже сработает:
```js
//+ run
setTimeout("alert('Привет')", 1000);
```
Однако, использование строк не рекомендуется, так как они могут вызвать проблемы при минимизации кода, и, вообще, сама возможность использовать строку сохраняется лишь для совместимости.
Вместо них используйте анонимные функции, вот так:
```js
//+ run
setTimeout(function() { alert('Привет') }, 1000);
```
### Отмена исполнения clearTimeout
Функция `setTimeout` возвращает числовой идентификатор таймера `timerId`, который можно использовать для отмены действия.
Синтаксис:
```js
var timerId = setTimeout(...);
clearTimeout(timerId);
```
В следующем примере мы ставим таймаут, а затем удаляем (передумали). В результате ничего не происходит.
```js
//+ run
var timerId = setTimeout(function() { alert(1) }, 1000);
alert(timerId); // число - идентификатор таймера
clearTimeout(timerId);
alert(timerId); // всё ещё число, оно не обнуляется после отмены
```
Как видно из `alert`, в браузере идентификатор таймера является обычным числом. Другие JavaScript-окружения, например Node.JS, могут возвращать объект таймера, с дополнительными методами.
**Такие разночтения вполне соответствуют стандарту просто потому, что в спецификации JavaScript про таймеры нет ни слова.**
Таймеры -- это надстройка над JavaScript, которая описана в [секции Timers](http://www.w3.org/TR/html5/webappapis.html#timers) стандарта HTML5 для браузеров и в [документации к Node.JS](http://nodejs.org/docs/latest/api/timers.html) -- для сервера.
## setInterval
Метод `setInterval` имеет синтаксис, аналогичный `setTimeout`.
```js
var timerId = setInterval(func/code, delay[, arg1, arg2...])
```
Смысл аргументов -- тот же самый. Но, в отличие от `setTimeout`, он запускает выполнение функции не один раз, а регулярно повторяет её через указанный интервал времени. Остановить исполнение можно вызовом `clearInterval(timerId)`.
Следующий пример при запуске станет выводить сообщение каждые две секунды, пока не пройдёт 5 секунд:
```js
//+ run
// начать повторы с интервалом 2 сек
var timerId = setInterval(function() {
alert("тик");
}, 2000);
// через 5 сек остановить повторы
setTimeout(function() {
clearInterval(timerId);
alert('стоп');
}, 5000);
```
[smart header="Модальные окна замораживают время в Chrome/Opera/Safari"]
Что будет, если долго не жать `OK` на появившемся `alert`? Это зависит от браузера.
В браузерах Chrome, Opera и Safari внутренний таймер "заморожен" во время показа `alert/confirm/prompt`. А вот в IE и Firefox внутренний таймер продолжит идти.
Поэтому, если закрыть `alert` после небольшой паузы, то в Firefox/IE следующий `alert` будет показан сразу же (время подошло), а в Chrome/Opera/Safari -- только через 2 секунды после закрытия.
[/smart]
### Рекурсивный setTimeout
Важная альтернатива `setInterval` -- рекурсивный `setTimeout`:
```js
/** вместо:
var timerId = setInterval(function() {
alert("тик");
}, 2000);
*/
var timerId = setTimeout(function tick() {
alert("тик");
*!*
timerId = setTimeout(tick, 2000);
*/!*
}, 2000);
```
В коде выше следующее выполнение планируется сразу после окончания предыдущего.
**Рекурсивный `setTimeout` -- более гибкий метод тайминга, чем `setInterval`, так как время до следующего выполнения можно запланировать по-разному, в зависимости от результатов текущего.**
Например, у нас есть сервис, который в 5 секунд опрашивает сервер на предмет новых данных. В случае, если сервер перегружен, можно увеличивать интервал опроса до 10, 20, 60 секунд... А потом вернуть обратно, когда всё нормализуется.
Если у нас регулярно проходят грузящие процессор задачи, то мы можем оценивать время, потраченное на их выполнение, и планировать следующий запуск раньше или позже.
**Рекурсивный `setTimeout` гарантирует паузу между вызовами, `setInterval` -- нет.**
Давайте сравним два кода. Первый использует `setInterval`:
```js
var i = 1;
setInterval(function() {
func(i);
}, 100);
```
Второй использует рекурсивный `setTimeout`:
```js
var i = 1;
setTimeout(function run() {
func(i);
setTimeout(run, 100);
}, 100);
```
При `setInterval` внутренний таймер будет срабатывать чётко каждые `100` мс и вызывать `func(i)`:
<img src="setinterval-interval.svg">
Вы обратили внимание?...
**Реальная пауза между вызовами `func` при `setInterval` меньше, чем указана в коде!**
Это естественно, ведь время работы функции никак не учитывается, оно "съедает" часть интервала.
Возможно и такое что `func` оказалась сложнее, чем мы рассчитывали и выполнялась дольше, чем 100мс.
В этом случае интерпретатор будет ждать, пока функция завершится, затем проверит таймер и, если время вызова `setInterval` уже подошло (или прошло), то следующий вызов произойдёт *сразу же*.
**Если функция и выполняется дольше, чем пауза `setInterval`, то вызовы будут происходить вообще без перерыва.**
Исключением является IE, в котором таймер "застывает" во время выполнения JavaScript.
А так будет выглядить картинка с рекурсивным `setTimeout`:
<img src="settimeout-interval.svg">
**При рекурсивном `setTimeout` задержка всегда фиксирована и равна 100мс.**
Это происходит потому, что каждый новый запуск планируется только после окончания текущего.
[smart header="Управление памятью"]
Сборщик мусора в JavaScript не чистит функции, назначенные в таймерах, пока таймеры актуальны.
При передаче функции в `setInterval/setTimeout` создаётся внутренняя ссылка на неё, через которую браузер её будет запускать, и которая препятствует удалению из памяти, даже если функция анонимна.
```js
// Функция будет жить в памяти, пока не сработал (или не был очищен) таймер
setTimeout(function() {}, 100);
```
<ul>
<li>Для `setTimeout` -- внутренняя ссылка исчезнет после исполнения функции.</li>
<li>Для `setInterval` -- ссылка исчезнет при очистке таймера.</li>
</ul>
Так как функция также тянет за собой всё замыкание, то ставшие неактуальными, но не отменённые `setInterval` могут приводить к излишним тратам памяти.
[/smart]
## Минимальная задержка таймера
У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4мс в современных браузерах. В более старых она может быть больше и достигать 15мс.
По [стандарту](http://www.w3.org/TR/html5/webappapis.html#timers), минимальная задержка составляет 4мс. Так что нет разницы между `setTimeout(..,1)` и `setTimeout(..,4)`.
Посмотреть минимальное разрешение "вживую" можно на следующем примере.
**В примере ниже каждая полоска удлиняется вызовом `setInterval` с указанной на ней задержкой -- от 0мс (сверху) до 20мс (внизу).**
Позапускайте его в различных браузерах. Вы заметите, что несколько первых полосок анимируются с одинаковой скоростью. Это как раз потому, что слишком маленькие задержки таймер не различает.
[iframe border="1" src="setInterval-anim" link edit]
[warn]
В Internet Explorer, нулевая задержка `setInterval(.., 0)` не сработает. Это касается именно `setInterval`, т.е. `setTimeout(.., 0)` работает нормально.
[/warn]
## Реальная частота срабатывания
В ряде ситуаций таймер будет срабатывать реже, чем обычно. Задержка между вызовами `setInterval(..., 4)` может быть не 4мс, а 30мс или даже 1000мс.
<ul>
<li>Большинство браузеров (десктопных в первую очередь) продолжают выполнять `setTimeout/setInterval`, даже если вкладка неактивна.
При этом ряд из них (Chrome, FF, IE10) снижают минимальную частоту таймера, до 1 раза в секунду. Получается, что в "фоновой" вкладке будет срабатывать таймер, но редко.</li>
<li>При работе от батареи, в ноутбуке -- браузеры тоже могут снижать частоту, чтобы реже выполнять код и экономить заряд батареи. Особенно этим известен IE. Снижение может достигать нескольких раз, в зависимости от настроек.</li>
<li>При слишком большой загрузке процессора JavaScript может не успевать обрабатывать таймеры вовремя. При этом некоторые запуски `setInterval` будут пропущены.</li>
</ul>
**Вывод: на частоту 4мс стоит ориентироваться, но не стоит рассчитывать.**
[online]
Посмотрим снижение частоты в действии на небольшом примере.
При клике на кнопку ниже запускается `setInterval(..., 90)`, который выводит список интервалов времени между 25 последними срабатываниями таймера. Запустите его. Перейдите на другую вкладку и вернитесь.
<div id="timer-interval-log"></div>
<button onclick="timerIntervalLog()">Запустить повтор с интервалом в 90 мс</button>
<button onclick="clearInterval(timerIntervalLogTimer)">Остановить повтор</button>
<script>
var timerIntervalLogTimer;
function timerIntervalLog() {
var arr = [];
var d = new Date;
timerIntervalLogTimer = setInterval(function() {
var diff = new Date - d;
if (diff > 100) diff = '<span style="color:red">'+diff+'</span>';
arr.push( diff );
if (arr.length > 25) arr.shift();
document.getElementById('timer-interval-log').innerHTML = arr;
d = new Date;
}, 90);
}
</script>
Если ваш браузер увеличивает таймаут при фоновом выполнении вкладки, то вы увидите увеличенные интервалы, помеченные <span style="color:red">красным</span>.
Кроме того, вы заметите, что таймер не является идеально точным ;)
[/online]
## Разбивка долгих скриптов
Нулевой или небольшой таймаут также используют, чтобы разорвать поток выполнения "тяжелых" скриптов.
Например, скрипт для подсветки синтаксиса должен проанализировать код, создать много цветных элементов для подсветки и добавить их в документ -- на большом файле это займёт много времени, браузер может даже подвиснуть, что неприемлемо.
Для того, чтобы этого избежать, сложная задача разбивается на части, выполнение каждой части запускается через мини-интервал после предыдущей, чтобы дать браузеру время.
Например, осуществляется анализ и подсветка первых 100 строк, затем через 20 мс -- следующие 100 строк и так далее. При этом можно подстраиваться под CPU посетителя: замерять время на анализ 100 строк и, если процессор хороший, то в следующий раз обработать 200 строк, а если плохой -- то 50. В итоге подсветка будет работать с адекватной быстротой и без тормозов на любых текстах и компьютерах.
## Итого
<ul>
<li>Методы `setInterval(func, delay)` и `setTimeout(func, delay)` позволяют запускать `func` регулярно/один раз через `delay` миллисекунд.</li>
<li>Оба метода возвращают идентификатор таймера. Его используют для остановки выполнения вызовом `clearInterval/clearTimeout`.</li>
<li>В случаях, когда нужно гарантировать задержку между регулярными вызовами или гибко её менять, вместо `setInterval` используют рекурсивный `setTimeout`.</li>
<li>Минимальная задержка по стандарту составляет `4мс`. Браузеры соблюдают этот стандарт, но некоторые другие среды для выполнения JS, например Node.JS, могут предоставить и меньше задержки.</li>
<li>В реальности срабатывания таймера могут быть гораздо реже, чем назначено, например если процессор перегружен, вкладка находится в фоновом режиме, ноутбук работает от батареи или по какой-то иной причине.</li>
Браузерных особенностей почти нет, разве что вызов `setInterval(..., 0)` с нулевой задержкой в IE недопустим, нужно указывать `setInterval(..., 1)`.

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style> div { height: 18px; margin: 1px; background-color:green; } </style>
</head>
<body>
<input type="button" id="start" value="Старт">
<input type="button" id="stop" value="Стоп" disabled>
<script>
for (var i=0; i<=20; i+=2) {
document.write('<div>'+i+'</div>');
}
var startButton = document.getElementById('start');
var stopButton = document.getElementById('stop');
var timers = [];
stopButton.onclick = function() {
startButton.disabled = false;
stopButton.disabled = true;
for(var i=0; i<timers.length; i++) clearInterval(timers[i]);
timers = [];
}
startButton.onclick = function() {
startButton.disabled = true;
stopButton.disabled = false;
var divs = document.getElementsByTagName('div');
for (var i=0; i<divs.length; i++) {
animateDiv(divs, i);
}
}
function animateDiv(divs, i) {
var div = divs[i], speed = div.innerHTML;
timers[i] = setInterval(function() {
div.style.width = (parseInt(div.style.width||0) + 2) % 400 + 'px'
}, speed);
}
</script>
</body>
</html>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="631px" height="223px" viewBox="0 0 631 223" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>setinterval-interval</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="setinterval-interval" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<path d="M32.5,122.5 L582.5,122.5" id="Line" stroke="#979797" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<g id="Rectangle-1-+-func(1)-+-noun_60230_cc" sketch:type="MSLayerGroup" transform="translate(128.000000, 41.000000)">
<rect id="Rectangle-1" stroke="#979797" fill="#FFFFFF" sketch:type="MSShapeGroup" x="3" y="60" width="90" height="40"></rect>
<text id="func(1)" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="25" y="84">func(1)</tspan>
</text>
<g id="noun_60230_cc" fill="#000000" sketch:type="MSShapeGroup">
<path d="M22.5818182,19.3424658 L12.9113636,16.4383562 L20.4909091,0.273972603 L0.470454545,21.0958904 L10.1931818,24 L4.28636364,39.890411 L22.5818182,19.3424658 L22.5818182,19.3424658 Z M2.19545455,20.6575342 L17.3022727,4.93150685 L11.6045455,17.0410959 L20.8568182,19.7808219 L6.9,35.6164384 L11.4477273,23.3972603 L2.19545455,20.6575342 L2.19545455,20.6575342 Z" id="Shape"></path>
</g>
<path d="M3,114.5 L3,44.5" id="Line" stroke="#373535" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
</g>
<g id="Rectangle-1-+-func(1)-+-noun_60230_cc-2" sketch:type="MSLayerGroup" transform="translate(271.000000, 41.000000)">
<rect id="Rectangle-1" stroke="#979797" fill="#FFFFFF" sketch:type="MSShapeGroup" x="3" y="60" width="90" height="40"></rect>
<text id="func(2)" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="25" y="84">func(2)</tspan>
</text>
<g id="noun_60230_cc" fill="#000000" sketch:type="MSShapeGroup">
<path d="M22.5818182,19.3424658 L12.9113636,16.4383562 L20.4909091,0.273972603 L0.470454545,21.0958904 L10.1931818,24 L4.28636364,39.890411 L22.5818182,19.3424658 L22.5818182,19.3424658 Z M2.19545455,20.6575342 L17.3022727,4.93150685 L11.6045455,17.0410959 L20.8568182,19.7808219 L6.9,35.6164384 L11.4477273,23.3972603 L2.19545455,20.6575342 L2.19545455,20.6575342 Z" id="Shape"></path>
</g>
<path d="M3,114.5 L3,44.5" id="Line" stroke="#373535" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
</g>
<g id="Rectangle-1-+-func(1)-+-noun_60230_cc-3" sketch:type="MSLayerGroup" transform="translate(405.000000, 41.000000)">
<rect id="Rectangle-1" stroke="#979797" fill="#FFFFFF" sketch:type="MSShapeGroup" x="3" y="60" width="90" height="40"></rect>
<text id="func(3)" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="25" y="84">func(3)</tspan>
</text>
<g id="noun_60230_cc" fill="#000000" sketch:type="MSShapeGroup">
<path d="M22.5818182,19.3424658 L12.9113636,16.4383562 L20.4909091,0.273972603 L0.470454545,21.0958904 L10.1931818,24 L4.28636364,39.890411 L22.5818182,19.3424658 L22.5818182,19.3424658 Z M2.19545455,20.6575342 L17.3022727,4.93150685 L11.6045455,17.0410959 L20.8568182,19.7808219 L6.9,35.6164384 L11.4477273,23.3972603 L2.19545455,20.6575342 L2.19545455,20.6575342 Z" id="Shape"></path>
</g>
<path d="M3,114.5 L3,44.5" id="Line" stroke="#373535" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
</g>
<text id="100" sketch:type="MSTextLayer" font-family="Consolas" font-size="18" font-weight="normal" fill="#000000">
<tspan x="115" y="183">100</tspan>
</text>
<text id="200" sketch:type="MSTextLayer" font-family="Consolas" font-size="18" font-weight="normal" fill="#000000">
<tspan x="259" y="183">200</tspan>
</text>
<text id="300" sketch:type="MSTextLayer" font-family="Consolas" font-size="18" font-weight="normal" fill="#000000">
<tspan x="394" y="183">300</tspan>
</text>
<path d="M221.5,122.5 L271.5,122.5" id="Line" stroke="#4990E2" stroke-width="3" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<path d="M364.5,122.5 L405.5,122.5" id="Line-2" stroke="#4990E2" stroke-width="3" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="631px" height="223px" viewBox="0 0 631 223" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>settimeout-interval</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="settimeout-interval" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<path d="M32.5,122.5 L582.5,122.5" id="Line" stroke="#979797" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<g id="Rectangle-1-+-func(1)-+-noun_60230_cc" sketch:type="MSLayerGroup" transform="translate(48.000000, 41.000000)">
<rect id="Rectangle-1" stroke="#979797" fill="#FFFFFF" sketch:type="MSShapeGroup" x="3" y="60" width="90" height="40"></rect>
<text id="func(1)" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="25" y="84">func(1)</tspan>
</text>
<g id="noun_60230_cc" fill="#000000" sketch:type="MSShapeGroup">
<path d="M22.5818182,19.3424658 L12.9113636,16.4383562 L20.4909091,0.273972603 L0.470454545,21.0958904 L10.1931818,24 L4.28636364,39.890411 L22.5818182,19.3424658 L22.5818182,19.3424658 Z M2.19545455,20.6575342 L17.3022727,4.93150685 L11.6045455,17.0410959 L20.8568182,19.7808219 L6.9,35.6164384 L11.4477273,23.3972603 L2.19545455,20.6575342 L2.19545455,20.6575342 Z" id="Shape"></path>
</g>
<path d="M3,114.5 L3,44.5" id="Line" stroke="#373535" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
</g>
<g id="Rectangle-1-+-func(1)-+-noun_60230_cc-2" sketch:type="MSLayerGroup" transform="translate(242.000000, 41.000000)">
<rect id="Rectangle-1" stroke="#979797" fill="#FFFFFF" sketch:type="MSShapeGroup" x="3" y="60" width="90" height="40"></rect>
<text id="func(2)" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="25" y="84">func(2)</tspan>
</text>
<g id="noun_60230_cc" fill="#000000" sketch:type="MSShapeGroup">
<path d="M22.5818182,19.3424658 L12.9113636,16.4383562 L20.4909091,0.273972603 L0.470454545,21.0958904 L10.1931818,24 L4.28636364,39.890411 L22.5818182,19.3424658 L22.5818182,19.3424658 Z M2.19545455,20.6575342 L17.3022727,4.93150685 L11.6045455,17.0410959 L20.8568182,19.7808219 L6.9,35.6164384 L11.4477273,23.3972603 L2.19545455,20.6575342 L2.19545455,20.6575342 Z" id="Shape"></path>
</g>
<path d="M3,114.5 L3,44.5" id="Line" stroke="#373535" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
</g>
<g id="Rectangle-1-+-func(1)-+-noun_60230_cc-3" sketch:type="MSLayerGroup" transform="translate(436.000000, 41.000000)">
<rect id="Rectangle-1" stroke="#979797" fill="#FFFFFF" sketch:type="MSShapeGroup" x="3" y="60" width="90" height="40"></rect>
<text id="func(3)" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="normal" fill="#000000">
<tspan x="25" y="84">func(3)</tspan>
</text>
<g id="noun_60230_cc" fill="#000000" sketch:type="MSShapeGroup">
<path d="M22.5818182,19.3424658 L12.9113636,16.4383562 L20.4909091,0.273972603 L0.470454545,21.0958904 L10.1931818,24 L4.28636364,39.890411 L22.5818182,19.3424658 L22.5818182,19.3424658 Z M2.19545455,20.6575342 L17.3022727,4.93150685 L11.6045455,17.0410959 L20.8568182,19.7808219 L6.9,35.6164384 L11.4477273,23.3972603 L2.19545455,20.6575342 L2.19545455,20.6575342 Z" id="Shape"></path>
</g>
<path d="M3,114.5 L3,44.5" id="Line" stroke="#373535" stroke-width="2" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
</g>
<text id="100" sketch:type="MSTextLayer" font-family="Consolas" font-size="18" font-weight="normal" fill="#000000">
<tspan x="179" y="201">100</tspan>
</text>
<text id="100" sketch:type="MSTextLayer" font-family="Consolas" font-size="18" font-weight="normal" fill="#000000">
<tspan x="372" y="201">100</tspan>
</text>
<path d="M142.5,122.5 L242.5,122.5" id="Line" stroke="#4990E2" stroke-width="3" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
<g id="Line-3" sketch:type="MSLayerGroup" transform="translate(142.000000, 171.000000)" stroke-linecap="square" stroke="#4990E2">
<path d="M0.5,5.5 L100.5,5.5" sketch:type="MSShapeGroup"></path>
<path d="M92.5,5.5 L100.5,0.5" id="Line-5" sketch:type="MSShapeGroup" transform="translate(96.500000, 3.000000) scale(-1, 1) translate(-96.500000, -3.000000) "></path>
<path d="M0.5,10.5 L8.5,5.5" id="Line-6" sketch:type="MSShapeGroup" transform="translate(4.500000, 8.000000) scale(-1, 1) translate(-4.500000, -8.000000) "></path>
<path d="M92.5,10.5 L100.5,5.5" id="Line" sketch:type="MSShapeGroup"></path>
<path d="M0.5,5.5 L8.5,0.5" id="Line-4" sketch:type="MSShapeGroup"></path>
</g>
<g id="Line-7" sketch:type="MSLayerGroup" transform="translate(336.000000, 171.000000)" stroke="#4990E2" stroke-linecap="square">
<path d="M0.5,5.5 L100.5,5.5" id="Line-3" sketch:type="MSShapeGroup"></path>
<path d="M92.5,5.5 L100.5,0.5" id="Line-5" sketch:type="MSShapeGroup" transform="translate(96.500000, 3.000000) scale(-1, 1) translate(-96.500000, -3.000000) "></path>
<path d="M0.5,10.5 L8.5,5.5" id="Line-6" sketch:type="MSShapeGroup" transform="translate(4.500000, 8.000000) scale(-1, 1) translate(-4.500000, -8.000000) "></path>
<path d="M92.5,10.5 L100.5,5.5" id="Line" sketch:type="MSShapeGroup"></path>
<path d="M0.5,5.5 L8.5,0.5" id="Line-4" sketch:type="MSShapeGroup"></path>
</g>
<path d="M336.5,122.5 L436.5,122.5" id="Line-2" stroke="#4990E2" stroke-width="3" stroke-linecap="square" sketch:type="MSShapeGroup"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB