Выполнение действия через интервал времени

Таймеры

Последнее обновление: 1.11.2015

Для выполнения действий через определенные промежутки времени в объекте window предусмотрены функции таймеров. Есть два типа таймеров:
одни выполняются только один раз, а другие постоянно через промежуток времени.

Функция setTimeout

Для одноразового выполнения действий через промежуток времени предназначена функция setTimeout(). Она может принимать два параметра:

var timerId = setTimeout(someFunction, period)

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

function timerFunction() {
	document.write("выполнение функции setTimeout");
}
setTimeout(timerFunction, 3000);

В данном случае через 3 секунды после загрузки страницы произойдет срабатывание функции .

Для остановки таймера применяется функция clearTimeout().

function timerFunction() {
	document.write("выполнение функции setTimeout");
}
var timerId = setTimeout(timerFunction, 3000);
clearTimeout(timerId);

Функция setInterval

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

Например, напишем небольшую программу для вывода текущего времени:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
</head>
<body>
<div id="time"></div>
<script>
function updateTime() {
	document.getElementById("time").innerHTML = new Date().toTimeString();
}
setInterval(updateTime, 1000);
</script>
</body>
</html>

Здесь через каждую секунду (1000 миллисекунд) вызывается функция , которая обновляет содержимое поля
, устанавливая в качестве его кода html текущее вемя.

requestAnimationFrame()

Метод requestAnimationFrame() действует аналогично за тем исключением,
что он больше заточен под анимации, работу с графикой и имеет ряд оптимизаций, которые улучшают его производительность.

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<style>
	#rect {
		margin: 100px;
		width: 100px;
		height: 100px;
		background: #50c878;
	}
	</style>
</head>
<body>
<div id="rect"></div>
<script>
var square = document.getElementById("rect");
var angle = 0;
function rotate() {
	angle = (angle + 2)%360;
	square.style.transform = "rotate(" + angle + "deg)";
	window.requestAnimationFrame(rotate);
}
var id = window.requestAnimationFrame(rotate);
</script>
</body>
</html>

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

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

window.cancelAnimationFrame(id);

НазадВперед

Цепляем кнопки

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

// EventListener для кнопки старта.startBtn.addEventListener(‘click’, function(e) {e.preventDefault();// Запуск анимации.requestID = requestAnimationFrame(animate);});// EventListener для кнопки стоп.stopBtn.addEventListener(‘click’, function(e) {e.preventDefault();// завершаем анимацию;cancelAnimationFrame(requestID);});// EventListener для кнопки сброса.resetBtn.addEventListener(‘click’, function(e) {e.preventDefault();// Сбрасываем X позицию на ноль.posX = 0;// Очищаем canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);// Отрисовываем изначальный блок.ctx.fillRect(posX, 0, boxWidth, canvas.height);});

Тут у нас три обработчика событий. Первые два запускают и останавливают анимацию, а последний запускается при нажатии на кнопку reset. Это выставит переменной значение . Также это очистит canvas — мы не так сильно обеспокоены производительностью, так что это нормально делать таким ленивым способом — и отрисовывать блок обратно на его стартовой позиции.

Теперь ваша демка готова! Откройте в браузере и кликните на кнопку start, чтобы увидеть анимацию. Вот и сама демка.

Потеря this

Одной из проблем
использования setTimeout и setInterval является потеря
this при вызове
методов объектов. Например:

let car = {
    model "bmw",
 
    showModel() {
         console.log( this.model );
    }
};
 
setTimeout(car.showModel, 1000);

В консоле мы увидим undefined. Почему? Дело в том, что здесь теряется контекст
при вызове функции. Это эквивалентно вот такому вызову:

let show = car.showModel;
show();

И, так как в JavaScript this вычисляется
динамически при каждом вызове функции, то здесь JavaScript-машина просто
не может связать функцию show с объектом car.

Исправить
ситуацию можно несколькими способами. Первый:

setTimeout(function() {car.showModel();}, 1000);

вызвать car.showModel
через анонимную функцию-обертку. Здесь мы при вызове явно указываем объект car, поэтому this будет определен
корректно. Второй способ – использовать метод bind, о котором мы
говорили на предыдущем занятии:

let show = car.showModel.bind(car);
setTimeout(show, 1000);

Этот способ
предпочтительнее использовать на практике, так как после вызова bind он не зависит от
значения переменной car. Если она будет изменена, то функция show все равно
корректно вызовется, а вот в первом случае мы бы получили ошибку. Вот это
следует иметь в виду при реализации отложенных вызовов.

И в заключение
занятия пару слов об особенностях работы стрелочных функций

Важно знать, что у
стрелочных функций нет своего контекста выполнения (лексического окружения), а
значит, нет и своего this. В некоторых случаях этот момент имеет
ключевое значение, например:

let group = {
    title "ТКбд-11",
    students "Иванов", "Петров", "Сидоров",
 
    showList() { 
         this.students.forEach(
             student => console.log(this.title + ': ' + student)
         );
    }
};
 
group.showList();

Здесь метод forEach при вызове
обычной функции устанавливает контекст this=undefined, но при
использовании стрелочной функции контекст неизбежно берется от функции showList и this=group. Например, если
заменить стрелочную функцию на обычную, то получим ошибку:

function(student) {console.log(this.title + ': ' + student);}

с сообщением undefined не имеет свойства title.

Видео по теме

JavaScipt #1: что это такое, с чего начать, как внедрять и запускать

JavaScipt #2: способы объявления переменных и констант в стандарте ES6+

JavaScript #3: примитивные типы number, string, Infinity, NaN, boolean, null, undefined, Symbol

JavaScript #4: приведение типов, оператор присваивания, функции alert, prompt, confirm

JavaScript #5: арифметические операции: +, -, *, /, **, %, ++, —

JavaScript #6: условные операторы if и switch, сравнение строк, строгое сравнение

JavaScript #7: операторы циклов for, while, do while, операторы break и continue

JavaScript #8: объявление функций по Function Declaration, аргументы по умолчанию

JavaScript #9: функции по Function Expression, анонимные функции, callback-функции

JavaScript #10: анонимные и стрелочные функции, функциональное выражение

JavaScript #11: объекты, цикл for in

JavaScript #12: методы объектов, ключевое слово this

JavaScript #13: клонирование объектов, функции конструкторы

JavaScript #14: массивы (array), методы push, pop, shift, unshift, многомерные массивы

JavaScript #15: методы массивов: splice, slice, indexOf, find, filter, forEach, sort, split, join

JavaScript #16: числовые методы toString, floor, ceil, round, random, parseInt и другие

JavaScript #17: методы строк — length, toLowerCase, indexOf, includes, startsWith, slice, substring

JavaScript #18: коллекции Map и Set

JavaScript #19: деструктурирующее присваивание

JavaScript #20: рекурсивные функции, остаточные аргументы, оператор расширения

JavaScript #21: замыкания, лексическое окружение, вложенные функции

JavaScript #22: свойства name, length и методы call, apply, bind функций

JavaScript #23: создание функций (new Function), функции setTimeout, setInterval и clearInterval

setTimeout and clearTimeout Example

setTimeout returns a value which stores a reference to the timer. The timer can then be cleared using the clearTimeout function. This is done in the following example:

<script language="Javascript">

var timeout;

function timeout_trigger() {
    document.getElementById('timeout_text').innerHTML = 'The timeout has been triggered';
}

function timeout_clear() {
    clearTimeout(timeout);
    document.getElementById('timeout_text').innerHTML = 'The timeout has been cleared';
}

function timeout_init() {
    timeout = setTimeout('timeout_trigger()', 3000);
    document.getElementById('timeout_text').innerHTML = 'The timeout has been started';
}

</script>

<div>
    <input type="button" value="test timeout" onclick="timeout_init()" />
    <input type="button" value="clear timeout" onclick="timeout_clear()" />
</div>
<div id="timeout_text"></div>

When timeout_init() is called, the timeout reference is stored in the «timeout» variable. The name of the variable can be whatever you want, but it needs to be in the global scope, hence the «var timeout;» declaration at the start of the code.

Clicking the «test timeout» button starts the timer and the «clear timeout» button clears the timeout at the end. Here it is in action. Again, if you reading this post in a feed reader you may need to click through to the actual post to see this working.

setTimeout с нулевой задержкой

Особый вариант использования: или просто .

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

Так вызов функции будет запланирован сразу после выполнения текущего кода.

Например, этот код выводит «Привет» и затем сразу «Мир»:

Первая строка помещает вызов в «календарь» через 0 мс. Но планировщик проверит «календарь» только после того, как текущий код завершится. Поэтому выводится первым, а – после него.

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

Минимальная задержка вложенных таймеров в браузере

В браузере есть ограничение на то, как часто внутренние счётчики могут выполняться. В говорится: «после пяти вложенных таймеров интервал должен составлять не менее четырёх миллисекунд.».

Продемонстрируем в примере ниже, что это означает. Вызов повторно вызывает себя через 0 мс. Каждый вызов запоминает реальное время от предыдущего вызова в массиве . Какова реальная задержка? Посмотрим:

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

Аналогичное происходит при использовании вместо : запускает несколько раз с нулевой задержкой, а затем с задержкой 4+ мс.

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

Этого ограничения нет в серверном JavaScript. Там есть и другие способы планирования асинхронных задач. Например, setImmediate для Node.js. Так что это ограничение относится только к браузерам.

setTimeout()

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

setTimeout ( expression, timeout );

Тут в JavaScript коде запустится по прошествии миллисекунд, указанных в аргументе .

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

Вот ещё один пример:

<input type="button" name="sayHello" value="Wait for my Hello!"onclick="setTimeout('alert(\'Hello!\')', 4000)"/>

При нажатии на кнопку запускается метод. Выражение, запуск которого по вашему предусмотрению должен произойти с задержкой в 4000ms или 4 секунды, уже передано.

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

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

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

Для примера, код ниже, вызывает через одну секунду:

function sayHello() {alert('Hello');}setTimeout(sayHello, 1000);

Вы можете также передавать аргументы вместе с функцией, например, как тут:

function sayHello(message, person) {alert( message + ', '+ person );}setTimeout(sayHello, 1000, "Hi", "Monica"); // Hi, Monica

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

Если первый аргумент это строка, то JavaScript может создать из неё функцию. Так что вот это тоже сработает:

setTimeout("alert('Hello')", 1000);

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

setTimeout(() => alert('Hello'), 1000);

setTimeout vs window.setTimeout

В приведенном выше синтаксисе используется window.setTimeout. Почему?

На самом деле, setTimeout и window.setTimeout – это практически одна и та же функция. Единственная разница заключается в том, что во втором выражении мы используем метод setTimeout как свойство глобального объекта window.

Лично я считаю, что это лишь сильно усложняет код. Если бы мы определили альтернативный метод JavaScript timeout, который может быть найден и возвращен в приоритетном порядке, то столкнулись бы с еще большими проблемами.

В данном руководстве я не хочу связываться с объектом window, но в целом, вы сами решаете, какой синтаксис стоит использовать.

Метод setInterval

Метод используется для повторного выполнения функции по истечении определенного периода времени. Схема построения этого метода следующая:

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

Синтаксис этого метода очень похож на синтаксис , но в отличие от последнего, запускает функцию несколько раз, пока она не будет отменена. Например:

function sayHello(name) {
    console.log(`Hello, ${name}`)
}

setInterval(sayHello, 3000, "John")

В примере метод будет повторно выводить в консоль Hello John каждые три секунды. 

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

Вернемся к нашему примеру:

let counter = 0;
function sayHello(name) {
    alert(`Hello, ${name}`)
    counter++;

    if (counter === 3) {
        clearInterval(timerID)
    }
}

let timerID = setInterval(sayHello, 3000, "John");

Функция будет выполнена всего три раза.

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

let counter = 0;
function sayHello(name) {
    alert(`Hello, ${name}`)
    counter++;
}

let timerID = setInterval(sayHello, 3000, "John")

if (counter === 3) {
    clearInterval(timerID)
}

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

Но есть операции ввода-вывода, которые не блокируются. Они обрабатываются базовым механизмом. К ним относятся:

  • получение данных через Ajax;
  • метод ;
  • метод .

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

Следовательно, если бы мы прописали реализацию метода вторым способом, таймер не перестал бы работать, ведь после выполнения строки JavaScript перешел бы к следующему блоку кода и условие не выполнилось бы. 

Özet

  • ve metodları ‘ın düzenli olarak ms aralıklar ile çalışmasını sağlar.
  • Çalışmayı durdurmak için fonksiyonları metodundan dönen değerler ile çağırılmalıdır.
  • İç içe çağrısı kullanmak ‘e göre daha esnektir. Ayrıca bu şekilde aralarda en kısa süre beklemesini sağlar.
  • 0 gecikmeli zamanlayıcı ise zamanlayıcıyı olabildiğince çabuk fakat o anki koddan sonra çağırılacak şekilde zamanlar.

‘ın bazı kullanım durumları:

  • CPU-aç görevleri parçalara ayırmak için, böylece kod sürekli tepki verebilir.
  • Böylece görev devam ederken tarayıcının başka işlere ( ilerleme çubuğu ) zaman ayırır.

Tüm zamanlama metodları tam olarak gecikmeyi garantilemez. Zamanlayıcıda bu varsayımın üzerine birşey inşa etmeyin.

Örneğin, tarayıcı zamanı birçok nedenden ötürü yavaşlayabilir:

  • İşlemcinin yükü artarsa.
  • Tarayıcının tab’ı arka plana alındıysa.
  • Laptop batarya ile çalışıyorsa.

Bunların hepsi tarayıcı zamanına etki eder. Aralardaki gecikme 300ms ile 1000ms arasında değişebilir. Tabi tarayıcı ve özellikleri de bu konuda etkin rol oynar.

Using Arrow Functions with setTimeout

Arrow functions were introduced with ES6. They have a much shorter syntax than a regular function:

You can, of course, use them with , but there’s one gotcha to be aware of — namely, that arrow functions don’t have their own value. Instead, they use the value of the enclosing lexical context.

Using a regular function:

Using an arrow function:

In the second example, points to the global object (which again, doesn’t have a property).

This can trip us up when using arrow functions with . Previously we saw how we can supply a function called in a with the correct value:

This won’t work when using an arrow function in the method, as the arrow function doesn’t have its own value. The method will still log .

Cleaner Code with Arrow Functions and setTimeout

However, because arrow functions don’t have their own value, it can also work to our advantage.

Consider code like this:

It can be rewritten more concisely with an arrow function:

If you’d like a primer on arrow functions, please read “ES6 Arrow Functions: Fat and Concise Syntax in JavaScript”.

Методы setInterval и clearInterval

Метод предназначен для вызова кода на языке JavaScript через указанные промежутки времени. Он в отличие от метода будет вызвать код до тех пор, пока Вы не остановите этот таймер.

Метод setInterval имеет два обязательных параметра:

  • 1 параметр представляет собой строку, содержащую код на языке JavaScript (например, вызов функции);
  • 2 параметр задаёт интервал времени в миллисекундах, через который данный код будет вызываться.

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

//запустим таймер и получим его идентификатор, который будет храниться в переменной timer1
//данный таймер будет выводить сообщение через каждые 5 секунд
var timer1 = window.setInterval("alert('Сообщение');",5000);
//остановим работу таймера с помощью метода clearInterval().
//Для этого в качестве параметра данному методу передадим идентификатор таймера, хранящийся в переменной timer1.
clearInterval(timer1);

Например, создадим цифровые часы:

<p id="clock"></p>
<a href="javaScript:startClock()">3anycтить таймер</a>
<br />
<a href="javaScript:stopClock()">Остановить таймер</a>

<script>
  // переменная, для хранения идентификатора таймера
  let timeoutId = null;
  // для предотвращения повторного запуска
  let hasStarting = false;

  // функция, которая будет запускать таймер
  function start() {
    // для предотвращения повторного запуска
    if (hasStarting) {
      return;
    }
    hasStarting = true;
    // получаем текущее время
    const startTime = new Date().getTime();
    // функция
    const run = () => {
      // определяем время, которое прошло с момента запуска функции start
      const time = new Date().getTime() - startTime;
      // получаем элемент .seconds и выводим в него значение time / 1000
      document.querySelector('.seconds').textContent = (time / 1000).toFixed(1);
      // запускаем вызов функции run через 50 мс
      timeoutId = window.setTimeout(run, 50);
    }
    // запускаем функцию run
    run();
  }

  // функция для остановки таймера по timeoutId
  function stop() {
    if (timeoutId) {
      // прекращаем работу таймера
      clearTimeout(timeoutId);
      // присваиваем переменной timeoutId значение null
      timeoutId = null;
      // присваиваем hasStarting значение false
      hasStarting = false;
    }
  }
</script>

Почему так происходит?

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

Рассмотрим простой пример, тестируемый код — это метод класса, который асинхронно меняет состояние экземпляра. И написан он не идеально:

Как разработчик напишет тест? Скорее всего он пойдет по пути наименьшего сопротивления, не станет менять тестируемый код и тоже вызовет setTimeout в тесте. При этом задаcт таймаут, превышающий таймаут внутри тестируемого кода (например, 101 мс вместо 100 мс). Вот так это будет выглядеть:

Чем такое решение грозит нашему разработчику? А тем, что он написал нестабильный тест, т.к. среда исполнения , что callback, переданный в setTimeout, будет исполнен точно спустя указанный промежуток времени. Более того — есть шанс, что данный тест не будет укладываться в предоставленный по умолчанию , что также делает его нестабильным. Вероятность проявления нестабильности растет пропорционально загрузке вычислительных мощностей машины, на которой производится тестирование. На это в свою очередь влияет как общее количество запускаемых в сборке юнит-тестов, так и активность разработчиков. У нас, например, в период где-то за неделю до релиза наблюдается повышенная активность, связанная с исправлением ошибок — в это время мы наблюдаем всплеск настабильности в юнит-тестах.
Все тоже самое справедливо для случаев с использованием функции setInterval.

The Problem with this

Code executed by is run in a separate execution context to the function from which it was called. This is problematic when the context of the keyword is important:

The reason for this output is that, in the first example, points to the object, whilst in the second example points to the global object (which doesn’t have a property).

To counteract this problem, there are various measures …

Explicitly Set the Value of

You can do this using bind, a method which creates a new function that, when called, has its keyword set to the provided value (in our case, the object). This would give us:

Note: was introduced in ECMAScript 5, so will only work in . You can read more about it (and other methods of setting the value of ) .

Use a Library

Many libraries come with built-in functions to address this issue. For example, jQuery’s jQuery.proxy() method. This takes a function and returns a new one that will always have a particular context. In our case, that would be:

Syntax

var intervalID
scope
func
delay
arg1
arg2
var intervalID
scope
code
delay

Parameters

not recommended
func

Note
: Passing additional arguments to setInterval() in the first syntax does not work in Internet Explorer 9 and earlier. If you want to enable this functionality on that browser, you must use a polyfill (see the section).

Return value

The returned intervalID is a numeric, non-zero value which identifies the timer created by the call to setInterval() ; this value can be passed to to cancel the timeout.

It may be helpful to be aware that setInterval() and setTimeout() share the same pool of IDs, and that clearInterval() and clearTimeout() can technically be used interchangeably. For clarity, however, you should try to always match them to avoid confusion when maintaining your code.

Note
: The delay argument is converted to a signed 32-bit integer. This effectively limits delay to 2147483647 ms, since it»s specified as a signed integer in the IDL.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector