002 ClassList и делегирование событий
С помощью приведённых ниже методов обычно создают изменения в классах для применения новых стилей на объекте и проявления элементов. Конкретно данный гамбургер был сделан через использование метода toggle()
Изначальный код на странице:
Сначала мы получим псевдомассив со всеми нашими кнопками со страницы. Через classList.length
можно получить количество классов на объекте. Через classList.item(индекс)
можно получить наименование класса объекта под определённым индексом
Метод classList.toggle()
крайне полезен и юзабелен. Данный метод добавит класс, если он отсутствует на объекте и удалит, если уже присуствует. Он может заменить целый блок с условной конструкцией, так как в его нутрянке и есть подобная проверка
Метод contains()
проверяет наличие класса в выбранном объекте. С помощью него можно прописать условие, которое будет срабатывать при наличии определённого класса
И вот пример добавления/удаления класса через условную конструкцию и через toggle()
className
– устаревший способ изменения классов. Он хранит в себе классы как строчку и использовать его неудобно
И сейчас нужно поговорить про добавление события для нескольких элементов на странице. Дело в том, что если нам нужно одинаковое событие на странице для нескольких элементов, то мы могли бы добавить addEventListener
через forEach
на все такие элементы. Однако такой подход немного устарел и имеет свои минусы в виде того, что если элемент отсутствовал на странице, то тот при появлении не будет иметь данный ивент.
Чтобы упростить задачу, нам нужно применить данный ивент на сам враппер, который в себе содержит эти элементы
Далее хорошо бы было узнать название элемента в самой HTML-структуре через дир - event.target
(название элемента можно увидеть при нажатии на него на странице)
И теперь при нажатии любой кнопки внутри wrapper
(даже если кнопка появилась позже в нём) у нас будет срабатывать event
. При нажатии на сам враппер, ничего происходить не будет
Такой способ называется делегированием
Так же можно присвоить ивенты только тем кнопкам, которые имеют определённый класс (тут – blue
)
И вот простой пример. Созданная кнопка после объявления ивента, уже имеет данный ивент, потому что он делегируется дочерним элементам
При таком коде новый ивент не будет добавлен на кнопку и она будет грустить одна такая☹
И так же есть продвинутая техника обращения к элементу через метод matches()
, внутрь которого мы положим наименование элемента и селектор класса
003 Создаем табы в новом проекте
Вот хороший пример структуры CSS-документа, когда разные стили находятся в разных блоках. Такой подход упростит создание большого сайта
Ну и немного дополнительных стилей, с которыми можно будет порабоать из JS
Так выглядят наши переключатели табов
Так выглядят сами табы
Для начала мы запишем весь наш код в ивентлистенер документа, который будет срабатывать при загрузке всей дом-структуры
Данной функцией мы прячем весь контент наших табов (добавляем хайд и снимаем шоу с анимацией фэйда на всех элементах через цикл)
И дальше реализована функция, которая показывает нужные табы по индексу. По умолчанию индекс равен нулю. Тут мы ремувим хайд и добавляем шоу и фэйд. Так же добавляем класс активности для переключателя табов
И самая сложная часть: смена активности таба. Её мы будем реализовывать через проверку тыкнутого элемента с event.target
. Его айди закинем в метод showTabContent()
Ивент будет срабатывать при клике внутри родителя.
Для начала мы упростим обращение к таргету. Потом мы создадим условие, которое будет срабатывать, если элемент имеет нужный нам класс (класс элемента внутри родителя).
Дальше мы внутри условия будем перебирать все наши кнопки для переключения изображений. В качестве аргументов используем саму кнопку и её индекс.
Внутри создаём ещё одно условие - если кнопка будет совпадать с таргетом, то будем прятать весь контент и показывать нашу кнопку по индексу
004 Скрипты и время их выполнения. setTimeout и setInterval
Примерно так можно написать setTimeout()
в обычных условиях. Первым аргументом функция принимает колбэк-функцию, вторым аргументом – таймаут. Все остальные аргументы – это аргументы для вложенной колбэк-функции
Так же мы можем вложить именованную функцию в таймаут. Тут нужно заметить, что внури мы не вызываем функцию, а просто вкладываем её имя
Когда мы передаём setTimeout()
в переменную, мы передаём числовой идентификатор этой функции. Делается это для того, чтобы чётко определять различные setTimeout
в коде, так как таких асинхронных функций может быть много
И так же мы можем очистить интервал и отменить его выполнение
Таким способом мы можем задать таймаут для определённой кнопки. Так же через таймаут часто задают всплытие каких-нибудь модальных окон на сайтах
И так же мы можем воспользоваться не только таймаутом, но и задать выполнение функции по интервалу (в примере каждые 3 секунды) через setInterval()
И уже в таком случае наш код сможет добраться до переменной с интервалом. Однако тут мы встречаемся с другой проблемой: clearInterval
не выполнится, так как он идёт в синхронном потоке кода. Дело в том, что на момент выполнения у него не будет никакого значения, вместо него в нём будет undefined
(clearInterval(longTO = undefined)
)
Чтобы очистка интервала не выходила из потока, нам нужно задать в самой функции определённое условие, по которому будет выполнятся очистка интервала изнутри самого этого интервала
И сейчас мы подбираемся к проблеме, которая заключается в том, что setInterval
не учитывает то, сколько времени выполняется функция внутри него. Он просто выполняет функцию раз в определённое время, которое ему задали. Сама же функция внутри него может выполняться и гораздо дольше.
И тут уже приходит рекурсивный setTimeout()
. Он, в свою очередь, сначала выполняет функцию и уже только потом ожидает выделенное время. Создаётся такой таймаут через перевызов по его идентификатору (переменной)
И тут уже представлено создание анимации блока, который перемещается по боксу при нажатии на кнопку.
Первым делом, получаем нашу кнопку, через которую будем запускать анимацию.
Во-вторых, создадим анимацию, внутри которой будем хранить сам бокс, который будем перемещать. Там же инициализируем наш счётчик позиции и идентификатор setInterval
. Так же внутри расположим функцию, которая будет менять позицию каждый кадр. Эта функция будет помещена в интервал.
Конечной точкой нашего блока будет сдвиг до конца блока (тут 600 бокс и 120 движущийся блок – 480 длина пути). Проверка будет выполняться, пока позиция не будет равна нужному нам числу. После мы очищаем интервал
Интересная особенность: даже если написать периодичнсть в интервале = 0, то всё равно на уровне кода она может иметь минимум в 4 миллисекунды.
005 (д) Сборщик мусора и утечки памяти
Если говорить просто, то JS – это высокоуровневый (все базовые команды прописаны за нас) интерпретируемый (его основа – это не компилятор, а интерпретатор) ЯП.
И вспомним, что в функции можно объявить переменную без let
и const
. Если залезть немного внутрь, то это будет то же самое, что и объявить переменную глобально через прототип window
. Дело в том, что такая переменная неудаляема и сборщик мусора ничего с ней не сделает
Так же сборщик мусора не может удалить оставленные нами таймеры. Дело в том, что они хранят ссылку на конкретные объекты, которые в данный момент используются. Такие таймеры нужно останавливать и выключать
Так же стоит упомянуть и обработчики событий. Дело в том, что в старых браузерах они так же создавали утечку памяти. Если мы удалим объект с ивентом, но не удалим сам ивент, то у нас в памяти останутся оба данных объекта и будут занимать место. Однако современные браузеры могут чистить и оставленные ивенты.
В React и JQery очистка ивентов реализована автоматически. Однако в нативном JS является хорошим тоном стирать ивенты через removeEventListener()
.
Так же если мы удалим DOM-элемент с сайта, то при наличии другой ссылки в JS на этот элемент, он останется в памяти и сборщик не сработает
В этом примере у нас будет утечка памяти. Дело в том, что функция deleteElement
удалит элемент из ДОМ-дерева, а переменная divBlock сохранит этот блок в памяти страницы – так делать не надо. Чтобы исправить ситуацию, нам нужно поместить переменную в такие условия, чтобы после создания она была удалена
Сейчас мы убрали лишнюю переменную из глобальной области видимости и создали оторванный от кода объект. При удалении этого объекта – он удалится полностью (и из ДОМ-дерева и из памяти)
Чтобы решать проблемы с памятью (если такое будет нужно), можно воспользоваться специальным инструментарием в DevTools
https://developer.chrome.com/docs/devtools/memory-inspector/
006 () WeakMap и WeakSet
Как мы знаем, сборщик мусора удаляет объекты, на которых уже нет ссылок в проекте. В данном примере ссылка на объект остаётся в массиве и поэтому он не стирается
Уже в этом примере объект не стирается, так как ссылку на объект хранит в себе карта
И дальше у нас идёт объект WeakMap
. Это объект, у которого
- ключи – только объекты
- При отсутствии ссылки на объект извне – объект будет удалён сборщиком мусора
Если сейчас мы решим проверить значения через keys()
, то мы получим ошибку, так как таких методов как keys
, values
и entries
– не существует для такой карты. get
, set
, delete
, has
– единственные методы, которые у нас остаются от обычной карты. Поэтому проверим через наличие значения has()
Если мы выведем просто карту, то наш интерпретатор не будет понимать, что за объект находится в карте
Самый простой пример использования WeakMap
– это отображение пользователей в онлайне в каком-нибудь чате. Если пользователь offline, то он получает значение null
и скрывается из онлайна чата.
И уже дальше у нас идёт WeakSet
. Если Set
хранит в себе только уникальные значения массива, то WeakSet
хранит эти значения только до тех пор, пока хоть какой-то объект ссылается на них в проекте. WeakSet
поддерживает только add
, has
, delete
.
Пример реализации прочитанных сообщений. WeakSet
принимает в себя значения массива с сообщениями. Как прочитать сообщение можно один раз, так и добавить в прочитанные сообщения система одно сообщение может только один раз.
И тут у нас сразу видна такая структура WeakSet
. В него мы добавили 3 разных объекта. Далее мы обращаемся к наличию третьего элемента в этом сете. Но так как мы из массива удалили первый элемент, то он съехал на одно значение назад и сам виксет теперь имеет только 2 значения вместо трёх, как это было до стирания первого элемента (при стирании объекта из массива все элементы по индексам сдвигаются на один элемент к началу)
007 Работа с датами
Дата задаётся обычно через конструктор Date()
Однако можно вписать и цифрами через запятую все нужные даты. Как можно увидеть, месяцы идут с 0, а время идёт по гринвичу. Поэтому при вводе 5 месяц 20 часов, мы получим – 6 месяц (так как он имеет индекс пять) и 17 часов (так как пояс +3 МСК)
Так же интересно знать, что все даты в JS выражаются в миллисекундах и их можно перевести как в дату, так и обратно в мс. Так же отсчёт времени идёт от 1970 года
Так же мы можем быстро посмотреть все нужные методы дат через get
Так же у нас есть возможность получить два разных часовых пояса
Через set
мы можем установить нужное нам время. Проверять такой код в консоли не получится – это нужно делать сразу в браузере
Ну и не самая очевидная часть заключается в том, что можно так же вставить и остальные значения времени
Два варианта записи даты: первый обычный, второй через парсинг
Так же дата позволяет сделать таймер и высчитывать миллисекунды между выполнением операций.
Тут было подсчитано время выполнения операции
008 Создаем таймер обратного отсчета на сайте
Собственно, блок кода, в котором у нас и происходит всё действо
Первая константа хранит в себе конечное время, когда таймер прекратит свою работу. Внутри функции getTimeRemaining
мы получаем всё время, дни, часы, минуты и секунды по-отдельности (получаем из разницы между нынешним временем и дедлайном) и возвращаем объект с данными значениями
Дальше в функции clockTime
мы производим изменение времени. Время меняется через функцию updateClock
, которая вызвается каждую секунду через setInterval
. Внутри обновления часов мы получаем данные из нашей первой функции и подставляем значения полученного объекта в innerHTML
наших блоков со временем.
Если время к моменту вызова часов уже уйдёт в минус (например, на дворе уже будет 1ое сентября), то интервал очистится.
Так же для вставляемых значений времени была применена отедельная функция, которая подставляет 0 к числам из одной цифры.
А вот и сама функция, которая переводит одиночные цифры в двуциферные(да-да)
И теперь мы имеем прекрасный счётчик на нашем сайте!
009 () Обработка прошедшей даты
Первый вариант починки даты, которая может уйти в минус – это при отрицательной дате не проводит расчёты самой даты, а просто выдать «0»
Так же можно при минусовом времени вместе с остановкой интервала просто выдать в текстовых блоках нули
Любой из этих вариантов будет работать нормально и очистит таймер
010 Параметры документа, окна и работа с ними
Есть три основных понятия среди глобальных объектов: document
(сам документ с элементами), window
(отображаемое окно, размер которого уменьшается, например, когда мы уменьшаем браузер) и screen
(весь видимый монитор)
И в первую очередь хочется поговорить про получение размеров элемента через его свйоства, которые мы моежем получить через JS
Пример использования:
Тут стоит отметить, что на получаемые значения так же влияет и тип box-sizing
(тот же border-box
, который часто используется на страницах)
Так же мы можем показать полностью свёрнутое окно через свойство бокса, в котором хранится полная высота свёрнутого объекта
Так же мы можем отобразить свойство JS, которое хранит в себе положение по скроллу в пикселях
Данные методы позволяют отобразить Computed Styles конкретного объекта
И тут хочется немного прояснить про Computes Styles (вычисленные стили). Это стили, которые уже применены на объект. Они могут вычисляться автоматически как margin: 0 auto
, который под копотом постоянно вычисляется браузером
И мы можем получить данные стили с помощью JS
Такой код и использование метрик относительно объектов позволяет, например сделать быстрый скролл по кнопке от начала страницы к середине (например)
Так же тут стоит отметить особенности обращения к самому документу, так как операции проходят не с самим document, а с documentElement
Уже данный метод позволяет проскроллить относительно текущего положения по нужным нам координатам
Второй метод будет уже работать относительно всей страницы
011 Создаем модальное окно
Важно! В HTML есть кастомные атрибуты, которые мы можем задавать самостоятельно. Это атрибуты, которые начинаются на «data-
» - после дефиса мы можем прописывать свои значения
Блок кода модального окна (display: none – по умолчанию)
Это кнопка вызова модального окна
Это код для стоковой странички. Тут стоит отметить, что при вызове модельного окна мы скрываем оверфлоу Так же при использовании в качестве значения для стилей «“”» пустой строки – добавляется значение по умолчанию самим браузером
Если мы хотим сделать отображение или закрытие через тугл, то нужно будет убрать настройки дисплея из стилей и добавить класс хайд изначально
Так же будет удобно для пользователя, если мы позволим закрывать модальное окно через клик по пустому пространству
Так же в плохом коде можно встретить и такое объявление ивента. Дело в том, что ивент уже объявлен внутри обработчика события. Однако такая запись считается плохим тоном
Ну и добавим закрытие модального окна через Escape. Тут уже нужно проверять кнопку через событие «keydown
» (кнопка нажата) и параметр внутри «e.code
»
Чтобы определить нужную нам кнопку, можно воспользоваться уже заранее заготовленными таблицами кейкодов или нужными сайтами
Примечание: если бы мы добавляли и удаляли класс с объекта через add()-remove()
, то нужно было бы дописать ещё и такое условие
012 Модификации модального окна
Сейчас перед нами стоит задача: проявить модальное окно через определённое время либо, когда пользователь прокрутит до конца страницы
Первая задача выполняется крайне просто через setTimeout
и очистку этого таймаута внутри функции, которая его вызывает (чтобы окно не открывалось, если его уже открывал пользователь)
Вот код, который выполняет вторую поставленную задачу. Срабатывание тут устроено немного сложно.
Для решения некоторых багов нужно добавить -1 пиксель от высоты всего документа пользователя
Тут нужно кое-что отметить – нам нужно сделать срабатывание только однажды. И первое, что приходит в голову – это использовать настройку для ивент-листенера. Однако проблема заключается в том, что ивент скролла срабатыает сразу, как мы сделали первый скролл на странице
Уже такой код позволит нам вызвать ивент ровно один раз в самом конце страницы
013 (д) MutationObserver, ResizeObserver и contenteditable
Атрибут contenteditable="true"
позволяет нам редактировать контент внутри определённого блока. Это интересный тег, который позволяет нам менять контент внутри блока
Такой атрибут используют чаще в CMS, когда нужно отредактировать контент на готовой странице. Сохранение данного контента уже выполняет логика бэкэнда
И примерно таким образом мы можем задать слежку за элементом через observer
. Конкретно тут обсервер получает в себя колбэк-функцию, которая хранит в себе рекорды (логи изменений). Далее мы инициализируем слежку через observe(элемент, выполняемая функция)
Отключить слежку можно через observer.disconnect()
тут находятся все опции мутировщика
И теперь мы можем просмотреть изменения элемента.
Observer
срабатывает уже после изменений. Мы работаем с результатом изменений- Это асинхронная операция. Поэтому она может выполниться чуть позже или чуть раньше
- При остановке
Observer
, он так же стирается и сборщиком мусора
Так же есть Perfomance
, Resize
и Intersection
обсёрверы
014 Функции-конструкторы
Как можно увидеть – новые объекты можно создать через конструктор. Такой синтаксис через new
означает, что дальше мы будем вызвать конструктор, который создаст экземпляр класса
И так же нужно сказать, что функции в JS – это объекты
И вот пример создания функции-конструктора. Внутри неё мы создаём внутренние переменные данной функции через «this
» (обращение к самому себе) и присвоение к ним внешних значений. Такая функция создаёт объект
Так же мы можем создать внутреннюю функцию, которую можно будет вызывать из всех экземпляров данного конструктора. Так же в прототип можно записать и дополнительные значения (например, функцию, которая будет наследоваться всеми экземплярами)
015 Контекст вызова. This
Понимать контекст вызова с «this
», стоит как присваивание переменной к определённому контексту (функции). То есть любая переменная, которую мы создаём – принадлежит всей области видимости. Если мы создаём переменную с «this
», то она будет принадлежать только данной области
Так же тут стоит отметить, что при обычном вызове функции с this
внутри, у нас будет выходить глобальная переменная Window
. Если мы добавим “use strict”
, то получим undefined
И тут представлен пример, когда мы используем «this» и избегаем его. Благодаря «замыканию функции» в обычной ситуации (первый скрин) у нас переменные ищутся сначала внутри самой функции, потом поиск заходит в функцию родителя.
На втором изображении уже используется вызов переменной через «this
» и функция ищет переменную только внутри себя и дальше не выходит. Поэтому и возвращается undefined
Контекст вызова у методов объекта – это сам объект
Уже в данном примере контекст вызова (объект) теряется, так как внутри метода просто вызывается функция
This в конструкторах и классах – это новый экземпляр объекта
То есть при создании свойства в конструкторе через «this
», это значение будет присваиваться только созданным экземплярам через данный конструктор
Ручная привязка this
: call
, apply
, bind
Так же мы можем вызывать функцию и подвязать ей контекст вызова через методы call
и apply
(оба метода выполняют одно и то же, но по-разному передают аргументы в функцию)
Так же можно сделать много вариаций одной и той же функции, используя контекст вызова и функцию bind
. bind
принимает в себя значение «this
» и возвращает в переменную новую функцию, где this
заменён на данный аргумент метода
И когда мы используем конструкцию с function()
в ивенте мы так же можем пользоваться и «this
», так как он будет вызывать конкретно наш нажатый объект
И работать с этим объектом мы так же можем
Как помним из примера выше – функция внутри функции имеет свой контекст вызова и поэтому в объекте такая вложенная функция вернёт undefined
. Но отличительной особенностью стрелочных функций является то, что у них нет своего контекста вызова. Контекст вызова они наследуют от родителя и поэтому код, приведённый ниже, будет работать
Ну и так как нет собственного контекста вызова у стрелочной функции, то берём пример чуть выше с function()
и переделываем его на стрелочную функцию. Работать уже такой ивент не будет, так как контекст, опять же, берётся у родителя
И чтобы заменить «this
» в таких функциях, используют таргет ивента
016 Классы (ES6)
Классы появились в стандарте ECMA2015. Если нам нужно будет перевести код в старый стандарт, то для этого используются трансплиттеры (тот же babel)
Синтаксис классов немного отличается от синтаксиса функции. Внутри класса обязательно описывается конструктор. Конструктор представляет собой те начальные значения, которые мы задаём концепции экземпляра. Синтаксис достаточно схож по смыслу с функциональными конструкторами
Дальше мы пишем методы класса. Внутри них мы спокойно можем обращаться к тем начальным значениям, что мы задали для экземпляра
И далее у нас идёт синтаксис наследования. Он представляет из себя передачу кода дочерним элементам. Реализуется наследование через extends
.
Метод super
вызывает код родителя. Помещать его нужно всегда до написания остального кода
018 Используем классы в реальной работе
Сама вёрстка. Она содержит в себе блок menu
, внутри которого располагаются три menu__item
, которые и являются нашими карточками
Первым делом мы создаём класс и конструктор, который будет в себя принимать уникальную для каждой карточки информацию. Так же чуть дальше пропишем метод, который будет переводить доллары (единица из БД) в рубли. Вызываем перевод прямо в конструкторе при создании
И дальше у нас идёт самый важный метод, который и выводит саму карточку на страницу. Первым делом мы инициализируем блок, в котором будет находиться карточка. Дальше мы в этот элемент помещаем саму карточку и забиваем её элементы нашими переменными.
В самом конце мы аппендим карточку на страницу через аппенд по родителю
Так же хочется упомянуть, что создать новый объект можно и без его объявления и присвоения в переменную. Конкретно тут, мы не собираемся над карточками проводить никаких операций, поэтому их можно просто создать через new
. Ну и сразу вызвать метод render
, который сконструирует блоки на сайте
Ну и дальше нам нужно создать три этих карты (вёрстку из html нужно будет удалить)
019 Rest оператор и параметры по умолчанию (ES6)
В JS есть оператор spread
– “…
”, который деструктуризирует объект (массив). Однако так же есть и оператор rest
, который собирает отдельные элементы в один массив – «…название
»
И попытаемся вставить данный оператор в наш проект. Представим, что нужно дать возможность вставить сразу несколько классов на карточку и для этого можно воспользоваться рестом, который вернёт массив этих значений
Так же нам нужно внести изменения в рендер наших карточек. В условии нужно проверить, переданы ли вообще классы и присвоить эти классы (если нет или если их передали). В первом случае, мы проверяем массив, который нам возвращает рест. Если ничего в качестве классов не будет передано, то сработает первое условие (так как рест всегда возвращает массив, даже если значений 0). Если что-то будет передано, то сработает второе условие, которое через forEach
передаст все нужные значения в качестве класса в наш див-элемент.
И сейчас у нас есть возможность не писать классы или написать сразу несколько
И примерно так это выглядит на странце