setTimeout и setInterval и Декоратор debounce

Лекция



Привет, сегодня поговорим про settimeout, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое settimeout, setinterval, декоратор debounce , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).

  1. setTimeout
    1. Параметры для функции и контекст
    2. Отмена исполнения
  2. setInterval
  3. Очередь и наложение вызовов в setInterval
  4. Повторение вложенным setTimeout
  5. Минимальная задержка таймера
  6. Реальная частота срабатывания
  7. Разбивка долгих скриптов
  8. Трюк setTimeout(func, 0)
  9. Итого

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

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

setTimeout

Синтаксис:

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

Параметры:

func/code

Функция или строка кода для исполнения.
Строка поддерживается для совместимости, использовать ее не рекомендуется.

delay

Задержка в милисекундах, 1000 милисекунд равны 1 секунде.

arg1, arg2

Аргументы, которые нужно передать функции. Не поддерживаются в IE9-.

Исполнение функции произойдет спустя время, указанное в параметре delay.

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

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

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

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

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

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

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

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

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

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

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

1 function sayHi(who) {
2 alert("Привет, я " + who);
3 }
4
5 setTimeout(sayHi, 1000, "Вася");

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

1 function sayHi(who) {
2 alert("Привет, я " + who);
3 }
4
5 setTimeout(function() { sayHi('Вася') }, 1000);

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

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

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

01 function User(id) {
02 this.id = id;
03
04 this.sayHi = function() {
05 alert(this.id);
06 };
07 }
08
09 var user = new User(12345);
10
11 setTimeout(user.sayHi, 1000); // ожидается 12345, но выведет "undefined"

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

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

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

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

01 function User(id) {
02 this.id = id;
03
04 this.sayHi = function() {
05 alert(this.id);
06 };
07 }
08
09 var user = new User(12345);
10
11 setTimeout(function() {
12 user.sayHi();
13 }, 1000);

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

В следующих главах мы разберем дополнительные способы привязки функции к объекту.

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

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

Синтаксис: clearTimeout(timerId).

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

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

setInterval

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

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

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

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

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

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

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

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

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

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

setTimeout и setInterval и Декоратор debounce

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

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

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

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

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

setTimeout и setInterval и Декоратор debounce

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

setTimeout и setInterval и Декоратор debounce

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

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

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

setTimeout и setInterval и Декоратор debounce

Давайте посмотрим на примере, как это работает.

Модальные окна в Safari/Chrome блокируют таймер

Внутренний таймер в браузерах Safari/Chrome во время показаalert/confirm/prompt не «тикает». Если до исполнения оставалось 3 секунды, то даже при показе alert на протяжении минуты — задержка остается 3 секунды.

Поэтому пример ниже не воспроизводится в этих браузерах. В других браузерах все в порядке.

  1. Запустите пример ниже в любом браузере, кроме Chrome/Safari и дождитесь всплывающего окна. Обратите внимание, что это alert. Пока модальное окошко отображается, исполнение JavaScript блокируется. Подождите немного и нажмите OK.
  2. Вы должны увидеть, что второй запуск будет тут же, а третий - через небольшое время от второго, меньшее чем 2000 мс.
  3. Чтобы остановить повторение, нажмите кнопку Стоп.

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

Происходит следующее.

  1. Браузер выполняет функцию каждые 2 секунды
  2. Когда всплывает окно alert — исполнение блокируется и остается заблокированным все время, пока alert отображается.
  3. Если вы ждете достаточно долго, то внутренние часики-то идут. Браузер ставит следующее исполнение в очередь, один раз (в Chrome/Safari внутренний таймер не идет! это ошибка в браузере).
  4. Когда вы нажмете OK - моментально вызывается исполнение, которое было в очереди.
  5. Следующее исполнение вызовется с меньшей задержкой, чем указано. Так происходит потому, что планировщик просыпается каждые 2000мс. И если alert был закрыт в момент времени, соответствующий 3500мс от начала, то следующее исполнение назначено на 4000 мс, т.е. произойдет через 500мс.

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

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

Важность: 5

Напишите функцию, которая последовательно выводит в консоль числа от 1 до 20, с интервалом между числами 100мс. То есть, весь вывод должен занимать 2000мс, в течение которых каждые 100мс в консоли появляется очередное число.

Нажмите на кнопку, открыв консоль, для демонстрации:

printNumbersInterval20_100()

Решение задачи должно использовать setInterval.

Решение

01 function printNumbersInterval20_100() {
02 var i = 1;
03 var timerId = setInterval(function() {
04 console.log(i);
05 if (i == 20) clearInterval(timerId);
06 i++;
07 }, 100);
08 }
09
10 // вызов
11 printNumbersInterval20_100();

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

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

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

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

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

setTimeout и setInterval и Декоратор debounce

Важность: 5

Сделайте то же самое, что в задаче Вывод чисел каждые 100мс, но с использованием setTimeout вместо setInterval.

Решение

01 function printNumbersTimeout20_100() {
02 var i = 1;
03 var timerId = setTimeout(function go() {
04 console.log(i);
05 if (i < 20) setTimeout(go, 100);
06 i++;
07 }, 100);
08 }
09
10 // вызов
11 printNumbersTimeout20_100();

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

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

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

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

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

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

Срабатывание может быть и гораздо реже

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

  • Большинство браузеров (десктопных в первую очередь) продолжают выполнятьsetTimeout/setInterval, даже если вкладка неактивна.

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

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

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

Посмотрим снижении частоты в действии на небольшом примере.

При клике на кнопку ниже запускается setInterval(..., 90), который выводит список интервалов времени между 25 последними срабатываниями таймера. Запустите его. Перейдите на другую вкладку и вернитесь.

Запустить повтор с интервалом в 90 мс
Остановить повтор

Если ваш браузер увеличивает таймаут при фоновом выполнении вкладки, то вы увидите увеличенные интервалы, помеченные красным.

Кроме того, вы точно увидите, что таймер не является идеально точным

Вывод интервалов в консоль

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

01 var timeMark = new Date;
02 setTimeout(function go() {
03 var diff = new Date - timeMark;
04
05 // вывести очередную задержку в консоль вместо страницы
06 console.log(diff);
07
08 // запомним время в самом конце,
09 // чтобы измерить задержку именно между вызовами
10 timeMark = new Date;
11
12 setTimeout(go, 100);
13 }, 100);

Разбивка долгих скриптов

Нулевой или небольшой таймаут также используют, чтобы разорвать поток выполнения «тяжелых» скриптов.

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

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

Для того, чтобы этого избежать, сложная задача разбивается на части, выполнение каждой части запускается через мини-интервал после предыдущей, чтобы дать браузеру время. Например, планируется подсветка 20 строк каждые 10мс.

Важность: 5

Стоит задача: реализовать подсветку синтаксиса в длинном коде при помощи JavaScript, для онлайн-редактора кода. Это требует сложных вычислений, особенно загружает процессор генерация дополнительных элементов страницы, визуально осуществляющих подсветку.

Поэтому решаем обрабатывать не весь код сразу, что привело бы к зависанию скрипта, а разбить работу на части: подсвечивать по 20 строк раз в 10мс.

Как мы знаем, есть два варианта реализации такой подсветки:

  1. Через setInterval, с остановкой по окончании работы:
    1 timer = setInterval(function() {
    2 if (есть еще что подсветить) highlight();
    3 else clearInterval(timer);
    4 }, 10);
  2. Через вложенные setTimeout:
    1 setTimeout(function go() {
    2 highlight();
    3 if (есть еще что подсветить) setTimeout(go, 10);
    4 }, 10);

Какой из них стоит использовать? Почему?

Решение

Нужно выбрать вариант 2, который гарантирует браузеру свободное время между выполнениями highlight.

Первый вариант может загрузить процессор на 100%, если highlight занимает время, близкое к 10мс или, тем более, большее чем 10мс, т.к. таймер не учитывает время выполнения функции.

Что интересно, в обоих случаях браузер не будет выводить предупреждение о том, что скрипт занимает много времени. Но от 100% загрузки процессора возможны притормаживания других операций. В общем, это совсем не то, что мы хотим, поэтому вариант 2.

Трюк setTimeout(func, 0)

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

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

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

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

Например:

01 var result;
02
03 function showResult() {
04 alert(result);
05 }
06
07 setTimeout(showResult, 0);
08
09 result = 2*2;
10
11 // выведет 4

декоратор debounce - это популярный паттерн проектирования в программировании, который используется для управления частотой выполнения функции. Он задерживает выполнение функции до тех пор, пока не пройдет определенное количество времени между последним вызовом функции и вызовом, который будет реально выполнен. Этот паттерн полезен, например, при обработке событий, которые могут происходить с высокой частотой, и вы хотите оптимизировать их обработку.

Вот пример реализации декоратора debounce на языке JavaScript:

function debounce(func, delay) {
  let timeoutId;
  
  return function() {
    const context = this;
    const args = arguments;

    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

// Пример использования:

function doSomething() {
  console.log("Выполнилось!");
}

const debouncedFunction = debounce(doSomething, 1000); // Вызов debounce с функцией и временем задержки

// Теперь debouncedFunction можно вызывать, и она будет выполняться только один раз, если не вызывать ее вновь в течение 1 секунды.
debouncedFunction(); // Не выполнится немедленно
debouncedFunction(); // Не выполнится немедленно
// Подождите 1 секунду
// "Выполнилось!" будет выведено в консоль только один раз после задержки

Позже, в главе Управление порядком обработки, setTimeout(…0), мы рассмотрим различные применения этого трюка при работе с событиями.

Итого

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

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

Особенности
setInterval setTimeout
Тайминг Идет вызов строго по таймеру. Если интерпретатор занят — один вызов становится в очередь.

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

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

Важность: 5

Есть два бегуна:

var runner1 = new Runner();
var runner2 = new Runner();

У каждого есть метод step(), который делает шаг.

Какой из двух бегунов будет быстрее?

01 // первый?
02 setInterval(function() {
03 runner1.step();
04 }, 15);
05
06 // или второй?
07 setTimeout(function go() {
08 runner2.step();
09 setTimeout(go, 15);
10 }, 15);
11
12 setTimeout(function() {
13 alert(runner1.steps);
14 alert(runner2.steps);
15 }, 5000);

Кто сделает больше шагов? Почему вы так думаете?

Решение

Обычно первый бегун будет быстрее.

Но возможен и такой вариант, что время совпадает.

Создадим реальные объекты Runner и запустим их для проверки:

01 function Runner() {
02 this.steps = 0;
03
04 this.step = function() {
05 doSomethingHeavy();
06 this.steps++;
07 }
08
09 function doSomethingHeavy() {
10 for(var i=0; i<10000; i++) {
11 this[i] = this.step + i;
12 }
13 }
14
15 }
16
17 var runner1 = new Runner();
18 var runner2 = new Runner();
19
20 // запускаем бегунов
21 setInterval(function() {
22 runner1.step();
23 }, 15);
24
25 setTimeout(function go() {
26 runner2.step();
27 setTimeout(go, 15);
28 }, 15);
29
30 // кто сделает больше шагов?
31 setTimeout(function() {
32 alert(runner1.steps);
33 alert(runner2.steps);
34 }, 5000);

  • Если бы в шаге step() не было вызова doSomethingHeavy(), то количество шагов было бы равным, так как времени на такой шаг нужно очень мало.

    Интерпретатор JavaScript старается максимально оптимизировать такие часто повторяющиеся «узкие места». В данном случае вызов step свелся бы к одной операции микропроцессора. Это пренебрежимо мало по сравнению с остальным кодом.

  • Так как у нас шаг, все же, что-то делает, и функция doSomethingHeavy()специально написана таким образом, что к одной операции свести ее нельзя, то первый бегун успеет сделать больше шагов.

    Ведь в setTimeout пауза 15 мс будет между шагами, а setInterval шагает равномерно, каждые 15 мс. Получается чаще.

  • Наконец, есть браузеры (IE9), в которых при выполнении JavaScript таймер «не тикает». Для них оба варианта будут вести себя как setTimeout, так что количество шагов будет одинаковым.

Важность: 5

Выполнение функции f занимает примерно 1 секунду.

Что выведет alert в коде ниже?

Когда сработает setTimeout? Выберите нужный вариант:

  1. До выполнения f.
  2. Во время выполнения f.
  3. Сразу же по окончании f.
  4. Через 10мс после окончания f.

01 setTimeout(function() {
02 alert(i);
03 }, 10);
04
05 var i;
06
07 function f() {
08 // точное время выполнения не играет роли
09 // здесь оно заведомо больше задержки setTimeout
10 for(i=0; i<1e8; i++) f[i%10] = i;
11 }
12
13 f();

Решение

Вызов alert(i) в setTimeout выведет 100000000, так как срабатывание будет гарантировано после окончания работы текущего кода.

Очередь до запланированных вызовов доходит всегда лишь после окончания текущего скрипта.

Можете проверить это запуском:

01 setTimeout(function() {
02 alert(i);
03 }, 10);
04
05 var i;
06
07 function f() {
08 // точное время выполнения не играет роли
09 // здесь оно заведомо больше задержки setTimeout
10 for(i=0; i<1e8; i++) f[i%10] = i;
11 }
12
13 f();

Ответ на второй вопрос: 3 (сразу после).

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

Важность: 5

Выполнение функции f занимает примерно 1 секунду.

Что выведет alert в коде ниже?

Когда сработает setInterval? Выберите нужный вариант:

  1. До выполнения f, во время и после, перемежаясь с выполнением f.
  2. Во время выполнения f, один раз.
  3. Во время выполнения f, возможно несколько раз.
  4. Сразу же по окончании f один раз.
  5. Сразу же по окончании f, возможно несколько раз.
  6. Через 10мс после окончания f, один раз.
  7. Через 10мс после окончания f, возможно несколько раз.

Является ли такое поведение кросс-браузерным?

01 var timer = setInterval(function() {
02 i++;
03 }, 10);
04
05 setTimeout(function() {
06 clearInterval(timer);
07 alert(i);
08 }, 50);
09
10 var i;
11
12 function f() {
13 // точное время выполнения не играет роли
14 // здесь оно заведомо больше 50мс
15 for(i=0; i<1e8; i++) f[i%10] = i;
16 }
17
18 f();

Решение

Вызов alert(i) в setTimeout введет 100000001. Почему — будет понятно из ответа на второй вопрос.

Можете проверить это запуском:

01 var timer = setInterval(function() {
02 i++;
03 }, 10);
04
05 setTimeout(function() {
06 clearInterval(timer);
07 alert(i);
08 }, 50);
09
10 var i;
11
12 function f() {
13 // точное время выполнения не играет роли
14 // здесь оно заведомо больше 50мс
15 for(i=0; i<1e8; i++) f[i%10] = i;
16 }
17
18 f();

Ответ на второй вопрос: 4 (сразу же по окончании f один раз).

Планирование setInterval будет вызывать функцию каждые 10мс после текущего времени. Но так как интерпретатор занят долгой функцией, то до конца ее работы никакого вызова не происходит.

За время выполнения f может пройти время, на которое запланированы несколько вызовов setInterval, но в этом случае остается только один, т.е. накопления вызовов не происходит. Такова логика работы setInterval.

После окончания текущего скрипта интерпретатор обращается к очереди запланированных вызовов, видит в ней setInterval и выполняет. А затем тут же выполняется setTimeout, очередь которого тут же подошла.

Итого, как раз и видим, что setInterval выполнился ровно 1 раз по окончании работы функции. Такое поведение кросс-браузерно.

Важность: 5

Напишите функцию delay(f, ms), которая возвращает обертку вокруг f, задерживающую вызов на ms миллисекунд.

Например:

1 function f(x) {
2 alert(x);
3 }
4
5 var f1000 = delay(f, 1000);
6 var f1500 = delay(f, 1500);
7
8 f1000("тест"); // выведет "тест" через 1000 миллисекунд
9 f1500("тест2"); // выведет "тест2" через 1500 миллисекунд

Иначе говоря, f1000 — это «задержанный на 1000мс» вызов f.

В примере выше у функции только один аргумент, но delay должна быть универсальной: передавать любое количество аргументов и контекст this.

Решение

01 function delay(f, ms) {
02
03 return function() {
04 var savedThis = this;
05 var savedArgs = arguments;
06
07 setTimeout(function() {
08 f.apply(savedThis, savedArgs);
09 }, ms);
10 };
11
12 }
13
14 function f(x) {
15 alert(x);
16 }
17
18 var f1000 = delay(f, 1000);
19 var f1500 = delay(f, 1500);
20
21 f1000("тест"); // выведет "тест" через 1000 миллисекунд
22 f1500("тест2"); // выведет "тест2" через 1500 миллисекунд

Обратим внимание на то, как работает обертка:

1 return function() {
2 var savedThis = this;
3 var savedArgs = arguments;
4
5 setTimeout(function() {
6 f.apply(savedThis , savedArgs);
7 }, ms);
8 };

Именно обертка возвращается декоратором delay и будет вызвана. Чтобы передать аргумент и контекст функции, вызываемой через ms миллисекунд, они копируются в локальные переменные savedThis и savedArgs.

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

Важность: 5

Напишите функцию debounce(f, ms), которая возвращает обертку, которая передает вызов f не чаще, чем раз в ms миллисекунд.

«Лишние» вызовы игнорируются. Все аргументы и контекст — передаются.

Например:

01 function f() { ... }
02
03 var f = debounce(f, 1000);
04
05 f(1); // выполнится сразу же
06 f(2); // игнор
07
08 setTimeout( function() { f(3) }, 100); // игнор (прошло только 100мс)
09 setTimeout( function() { f(4) }, 1100); // выполнится
10 setTimeout( function() { f(5) }, 1500); // игнор

Исходный документ с более развернутым тестом: tutorial/timers/debounce-src.html

Решение

Вызов debounce возвращает функцию-обертку. Все необходимые данные для нее хранятся в замыкании.

При вызове ставится таймер и состояние state меняется на константу COOLDOWN («в процессе охлаждения»).

Последующие вызовы игнорируются, пока таймер не обнулит состояние.

Важность: 5

Напишите функцию throttle(f, ms) — «тормозилку», которая возвращает обертку, передающую вызов f не чаще, чем раз в msмиллисекунд.

У этой функции должно быть важное существенное отличие от debounce: если игнорируемый вызов оказался последним, т.е. после него до окончания задержки ничего нет — то он выполнится.

Чтобы лучше понять, откуда взялось это требование, и как throttle должна работать — разберем реальное применение, на которое и ориентирована эта задача.

Например, нужно обрабатывать передвижения мыши. В JavaScript это делается функцией, которая будет запускаться при каждом микро-передвижении мыши и получать координаты курсора. По мере того, как мышь двигается, эта функция может запускаться очень часто, может быть 100 раз в секунду (каждые 10мс).

Функция обработки передвижения должна обновлять некую информацию на странице. При этом обновление — слишком «тяжелый» процесс, чтобы делать его при каждом микро-передвижении. Имеет смысл делать его раз в 100мс, не чаще.

Пусть функция, которая осуществляет это обновление по передвижению, называетсяonmousemove.

Вызов throttle(onmousemove, 100), по сути, предназначен для того, чтобы «притормаживать» обработку onmousemove. Технически, он должен возвращать обертку, которая передает все вызовы onmousemove, но не чаще чем раз в 100мс.

При этом промежуточные движения можно игнорировать, но мышь в конце концов где-то остановится. И это последнее, итоговое положение мыши обязательно нужно обработать!

Визуально это даст следующую картину обработки перемещений мыши:

  1. Первое обновление произойдет сразу (это важно, посетитель тут же видит реакцию на свое действие).
  2. Дальше будет много вызовов (микро-передвижений) с разными координатами, но пока не пройдет 100мс — ничего не будет.
  3. По истечении 100мс — опять обновление, с последними координатами. Промежуточные микро-передвижения игнорированы.
  4. В конце концов мышь где-то остановится, обновление по окончании очередной паузы 100мс (иначе мы не знаем, последнее оно или нет) сработает с последними координатами.

Еще раз заметим — задача из реальной жизни, а в ней принципиально важно, чтопоследнее передвижение обрабатывается. Пользователь должен увидеть, где остановил мышь.

Чтобы было удобнее такой throttle писать, в исходном документе содержатся еще два теста: tutorial/timers/throttle-src.html

Решение

Вызов throttle возвращает функцию-обертку. Все необходимые данные для нее хранятся в замыкании.

В первую очередь — это состояние state, которое вначале не назначено (null), при первом вызове получает значение COOLDOWN («в процессе охлаждения»), а при следующем вызове CALL_SCHEDULED.

При таймауте состояние проверяется, и если оно равно CALL_SCHEDULED — происходит новый вызов.

К сожалению, в одной статье не просто дать все знания про settimeout. Но я - старался. Если ты проявишь интерес к раскрытию подробностей,я обязательно напишу продолжение! Надеюсь, что теперь ты понял что такое settimeout, setinterval, декоратор debounce и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

создано: 2014-10-07
обновлено: 2024-11-14
509



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей

Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)