Отложенные действия - setTimeout, setInterval #
Таймеры в работе нужны достаточно часто. Обновление погоды для городского портала, курса валют для информера, времени на домашнем сайте это типичные вещи, которые понятны и новичку. Через таймер мы можем управлять повторным запросом к базе, если с перового раза доступа не было. Удобная вещь и в JavaScript есть два метода, которые помогают работать с таймерами.
Сразу поправлюсь, что это не JS-Core, a Web API, но эти методы (обычно) работают в любой JS среде, как в браузере, так и на бэкенде.
setTimeout #
И так у нас есть функция, которая выводит на экран сообщение дважды. Один раз заглавными буквами, второй раз красным, как ошибку(в браузерной среде).
function toPrint(message) {
console.log(message.toUpperCase());
console.error(message.toUpperCase());
}
Мы можем запустить эту функцию с задержкой в две секунды. Для этого мы воспользуемся методом setTimeout(). В этот метод мы передаём функцию, задержку и возможные аргументы для функции, которые мы хотим передать.
setTimeout(toPrint, 1000, "андрей");
- toPrint - это наша функция
- 1000 - это одна секунда в миллисекундах
- “андрей” - это аргумент, который надо передать в функцию toPrint()
Конечно же мы можем сразу написать функцию в setTimeout. Например, так:
setTimeout(function() {
console.log("андрей".toUpperCase());
console.error("андрей".toUpperCase());
}, 1000);
Или используя стрелочную функцию:
setTimeout(() => {
console.log("андрей".toUpperCase());
console.error("андрей".toUpperCase());
}, 1000);
Давайте посмотрим, что мы можем сделать на практике.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script defer src="script.js"></script>
<style>
.absolute {
position: absolute;
right: 1rem;
bottom: 3rem;
width: 30vw;
height: 20vh;
font-size: 2rem;
}
</style>
<title>Document</title>
</head>
<body>
</section>
<section class="absolute">
</body>
</html>
И к нему вот такой скрипт:
let absolute = document.querySelector(".absolute");
setTimeout(() => {
absolute.innerHTML = "Вы на сайте уже несколько секунд, а ещё ничего не купили." + "<br>" + "Чем я могу вам помочь?";
}, 2000);
Примерно вот так и появляются эти модальные окна на сайтах магазина. Особо сложной логики там обычно нет. Выставляется 20-60 секунд и для этого используется обычный setTimeout.
setInterval - актуальная дата и время на сайте #
Давайте закрепим и реализуем дату и время на сайте. Создадим html-файл и подготовим две секции для нашего контента.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script defer src="script.js"></script>
<style>
body {
display: flex;
align-items: center;
justify-content: center;
color: tomato;
font-weight: 900;
font-size: 3rem;
}
.message {
width: 30vw;
height: 30vh;
border: 1px solid tomato;
margin: 2rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
<title>Document</title>
</head>
<body>
<section class="message">
<section class="time" id="time"></section>
<section class="sec" id="sec"></section>
</section>
</body>
</html>
К этому файлу добавим script.js. В ней есть две функции приведения даты в тот формат, который нам требуется, один раз число, месяц, год; а в другом случае часы, минуты, секунды.
И функция обновления даты, которую мы вызываем через setInterval.
let dateSection = document.querySelector(".time");
let secSection = document.querySelector(".sec");
let absolute = document.querySelector(".absolute");
updateTime();
setInterval(updateTime, 1000);
function updateTime() {
let myDate = new Date();
let message = getEuropeDate(myDate);
dateSection.innerHTML = message;
secSection.innerHTML = getTime(myDate);
}
function getEuropeDate(date) {
return date.getDate() + "." +
(date.getMonth() + 1) + "." +
date.getFullYear();
}
function getTime(date) {
return date.getHours() + ":" +
date.getMinutes() + ":" +
date.getSeconds();
}
clearInterval #
Возможно, пользователь или бизнес логика приложения захочет удалить таймер со страницы. Например, остановить время в в виртуальных часах для шахмат.
Давайте напишем простейший таймер:
let count = 10;
const timerID = setInterval(function() {
console.log(count--);
if (count === 0) {
console.log("stop");
clearInterval(timerID);
}
}, 1000);
Прошу отметить, что останавливаем мы таймер указывая переменную timerID, в которой сохранена функция setInterval. clearInterval(timerID) удаляет таймер и останавливает обратный отсчёт.
clearTimeout #
clearTimeout работает по такому же принципу, как и его родственник clearInterval. Мы должны передать ссылку на таймер, что бы его удалить из памяти. Пример:
const timer = setTimeout(() => {
console.log("тест с задержкой");
}, 2000);
clearTimeout(timer);
console.log("конец работы скрипта");
requestAnimationFrame() и cancelAnimationFrame() #
Для работы с отрисовкой в браузерах и нашей анимацией есть более новый и в некоторых случаях более рекомендуемый метод - requestAnimationFrame(). Если хотите, то можете этот метод считать болеее современной версией setInterval. Метод, который удаляет таймер requestAnimationFrame - cancelAnimationFrame().
Технический вызов происходит абсолютно так же как и с интервалом. Детали спрятаны в отрисовке:
- Если вкладка неактивна, то requestAnimationFrame() останавливает свою работу и браузер бережёт свои силы и ресурсы.
- Отрисовка происходит 60 раз в секунду или совпадает с частотой обновления экрана.
- requestAnimationFrame именно браузерная отрисовка экрана. В node.js мы получим “requestAnimationFrame is not defined”
То есть, методы requestAnimationFrame и setInterval очень схожи, но все же имеют собственные недостатки и достоинства.
Таймер как экземпляр класса #
Я написал класс Timer для подсказки к домашнему заданию. Класс не является темой урока, по этому приведу просто код без пояснений.
class Timer {
id;
started;
remaining;
running;
callback;
constructor(callback, timeout) {
this.remaining = timeout;
this.callback = callback;
}
start = () => {
this.running = true;
this.started = new Date();
this.id = setTimeout(this.callback, this.remaining);
}
pause = () => {
this.running = false;
clearTimeout(this.id);
this.remaining -= new Date() - this.started;
}
getTimeLeft = () => {
if (this.running) {
this.pause();
this.start();
}
return this.remaining;
}
getStateRunning = () => {
return this.running;
}
}
let myTimer = new Timer(() => {
console.log("Hello");
}, 1000);
myTimer.start();
console.log(myTimer.getTimeLeft());
Event Loop #
Асинхронное программирование и Event Loop любимые темы на собеседованиях. Так как JS является синхронным языком, то достаточно важно понимать, как происходят отложенные события.
Все методы выполняются по очереди. В порядке запуска. Вызываются методы в стэке вызова (Call Stack), если JS встречает асинхронный метод, например setTimeout, то содержимое передаётся в очередь исполнения (QUEUE). Это значит, что вконец очереди. То есть, если у нас изначально было 10 методов, и третий метод оказался асинхронным, то его выполнение будет не раньше чем выполнятся 10-ый метод.
Подробнее об очереди и стэке вызова в дополнительных ссылках.
Дополнительные ссылки #
- Объединённый асинхронный JavaScript: Таймауты и интервалы
- window.requestAnimationFrame
- JavaScript Visualized: Event Loop
Домашнее задание #
- Что выдаст консоль?
let count = 0; setTimeout(() => console.log(count), 1000); // ? for (let i = 0; i < 100; i++) { count++; }
- Что произойдёт? Объясните почему так вышло.
for (var i = 0; i < 4; i += 1) { setTimeout(() => console.log(i), 0); }
- Что произойдёт? Объясните почему так вышло.
for (let i = 0; i < 4; i += 1) { setTimeout(() => console.log(i), 0); }
- В каком порядке сработают распечатки? Объясните почему так вышло.
function printing() { console.log(1); setTimeout(function () { console.log(2); }, 1000); setTimeout(function () { console.log(3); }, 0); console.log(4); // если не учили промисы, то можете закомментировать строчку Promise.resolve().then(() => console.log(5)); } printing();
- Сделайте таймер, с кнопкой, с обратным отсчётом.
- Сделайте на странице HTML 16 квадратов - 4*4. Поменяйте у одного произвольного квадрата цвет каждые пол-секунды. Подсказка для гридов:
body { display: grid; grid-template-columns: repeat(4, 20vw); grid-template-rows: repeat(4, 20vh); gap: 1rem; }
задача с settimeout
- Реализуйте электронные часы для шахматистов.
- Нарисуйте квадратик в большом квадрате, который:
- при нажатии кнопки старт медленно переезжает из верхнего левого угла квадрата в нижний правый. Используете setInterval
- отключите кнопку старт после нажатия и активируйте кнопку стоп
- квадратик должен ездить туда сюда пока не нажмут стоп.
- перепишете setInterval на requestAnimationFrame()
- Используя requestAnimationFrame нарисуйте моргающий элемент в правом верхнем углу экрана. Появление-исчезание, частота - раз в секунду.