setTimeout и setInterval

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

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

setTimeout

Синтаксис:

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

Параметры:

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

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

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

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

setTimeout(func, 1000);

С передачей аргументов (не сработает в IE9-):

function func(phrase, who) {
  alert( phrase + ', ' + who );
}

setTimeout(func, 1000, "Привет", "Вася"); // Привет, Вася

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

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

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

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

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

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

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

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

Синтаксис:

var timerId = setTimeout(...);
clearTimeout(timerId);

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

var timerId = setTimeout(function() { alert(1) }, 1000);
alert(timerId); // число - идентификатор таймера

clearTimeout(timerId);
alert(timerId); // всё ещё число, оно не обнуляется после отмены

Как видно из alert, в браузере идентификатор таймера является обычным числом. Другие JavaScript-окружения, например Node.JS, могут возвращать объект таймера, с дополнительными методами.

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

Таймеры – это надстройка над JavaScript, которая описана в секции Timers стандарта HTML5 для браузеров и в документации к Node.JS – для сервера.

setInterval

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

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

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

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

// начать повторы с интервалом 2 сек
var timerId = setInterval(function() {
  alert( "тик" );
}, 2000);

// через 5 сек остановить повторы
setTimeout(function() {
  clearInterval(timerId);
  alert( 'стоп' );
}, 5000);
Модальные окна замораживают время в Chrome/Opera/Safari

Что будет, если долго не жать OK на появившемся alert? Это зависит от браузера.

В браузерах Chrome, Opera и Safari внутренний таймер «заморожен» во время показа alert/confirm/prompt. А вот в IE и Firefox внутренний таймер продолжит идти.

Поэтому, если закрыть alert после небольшой паузы, то в Firefox/IE следующий alert будет показан сразу же (время подошло), а в Chrome/Opera/Safari – только через 2 секунды после закрытия.

Рекурсивный setTimeout

Важная альтернатива setInterval – рекурсивный setTimeout:

/** вместо:
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:

var i = 1;
setInterval(function() {
  func(i);
}, 100);

Второй использует рекурсивный setTimeout:

var i = 1;
setTimeout(function run() {
  func(i);
  setTimeout(run, 100);
}, 100);

При setInterval внутренний таймер будет срабатывать чётко каждые 100 мс и вызывать func(i):

Вы обратили внимание?…

Реальная пауза между вызовами func при setInterval меньше, чем указана в коде!

Это естественно, ведь время работы функции никак не учитывается, оно «съедает» часть интервала.

Возможно и такое что func оказалась сложнее, чем мы рассчитывали и выполнялась дольше, чем 100 мс.

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

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

Исключением является IE, в котором таймер «застывает» во время выполнения JavaScript.

А так будет выглядеть картинка с рекурсивным setTimeout:

При рекурсивном setTimeout задержка всегда фиксирована и равна 100 мс.

Это происходит потому, что каждый новый запуск планируется только после окончания текущего.

Управление памятью

Сборщик мусора в JavaScript не чистит функции, назначенные в таймерах, пока таймеры актуальны.

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

// Функция будет жить в памяти, пока не сработал (или не был очищен) таймер
setTimeout(function() {}, 100);
  • Для setTimeout – внутренняя ссылка исчезнет после исполнения функции.
  • Для setInterval – ссылка исчезнет при очистке таймера.

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

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

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

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

Посмотреть минимальное разрешение «вживую» можно на следующем примере.

В примере ниже каждая полоска удлиняется вызовом setInterval с указанной на ней задержкой – от 0 мс (сверху) до 20 мс (внизу).

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

Важно:

В Internet Explorer, нулевая задержка setInterval(.., 0) не сработает. Это касается именно setInterval, т.е. setTimeout(.., 0) работает нормально.

Откуда взялись эти 4 мс?

Почему минимальная задержка – 4 мс, а не 1 мс? Зачем она вообще существует?

Это – «привет» от прошлого. Браузер Chrome как-то пытался убрать минимальную задержку в своих ранних версиях, но оказалось, что существуют сайты, которые используют setTimeout(..,0) рекурсивно, создавая тем самым «асинхронный цикл». И, если задержку совсем убрать, то будет 100% загрузка процессора, такой сайт «подвесит» браузер.

Поэтому, чтобы не ломать существующие скрипты, решили сделать задержку. По возможности, небольшую. На время создания стандарта оптимальным числом показалось 4 мс.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Например, осуществляется анализ и подсветка первых 100 строк, затем через 20 мс – следующие 100 строк и так далее. При этом можно подстраиваться под CPU посетителя: замерять время на анализ 100 строк и, если процессор хороший, то в следующий раз обработать 200 строк, а если плохой – то 50. В итоге подсветка будет работать с адекватной быстротой и без тормозов на любых текстах и компьютерах.

Итого

  • Методы setInterval(func, delay) и setTimeout(func, delay) позволяют запускать func регулярно/один раз через delay миллисекунд.
  • Оба метода возвращают идентификатор таймера. Его используют для остановки выполнения вызовом clearInterval/clearTimeout.
  • В случаях, когда нужно гарантировать задержку между регулярными вызовами или гибко её менять, вместо setInterval используют рекурсивный setTimeout.
  • Минимальная задержка по стандарту составляет 4 мс. Браузеры соблюдают этот стандарт, но некоторые другие среды для выполнения JS, например Node.JS, могут предоставить и меньше задержки.
  • В реальности срабатывания таймера могут быть гораздо реже, чем назначено, например если процессор перегружен, вкладка находится в фоновом режиме, ноутбук работает от батареи или по какой-то иной причине.

Браузерных особенностей почти нет, разве что вызов setInterval(..., 0) с нулевой задержкой в IE недопустим, нужно указывать setInterval(..., 1).

Задачи

важность: 5
function printNumbersInterval() {
  var i = 1;
  var timerId = setInterval(function() {
    console.log(i);
    if (i == 20) clearInterval(timerId);
    i++;
  }, 100);
}

// вызов
printNumbersInterval();

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

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

P.S. Функция должна использовать setInterval.

важность: 5
function printNumbersTimeout20_100() {
  var i = 1;
  var timerId = setTimeout(function go() {
    console.log(i);
    if (i < 20) setTimeout(go, 100);
    i++;
  }, 100);
}

// вызов
printNumbersTimeout20_100();

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

важность: 5

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

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

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

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

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

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

  1. Через setInterval, с остановкой по окончании работы:

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

    setTimeout(function go() {
      highlight();
      if (есть еще что подсветить) setTimeout(go, 10);
    }, 10);

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

важность: 5

Ответы:

  • alert выведет 100000000.
  • 3, срабатывание будет после окончания работы hardWork.

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

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

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

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

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

setTimeout(function() {
  alert( i );
}, 100);

var i;

function hardWork() {
  // время выполнения этого кода >100 мс, сам код неважен
  for (i = 0; i < 1e8; i++) hardWork[i % 2] = i;
}

hardWork();
важность: 5

Вызов alert(i) в setTimeout введет 100000001.

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

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 раз по окончании работы функции. Такое поведение кросс-браузерно.

В коде ниже запускается setInterval каждые 10 мс, и через 50 мс запланирована его отмена.

После этого запущена тяжёлая функция f, выполнение которой (мы точно знаем) потребует более 100 мс.

Сработает ли setInterval, как и когда?

Варианты:

  1. Да, несколько раз, в процессе выполнения f.
  2. Да, несколько раз, сразу после выполнения f.
  3. Да, один раз, сразу после выполнения f.
  4. Нет, не сработает.
  5. Может быть по-разному, как повезёт.

Что выведет alert в строке (*)?

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();
важность: 5

Задача – с небольшим «нюансом».

Есть браузеры, в которых на время работы JavaScript таймер «застывает», например таков IE. В них количество шагов будет почти одинаковым, ±1.

В других браузерах (Chrome) первый бегун будет быстрее.

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

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 мс. Получается чаще.

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

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

У каждого есть метод step(), который делает шаг, увеличивая свойство steps.

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

Если запустить первого бегуна через setInterval, а второго – через вложенный setTimeout – какой сделает больше шагов за 5 секунд?

// первый?
setInterval(function() {
  runner1.step();
}, 15);

// или второй?
setTimeout(function go() {
  runner2.step();
  setTimeout(go, 15);
}, 15);

setTimeout(function() {
  alert( runner1.steps );
  alert( runner2.steps );
}, 5000);
важность: 5
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 миллисекунд

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

return function() {
  var savedThis = this;
  var savedArgs = arguments;

  setTimeout(function() {
    f.apply(savedThis, savedArgs);
  }, ms);
};

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

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

Открыть решение с тестами в песочнице.

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

Например:

function f(x) {
  alert( x );
}

var f1000 = delay(f, 1000);
var f1500 = delay(f, 1500);

f1000("тест"); // выведет "тест" через 1000 миллисекунд
f1500("тест2"); // выведет "тест2" через 1500 миллисекунд

Упрощённо можно сказать, что delay возвращает "задержанный на ms" вариант f.

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

Открыть песочницу с тестами для задачи.

важность: 5
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 («в процессе охлаждения»).

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

Открыть решение с тестами в песочнице.

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

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

Например:

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); // игнор

Упрощённо можно сказать, что debounce возвращает вариант f, срабатывающий не чаще чем раз в ms миллисекунд.

Открыть песочницу с тестами для задачи.

важность: 5
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;
}

Шаги работы этой функции:

  1. Декоратор throttle возвращает функцию-обёртку wrapper, которая при первом вызове запускает func и переходит в состояние «паузы» (isThrottled = true).
  2. В этом состоянии все новые вызовы запоминаются в замыкании через savedArgs/savedThis. Обратим внимание, что и контекст вызова и аргументы для нас одинаково важны и запоминаются одновременно. Только зная и то и другое, можно воспроизвести вызов правильно.
  3. Далее, когда пройдёт таймаут ms миллисекунд – пауза будет снята, а wrapper – запущен с последними аргументами и контекстом (если во время паузы были вызовы).

Шаг (3) запускает именно не саму функцию, а снова wrapper, так как необходимо не только выполнить func, но и снова поставить выполнение на паузу. Получается последовательность «вызов – пауза… вызов – пауза … вызов – пауза …», каждое выполнение в обязательном порядке сопровождается паузой после него. Это удобно описывается рекурсией.

Открыть решение с тестами в песочнице.

Напишите функцию 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 мс сработает с последними координатами.

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

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

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 игнорируется

Открыть песочницу с тестами для задачи.

Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.