itmo_conspects

Web-разработка: Frontend

Презентации доступны по ссылке https://xrem.github.io/web/

Лекция 1. Как устроен Web?

Всемирная паутина (World Wide Web, или же просто Web) - распределенная система компьютеров, предоставляющая доступ к связанным между собой документам

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

Для этого браузер, специальная программа для доступа и обработки веб-страниц, отправляет запрос DNS-серверу

В сети Интернет для непосредственного доступа к другому компьютеру не используются URL-адреса (Uniform Resource Locator), которыми мы пользуемся и которые мы легко понимаем, например itmo.ru. Вместо них используются IP-адреса (например, 247.207.70.150), которые присвоены каждому компьютеру в Интернете

Чтобы по URL-адресу получить IP-адрес, браузер делает запрос DNS-серверу, который возвращает IP-адрес сервера

Подробнее про DNS - https://pelmesh619.github.io/itmo_conspects/telecomm/telecomm_superconspect.html#-%D0%BB%D0%B5%D0%BA%D1%86%D0%B8%D1%8F-12-dns

Далее браузер делает запрос этому серверу на получение веб-страницы. Основным протоколом в обмене веб-страниц и совершении других запросов является HTTP, HyperText Transfer Protocol (Протокол передачи гипертекста). HTTP работает поверх протокола TCP. Сейчас использует более безопасная версия HTTPS (HTTP Secured). Для создания запроса браузер создает TCP-соединение между текущим компьютером и сервером. Для браузеры выбирается случайный TCP-порт больше 1024, а для сервера порт заранее известен: 80 для HTTP и 443 для HTTPS. Имя протокола указывается в начале полного адреса, например, https://itmo.ru

Веб-сервер, компьютер, принявший запрос, имеет специальное программное обеспечение, которое обрабатывает запрос. Основной функцией веб-сервера является хранение, обработка и передача веб-страниц. Имя страницы представляет собой путь после доменного имени и в простейшем случае может быть файлом на веб-сервере, например, https://itmo.ru/promo/itmo-logo-dark.svg

Если веб-страница по данному адресу существует, то сервер отправляет ее. Ответ содержит HTTP-код, например, 200, что означает успешный запрос. Если запрошенной страницы нет, то отправляется ответ с кодом 404 и страница-заглушка. Дополнительно, сервер может отправить код 304, что означает, что страница не изменилась, и браузер может загрузить ее из своего кеша

Далее TCP-соединение закрывается, и полученный контент обрабатывается. Сейчас все веб-страницы представляют из себя набор документов из HTML-файла, стилей и скриптов. Браузер обрабатывает HTML-код, обнаруживает дополнительные ресурсы, которые нужны для работы страницы, такие, как изображения, стили, скрипты и прочее, и загружает их, делая дополнительные запросы

Запросы

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

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


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

Лекция 2. HTML и CSS

Язык верстки HTML

Структура веб-страницы создается с помощью языка верстки HTML (HyperText Markup Language, язык гипертекстовой разметки). В основе этого языка лежат теги в формате <tag></tag> или <tag>, которые указывают семантику содержимого внутри себя контента и содержат дополнительные атрибуты <tag key="value">

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

<!DOCTYPE html>

В старой версии HTML до его общего принятия тип документа определялся так:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
    "http://www.w3.org/TR/html4/strict.dtd">

Далее идет тег <html></html>. В нем указываются теги <head></head> и <body></body>. Тег head содержит служебную информацию о странице, такую как:

<!DOCTYPE html>

<html>

<head>
    <!-- Заголовок -->
    <title>Web программирование</title>

    <!-- Подключение стилей -->
    <link href="styles.css" rel="stylesheet">
    <!--        ^ ссылка         ^ типа файла (то есть таблица стилей) -->

    <meta charset="utf-8">
    <!--           ^ кодировка -->

    <meta name="keywords" content="web, веб, программирование">
    <!--                           ^ ключевые слова -->
    
    <meta name="description" content="краткое описание"> 
    <!--                              ^ описание -->
</head>

<body>
</body>

</html>

В общем случае теги в head не отображаются непосредственно на странице, но влияют на ее обработку в браузере и поиск в поисковых движках

Далее идет тег <body></body>. Он представляет тело страницы и обычно имеет такую структуру:

<body>
    <header></header>
    <main></main>
    <footer></footer>
</body>

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

Тег main окружается тегами header и footer. Первый предназначенный для вводной части страницы (так называемой шапки), где представлены логотип компании и главные ссылки на другие страницы (“Каталог”, “О нас” и прочее)

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

Примерная HTML-страница выглядит так:

<!DOCTYPE html>

<html>

<head>
    <title>Рога и копыта</title>
</head>

<body>
    <header>Компания "Рога и копыта"</header>
    <main></main>
    <footer>+7 (777) 77-77-77</footer>
</body>

</html>

Помимо них также существуют эти теги, обозначающие другие блоки страницы:

Наконец, если подходящего тега с нужной семантикой нет, то используют два общих тега: <div></div> и <span></span>

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

Таблицы стилей CSS

Чтобы придать стиля веб-странице, используют таблицы стилей, написанные на языке CSS (Cascading Style Sheets, каскадные таблицы стилей). Обычно они пишутся в отдельном файле, подключаемом с помощью тега <link>, но также и могут находиться в исходной HTML-странице в теге <style></style>

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

селектор {
    свойство1: значение1;
    свойство2: значение2;
    свойство3: значение3;
    /* ... */
}

Так селектором могут быть:

Далее к ним применяются могут применяться особые условия:

CSS-стили могут выглядеть так:

/* для всех элементов цвет текста оранжевый */
* {              
    color: orange;
}

/* для всех блоков div фоновый цвет серый */
div {
    background-color: gray;
}

/* 
    для параграфов с классами `mytext` и `quote` 
    цвет текста красный 
*/
p.mytext.quote {
    color: red;
}

/* 
    для элементов с идентификатором example 
    цвет текста зеленый 
*/
#example {
    color: green;
}

Приоритет правил определяется специфичностью селектора, а при равной специфичности - последним объявлением в коде.

Свойств и значений для них в CSS есть огромное множество. Хорошей практикой является использование классов для применения стилей к элементам, а не идентификаторов

По умолчанию, каждый браузер применяет свои собственные стили для элементов (например, использует свой шрифт), поэтому контент может по-разному отображаться в разных браузерах. В таком случае имеет смысл применять сброс CSS - специальный набор правил, которые упраздняют их. Один из самых популярных от Эрика Мейера выглядит так:

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}

Также используется сброс стилей normilize.css

Лекция 3. Блочная модель

Каждому HTML-элементу соответствует прямоугольная область, или бокс. Боксы бывают блочными и строчными

Блочные бокс представляется собой крупную неразрывную область. Блочными боксами обладают теги <p>, <h1>, <h2>, <ul> и так далее. В частности, тег <div>, благодаря которому можно создать сетку элементов, обозначает простой прямоугольный контейнер и обладает блочным боксом

Блочные боксы:

Строчные боксы располагаются друг за другом в одной строке, могут разрываться и находиться на нескольких строках. Строчными боксами обладают такие элементы, как <span>, <a>, <i>, <em>, <b>, <strong>, и предназначены для оформления текста

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

При рендере HTML-страницы создает поток элементов - порядок их отображения на странице. Блочные элементы располагаются в поток сверху вниз, а строчные - слева направо. Элемент считается выпавшим из потока, если он не влияет на расположение последующих блоков


По умолчанию, блочные боксы имеют ширину родительского блока, а высота зависит от содержимого (если бокс пустой, то 0px). Ширина и высота боксов задаются с помощью свойств width и height, например:

selector {
    width: 100px;
    height: 100px;
}

Чтобы вернуть значения по умолчанию, можно воспользоваться значением auto:

selector {
    width: auto;
    height: auto;
}

Строчные боксы имеют ширину и высоту ровно столько, сколько занимает контент внутри, и не реагируют на свойства width и height

Внутренние отступы (или поля) задаются свойством padding:

selector {
    padding: 10px; /* отступ 10 пикселей со всех сторон */
}

Есть 4 формы записи:

padding examples

Можно задать отступ для каждой стороны:

selector {
    padding-left: 10px;   /* слева  */
    padding-right: 10px;  /* справа */
    padding-top: 10px;    /* сверху */
    padding-bottom: 10px; /* снизу  */
}

Аналогично задаются внешние отступы от границы с помощью свойств margin, margin-top, margin-bottom, margin-left, margin-right

Также для боксов можно указать границу (или рамку), а именно:

У этих свойств также есть конкретные для каждой стороны, например border-left-width. С помощью свойства border можно указать сокращенную запись этих параметров в любой порядке:

selector {
    border: 20px solid red;
    border-left: 20px dashed green;
    border-bottom: 10px solid blue;
}

border examples

Также углы рамок можно скруглять под определенным радиусом с помощью свойств border-radius, border-top-radius, border-left-radius, border-right-radius, border-bottom-radius

Занимаемое место бокса на странице вычисляется как сумма

Вертикальные отступы двух соседних элементов имеют свойство схлопываться, тогда интервал между ними становится равным максимуму отступов этих элементов

margin collapse

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


Теперь можно научиться базовому приему центрирования элемента:

  1. Делаем ширину дочернего элемента меньше ширины родителя
  2. Делаем внешние отступы слева и справа auto

Получаем:

div {
    width: 70%;
    margin: 0 auto;
}

Ширину для элемента можно указать в процентах от ширины родительского элемента. Нужно быть аккуратным, так как если родительский элемент имеет внутренний отступ слева и справа, то width: 100% может привести к выпаданию элемента за пределы

Кроме auto такое исправить можно с помощью изменения свойства box-sizing. По умолчанию, оно равно content-box, что определяет ширину и высоту для области с контентом. Значение border-box установит, что ширина и высота задана для области контента, внутренних отступов и границ

width 100%


Тип бокса можно изменить с помощью свойства display

Позиционирование элемента также можно изменить. Тип позиционирования можно выбрать с помощью свойства position:

Позиционирование задается с помощью свойств top, left, right, bottom. Например, top: 10px; position: relative; сделает элемент на 10 пискелей ниже исходного положения, а right: 20px; position: relative; сделает его на 20 пикселей правее от исходного положения. Также можно применять отрицательные значения

Позиционирование

Как можно заметить в примере, элементы перекрывают друг друга. Чтобы изменить порядок перекрытия, нужно указать свойства z-index для элемента. Чем больше число z-index, тем выше приоритет того, что элемент окажется выше других

Также элементу можно включить обтекание с помощью свойства float:

Если float указан у строчного, то его поведение проявляется как у блочного. Блоки с float выпадают их потока. Если у нескольких обтекаемых элементов нет места, то они переносятся на следующих строчки - таким образом создавались сетки элементов в 2000-ых до появления display: flex и display: grid. Сейчас для сеток лучше использовать flex или grid, так как поведение переноса обтекаемых элементов может быть неочевидным

Свойство clear: left запрещает обтекание слева, clear: right - справа, clear: both - с обеих сторон

Помимо display: block и display: inline для выравнивания можно использовать display: flex. Значение flex превращает блоки во флекс-контейнеры, внутри которых можно менять направление потока элементов

Внутри флекс-контейнера существует главная ось, вдоль которой следует поток, и которая задается с помощью свойства flex-direction:

С помощью свойства justify-content можно выравнять элементы относительно главной оси:

Поперечная ось расположена перпендикулярно и направлена вниз или направо в зависимости от направления главной. Вдоль поперечной оси работает выравнивание элементов с помощью свойства align-items:

Если нужно другое выравнивание по поперечной оси для отдельного элемента, для него применяют свойство align-self с теми же значениями, что и align-items

Лекция 4. Типы верстки

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

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

Рассмотрим виды верстки:

10 видов верстки, вы будете ржать...

Далее верстка делится по степени адаптивности:

Верстка считается правильной, если соответствует нескольким критериям:

Хорошими практиками являются:


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

  1. Bootstrap - классический CSS-фреймворк с готовыми компонентами. В нем есть сетка на флекс-блоках, готовые компоненты и JavaScript-плагины

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

  2. Tailwind CSS. В нем почти нет готовых компонентов, и всё собирается из классов, поэтому тут полный контроль над дизайном, минимальный итоговый CSS, но HTML перегружен классами

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

  3. Framework7 - фреймворк для мобильных и прогрессивных веб-приложений. Создает стили iOS- или Android-приложений, имеет мобильные компоненты, работает с Vue, React, Svelte

  4. Material Design - дизайн-система от Google, имеющая строгие правила дизайна, тени, уровни, анимации и консистентный опыт пользователя

    Лучше использовать для Android-ориентированных продуктов и больших приложений

Единицы в CSS

В CSS для указания длины есть разные единицы

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

Другая величина em обозначает высоту текущего шрифта и определяются по текущему контексту. Например, в таком примере:

<div style="font-size:2em">
  Арбуз
  <div style="font-size:2em">Абрикос</div>
</div>

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

Проценты % - еще одна относительная единица. 1% - это одна сотая от ширины/высоты родительского элемента. Для свойства line-height, определяющего высоту пространства, занимаемого текстом, 1% - это одна сотая от размера шрифта. Однако, если position: fixed, то 1% - это одна сотая от ширины/высоты окна браузера

Другая величина rem (от root-em) задается как размер шрифта элемента html и не определяется другими родительскими тегами, поэтому ее пользоваться удобнее, чем другими

Наконец, есть величины vw и vh, которые зависят от ширины и высоты окна браузера соответственно. 1vw - это одна сотая от ширина окна и аналогично для vh. Таким образом, стало проще создавать верстку для мобильных устройств

Лекция 5. Продвинутый CSS

Каскадность и приоритет селекторов

В CSS свойства для одного элемента могут накладываться друг на друга. Допустим, что есть такой элемент:

<p class="bold dark">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</p>

с такими стилями:

p {
    font-size: 20px;
}

.bold {
    font-weight: bold;
}

.dark {
    color: #666666;
}

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Элемент имеет два класса, CSS-правила к нему применяются, поэтому в итоге свойства для элемента будут такими:

{
    color: #666666;
    font-weight: bold;
    font-size: 20px;
}

Это и называется каскадностью правил. При этом у правил действует приоритет селекторов. Рассмотрим такой пример:

p {
    color: green;
}

#blue {
    color: blue;
}

.red {
    color: red;
}

Цвет текста такого элемента:

<p class="red" id="blue">Бургер</p>

будет синим:

Бургер

Связано это с тем, что правила имеют следующий приоритет:


Если элемент имеет несколько классов, свойства для которых противоречат друг другу, то применяться будут те свойства, что стоят последними - браузер просто перезапишет правила, которые стояли перед ним:

.my-text {
    color: blue;
}

.your-text {
    color: red;
}
<p class="your-text my-text">Яблоко лежало на поляне</p>

Яблоко лежало на поляне


Если два правила, то приоритет вычисляет немного сложнее. Для этого составляется кортежи из 4 чисел:

Затем эти кортежи сравниваются. Так селектор p.my-text.bold-text#this-id (0, 1, 2, 1) имеет больший приоритет, чем p.your-text#that-id (0, 1, 1, 1)


Помимо этого существует еще один список приоритетов:

Важные правила в коде обозначаются с помощью !important:

p {
    color: red !important;
}

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

Методологии CSS

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

БЭМ

Методология БЭМ (Блок-Элемент-Модификатор) была создана в Яндексе. Основная ее концепция заключается в том, чтобы легко поддерживать проекты и повторно использовать компоненты

Для этого в БЭМ есть три понятия:

Плюсы БЭМ:

Подробнее ознакомиться можно на сайте https://bem.info/

SMACSS

Методология SMACSS (Scalable and Modular Architecture for CSS) создана Джонатаном Снуком и основана на разделении правил на категории:

Для классов правил Layout добавляется префикс l-, grid- или layout-, а для правил State префиксы состояния is- или has-

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

В отличие от БЭМ, SMACSS не предписывает слишком строгое соглашение об именовании

Преимущества SMACSS:

Подробнее о SMACSS: https://smacss.com/

eCSS

Методология eCSS (Enduring CSS) была создана Беном Фрейном и состоит в изоляции компонентов, чтобы повторно их использовать

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

eCSS отходит от таких методологий, как БЭМ и SMACSS, которые расширяют и абстрагируются от существующих компонентов, тем самым избегая или пытаясь как можно сильнее избежать повторения кода

Бен Фрейн пришел к выводу, что современные алгоритмы сжатия хорошо сжимают повторяющийся CSS-код, поэтому разница в весе файлов незначительна

Преимущества eCSS:

Лекция 6. Основы JavaScript

JavaScript - это

язык программирования, который реализует стандарт ECMAScript

Когда язык создавался в 1995 году для добавления интерактивности веб-страницам, у него было множество других названий. Остановились на JavaScript, так как в 90-ых был очень популярен язык Java

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

JavaScript - язык интерпретируемый, поэтому для его исполнения нужен интерпретатор. В браузерах на движке Chromium используется интерпретатор V8, в Mozilla Firefox - интерпретатор SpiderMonkey, в Safari - JavaScriptCore

Интерпретаторы в большинстве современных браузеров поддерживают JIT-компиляцию (преобразования языка в машинные инструкции) и язык WebAssembly, который представляет низкоуровневый бинарный формат байткода, предназначенный для быстрого исполнения в браузере

Для добавления скрипта в HTML-страницу используется тег <script>:

<script>
    window.alert("Boo");
</script>

<!-- или -->

<script src="./script.js"></script>

JavaScript имеет C-подобный синтаксис, является регистрозависимым

Переменные в JavaScript объявляются с помощью ключевых слов:

В JavaScript всего существует 8 типов:


Для управления потоком исполнения используют следующие структуры:


Взаимодействие с пользователем осуществляется с помощью API:


Функцию в JavaScript можно объявить несколькими способами:


JavaScript предлагает набор встроенных объектов:

Помимо этого браузер предлагает свои объекты:

Лекция 7. Объектная модель документа

С помощью JavaScript можно изменять структуру HTML-документа, напрямую модифицируя объектную модель документа (DOM, Document Object Model). Объектная модель обеспечивает доступ ко всем тегам и их атрибутам, позволяет создавать, удалять и изменять элементы

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

Всего существует 12 типов узлов в объектной модели:

ELEMENT_NODE представляет элемент, который задается HTML-тегом, а TEXT_NODE - это узел с сырым текстом

Так <p>Это параграф</p> преобразуется в дерево с узлом p типа ELEMENT_NODE и дочерний текстовый узел Это параграф типа TEXT_NODE

Корень дерева в JavaScript представлен объектом document. От него можно получить непосредственно элемент html с помощью document.documentElement и элемент body с помощью document.body


В JavaScript есть 6 основных методов получения элементов из дерева объектной модели:

Далее от них можно идти по дереву с помощью свойств:

С ними нужно быть осторожными, так как некоторые браузеры обрабатывают новую строку в HTML-документе как текстовый узел

Еще узлы имеют такие свойства:


Содержимое элемента можно узнать или заменить с помощью:

Значения innerText и outerText равны, однако при записи строка принимается как экранированный текст, а не HTML-код, поэтому запись в outerText ведет к удалению исходного элемента

Запись свойств innerHTML и outerHTML ведет к парсингу нового значения и изменения объектной модели. Для изменения HTML-страницы предпочтительнее использовать не эти свойства, принимающие строки, а методы node.appendChild, node.insertBefore и другие

Также есть свойство node.textContent, которое показывает весь текст, включая скрытый с помощью display: none


Для редактирования дерева есть методы:

При вызове таких методов происходит событие пересчета дерева (reflow event), который можно поймать в своем скрипте (об этом позже)

Для редактирования узлов есть такие методы:

Обработка событий

На жизненном цикле веб-страницы может происходить множество событий, например, нажатие кнопки или прокрутка колеса мыши

Событие - это сигнал браузера о том, что что-то произошло. Для них браузер предоставляет API

Так событие возникает для какого-то элемента, и браузер вызывает для этого события привязанные к элементу функции JavaScript

События можно добавлять:

С помощью событий можно обрабатывать нажатия на элемент (click), наведение на элемент (mouseover), на правый клик мыши (contextmenu), на нажатие клавиатуры (keydown), на отправку формы (submit) и многое другое

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

document.addEventListener("DOMContentLoaded", function () {
    const elem = document.getElementById("myid");

    // ...
});

Лекция 8. ООП в JavaScript

Классы в JavaScript появились в стандарте ECMAScript в 2015 году

В JavaScript наследование реализовано через прототипную модель, а классы являются синтаксическим сахаром над прототипами. Прототипное наследование - это механизм, где объекты могут заимствовать свойства и методы у других объектов через цепочку прототипов

Чтобы создать класс в JavaScript, есть несколько способов:

Классы в JavaScript также поддерживают наследование:

class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
    }

    getFullName() {
        return this.firstName + ' ' + this.lastName
    }
}

class User extends Person {
    constructor(firstName, lastName, email) {
        // обязательно нужно вызвать super, конструктор класса-родителя
        super(firstName, lastName)
        this.email = email
    }

    getEmail() {
        return this.email
    }
}

Каждый объект в JavaScript имеет скрытую ссылку на другой объект - прототип. Новый объект создаётся со ссылкой на прототип, а не копирует его свойства

Когда происходит обращение к свойству, интерпретатор ищет его в самом объекте, если не находит - идёт в прототип, если и там его нет, то ищет в прототипе прототипа и так далее

Такая структура образует цепочку прототипов (prototype chain)

Например, массивы Array наследуются от Array.prototype, который включает методы:

Чтобы расширить такой массив, можно добавить новое поле в его прототип:

Array.prototype.partition = function(pred) {
    let passed = []
    let failed = []
    for (let i = 0; i < this.length; i++) {
        if (pred(this[i])) {
            passed.push(this[i])
        } else {
            failed.push(this[i])
        }
    }

    return [passed, failed]
}

И тогда его можно будет вызвать из массива:

[1, 2, 3, 4, 5].partition(a => a < 4) // [[1, 2, 3], [4, 5]]

Расширять встроенные прототипы (например, Array.prototype) в реальных проектах не рекомендуется, так как это может привести к конфликтам


Ранее приватных полей в JavaScript не было. Вместо них, как в Python, можно обозначить нижним подчеркиванием спереди:

class User {
    constructor(firstName, lastName, email) {
        this._firstName = firstName
        this._lastName = lastName
        this._email = email
    }
}

Однако в новых версиях со стандарта ES2022 появилась возможность определять приватные поля:

class ClassWithPrivate {
    #privateField;
    #privateFieldWithInitializer = 42;

    #privateMethod() {
        // …
    }
}

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

Также в качестве инкапсуляции может выступать замыкание функции:

function outside() {
    const food = "Hamburger"

    return function inside() {
        console.log(food)
    }
}

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


JavaScript позволяет расширять функциональность методов родителей:

class Developer extends Human {
    sayHello() {
        // Вызываем родительский метод
        super.sayHello()

        // Создаем новый метод
        console.log(`I'm a developer`)
    }
}

const chris = new Developer('Walter', 'White')
chris.sayHello()

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

function Developer (firstName, lastName) {
    const human = Human(firstName, lastName)

    return Object.assign({}, human, {
        sayHello() {
            // Вызываем родительский метод
            human.sayHello()

            // Создаем новый метод
            console.log(`I'm a developer`)
        }
    })
}

const chris = new Developer('Walter', 'White')
chris.sayHello()

Лекция 9. Локальное хранилище

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

Сайты можно разделить:

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

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

Часто, хранилища на сторонах клиента и сервера используются совместно

С первых дней Интернета, использовали куки (cookies) для хранения информации, чтобы персонализировать пользовательский опыт на веб-сайтах - это самая ранняя форма хранилища на стороне клиента

Куки представляют небольшую порцию текстовых данных, хранящихся в браузере. Всякий раз при открытии страницы браузер соответствующего сайта пересылает сохранённые куки обратно веб-серверу через HTTP-заголовки:

// Создание куки
document.cookie = "username=Chris; max-age=3600; path=/";

// Чтение куки
console.log(document.cookie);

// Удаление куки
document.cookie = "username=Chris; max-age=0";

Как можно заметить, у куки есть время жизни. Также куки могут хранить 4 Кб данных

Современные браузеры имеют гораздо более простые и эффективные API для хранения данных на стороне клиента, чем при использовании куки:

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

// Сохранить
localStorage.setItem("theme", "dark");

// Получить
const theme = localStorage.getItem("theme");
console.log(theme);

// Удалить ключ
localStorage.removeItem("theme");

// Очистить всё
localStorage.clear();

// Аналогично для сессионного хранилища
sessionStorage.setItem("token", "12345");
console.log(sessionStorage.getItem("token"));

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

Помимо обычного хранения данных хранилища часто используют для того чтобы реализовывать общение между несколькими вкладками веб-приложения с помощью события StorageEvent (например, смена музыки во второй вкладке приводит к ее воспроизведению в первой):

window.addEventListener("storage", (event) => {
    console.log("Изменение:", event.key);
    console.log("Новое значение:", event.newValue);
});

Важно заметить, что событие не сработает в той вкладке, где было изменено хранилище

IndexedDB API (или IDB) - это полноценная база данных, доступная в браузере, в которой можно хранить сложные связанные данные, типы которых не ограничиваются простыми значениями, такими как строки или числа

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

const request = indexedDB.open("MyDatabase", 1);

request.onupgradeneeded = function (event) {
    const db = event.target.result;
    db.createObjectStore("users", { keyPath: "id" });
};

request.onsuccess = function (event) {
    const db = event.target.result;

    const transaction = db.transaction("users", "readwrite");
    const store = transaction.objectStore("users");

    store.add({ id: 1, name: "Chris" });
};

Другие преимущества IndexedDB:

IndexedDB может хранить любые сериализуемые объекты напрямую

В хранилищах же сложные объекты можно хранить в виде JSON-объектов. JSON (JavaScript Object Notation) - формат обмена данными. Его синтаксис и типы схожи на те, что есть в JavaScript

JSON-объект представляет собой множество пар ключ-значение. Ключ в JSON-объекте всегда является строкой. Для работы с JSON внутри JavaScript существуют методы JSON.parse() и JSON.stringify(obj):

const user = {
    name: "Chris",
    age: 35
};

// Сохраняем
localStorage.setItem("user", JSON.stringify(user));

// Получаем
const savedUser = JSON.parse(localStorage.getItem("user"));
console.log(savedUser.name);

Лекция 10. Асинхронность в JavaScript

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

В каждом окне выполняется только один главный поток, который занимается выполнением скриптов JavaScript, отрисовкой и работой с DOM. Этот поток выполняет команды последовательно, может делать только одно дело одновременно и блокируется при выводе модальных окон, с помощью таких методов как window.alert()

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

Внутри браузера непрерывно работает внутренний событийный цикл (Event Loop), который следит за состоянием очереди и обрабатывает события, запускает соответствующие обработчики. В нем движок JavaScript ожидает задачи, исполняет их и снова ожидает появления новых

Иногда события добавляются в очередь сразу набором, например, при клике на элементе генерируется несколько событий - mousedown при нажатии и mouseup при отпуске кнопки

Но в тех случаях, когда событие инициируется не посетителем, а кодом, то оно, как правило, обрабатывается синхронно, то есть прямо сейчас. Например, когда посетитель фокусируется на элементе, возникает событие onfocus, например, при нажатии на поле ввода. Но ту же фокусировку можно вызвать и явно методом elem.focus()

Движок JavaScript большую часть времени ничего не делает и работает, только если требуется исполнить скрипт или обработать событие. Может так случиться, что задача поступает, когда движок занят чем-то другим, тогда она ставится в очередь. Очередь, которую формируют такие задачи, называют очередью макрозадач (Macrotask Queue)

При этом:

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


Помимо макрозадач существуют микрозадачи. Микрозадачи приходят только из кода и обычно они создаются объектами обещаний (или промисами, от promise). Сразу после каждой макрозадачи движок исполняет все задачи из очереди микрозадач перед тем, как выполнить следующую макрозадачу или отобразить изменения на странице

Объект обещания Promise в JavaScript - это объект, представляющий результат асинхронной операции, которая еще не завершилась. Они нужны для управления асинхронным кодом - вместо того чтобы ждать ответа (например, от сервера), программа продолжает работу, а объект Promise “обещает” вернуть результат позже:

const fetchData = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = true;
    if (success) resolve("Данные получены ✅");
    else reject("Ошибка ❌");
  }, 1000);
});

fetchData
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log('Конец'))

Promise принимает функцию от двух аргументов - других функций, которые вызываются в случае успеха или неудачи выполнения

Ключевые слова async и await являются синтаксическим сахаром над Promise и делают код более читаемым:

async function loadData() {
  try {
    const result = await fetchData;
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}

Слово await приостанавливает выполнение текущей функции до завершения выполнения Promise

У объекта Promise есть внутренние свойства:

Также у Promise есть продвинутые статические методы:

// Ждёт завершения всех функций
Promise.all([fetch('/a'), fetch('/b')]).then(([a, b]) => ...)

// Возвращает первый завершившийся
Promise.race([slowRequest, timeout]).then(...)

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

Если мы хотим запустить функцию асинхронно (после текущего кода), но до отображения изменений и до новых событий, то можно запланировать это через queueMicrotask и через MutationObserver

Петля событий

Fetch API

Fetch API — это современный браузерный интерфейс для выполнения HTTP-запросов к серверу. Он пришёл на смену устаревшему объекту XMLHttpRequest и предоставляет более удобный синтаксис, основанный на Promise

fetch() возвращает Promise, который разрешается в объект Response:

fetch(url, options)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Ошибка:', error));

fetch() принимает адрес запроса url и опции options (метод, заголовки, тело и так далее)

После получения ответа нужно его распарсить с помощью нужного метода:

Метод Описание
response.json() Парсит тело как JSON → возвращает объект
response.text() Возвращает тело как строку
response.blob() Возвращает бинарные данные (файлы, изображения)
response.formData() Парсит тело как FormData
response.arrayBuffer() Возвращает сырые бинарные данные

Иногда нужно отменить запрос, например при смене страницы. Тогда можно воспользоваться AbortController:

const controller = new AbortController();

fetch('/api/data', { signal: controller.signal })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Запрос отменён');
    }
  });

// Отмена запроса через 3 секунды
setTimeout(() => controller.abort(), 3000);

X. Программа экзамена

  1. Как устроен браузер, в чем заключается его задача. Назовите какие внутренние программные компоненты есть внутри и какие задачи они решают.
  2. Что такое URL? Расскажите из каких частей состоит URL? Как браузер понимает по URL на какой сервер должен пойти ваш HTTP запрос?
  3. В каком виде представлен документ внутри вашего браузера? Какие вы знаете способы взаимодействия с элементами, представленными в разметке страницы.
  4. Из каких обязательных тэгов должна состоять веб страница? Какая секция для чего предназначена? Какие отличия будут, если мы будет размещать скрипты в начале документа, а не в конце?
  5. Что такое CSS? Как применять различные правила и по каким признакам мы можем выбирать элементы на странице.
  6. Какие виды вёрстки вы знаете? Какие подходы для верстки страниц существуют? Назовите плюсы и минусы данных подходов.
  7. Какие средства реализации объектно-ориентированного програмирования существуют в JavaScript. С помощью каких вспомогательные классов, добавленных в последних стандартах языка, можно достичь полноценной инкапсуляции?
  8. Как устроен Event Loop, какие типы задач бывают, в какие очереди выполнения они попадают?
  9. С помощью каких встроенных возможностей языка JS происходит взаимодействие между клиентом и сервером? В каком виде обычно предоставляются данные и как их десериализовать?
  10. Подходы применяемых при разработке приложений (страниц) для устаревших браузеров. Назовите пример решения ситуации, когда технология, которую вы решили использовать на странице, может быть вдруг недоступна.
  11. Устройство нативных веб компонентов. Какие инструменты позволяют нам создать свой собственный компонент для дальнейшего переиспользования? Какие могут вылезти подводные при использовании нативных веб компонентов?
  12. Web-Framework’и. Какие типы фреймворков бывают? В чём отличие фреймворка от библиотеки? Какие обязательные компоненты идут из коробки в современных фреймворках? Назовите несколько примеров