/
Tags: языки программирования компьютерные технологии
ISBN: 978-5-9775-1942-7
Text
Владимир Дронов
Владимир Дронов
Санкт-Петербург
«БХВ-Петербург»
2024
УДК 004.438JavaScript
ББК 32.973.26-018.1
Д75
Дронов В. А.
Д75 JavaScript. 32 урока для начинающих. — СПб.: БХВ-11етербург, 2024. —
576 с., ил. — (Для начинающих)
ISBN 978-5-9775-1942-7
В книге 32 иллюстрированных урока, 40 практических упражнений на тему
программирования веб-сцеиарисв и более 20 заданий для самостоятельной работы.
Изложены основы JavaScript: тины данных, переменные, управляющие конструк-
ции, функции, массивы, объекты, классы, регулярные выражения, модули, средства
для обработки исключений и отладки. Раскрыты события и их обработка, генери-
рование содержимого страниц, работа с графикой и мультимедиа, веб-формами и
элементами управления, взаимодействие с веб-обозревателем, детекторы видимо-
сти и изменения размеров. Рассмотрены таймеры, фоновые потоки, использование
промисов и асинхронных функций, работа с файлами, программная графика и за-
1рузка данных с веб-сервера. Рассказано о программировании фронт енд- и бэкенд-
приложений в среде Node.js, а также об обработ ке межхостовых запросов.
Электронный архив на сайте издательства содержит коды всех примеров и ре-
зультаты выполнения упражнений.
Для начинающих вед-разработчиков
УДК 0O4.438JavaScript
БЬК 32.973.26-018.1
Группа под!оговки издания:
Руководитель проекта
Зав. редакцией
Редактор
Компьютерная верстка
Дизайн серии
Оформление обложки
Евгений Рыбаков
Людмила 1 ауль
Григорий Додин
Ольги Сергиенко
Карины Соловьевой
Зои Канторович
"БХВ-Петербург" 191036, Санкт-Петербург, г ончарная ул , 20
ISBN 978-5-9775-1942-7
© ООО "БХВ", 2024
Оформление. ООО "БХВ-Петербург", 2024
Оглавление
Предисловие............................................................ 15
Почему JavaScript?..........................................................15
Что вы найдете в этой книге?.................................................15
Что нового?..................................................................16
Что вам понадобится?.........................................................17
Типографские соглашения......................................................17
ЧАСТЬ 1. НАЧАЛА JAVASCRIPT И ВЕБ-IIPOf РАММИРОВАНИЯ.........................21
Урок 1. Введение в язык JavaScript...........................................23
I. I Упражнение. Консоль веб-обозревателя.................................. 23
1.2. Упражнение. Типы данных и операторы.....................................28
1.3. Упражнение. Переменные и константы......................................31
1.4. Упражнение. Управляющие выражения.......................................33
1.5. Упражнение. Функции.....................................................36
1.6. Упражнение. Объекты, свойства, методы и классы .........................37
1.7. Упражнение. Массивы.....................................................42
1.8. Самостоятельные упражнения..............................................44
Урок 2. Введение в веб-программирование......................................45
2.1. Упражнение. Программное формирование содержимого веб-страницы ..........45
2.2. Упражнение. Доступ к элементам веб-страницы.............................48
2.3. Упражнение. Обработка событий...........................................51
2.4. Упражнение. Взаимодействие с элементами управления......................54
2.5. Самостоятельные упражнения..............................................56
ЧАСТЬ II. Я1ЫК JAVASCRIPT....................................................57
Урок 3. Основные понятия JavaScript................................... ...59
3.1. Выражения, программы и веб-сценарии.....................................59
3.1.1. Правила написания выражений.......................................59
3.1.2. Встраивание веб-сценариев в веб-страницы .........................62
3.1.3. Выполнение веб-сценариев..........................................64
3.2. Ключевые слова JavaScript...............................................65
3.3. Комментарии.............................................................65
3.4. Строгий режим...........................................................66
3.5. Блоки...................................................................67
3.6. Операторы, их приоритет и последовательность выполнения. Круглые скобки.68
3.7. Типы данных.............................................................69
3.7,1. Получение типа данных, к которому принадлежит значение............70
3.7.2. Неопределенный тип {undefined)....................................71
3.7.3. Значащие и ссылочные типы данных..................................71
3.8. Присваивание...........................................................72
3.8.1. Простое присваивание.............................................72
3.8.2. Комбинированное присваивание.....................................73
3.8.3. Условное присваивание............................................74
3.9. Оператор выбора........................................................74
3.10. Переменные и константы................................................75
3.10.1. Объявление переменных. Область видимости........................75
3.10.2. Обращение к переменным..........................................78
3.10.3. Проверка существования переменной...............................78
3.10.4. Присваивание значения несуществующей переменной.................79
3.11. Глобальный объект, контекст выполнения и переменная this..............79
Урок 4. Логические величины и управляющие выражения..................81
4.1. Логические величины....................................................81
4.1.1. Введение в логический тип данных.................................81
4.1.2. Операторы сравнения..............................................81
4.1.3. Логические операторы.............................................83
4.1.4. Преобразование нелогических значений в логические................84
4.1.4.1. Неявное преобразование....................................84
4.1.4.2. Явное преобразование......................................85
4.1.4.3. Еще раз о проверке существования переменной...............85
4.2. Управляющие выражения..................................................86
4.2.1. Условные выражения...............................................86
4.2.1.1. Множественные условные выражения..........................87
4.2.2. Условный оператор................................................87
4.2.3. Выражение выбора.................................................88
4.2.4. Циклы............................................................90
4.2.4.1. Цикл со счетчиком.........................................90
4.2.4.2. Цикл с предусловием.......................................92
4.2.4.3. Цикл с постусловием.......................................92
4.2.4.4. Цикл перебора последовательности..........................93
4.2.4.5. Цикл по свойствам объекта.................................93
4.2.4.6. Прерывание цикла..........................................94
4.2.4.7. Прерывание итерации цикла............................... 94
4.2.4.8. Бесконечный цикл..........................................94
Урок 5. Числа...............................................................96
5.1. Обычные числа..........................................................96
5.1.1. Запись обычных чисел.............................................96
5.1.2. Арифметические операторы.........................................98
5.1.3. Методы и свойства класса Number..................................99
5.1.3.1. Форматированный вывод чисел..............................102
5.1.4. Класс Math......................................................106
5.1.5. Двоичные операторы..............................................108
5.1.6. Преобразование значений других типов в числовые.................111
5.1.6.1. Неявное преобразование...................................111
5.1.6.2. Явное преобразование.....................................111
5.1.7. Точность представления обычных чисел. Потеря точности...........113
5.2. Упражнение. Реализуем форматированный вывод чисел.....................114
5.3. Большие целые числа...................................................115
5.4. Самостоятельное упражнение............................................117
У рок 6. Строки.........................................................................118
6.1. Запись строк.......................................................................118
6.1.1. Шаблонные строки.............................................................120
6.1.1.1.11омеченные строки....................................................120
6.2. Простейшие операции со строками....................................................122
6.3. Свойства и методы класса Siring.........................................................................................124
6.3.1. Сравнение строк с учетом текущей локали......................................128
6.4. Преобразование значений других типов в строки......................................128
6.4.1. Неявное преобразование.......................................................128
6.4.2. Явное преобразование.........................................................129
6.5. Упражнение. Использование средств для обработки строк при программном
генерировании содержимого веб-страницы..................................................129
6.6. Самостоятельное упражнение.........................................................133
Урок 7. Временные отметки...............................................................134
7.1. Создание временных отметок.........................................................134
7.2. Методы класса Date.............................................................................................................. 135
7.2.1. Форматированный вывод временных отметок......................................137
Урок 8. Функции.........................................................................140
8.1. Вызов функций......................................................................140
8.2. Объявление функций.................................................................141
8.2.1. Объявление локальных переменных..............................................145
8.2.2. Необязательные параметры функций.............................................145
8.2.3. Функции с произвольным количеством параметров................................146
8.2.4. Вложенные функции............................................................147
8.3. Функция как значение. Функциональный тип данных....................................147
8.3.1. Присваивание функций.........................................................148
8.3.2. Проверка существования функции...............................................148
8.4. Упражнение. Спойлер, часть I.......................................................149
8.5. Специальные разновидности функций..................................................153
8.5.1. Анонимные функции............................................................153
8.5.2. Стрелочные функции...........................................................154
8.5.3. Функции однократного вызова..................................................155
8.5.4. Замыкания....................................................................156
8.5.4.1. Фабрики функций......................................................157
8.5.4.2. Замыкания однократного вызова........................................158
8.6. Упражнение. Спойлер, часть 2.......................................................158
8.7. Упражнение. Рекурсия и ее использование............................................160
8.8. Контекст выполнения функции и его указание.........................................162
8.8.1. Контекст выполнения обычных и стрелочных функций.............................162
8.8.2. Явное указание контекста выполнения..........................................163
8.9. Самостоятельные упражнения.........................................................164
Урок 9. Массивы, множества и словари..............................................165
9.1. Массивы............................................................................165
9.1.1. Обычные массивы..............................................................165
9.1.1.1. Создание обычных массивов............................................165
9.1.1.2. Простейшие операции с обычными массивами.............................167
9.1.1.3. Вложенные массивы....................................................168
9.1.1.4. Свойство и методы класса Array'......................................169
9.1.1.5. Сортировка массивов......................................176
9.1.1.6. Деструктурирование массивов..............................177
9.1.1.7. Копирование массивов.....................................180
9.1.2. Типизированные массивы..........................................181
9.2. Множества.............................................................182
9.2.1. Создание множеств...............................................182
9.2.2. Простейшие операции со множествами..............................183
9.2.3. Свойство и методы класса Set....................................184
9.3. Словари...............................................................185
9.3.1. Создание словарей...............................................185
9.3.2. Простейшие операции со словарями................................186
9.3.3. Свойство и методы класса Мар....................................187
9.4. Самостоятельные упражнения............................................189
Урок 10. Объекты...........................................................190
10.1. Создание объектов....................................................191
10.2. Работа с объектными свойствами и методами............................191
10.2.1. Доступ к объектным свойствам и методам.........................192
10.2.1.1. Вложенные объекты. Цепочки операторов доступа.........192
10.2.1.2. Условный доступ.......................................193
10.2.1.3. Альтернативный формат доступа к объектным свойствам
и методам.......................................................194
10.2.2. Проверка существования свойства или метода....................194
10.2.3. Добавление свойств к объектам. Добавленные свойства...........196
10.2.4. Удаление свойств..............................................197
10.3. Простые объекты......................................................197
10.3.1. Методы в простых объектах.....................................199
10.3.2. Копирование простых объектов..................................200
10.3.3. Фабрики объектов..............................................201
10.4. Простейшие действия с объектами......................................201
10.5. Деструктурирование объектов..........................................204
10.6. Нулевая ссылка.......................................................206
10.7. Объектная природа значащих типов.....................................207
10.8. Работа со статическими свойствами и методами.........................208
10.9. Эмблемы..............................................................208
Урок 11. Классы............................................................211
11.1. Объявление классов...................................................211
11.1.1. Объявление объектных свойств класса...........................212
11.1.2. Объявление объектных методов класса...........................213
11.1.3. Объявление конструктора.......................................214
11.1.4. Объявление объектных динамических свойств......................215
11.1.5. Объявление статических свойств и методов.......................216
11.1.6. Закрытые свойства и методы.....................................219
11.2. Упражнение. Спойлер, часть 3.........................................220
11.3. Наследование классов.................................................224
11.3.1. Перекрытие свойств и методов..................................225
11.3.2. Переопределение методов.......................................226
11.3.3. Переопределение динамических свойств..........................228
11.3.4. Неявное наследование от класса Object.........................229
11.4. Классы как значения..................................................229
11,4.1. Присваивание классов..........................................229
11.4.2. Проверка существования класса, его свойств и методов..........229
11.5. Анонимные классы.....................................................230
11.6. Фабрики классов......................................................231
11.7. Упражнение. Усовершенствованный спойлер..............................231
11.8. Управление преобразованием объектов в значения значащих типов........234
11.9. Пространства имен....................................................236
11.10. Расширение функциональности встроенных классов. Полифиллы...........237
11.11. Самостоятельное упражнение..........................................238
Урок 12. Итераторы и генераторы............................................239
12.1. Итераторы............................................................239
12.1.1. Итерируемые функции и методы..................................240
12.1.2. Итерируемые объекты и классы..................................241
12.2. Упражнение. Числа Фибоначчи..........................................242
12.3. Генераторы...........................................................245
12.3.1. Генераторы-функции и генераторы-методы........................245
12.3.2. Генераторы-объекты и генераторы-классы........................247
12.4. Самостоятельное упражнение...........................................247
Урок 13. Регулярные выражения..............................................248
13.1. Введение в регулярные выражения......................................248
13.1.1. Создание регулярных выражений.................................248
13.1.2. Использование регулярных выражений: простые случаи............250
13.2. Специальные символы, применяемые в регулярных выражениях.............250
13.2.1. Метасимволы...................................................250
13.2.2. Поднаборы.....................................................251
13.2.3. Вариант.......................................................252
13.2.4. Квантификаторы................................................252
13.2.5. Подквантификатор..............................................254
13.2.6. Группы и обратные ссылки......................................254
13.2.7. Обычные символы...............................................256
13.3. Поиск и замена с помощью регулярных выражений........................256
13.3.1. Обычный режим поиска..........................................256
13.3.2. Глобальный поиск..............................................257
13.3.3. Многострочный поиск...........................................259
13.3.4. Замена совпавших фрагментов...................................259
13.3.5. Прочие полезные инструменты...................................260
13.4. Упражнение. Использование регулярных выражений для обработки строк...260
13.5. Самостоятельное упражнение...........................................262
Урок 14. Модули............................................................263
14.1. Введение в модули....................................................263
14.2. Экспорт..............................................................265
14.2.1. Экспорт по умолчанию..........................................265
14.2.2. Множественный экспорт.........................................265
14.2.2.1. Синтаксис № I.........................................266
14.2.2.2. Синтаксис № 2.........................................266
14.2.3. Гибридный экспорт.............................................267
14.3. Импорт...............................................................267
14.3.1. Импорт по умолчанию...........................................267
14.3.2. Множественный импорт..........................................268
14.3.2.1. Синтаксис № I.........................................268
14.3.2.2. Синтаксис №2..........................................269
14.3.3. Гибридный импорт..............................................269
14.3.4. Написание интернет-адресов импортируемых модулей..............270
14.3.5. Как выполняется импорт?.......................................270
14.4. Прогон модулей.......................................................271
14.5. Упражнение. Реорганизация кода с применением модулей.................271
14.6. Самостоятельное упражнение...........................................272
Урок 15. Обработка программных ошибок. Исключения..........................273
15.1. Исключения...........................................................273
15.2. Обработка исключений.................................................274
15.3. Принудительное возбуждение исключений................................276
15.3,1. Объявление своих классов исключений...........................277
15.4. Упражнение. Спойлер, часть 4.........................................277
15.5. Самостоятельное упражнение...........................................278
Урок 16. Средства отладки..................................................279
16.1. Консоль веб-обозревателя.............................................279
16.1.1. Сообщения об ошибках, выводимые в консоли.....................279
16.1.2. Вывод произвольных сообщений в консоли........................281
16.2. Отладчик.............................................................282
16.2.1. Открытие файлов с программным кодом...........................283
16.2.2. Создание и удаление точек останова............................283
16.2.3. Пошаговое выполнение кода.....................................285
16.2.4. Просмотр значений переменных..................................287
16.3. Инспектор DOM........................................................288
16.4. Самостоятельное упражнение...........................................289
ЧАСТЬ III. УПРАВЛЕНИЕ ВЕБ-СТРАНИЦЕЙ
И ВЕБ-ОБОЗРЕВАТЕЛЕМ........................................................291
Урок 17. События и их обработка............................................293
17.1. Обработчики событий..................................................293
17.1.1. Привязка обработчиков к событиям..............................293
17.1.2. Реализация обработчиков событий...............................294
17.1.2.1. Реализация обработчиков событий в виде функций.........294
17.1.2.2. Реализация обработчиков событий в виде методов.........295
17.1.3. Удаление привязки обработчика к событию.......................297
17.1.3.1. Удаление вызовом метода removeEventListenerf).........297
17.1.3.2. Удаление с помощью прерывателя........................297
17.1.4. Альтернативные способы привязки обработчиков к событиям.......298
17.1.4.1. Привязка в атрибутах тегов............................298
17.1.4.2. Обработка щелчков на гиперссылках.....................298
17.2. События, поддерживаемые элементами веб-страниц.......................299
17.3. Объект события. Классы событий и их свойства.........................300
17.4. Упражнение. Слайдер, часть I.........................................303
17.5. Распространение события. Фазы события................................308
17.6. Упражнение. Слайдер, часть 2..............................................310
17.7. Обработка событий по умолчанию и ее отмена................................312
17.8. Упражнение. Слайдер, часть 3..............................................313
17.9. Самостоятельное упражнение................................................314
Урок 18. Управление элементами веб-страниц......................................315
18.1. Получение доступа к элементам страницы....................................315
18.1.1. Доступ к элементам определенного типа..............................315
18.1.2. Доступ к любому элементу страницы..................................316
18.1.3. Доступ к родителю, соседям и потомкам..............................318
18.2. Управление элементами веб-страницы........................................319
18.2.1. Работа с атрибутами тегов..........................................319
18.2.1.1. data-атрибуты и работа с ними..............................320
18.2.2. Получение местоположения и размеров элементов веб-страниц..........320
18.2.3. Прочие инструменты для управления элементами веб-страниц...........321
18.3. Упражнение. Стильная полоса прокрутки.....................................324
18.4. Управление стилями CSS....................................................326
18.4.1. Управление стилевыми классами......................................326
18.4.2. Управление встроенными стилями.....................................327
18.5. Изменение содержимого элементов...........................................328
18.5.1. Методы write() nwritelnf)..........................................329
18.6. Создание элементов веб-страниц путем конструирования......................330
18.7. Упражнение. Фотогалерея с лайтбоксом......................................334
18.8. Получение сведений о веб-сгранице.........................................337
18.9. Самостоятельные упражнения................................................338
Урок 19. Графика и мультимедиа..................................................339
19.1. Получение сведений о графических изображениях.............................339
19.2. Управление мультимедийными элементами.....................................340
19.2.1. Свойства...........................................................340
19.2.2. Методы.............................................................342
19.2.3. События............................................................342
19.3. Упражнение. Стильный видеопроигрыватель...................................343
19.4. Самостоятельное упражнение................................................346
Урок 20. Веб-формы и элементы управления........................................347
20.1. Работа с элементами управления............................................347
20.1.1. Свойства, методы и события элементов управления....................347
20.1.2 11олучение и обработка введенного в элемент значения................350
20.1.3. Программное создание и удаление пунктов списка.....................352
20.1.4. Индикатор процесса.................................................352
20.2. Свойства, методы и события веб-формы......................................353
20.3. Упражнение. Клиентское веб-приложение, формирующее CSS-код рамки, часть I.354
20.4. Валидация.................................................................356
20.4 I. Валидация средствами HTML.........................................357
20.4.1.1. Вывод собственных сообщений об ошибках.....................357
20.4.2. Программная валидация..............................................359
20.4.3. Дополнительные инструменты валидации...............................360
20.5. Упражнение. Клиентское веб-приложение, формирующее CSS-код рамки, часть 2.361
20.6. Диалоги...................................................................362
20.7. Самостоятельные упражнения................................................365
Урок 21. Взаимодействие с веб-обозревателем...............................366
21.1. Работа с окном веб-обозревателя.....................................366
21.1.1. Еще один способ записи веб-сценариев, манипулирующих элементами
страницы..............................................................368
21.1.2. Предотвращение ухода с веб-страницы...........................369
21.2. Работа с текущим интернет-адресом...................................369
21.3. Получение сведений о клиентском компьютере и веб-обозревателе.......371
21.4. Вывод стандартных диалоговых окон...................................371
21.5. Детектор видимости..................................................373
21.5.1. Область видимости и детектор видимости........................373
21.5.2. Создание детектора видимости..................................374
21.5.3. Указание отслеживаемых элементов..............................376
21.5.4. Отслеживание попадания элементов в область видимости..........376
21.6. Упражнение. Отложенная загрузка изображений.........................377
21.7. Детектор изменения размеров.........................................379
21.7.1. Создание детектора изменения ра шеров.........................379
21.7.2. Указание отслеживаемых элементов..............................379
21.7.3. Отслеживание изменения размеров элементов.....................380
21.8. Самостоятельное упражнение..........................................382
ЧАСТЬ IV. АСИНХРОННОЕ И ПАРАЛЛЕЛЬНОЕ
ПРОГРАММИРОВАНИЕ..........................................................383
Урок 22. Таймеры и фоновые потоки.........................................385
22.1. Таймеры, периодические и однократные................................385
22.2. Упражнение. Отзывчивое веб-приложение, вариант 1....................386
22.3. Фоновые потоки......................................................390
22.3.1. Создание фонового потока......................................390
22.3.2. Передача данных между потоками................................390
22.3.3. Завершение фонового потока....................................391
22.4. Упражнение. Отзывчивое веб-приложение, вариант 2....................391
22.5. Самостоятельные упражнения..........................................393
Урок 23. Промисы и асинхронные функции....................................394
23.1. Промисы.............................................................394
23.1.1. Создание нромисов.............................................394
23.1.2. Обработка промисов............................................395
23.1.3. Массовая обработка промисов...................................397
23.1.4. Дополнительные инструменты для работы с промисами.............398
23.2. Асинхронные функции.................................................399
23.2.1. Асинхронные методы в классах..................................400
23.3. Оператор ожидания...................................................401
23.4. Упражнение. Отзывчивое веб-приложение, вариант 3....................402
23.5. Самостоятельное упражнение..........................................404
Урок 24. Асинхронные итераторы и генераторы...............................405
24.1. Асинхронные итераторы...............................................405
24.2. Цикл перебора последовательности с ожиданием........................406
24.3. Асинхронные генераторы..............................................407
24.4. Упражнение. Отзывчивое веб-приложение, вариант 4....................408
ЧАСТЬ V. WEB API...........................................................411
Урок 25. Работа с локальными файлами и перетаскивание......................413
25.1. Работа с локальными файлами..........................................413
25.1.1. Получение сведений о файлах...................................413
25.1.2. Считывание текстовых файлов...................................414
25.1.3. Считывание графических, аудио- и видеофайлов..................415
25.2. Упражнение. Предварительный просмотр выбранного графического файла...416
25.3. Перетаскивание.......................................................417
25.3.1. Превращение элемента-источника в перетаскиваемый..............417
25.3.2. Задание перемещаемых данных в источнике.......................417
25.3.3. Указание допустимых операций..................................418
25.3.4. Подготовка элемента-приемника.................................419
25.3.5. Завершение перетаскивания.....................................420
25.4. Упражнение. Экзаменатор..............................................421
25.5. Перетаскивание файлов в поле выбора файлов...........................423
25.6. Самостоятельные упражнения...........................................424
Урок 26. Долговременное хранение данных....................................426
26.1. Хранилища: локальное и сессионное....................................426
26.2. Упражнение. Передача данных между веб-страницами.....................427
26.3. Cookie...............................................................428
26.4. Самостоятельное упражнение...........................................431
Урок 27. Программная графика...............................................432
27.1. Холст................................................................432
27.2. Рисование прямоугольников............................................433
27.3. Указание основных параметров графики.................................433
27.3.1. Цвета линий и заливок.........................................433
27.3.2. Параметры тени................................................434
27.4. Вывод текста.........................................................435
27.5. Упражнение. Столбчатая диаграмма.....................................437
27.6. Рисование сложных фигур..............................................439
27.6.1. Начало и завершение рисования.................................439
27.6.2. Перемещение пера..............................................440
27.6.3. Рисование прямых линий........................................440
27.6.4. Замыкание контура.............................................441
27.6.5. Указание параметров линий.....................................441
27.6.6. Рисование дуг.................................................443
27.6.7. Рисование кривых Безье........................................444
27.6.8. Рисование прямоугольников.....................................446
27.6.9. Альтернативный способ рисования сложных фигур.................447
27.7. Градиенты и графические закраски.....................................448
27.7.1. Линейные градиенты............................................448
27.7.2. Радиальные градиенты..........................................450
27.7.3. Угловые градиенты.............................................451
27.7.4. Графическая закраска..........................................452
27.8. Преобразования.......................................................453
27.9. Вывод сторонних изображений..........................................454
27.10. Управление композицией..............................................455
27.11. Создание масок......................................................457
27 12. Сохранение и восстановление состояния холста........................458
27.13. Упражнение. Рисование цветка.........................................458
27.14. Самостоятельные упражнения...........................................460
Урок 28. Заг рузка данных с веб-сервера....................................4б1
28.1. Отправка клиентского запроса на загрузку данных......................461
28.1 1.1 Тараметры клиентского запроса.................................461
28.1.1.1. Задание заголовков клиентского запроса................464
28.1.2. Передача данных веб-серверу....................................465
28.1.2.1. Передача данных HTTP-методом GET......................465
28.1.2.2. Передача данных HTTP-методом POST.....................468
Кодирование данных методом application/x-www~form-urlencoded..468
Кодирование данных методом multipart/form-data................468
28.1.3. Использование объектов запросов................................470
28.2. Обработка серверных ответов..........................................471
28.2.1. 11олучение сведений о серверном ответе.........................471
28.2.2. Получение данных, отправленных веб-сервером....................472
28.3. Упражнение. Сборка веб-страницы из отдельных частей..................473
28.4. Формат JSON..........................................................474
28.5. Упражнение. Программное создание веб-страницы на основе
загруженных JSON-данных.....................................................476
28.6. Работа с интернет-адресами...........................................477
28.7. Самостоятельное упражнение...........................................478
ЧАСТЬ VI. РАЗРАБОТКА ВЕБ-ПРИЛОЖЕНИЙ........................................479
Урок 29. Разработка традиционных веб-приложений. Платформа Node.js..........481
29.1. Упражнение. Проект и манифест Node-приложения........................482
29.2. Упражнение. Простейшее веб-приложение, выдающее приветствие.
Сценарии проекта............................................................483
29.3. Упражнение. Маршрутизатор и контроллеры..............................486
29.3 1. Теория.........................................................487
29.3 .2. Практика......................................................487
29,4. Упражнение. Обработка GET-параметров.................................496
29.5. Упражнение. Обработка POST-параметров............................... 499
29.6. Самостоятельное упражнение...........................................505
Урок 30. Двух шейные веб-приложения. Разработка бзкендов...................506
30.1. Введение в двухзвенные веб-приложения и REST.........................506
30.1 I. Двухзвенные веб-приложения ....................................506
30.1 .2. REST..........................................................507
30.2. Упражнение. Разработка бэкенда.......................................507
30.3. Поддержка межхостовых запросов.......................................512
30.3.1. Простые межхостовые запросы....................................512
30.3.2. Расширенные межхостовые запросы................................513
30.4. Упражнение. Реализация поддержки межхостовых запросов................514
Урок 31. Разработка фронтендов, часть 1. Компонеты.........................516
31.1. Принципы разработки фронтендов...................................... 516
31.2. Упражнение. Базовый класс компонента.................................517
3I.3. Упражнение. Компонент перечня изображений...........................518
31.4. Упражнение. Компонент отдельной позиции перечня изображений........521
Урок 32. Разработка фронтендов, часть 2. Маршрутизация...................524
32.1. Маршрутизация на стороне клиента...................................524
32.2. Упражнение. Маршрутизация и компонент выбранного изображения.......525
32.3. Упражнение. Добавление изображений.................................528
Заключение...............................................................531
ПРИЛОЖЕНИЯ...............................................................533
Приложение 1. Приоритет операторов.......................................535
Приложение 2. Node.js: подгоювка к работе................................537
П2.1. Установка Node.js..................................................537
П2.2. Проверка работоспособности Node.js.................................543
Приложение 3. Веб-сервер http-server: установка и использование...544
ПЗ. 1. Установка http-server.............................................544
П3.2. Запуск веб-сайта с помощью http-server.............................544
ПЗ.З. Останов http-server................................................545
Приложение 4. Устаревшие прозраммные инструменты JavaScript,
применяющиеся до сих пор.................................................546
П4.1. Условное присваивание и выбор......................................546
П4.2. Необязательные параметры функций...................................547
П4.3. Функции с произвольным количеством параметров......................547
П4.4. Объявление классов.................................................548
П4.4.1. Объявление функции-конструктора.............................548
П4.4.2. Создание свойств и методов..................................549
П4.4.2.1. Объявление свойств и методов в функции-конструкторе.549
П4.4.2.2. Объявление свойств и методов в прототипе......550
П4.4.3. Создание динамических свойств...............................551
П4.4.4. Создание статических свойств и методов.......................551
Г14.4.5. Наследование классов.......................................552
П4.5. Загрузка данных с веб-сервера......................................553
П4.5.1. Подготовка объекта AJAX.....................................554
П4.5.2. Указание интернет-адреса загружаемого файла.................554
П4.5.3. Получение загруженного файла................................554
П4.5.4. Отправка веб-серверу запроса на получение файла.............555
П4.5.5. Отправка данных веб-серверу в POST-параметрах...............555
П4.5.6. Синхронная загрузка файлов по технологии AJAX...............556
П4.6. Работа с GET-параметрами...........................................556
Приложение 5. Описание файлового архива..................................558
Предметный указатель.....................................................559
Предисловие
Здравствуйте, уважаемый читатель!
Если вы держите в руках эту книгу, значит, желаете научиться языку программиро-
вания JavaScript. Это очень правильное желание. А это очень правильная книга!
JavaScript — один из популярнейших в настоящее время языков для написания
программ, а в деле веб-программирования у него вообще нет конкурентов. Знание
JavaScript требуется от каждого, кто намеревается серьезно заняться написанием
сайтов. Любой работодатель, узнавший, что претендент на должность веб-раз-
работчика не владеет этим языком, тут же даст ему от ворот поворот.
Почему JavaScript?
Прежде всего, он — один из трех «китов», на которых зиждется величественный
дворец веб-разработки. Первый «кит» — язык HTML — описывает саму веб-стра-
ницу, ее содержание, второй — CSS — ее оформление, a JavaScript — поведение.
JavaScript может «оживить» страницу, заставляя ее реагировать на действия поль-
зователя. Он может превратить набор обычных картинок в красивую фотогалерею.
Он может загрузить с сервера какие-либо данные и вывести их прямо на странице
в виде абзаца, списка, таблицы или графика. Он даже может превратить страницу
в настоящую программу, обрабатывающую занесенные пользователем данные и
выводящую результаты их обработки.
JavaScript — единственный язык программирования, позволяющий «влезть» внутрь
веб-страницы, исправить или дополнить ее содержание. JavaScript — единственный
язык программирования, непосредственно поддерживаемый веб-обозревателями.
Ранее говорилось, что у этого языка в своей области нет конку рентов. И в ближай-
шем будущем они не появятся.
Что вы найдете в этой книге?
Философия программирования — эго часть программирования,
со всех сторон окруженная водой
♦ 1 ридцать два коротких, емких, наглядных, иллюстрированных урока, дающих
необходимые теоретические знания
♦ Более сорока практических упражнений, выполняемых под руководством авто-
ра. Выполняя их, вы закрепите полученные знания и приобретете программист-
ский опыт, что, безусловно, оценит ваш будущий работодатель.
♦ Более двадцати упражнений, рассчитанных на самостоятельное выполнение.
Чтобы справиться с ними, необходимо лишь внимательно изучать приведенный
в уроках теоретический материал.
♦ Описание самых востребованных в настоящее время приемов программирова-
ния: использование регулярных выражений, фоновых процессов, промисов и
асинхронных операций, разбиение кода на модули, разработка компонентов,
программирование фронтендов и бэкендов.
♦ Вводный курс популярной программной плаз формы Node.js, знание которой
очень пригодится веб-программисту.
♦ Полное отсутствие описаний устаревших и малополезных инструментов, отвле-
ченных рассуждений, философствований на тему программирования и прочей
«воды».
Чтобы по этой книге овладеть JavaScript-программированием, вам хватит
и месяца.
Книгу сопровождает файловый архив (см. приложение 5), содержащий результаты
выполнения всех упражнений, равно как и необходимые исходные файлы. Архив
доступен по ссылке https://zip.bhv.ru/9785977519427.zip и со страницы книги на
сайте https://bhv.ru.
Что нового?
За прошедшие с момента выхода предыдущей книги1 годы в JavaScript и сопутст-
вующих технологиях появилось много нового. Наиболее значительные и востребо-
ванные нововведения представлены в табл. П.1.
Таблица П.1. Наиболее значительные нововведения в JavaScript
и сопутствующих технологиях
Нововведение Где описаны
Цикл перебора последовательности Разд 4 2 4 4
Средства для фооматирования чисел Разд 5 1.31
Шаблонные строки Разд 6 1 1
Помеченные строки Разд 6 111
Средства для форматирования временных отметок Разд 7 2 1
Деструктурирование массивов Разд 9 116
Деструктурирование объектов Разд 10 5
Новый синтаксис обьявления классов Разд 11.1 w 11 3
Итераторы и генераторы Гпава 12
Дронов В. JavaScript. 20 уроков для начинающих. — Cl I6.: БХВ-11етербург, 2020.
http://tinyurl.com/bdcucrau
Таблица П. 1 (окончание)
Нововведение Где описаны
Модули Гпава 14
Детектор • идимости РазО 21 5
Детектор изменения размеров Разд 21 7
Промисы и асинхронные функции Глава 23
Асинхронные итераторы и генераторы Гпава 24
Новые средства для загрузки данных Гпава 28
Что вам понадобится?
Для изучения этой книги вам, уважаемый читатель, нужны следующие программы:
♦ веб-обозреватель, разумеется. Автор тестировал подготовленные им примеры
в Google Chrome и Mozilla Firefox (остальные веб-обозреватели либо аналогич-
ны Chrome, либо малопопулярны);
♦ текстовый редактор — можно использовать Блокнот, но автор рекомендует
какой-либо из чисто «программистских» редакторов: Visual Studio Code (https://
code.visualstudio.com/), Notepad-н- (https://notepad-plus-plus.org/), Sublime Text
(https://www.sublimetext.com/), Brackets (http://brackets.io/) и т. п.;
♦ программная платформа Node.js (https://nodejs.org/en/). Автор применял вер-
сию 21.1.0. Инструкция по установке этой платформы на компьютер приведена
в приложении 2.
Еше от вас требуется
♦ владеть языками HTML и CSS, на которых пишутся сами веб-страницы и
оформление для них;
♦ иметь минимальные навыки веб-верстки;
♦ знат ь в общих чертах, как работает веб-сервер (это понадобится нам в части VI
книги).
Типографские соглашения
В книге часто будут приводиться различные языковые конструкции, применяемые
в JavaScript. Для наглядности при их написании использованы следующие типо-
графские соглашения (в реальном коде они недействительны).
♦ Н i ML-, CSS- и JavaScript-код набран моноширинным шрифтом. Пример:
<body>
<div>
<rmq id="interactive" src="2.3.jpg">
</div>
<sc.ript>
const interactive = document.getElementByld.('interactive');
</script>
</body>
♦ В угловые скобки о заключаются наименования различных значений, которые
дополнительно выделяются курсивом. В реальный код. разумеется, должны быть
подставлены реальные значения. Пример:
typeof <значенпе>
Здесь вместо подстроки значение должно быть подставлено реальное значение.
♦ В квадратные скобки [ ] заключаются необязательные фрагменты кода. Пример*
return l<возвращаемое значение?]
Здесь возвращаемое значение может указываться, а может и не указываться.
♦ У необязательных параметров функций и методов могут быть указаны значения
по умолчанию — после наименования параметра через символ =. Пример:
toString([<основание>=10])
У параметра основание значение по умолчанию — Ю.
♦ Вертикальной чертой | разделяются различные варианты языковой конструкции,
из которых следует указать лишь какой-то один. I (ример.
stringifу(<простой объект><массив>)
Здесь следует указать либо простой объект, либо массив.
♦ Слишком длинные фрагменты, не помещающиеся на одной строке книги, автор
разрывал на несколько строк и в места,х разрывов ставил знаки (Ь. Пример:
'понедельник, 6 ноября 2023 г. от Рождества Христова в J8:ll:('4 ’Ъ
Волгоград, стандартное время'
Приведенный код здесь разбит на две строки, ио должен быть набран в одну.
Символ ’Ъ при этом следует удалить.
♦ Троеточием . . . помечены фрагменты кода, пропущенные ради сокращения
объема текста книги:
<table?
<thead?
</thead>
ctbody id="output"?
</tbcdy?
</tabLe>
Здесь все содержимое тега <thead> пропущено.
Обычно такое можно встретить в исправленных фрагментах кода — приведены
лишь собственно исправленные выражения, а оставшиеся неизмененными про-
пущены. Также троеточие используется, чтобы показать, в какое место должен
быть вставлен вновь написанный код: в начало исходного фрагмента, в его
конец или в середину, между уже присутствующими в нем выражениями.
♦ 11олу жирным шрифтом выделен вновь добавленный код.
♦ Зачеркнутым шрифтом выделяется код, подлежащий удалению.
J 1ример:
function onlnceracciveMouseOver() {
interactive. daesNanre ~—'hovered';
this.className = 'hovered1;
}
Здесь первое выражение тела функции oninteractiveMouseOver () удалено, а вто-
рое — добавлено.
Внимание!
Все приведенные здесь типографские соглашения имеют смысл только в примерах
написания языковых конструкций JavaScript В реальном программном коде использу-
ются только знак троеточие, полужирный и зачеркнутый шрифт
ЧАСТЬ I
Начала JavaScript
и веб-программирования
С Урок 1 введение в язык JavaScript
Э Урок 2 Введение в веб-программирование
Урок 1
Введение в язык JavaScript
Начать изучение того или иного языка программирования лучше всего, выполняя
практические примеры и по ходу дела знакомясь с новыми понятиями и терми-
нами.
1.1. Упражнение. Консоль веб-обозревателя
Учить JavaScript будем, набирая выражения в консоли веб-обозревателя.
Консоль веб-обозревателя (далее — просто консоль) — интерактивная испол-
няющая среда, встроенная в веб-обозреватель. В консоли можно набрать какое-
либо выражение и сразу увидеть результат его выполнения.
Исполняющая среда, или интерпретатор — программа, которая преобразует
выражения, написанные на каком-либо языке программирования, в вид, при-
годный для выполнения центральным процессором компьютера.
Исполняющая среда JavaScript встроена в каждый современный веб-обозреватель.
Выражение — команда, написанная на языке программирования и выполняю-
щая какое-либо законченное действие.
Отдельное выражение может, например, выполнять сложение двух чисел, извле-
кать фрагмент из строки или получать значение, занесенное в поле ввода.
Вызовем консоль веб-обозревателя и выполним в ней пару простых выражений.
I. Запуст им какой-либо веб-обозреватель (автор книги использовал Google Chrome).
2. Нажмем клавишу <F12>, чтобы вывести панель со встроенными в веб-обозре-
ватель отладочными инструментами (их также называют инструментами разра-
ботчика), в состав которых входит консоль.
По умолчанию эта панель выводится внутри главного окна веб-обозревателя,
в его правой части (на рис. l.l выделена сплошной рамкой).
Изначально все надписи в панели отладочных инструментов выводятся по-
английски (даже если сам веб-обозреватель русифицирован). Однако, если эта
панель вызывается впервые, веб-обозреватель сразу же предложит переключить
ее на русский язык. Рекомендуется так и сделать.
3. Щелкнем на синей кнопке Switch DevTools Io Russian (Переключить отладоч-
ные инструменты на русский язык), которая находится в верхней части панели
(на рис. 1.1 эта кнопка выделена штриховой рамкой).
О X
& Новы «кладка X +
г С Ф about blank
Q DcvTooH n now available m Russun
Always makh Chronwt language ^Switch DwvTbdi to Rmwi j
Don’t snow again
К £B Hwn*ntt Cem ok Sources Notw . »
01 O1
<№«!>
<headx/heao>
<bodyx/body> $0
Mml body
•/y>4 Computed
layout
Event Listeners DOM Breakpoints Properties
Accessibility
:hov .cli +,9 3]
Ie {
deploys block,
♦ tpx;
user agent styles-.
£ ☆ 3 A
&
X
X
Рис 1.1 Главное окно веб-обозревателя с панелью отладочных инструментов
Внимание!
В дальнейшем при описании панели отладочных инструментов предполагается, что
она уже переключена на русский язык
Выбранный язык сохраняется в настройках веб-обозревателя и автомагически
устанавливается при последующих вызовах панели.
В верхней части панели отладочных инструментов находится набор вкладок,
посредством которых производится выбор нужного инструмента (рис. 1.2).
Элементы Конель. Иыомники Сеть » (•? : X
Производительность
</head>
</body> so Память
Приложение
Защита
lightnouse
Регистратор
Статистика производительности
Рис. 1 2. Набор вкладок для выбора отладочных инструментов
Если r панели не хватает места для вывода всех вкладок. будет выведена лишь
их часть, а также кнопка Еще вкладки, имеющая вид стилизованной двойной
стрелки вправо. По ее нажатию появится меню, содержащее оставшиеся не
выведенными вкладки (на рис. 1.2 показаны и эта кнопка, и ее меню).
4. Переключимся на вкладку Консоль (на рис. 1.2 выделена рамкой).
Большую часть консоли занимает белая область редактирования с мигающим
текстовым курсором (рис. 1.3). Там вводятся выражения и выводятся результаты
их выполнения.
к £с Элементы Консоль Источники Сеть Производительность » ® : X
Е 0 >1 top ▼ Фильтр Уровни по умолчанию ▼ Hei проблем
Рис. 1.3. Консоль веб-оЬозревателя
Для начала сложим числа 27 и 14.
5. Введем в консоли следующее выражение:
27 + 14
После чего нажмем клавишу <Enter>.
Внимание!
Нажатие клавиши <Enter> в консоли веб-обозревателя служит указанием исполняю-
щей среде выполнить введенное выражение
Результат — сумма чисел — будет показан ниже введенного выражения (рис. 1.4).
> 27 t 14
<• 41
> I
Рис 1 4 Выражение JavaScript, введенное в консоли и результат его вычисления
А еще ниже появится текстовый курсор, предлагающий ввести следующее вы-
ражение.
Взглянем еще раз на консоль, введенное выражение и выданный результат
(см. рие. 1.4):
• строка, в которой было введено выражение, — помечается стилизованной
черной стрелкой вправо, расположенной левее самого выражения;
• строка, в которой был выведен результат выражения, — помечается черной
стрелкой влево;
• строка, в которой будет вводиться новое выражение. — помечается синей
стрелкой вправо.
Отдельные строки в консоли для удобства разделяются тонкими серыми гори-
зонтальными линиями.
Посмотрим, умеет ли JavaScript выполнять арифметическое деление, разделив
число 100 на 7. Только на этот раз разобьем выражение на две строки.
6. Введем в консоли начало выражения деления- делимое и символ /, обозначаю-
щий операцию деления:
100 /
7. Нажмем комбинацию клавиш <Shift>+<Enter>, чтобы перейти на новую строку.
Внимание!
Нажатие комбинации клавиш <Shift>+<Enter> в консоли веб-обозревателя приводит
к переходу на новую строку Для выполнения введенного выражения необходимо на-
жать клавишу <Entep*.
8. Введем остаток выражения — делитель:
7
И снова нажмем <Enter>.
Посмотрим на результат (рис. 1.5).
> 100 /
7
<• 14.285714285714286
> I
(Е 0 top ’
>27+14
Рис. 1.5. Выражение разбитое на две строки, Рис. 1.6. Фрагмент панели инструментов консоли
и результат его вычисления с кнопкой Очистить консоль
Сразу же отметим, что вторая строка выражения отображается с заметным от-
ступом слева. Гак консоль сообщает нам, что выражение введено не до конца,
и предлагает закончить его.
Кроме того, взглянув на выведенный результат, отметим, что для разделения
целой и дробной частей чисел используется точка, а не запятая.
Что ж. пока хватит... Очистим консоль от введенных выражений и результатов
их вычисления.
9. Щелкнем на кнопке Очистить консоль, выводящейся на небольшой панели
инструментов в верхней части консоли и имеющей вид перечеркнутого кружка
(на рис. L6 выделена рамкой).
Очистит ь консоль также можно нажатием комбинации клавиш <Ctrl>+<L>.
Ю. Закроем панель отладочных инструментов повторным нажатием клавиши
<Fl2>.
Закрыть панель также можно щелчком на кнопке Закрыть, выводящейся
в правой части полосы, на которой отображаются ярлыки вкладок, и имеющей
вид крестика.
Урок 1 Введение в язык JavaScript
ПОЛЕЗНО ЗНАТЬ
Есть возможность расположить панель отладочных инструментов вдоль верхней или
нижней стороны главного окна веб-обозреватели, а также вынести ее в отдельное окно.
В правой части полосы, где отображаются ярлыки вкладок, левее кнопки закрытия на-
ходится кнопка Настройка инструментов разработчика и управление ими. которая
имеет вид трех точек, выстроенных по вертикали. По щелчку на ней появится меню
настроек панели (на рис. 1.7 эта кнопка выделена рамкой, а ее меню показано).
Источники
Сеть »
Закрепить сбоку © D □ □:
Показать выдвигающуюся панель консоли Esc
Поиск
Выполнить команду
Открыть файл
Другие инструменты
Ctrl + Shift+ F
Ctrl + Shift + P
Ctrl 4 P
Сочетания клавиш
Справка
►
Рис. 1 7. Кнопка Настройка инструментов разработчика и управление ими и ее меню
В верхней части меню настроек присутствует группа Закрепить сбоку, содержащая
четыре кнопки: Перенести в отдельное окно. Закрепить слева. Закрепить внизу и
Закрепить справа Щелчок на какой-либо из этих кнопок переносит панель отладоч-
ных инструментов в соответствующее местоположение.
За годы практики автор книги нашел, чго удобнее вынести панель в отдельное окно
(рис. 1.8).
Рис. 1.8. Расположение панели отладочных инструментов в отдельном окне
1.2. Упражнение. Типы данных и операторы
JavaScript может работать со значениями, относящимися к разным тинам.
Тип данных — определяет характер значения и набор выполняемых с ним опе-
раций.
Выполним в консоли веб-обозревателя несколько выражений со значениями раз-
ных типов. Заодно познакомимся с операторами и еще несколькими ключевыми
понятиями JavaScript.
Внимание!
В дальнейшем каждое выражение. Биодиыое в консоли веб-обозревателя, будет по-
мечаться символом «больше» (>), поставленным слева от него, а результат вычисле-
ния этого выражения — символом «меньше» (<). Вводить эти символы в консоли не
нужно'
I. Сложим числа 127 и 59:
> 127 + 59
< 186
Значения 127 и 59 принадлежат к числовому типу.
| Числовой тип данных — обычное число, целое или дробное.
Символ «плюс» (+), поставленный между числами, — это оператор сложения.
Оператор — команда языка программирования, выполняющая какое-либо эле-
ментарное действие над заданными операндами и выполняющая возврат полу-
ченного результата.
Операнд — значение, которым манипулирует оператор.
Разные операторы принимают один, два или три операнда.
Возврат результата— выдача оператором или целым выражением вычислен-
ного результата.
В приведенном ранее примере консоль вывела результат, возвращенный опера-
тором сложения
В дробных числах для разделения целой и дробных частей применяется точка,
а не запятая.
2. Вычтем из числа 5,3 число 2,74, применив оператор вычитания - (минус):
> 5.3 - 2.74
< 2.5599999999999996
К сожалению, из-за особенностей обработки дробных чисел в JavaScript мы по-
лучили вот такой результат... По-хорошему, его следует округлить, и мы это
обязательно сделаем, но позже.
3. Сложим числа 20 и 4 и умножим получившуюся сумму на 10 (оператор умноже-
ния обозначается звездочкой *):
> 20 + 4 * 10
< 60
Что-то у нас получилось не то... Похоже, JavaScript сначала умножил 4 на 10, а
потом сложил 20 с получившимся произведением. Это произошло из-за того,
что оператор умножения имеет больший приоритет, нежели оператор сложения.
Приоритет операторов — задает очередность их выполнения. Операторы
с большим приоритетом выполняются раньше операторов с меньшим приори-
тетом.
Повысить приоритет выполнения низкоприоритетного оператора можно, заклю-
чив его, вместе с операндами, в круглые скобки.
4. Исправим набранное ранее выражение, чтобы оно возвращало правильный
результат:
> (20 +4) * 10
< 240
5. Умножим числа 20 на 4 и разделим полученное произведение на 50:
> 20 * 4 / 50
< 1.6
Операторы умножения и деления (обозначается слешем /) имеют одинаковый
приоритет и соответственно выполняются в последовательности слева направо.
Последовательность выполнения операторов— задает очередность выполне-
ния операторов с одинаковым приоритетом.
Сначала выполнится оператор умножения, который перемножит числа 20 и 4.
Затем выполнится оператор деления, который разделит результат выполнения
предыдущего оператора — произведение чисел 20 и 4 — на 50.
В JavaScript подобные цепочки операторов применяются на практике очень часто.
11омимо чисел, этот язык позволяет работать со строками.
Строковый тип данных (строка)— произвольная последовательность любых
символов.
Строки заключаются в одинарные или двойные кавычки.
6. Объединим строки 'Java' и 'script' в одну, воспользовавшись оператором объ-
единения +:
> 'Java' + 'Script'
< 'JavaScript'
Если операнды представляют собой числа, символ «плюс» обозначает оператор
арифметического сложения, если же операнды имеют строковый тип — опера-
тор объединения строк.
7. Попробуем объединить строку 'Число "пи" = ' и число 3,14:
> 'Число "пи" = ' +3.14
< 'Число "пи" = 3.14'
Поскольку объединять можно лишь строки, JavaScript предварительно произвел
преобразование типа второго операнда из числового в строковый.
Преобразование типа— превращение значения одного типа в значение друго-
го типа.
В этом случае произошло неявное преобразование типа. Также можно выпол-
нить явное преобразование (будет рассмотрено на следующих уроках).
Неявное преобразование типа— выполняется самим JavaScript без какого бы
то ни было указания со стороны программиста.
Явное преобразование типа — явно предписывается программистом путем ука-
зания особого оператора или иной команды языка.
И еще один тип данных, весьма специфический.
8. Проверим, что больше: произведение чисел 2,5 и 4 или частное от деления 80
на 10:
>2.5*4>80/10
< true
Оператор сравнения «больше» > проверяет, является ли значение левого операн-
да большим, чем значение правого операнда, и выдает результат в виде значения
логического типа.
Логический тип данных (логическая величина) — признак истинности или лож-
ности какою-либо утверждения. Значение, представляющее истинность, обо-
значается ключевым словом true, «ложное» значение — ключевым словом
false.
Ключевое слово — слово, используемое для написания различных языковых
конструкций в языке программирования.
Поскольку произведение чисел 2.5 и 4 и в самом деле больше частного от деле-
ния 80 на 10, оператор «больше» вернул значение true — «истина».
Мы только что написали наше первое условие.
Условие— выражение, в качестве результата возвращающее логическую вели-
чину.
Истинное условие' — условие, возвращающее true.
Ложное условие* 2 — условие, возвращающее false.
Следует иметь в виду, что одно и то же условие в разных ситуациях (например,
в зависимости от переданных ему исходных данных) может оказаться как ис-
тинным. так и ложным.
Также часто встречается термин «выполняющееся условие». Однако автор не использует его. чтобы
избежать путаницы с терминами «выполнение выражения» и т. п.
2 Также имеет хождение термин «невыполняющееся условие». Автор не применяет его по причине,
описанной ранее.
Условия активно используются при написании управляющих выражений, с ко-
торыми мы познакомимся при выполненииупражнения 1.4.
Полезно знать
Синтаксис написания арифметических выражений в JavaScript схож с используемым
в математике. Говорят, что JavaScript использует алгебраическую нотацию.
1.3. Упражнение. Переменные и константы
Ранее все результаты выполнения записываемых нами выражений выводились
в консоли. Однако результат любого выражения можно сохранить в переменной,
чтобы воспользоват ься им в дальнейшем.
Переменная — ячейка памяти, предназначенная для временного хранения како-
го-либо значения. Должна иметь уникальное имя.
Поэкспериментируем с переменными.
I. Объявим переменную с именем — например, al — для хранения результата вы-
полнения выражения, которое запишем позже:
> let al
< undefined
Оператор let выполняет объявление переменной, имя которой записано после
него через пробел.
Объявление переменной— указание исполняющей среде создать переменную
с заданным именем. Должно предшествовать первому использованию этой
переменной.
Оператор let в качестве результата возвращает особое значение undefined, кото-
рое обозначает отсутствие какого бы то ни было «значащего» значения. Этот
результат совершенно не нужен, поэтому не будем обращать на него внимание.
2. Вычислим разность чисел 100 и Юи запишем результат в только что объявлен-
ную переменную ai:
> al = 10С - 10
< 90
Оператор = (символ «равно») выполняет присваивание значения, записанного
справа от него, переменной, чье имя указано слева.
I1 Присваивание — занесение значения в переменную.
В качестве результата этот оператор возвращает присвоенное значение.
3. Разделим значение, хранящееся в переменной al, на 2:
> al / 2
< 45
Здесь мы выполнили обращение к переменной.
Обращение к переменной — извлечение из нее значения. Производится про-
стым указанием имени этой переменной.
Объявим еще пар} переменных разными способами.
4. Объявим переменную с именем а2 и сразу же присвоим ей число 3:
> let а2 = 3
< undefined
Мы совместили в одном выражении объявление переменной и присваивание ей
значения — так короче.
5. Объявим переменную languageName и сразу же присвоим ей результат объедине-
ния строк: 'Java' и ’ Script’:
> let languageName = 'Java' + 'Script'
< undefined
При объявлении переменной можно присвоить ей и результат каких-либо
вычислений.
После объявления переменной можно присвоизь любое другое значение. При
этом значение, хранившееся в этой переменной ранее, будет потеряно.
6. 11рисвоим ргшее объявленной переменной а2 значение 444:
> а2 = 444
< 444
Если же но каким-либо причинам требуется не допустить присваивание пере-
менной другого значения, закую переменную следует объявить в качестве кон-
станзы.
Константа — переменная, которой можно присвоизь значение лишь еди-
ножды — при объявлении.
7. Объявим константу pi:
> const pi = 3.14
< undefined
Оператор const выполняет объявление константы.
Попытка присвоить константе новое значение вызовет программную ошибку.
8. Ради эксперимента— попробуем присвоить только что объявленной константе
pi другое значение:
> pi = 2.72
Результат можно увидеть на рис. 1.9.
> pi = 2.72
О > uncaught ЗуреЕггог: Assignment to constant variable,
at <anonymous>:l:4
Рис. 1.9 Выражение вызвавшее программную ошибку
и сообщение об этой ошибке в консоли веб-обозревателя
Под выражением ярко-красным шрифтом на темно-красном фоне будет выве-
дено сообщение о возникшей ошибке. В тексте этого сообщения мы увидим тин
ошибки (ТуреЕггог) и, через двоеточие, ее текстовое описание (Assignment to
constant variable, присваивание значения константе). Ошибки и их типы мы под-
робно рассмотрим на уроке 15.
Внимание!
Повторное объявление переменной или константы недопустимо и приводит к про-
граммной ошибке
Все переменные, объявленные в консоли, будут существовать, пока открыт веб-
обозреватель. Они останутся в памяти, даже если выполнить очистку консоли
(см. упражнение 1.1).
Очистить память от переменных и подготовиться к выполнению следующего
упражнения можно, лишь закрыв веб-обозреватель и запустив его снова.
Внимание!
Перед началом выполнения очередного упражнения рекомендуется перезапустить
веб-обозреватель, чтобы очистить память от объявленных ранее переменных и кон-
стант
1.4. Упражнение. Управляющие выражения
Все выражения, набираемые в консоли, выполняются в обязательном порядке и
лишь по одному разу. Однако можно выполнить какое-либо выражение лишь при
истинности указанного условия или даже заставить выражение выполняться раз за
разом, пока заданное условие остается истинным Для этого применяются управ-
ляющие выражения.
Управляющее выражение— сложное выражение, управляющее порядком вы-
полнения других выражений, которые входят в его состав.
Сложное выражение— выражение, состоящее из более простых выражений.
Сложные выражения JavaScript обычно занимают несколько строк текста.
Правила набора многострочных выражений
в консоли веб-обозревателя
• Для перехода на новую строку — нажимаем комбинацию клавиш <Shift>+<Enter>.
Если вместо этого нажать клавишу <Enter>. исполняющая среда воспримет это как
указание выполнить выражение. а выполнение выражения, введенного не до кон-
ца. вызовет ошибку.
• Написав последнюю строку многострочного выражения — нажимаем клавишу <Enter>,
чтобы выполнить это выражение
Цвета подобраны, прямо скажем, неудачно — текст на фоне плохо различим...
Познакомимся с двумя разновидностями управляющих выражений, наиболее часто
применяемыми на практике.
При выполнении упражнения 1 2 мы проверяли, что больше: произведение чи-
сел 2.5 и 4 или частное отделения 80 на 10. Тогда мы получили результат в виде
логической величины, мало понятной непрограммисту.
I. Выполним аналогичную проверку, только с выводом более развернутого, стро-
кового результата, для чего наберем следующее многострочное выражение (пра-
вила написания таких выражений были приведены чуть ранее):
> if (2.5 * 4 > 80 / 10)
'Да, 2.5 * 4 больше, чем 80 / 10!’
else
'Нет, 2.5 * 4 не больше, чем 80 / 10'
< 'Да, 2.5 * 4 больше, чем 80 / 10!'
Для проверки мы использовали условное выражение.
Условное выражение— сложное выражение, при истинности указанного усло-
вия исполняющее одно из входящих в него выражений, а при ложности усло-
вия — другое.
В первой строке после ключевого слова if («если») мы записали условие, взяв
его в круглые скобки (это обязательно). Во второй строке указали выражение,
которое будет исполнено при истинности условия, заданного в первой строке,
после слова if. В третьей строке записали ключевое слово else («иначе»),
а в четвертой— выражение, которое будет исполнено при ложности условия.
Оба этих выражения представляют собой обычные строки, которые будут про-
сто выведены в консоли.
Результатом станет вывод в консоли строки «Да. 2.5 * 4 больше, чем 80 / 10!»
Вполне ожидаемый..
Теперь нам пришла в голову идея вывести в консоли квадраты всех целых чисел
от 1 до 10. Только вот писать десять однотипных выражений мы не хотим. Мы
поступим по-другому.
2. Выведем в консоли квадраты всех целых чисел в диапазоне от 1 до 10, написав
следующее многострочное выражение:
> for (let i = 1; i < 11; i++)
window.console.log(i * i)
< 1
< 4
< 9
< 16
< 25
< 36
< 49
< 64
< 81
< 100
< undefined
Здесь мы использовали цикл.
Цикл — сложное выражение, многократно выполняющее заданное выражение,
пока истинно указанное условие.
JavaScript предоставляет несколько разновидностей циклов. Сейчас мы рассмот-
рим одну из них (а остальные — на следующих уроках).
В первой строке после ключевого слова for («для») в круглых скобках мы
записали три выражения, с сутью которых разберемся чуть позже.
Во второй строке записано тело цикла.
|| Тело цикла— выражение, которое требуется исполнить многократно.
В теле цикла вычисляется квадрат значения переменной i, для чего это значение
умножается само на себя. Для вывода в консоли получившегося результата ис-
пользуется языковая конструкция window.console.log, после которой в круглых
скобках помешается выводимое значение (что это за конструкция, мы узнаем на
уроке 1.6).
Цикл выполняется в такой последовательности:
1. Первое выражение из помещенных в круглые скобки после ключевого слова
for— объявляет переменную i и присваивает ей начальное значение из требуе-
мого диапазона (1).
Это выражение выполняется всего один раз.
2, Второе выражение из круглых скобок после слова for — условие, истинное
в случае, если значение переменной i меньше 11 (конечное значение тре-
буемого диапазона: 10 плюс 1). Здесь применяется оператор сравнения <
(«меньше»):
• если условие истинно — выполняется следующий шаг;
• если условие ложно — исполнение цикла завершается.
3. Тело цикла — выводит в консоли квадрат числа из переменной i.
4. Третье выражение из круглых скобок после слова for — увеличивает значение
переменной i на 1. Для этого применяется специализированный оператор ++.
После выполнения этого выражения производится переход на шаг 2.
В результате в переменной i будут последовательно оказываться числа от I до 10.
и в консоли — выведены их квадраты. Как только в этой переменной окажется чис-
ло 11, записанное в цикле условие станет ложным, и цикл завершится.
Значение undefined, выведенное в консоли последним, — это результат, возвра-
щенный самим выражением цикла, совершенно бесполезный.
1.5. Упражнение. Функции
Предположим, что нам нужно сформировать ряд фрагментов HTML-кода, создаю-
щих блочные контейнеры (блоки, теги <div>), каждый — с заданными содержимым
и стилевым классом. Мы можем написать нужное количество обычных выражений.
Но проще оформить выражение, создающее такой фрагмент HFML-кода, в виде
функции.
Функция— набор выражений, выполняющий требуемые вычисления и предна-
значенный для последующего многократного использования Должна иметь
уникальное имя. Может принимать произвольное количество параметров и воз-
вращать результат.
Параметр функции—значение, переданное функции для выполнения вычис-
лений.
Вызов функции— обращение к объявленной ранее функции с передачей ей
требуемых параметров (если она принимает параметры).
Вызывать функцию можно произвольное количество раз из произвольных мест
кода, каждый раз передавая ей разные значения параметров.
Напишем нашу первую функцию и вызовем ее несколько раз с разными парамет-
рами.
I. Объявим функцию с именем getDiv, принимающую в качестве параметров со-
держимое создаваемого блока (дадим этому параметру имя content) и имя при-
вязываемого стилевого класса (className), написав такое многострочное выра-
жение:
> function getDiv(content, className) {
return ’<div class-"' + className + + content + '</div>';
< undefined
Объявление функции— выражение, описывающее функцию, включая набор
принимаемых ею параметров и ее тело.
Тело функции— выражения, собственно, оформляемые в виде функции.
В первой строке после ключевого слова function мы указали имя объявляемой
функции (getoiv), а потом в круглых скобках привели через запятую принимае-
мые ею параметры (content и className). Далее в фигурных скобках записали
тело функции— выражение, формирующее HTML-код создаваемого блока
на основе значений параметров, полученных функцией. Оператор return вернет
из функции результат вычисления этого выражения.
Параметры функции, приведенные в ее объявлении, можно рассматривать как
своего рода переменные, которым при обращении к этой функции будут при-
своены реальные значения передаваемых параметров. Соответственно обра-
щаться к ним можно так же. как к обычным переменным. Следует только пом-
нить, что эти переменные существуют лишь внутри тела функции.
Значение undefined, выведенное в консоли последним. — это результат, возвра-
щенный выражением объявления функции.
2. Выполним вызов функции getDiv, передав ей в качестве параметров строки 'Это
блок.' и 'test', для чего напишем выражение:
> qetCiv('Это блок. ', 'test')
< '<div c]ass="test">3To 6noK.</div>'
Для вызова функции мы записали ее имя, поставили круглые скобки и привели
в них — через запятую — значения, передаваемые функции в качестве парамет-
ров. При исполнении функции строка 'Это блок.' будет присвоена параметру
content, а строка 'test* — параметру className. Выражение, записанное в каче-
стве тела функции, выполнится, и функция вернет результат, который появится
в консоли.
3. Вызовем функцию getDiv еще раз. передав ей в качестве параметров пустую (не
содержащую ни единого символа) строку и 'main bordered padded':
> getDiv('', 'main bordered padded')
< '<div class="main bordered padded"x/div>'
JavaScript также поддерживает ряд встроенных функций.
Встроенная функция — функция, реализованная в исполняющей среде
JavaScript. Может быть использована без объявления.
Мы изучим встроенные функции на следующих уроках.
Полезно знать
При указании функции в обычном тексте, не являющемся программой (например,
в руководстве по программированию), после ее имени всегда ставят пустые круглые
скобки — это является признаком того, что речь идет о функции, а не, скажем, о пере-
менной (пример такой записи: getDiv ()).
1.6. Упражнение.
Объекты, свойства, методы и классы
Числа, строки и логические величины— простые сущности. JavaScript позволяет
работать и с более сложными сущностями, представленными в виде объектов.
Объект— значение, представляющее какую-либо сложную сущность. Содер-
жит свойства и методы. Создается на основе определенного класса.
Свойство— переменная, принадлежащая объекту и хранящая либо часть пред-
ставляемой им сложной сущности, либо одну из характеристик этой сущности.
Метод— функция, принадлежащая объекту и либо выполняющая какое-либо
действие над представляемой им сущностью, либо возвращающая одну из
характеристик этой сущности.
Класс— образец для создания объектов определенного вида. Задает набор
свойств и методов, которые будут поддерживаться всеми объектами, относя-
щимися к этом} виду.
Так, JavaScript содержит класс Dace, являющийся образцом для создания объектов,
которые представляют временные отметки.
|| Временная отметки — комбинация значений даты и времени.
Любой объект класса Date поддерживает ряд методов для получения отдельных
частей временной отметки и их изменения.
Уясним несколько принятых в программировании терминов.
♦ объект класса nnn — объект, созданный на основе класса nnn;
♦ свойство класса nnn — свойство, объявленное к классе nnn:
♦ метод класса nnn — метод, объявленный в классе nnn.
Теперь поработаем с объектами, относящимися к разным классам.
I. Создадим объект класса Date, хранящий текущие дату и время, и сохраним его
в переменной current Date:
> let currentDate = new Date()
< undefined
Для создания объекта применяется оператор new. За ним записывается имя клас-
са. на основе которого создается объект, и круглые скобки, в которых при необ-
ходимости через запятую указываются параметры, передаваемые создаваемому
объекту. Если параметров передавать не требуется (как в нашем случае), все
равно следует поставить пустые круглые скобки.
2. Извлечем год из только что созданного объекта временной отметки, для чего
воспользуемся не принимающим параметров методом getFullYear (), поддержи-
ваемым классом Date:
> current Date.getFullYear()
< 2023
Чтобы вызвать метод у объекта, следует указать сам объект (в нашем случае он
хранится в переменной currentiate), поставить точку и записать собственно вы-
зов метода, который выглядит так же, как вызов функции (см. упражнение 1.5).
Несмотря на то что метод getFullYear(), извлекающий год из текущего объекта
временной отметки, не принимает параметров, после его имени все равно следу-
ет поставить пустые круглые скобки.
Текущий объект — объект, к свойству или методу которого выполняется обра-
щение.
3. Изменим год, хранящийся в объекте из переменной currentDate, на 2024-й, для
чего воспользуемся методом setFullYearO, принимающим в качестве единст-
венного параметра задаваемый год:
> currentDate.setFullYear(2П24)
< 1726407222774
Временная отметка в объекте класса Date хранится в виде количества секунд,
прошедших с полуночи 1 января 1970 года. Оно возвращается методом
setFullYear () в качестве результата.
4. Снова извлечем год из этого объекта, чтобы проверить, успешно ли он изменился:
> currentDate.getFullYear()
< 2024
Отлично!
5. Создадим еще один объект класса Date, на этот раз хранящий дату' 27 октября
1970 года и время 12:30:00. и присвоим его переменной someDate:
> let someDate = new Date(1970, 9, 27, 12, 30, 0)
< undefined
Здесь мы указали составные части создаваемой временной отметки: год, номер
месяца, число, часы, минуты и секунды — в круглых скобках через запятую.
Только будем иметь в виду, что в JavaScript ну мерапия месяцев начинается с ну-
ля, поэтому, чтобы задать октябрь (месяц № 10), мы указали число 9.
6. Выясним, на какой день недели приходится эта дата:
> someDate. getDayO
< 2
Не принимающий параметров метод getDayO класса Date возвращает номер дня
недели: 0 — воскресенье, 1 — понедельник и т. д. Возвращенное число 2 — это
вторник.
А теперь внимание! Все тины данных, поддерживаемые JavaScript, на самом
деле являются классами, а значения этих типов — объектами этих классов. Так,
любое число — это объект класса Number, а любая строка — объект класса
string. Эти классы поддерживают различные полезные свойства и методы.
7. Присвоим строку ' веб-программирование' переменной someString и получим
длину этой строки в символах:
> let someString = 'веб-программирование’
< undefined
> someString.length
< 20
Получить длину строки можно из свойства length класса string. Обратиться
к любому свойству можно, указав объект, поставив точку и написав имя этого
свойства (по аналогии с вызовом метода).
8. Приведем строку из переменной someString к верхнему регистру:
> someString.t©Uppercase()
< 'ВЕБ-ПРОГРАММИРОВАНИЕ'
Здесь нам помог не принимающий параметров метод t©Uppercase о класса
String.
9. Извлечем из строки someString подстроку, начинающуюся с символа № 5 и за-
канчивающуюся символом № 20:
> someString.substring(4, 20)
< 'программирование'
| Подстрока — фрагмент строки.
Метод substring () класса string, извлекающий и возвращающий подстроку из
текущей строки, требует указания номера начального символа извлекаемой
подстроки и номер символа, следующего за конечным. Нумерация символов
в строках языка JavaScript начинается с нуля, поэтому в качестве номеров сим-
волов мы указали соответственно 4 (5 минус I) и 20 (20 минус I и плюс 1, по-
скольку требуется указать номер символа, следующего за конечным).
10. Извлечем из строки someString эту же подстроку и приведем ее к верхнему
регистру:
> someString.substring(4, 20).toUpperCase()
< 'ПРОГРАММИРОВАНИЕ'
Здесь мы сначала вызвали у строки someString метод substring(), чтобы полу-
чить подстроку, а потом вызвали у полученной подстроки метод toUpperCaseO
для преобразования ее в верхний регистр. Подобные цепочки обращений
к свойствам и вызовов методов позволяют заметно сократить текст программ и
применяются очень часто.
До этого момента мы имели дело с объектными свойствами и методами.
Объектное свойство— принадлежит конкретному объекту, хранит значение,
являющееся частью этого объекта, или содержит характеристику этого объекта.
Доступ к такому свойству осуществляется через объект.
Объектный метод— принадлежит конкретному объекту, выполняет действие
над эт им объектом или возвращает характеристику этого объекта. Вызов такого
метода осуществляется через объект.
Настала пора познакомиться со статическими свойствами и методами.
Статическое, или классовое, свойство (не путать со свойством класса)— при-
надлежит классу. Доступ к такому свойству осуществляется через класс.
Статический, или классовый, метод (не путать с методом класса)— принад-
лежит классу и вызывается через класс.
Класс Math, встроенный в JavaScript, содержит ряд статических свойств, хранящих
фундаментальные константы, и статических методов, выполняющих алгебраиче-
ские и тригонометрические вычисления над заданными числами.
I. Получим значение числа я, обратившись к статическому свойству pi класса
Math:
> Math.PT
< 3.141592653589793
2. Извлечем квадратный корень из числа 2, воспользовавшись статическим мето-
дом sqrt() класса Math:
> Mdth.sqrt (2)
< 1.4142135623730951
Выполняя упражнение 1.2, мы столкнулись с тем, что вычисление разности чи-
сел 5.3 и 2.74 дает неточный результат: 2.5599999999999996. Это связано с осо-
бенностями обработки дробных чисел в JavaScript (подробнее мы их рассмотрим
на уроке 5).
3. Произведем то же вычисление, только на этот раз округлим результат до третье-
го знака после запятой:
> let а = 5.3 - 2.74
< undefined
> Math.round(а * 1000) / 1000
< 2.56
Статический метод round () класса Math округляет число, переданное ему в каче-
стве единственного параметра, до целого.
Во втором выражении сначала значение переменной а будет умножено на 1000
(поскольку это действие взято в круглые скобки), потом выполнится метод
round(), а далее возвращенный им результат будет разделен на 1000.
До сих пор здесь рассматривались лишь встроенные классы.
|| Встроенный класс — класс, реализованный в исполняющей среде JavaScript.
Можно объявить и свои классы, создав в них произвольные свойства и методы.
Объявление классов мы будем изучать на уроке 11.
А пока что кратко познакомимся еще с одним программным инструментом.
|| Простой объект — объект, содержащий произвольный набор свойств.
I. Создадим простой объект, содержащий свойства name и version со значениями
’Google Chrome' И 1 16 соответственно. И сохраним его В константе browserData:
> const browserData = {
name: 'Google Chrome',
version: 116
)
< undefined
Содержимое простого объекта записывается в фигурных скобках и представляет
собой набор описаний его свойств, разделенных запятыми. Описание каждого
свойства состоит из его имени, символа двоеточия и значения, присваиваемого
свойству.
2. Извлечем значения свойств простого объекта browserData:
> browserData.name
< 'Google Chrome'
> browserData.version
< 116
3. 11рисвоим свойству version простого объекта browserData новое значение:
> browserData.version =117
< 117
1 Тростые объекты незаменимы, если требуется создать набор каких-либо произ-
вольных значений и в дальнейшем работать с ним как с единым целым (напри-
мер. передать функции или методу).
ПОЛЕЗНО ЗНАТЬ
Поскольку язык JavaScript работает с объектами, он носит название объектного, или
объектно-ориентированного.
1.7. Упражнение. Массивы
Часто требуется создать набор каких-либо значений, имеющих порядковые номера,
чтобы впоследствии извлекать эти значения по их номерам. Такой набор лучше
представить r виде массива.
Массив— изменяемый набор упорядоченных пронумерованных значений. Зна-
чения могут быть произвольными и принадлежать любому типу.
Элемент массива — отдельное значение, хранящееся в массиве.
Индекс элемента— порядковый номер элемента массива. Нумерация элемен-
тов массива начинается с нуля.
Размер массива — количество элементов, содержащихся в массиве.
Немного поработаем с массивами.
I. Создадим массив languages, содержащий названия различных языков програм-
мирования:
> let languages = ['JavaScript', 'Pyhton', 'PHP', 'Ruby']
< undefined
Для создания массива достаточно записать все входящие в него элементы через
запятую, взяв их в квадратные скобки.
2. Извлечем третий элемент массива languages:
> languages[2]
< 'PHP'
Получить нужный элемент массива можно, записав массив и указав после него
индекс требуемого элемента в квадратных скобках. Не забываем, что нумерация
элементов в массиве начинается с нуля, поэтому для получения третьего эле-
мента мы укажем индекс 2.
Вот незадача!.. Значение второго элемента массива — название языка програм-
мирования Python — мы записали с ошибкой (Pyhton).
3. Исправим значение второго элемента и проверим, действительно ли оно изме-
нилось:
> languages[1] = ’Python’
< 'Python'
> languages[1]
< 'Python'
] 1рисвоить новое значение элементу массива можно, поставив слева от операто-
ра присваивания массив и индекс изменяемого элемента, взятый в квадратные
скобки.
4. Добавим в массив еще один язык:
> languages[4] = ’C++'
< 'C++'
Для добавления нового элемента в массив достаточно присвоить нужное значе-
ние элементу, индекс которого на единицу больше индекса последнего из
имеющихся элементов. Новый элемент будет тотчас создан.
Массив — это объект класса Array, поддерживающий ряд свойств и методов.
5. Выясним размер массива languages:
> languages.length
< 5
Размер текущего массива хранится в свойстве length.
6. Узнаем, есть ли в массиве язык программирования Perl:
> languages.includes('Perl')
< false
Метод includes() класса Array ищет в текущем массиве элемент со значением,
переданным ему в качестве единственного параметра, и возвращает true, если
такой элемент есть, и false — в противном случае.
7. Выведем все элементы массива построчно:
> for (let language of languages)
window.console.log(language)
< JavaScript
< Python
< PHP
< Ruby
< C++
Здесь мы применили особую разновидность цикла, которая по очереди извлека-
ет из заданного массива элемент за элементом и выполняет над ними заданные
действия.
В первой строке после ключевого слова for в круглых скобках мы записали опе-
ратор объявления переменной language, в которую будет заноситься очередной
элемент массива, поставили ключевое слово of («из») и записали сам переби-
раемый массив. Во второй строке записано тело цикла, выводящее извлеченный
элемент в консоли.
Массив относится к довольно большой когорте классов, называемых последова-
тельностями.
Последовательность— набор упорядоченных значений, называемых элемен-
тами. Может быть изменяемой или неизменяемой (т. е. позволять или не по-
зволять изменять значения содержащихся в ней элементов), пронумерованной
или непронумерованной (предоставлять или не предоставлять доступ к элемен-
там по их индексам), хранить значения строго одного типа или относящиеся
к разным типам.
В частности, массив — это изменяемая последовательность пронумерованных эле-
ментов произвольного типа. Из всех последовательностей массив наиболее универ-
сален. вследствие чего находит наиболее широкое применение.
1.8. Самостоятельные упражнения
1. Вычислите длину гипотенузы прямоугольного треугольника с катетами дли-
ной 2 и 3 м.
2. Вычислите синус, косинус и тангенс угла 30°. Для этого используйте статиче-
ские методы sin(), cos о и tan() класса Math, в качестве единственного парамет-
ра принимающие значение угла.
Имейте в виду, что все эти три метода принимают значения угла в радианах. Для
пересчета углов из градусов в радианы используйте формулу:
СЦрад) Ct(гр) ' It 1 8О
Здесь а(рад) — угол в радианах, а|гр( — угол в градусах.
3. Объявите функцию для пересчета углов из градусов в радианы, дав ей имя на
свое усмотрение.
4. Выведите элементы массива languages (см. упражнение 1 7) в обратном порядке.
Подсказка: используйте разновидность цикла, рассмотренную в упражнении 1.4,
и оператор уменьшения значения переменной на единицу — (два минуса).
Урок 2
Введение
в веб-программирование
Язык JavaScript, с которым мы познакомились (пока, что называется, шапочно) на
уроке 7, предназначен для написания особого рода программ, называемых веб-
сценариями.
Программа— последовательность выражений, которые компьютер должен вы-
полнить для достижения нужного результата.
Веб-сценарий (или. просто, сценарии)— программа, манипулирующая содер-
жимым веб-страницы. Используется для придания с границе интерактивности.
На этом уроке мы начнем разрабатывать настоящие веб-сценарии и писать настоя-
щий программный код.
Программный код (или просто код) — текст программы, написанный на каком-
либо языке программирования.
2.1. Упражнение. Программное формирование
содержимого веб-страницы
Начнем с чего попроще. Возьмем изначально пустую, ничего не содержащую стра-
ницу и программно сформируем на ней таблицу, включающую целые числа от I
до I О, их квадраты и кубы.
I. Найдем в папке ZVsources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 2 1 html. Скопируем ее куда-либо на локальный диск.
Страница из файла 2.1 .html не имеет содержимого и включает лишь внутреннюю
таблицу стилей, задающую оформление создаваемой таблицы. Мы используем
этот файл в качестве «заготовки» для создания страницы с программно форми-
руемым содержимым
2. Откроем копию страницы 2 1 html в любом текстовом редакторе, предназначен-
ном для работы с обычными текстовыми файлами (например, комплектном
Блокноте), и найдем в ее HTML-коде пустой тег <body>.
3. Вставим в пустой тег <body> тег <script>:
<body>
<script>
</scrxpt>
</body>
Любой веб-сценарий. помещенный непосредственно в HTML-код страницы,
должен заключаться в парный тег <script>.
4. Поместим в тег <script> сам сценарий:
<body>
<script>
window.document.write('<table>');
window.document.write('<tr>’);
window.document.write('<еЬ>Число</th>');
window. document. write (' <th>4Mcnocsup>2</supX/th>1) ;
window. document. write ('<tii>4MCJio<sup>3</supX/th>') ;
window.document.write('</tr>');
for (let i = 1; i < 11; i++) {
window.document.write('<tr>');
window.document.write(* <td>' + i + '</td>');
window.document.write('<td>' + i ** 2 + '</td>');
window.document.write('<td>' + i ** 3 + '</td>‘);
window.document.write('</tr>');
}
window.document.write('</table>1);
</script>
</body>
Переменная window создается самой исполняющей средой и хранит объект клас-
са window, который представляет текущее окно веб-обозревателя. Свойство
document этого класса хранит объект класса HTMLDocument, представляющий от-
крытую в окне веб-страницу.
Класс HTMLDocument поддерживает метод write(), который выводит значение
своего единственного параметра в том месте страницы, в котором присутствует
его вызов Посредством этого метода можно вывести как обычный текст, так и
HTML-код, который будет обработан и преобразован в соответствующие эле-
менты страницы.
Мы выводим с помощью метода write() класса один за другим теги, создающие
нужную таблицу, ее строки и ячейки.
Для формирования строк таблицы с исходными числами, их квадратами и куба-
ми используем цикл, с которым познакомились при выполнении упражне-
ния 1.4.
Для вычисления степеней очередного числа применяем оператор возведения
в степень ** (две звездочки). Основание степени записывается слева от операто-
ра. показатель — справа.
И еще один важный момент. Выражения JavaScript, составляющие веб-сценарий,
должны завершаться символами точки с запятой ; Этот символ обозначает ко-
нец выражения и помогает исполняющей среде быстрее проанализировать код
и подготовить его к выполнению.
5. Сохраним открытую в текстовом редакторе страницу 2 1 htinl.
Внимание!
Перед тем как открыть (или перезагрузить) разрабатываемую веб-страницу е веб-обо-
зреиателе для тестирования, следует сохранить ее код, открытый в текстовом редак-
торе
6. Откроем готовую страницу 2 1 html в веб-обозревателе и посмотрим на результат
(рис. 2.I).
Проще всего открыть страницу в веб-обозревателе, перетащив в него файл стра-
ницы из Проводника или аналогичной утилиты для управления файлами. Также
можно переключиться на веб-обозреватель, нажать комбинацию клавиш
<Ctrl>+<O> и выбрать нужный файл в появившемся на экране стандартном диа-
логовом окне открыт ия файла.
Число Число2 Число3
1 2 1 1
— 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
Рис. 2.1. Результат выполнения упражнения 2 1
Всем хороша наша первая настоящая программа! Одно плохо: ее код чересчур
громоздок. Сократим его.
7. Исправим первое выражение сценария следующим образом:
window, oocumc-rit. ;wri to < '<tab]o>');
document,. write (' <table>') ;
Объект текущего окна веб-обозревателя, тот самый, что хранится в переменной
window, является глобальным.
Глобальный объект — объект, внутри которого выполняется программный код.
Обращаться к свойствам и вызывать методы глобального объекта можно без
указания самого этого объекта.
Следовательно, мы можем убрать из всех выражений сценария указание на объ-
ект window, тем самым заметно сократив код.
8. Исправим остальные выражения сценария аналогичным образом.
Еще раз внимание!
Не забываем сохранять исправленный код перед тестированием! Автор больше не
будет напоминать об этом
Перезагрузим открытую в веб-обозревателе страниц}' нажатием клавиши <F5>.
Страница должна открыться в том же виде, что показан на рис. 2.1.
Класс window также поддерживает свойство console, в котором хранится объект
класса Console, представляющий консоль веб-обозревателя. Класс Console поддер-
живает метод logo, который выводит в консоль переданное ему в параметре зна-
чение. Этот метод мы уже использовали при выполнении упражнений 1.4 и 1.7
и изучим подробно на уроке 16.
На заметку
Программный код на языке JavaScript можно набирать в любом текстовом редакторе,
предназначенном для работы с обычными текстовыми файлами (наподобие имеющих
расширение txt) Примером может послужить редактор Блокнот, поставляемый в со-
ставе Windows
Однако все же для разработки удобнее пользоваться редактором, специально пред-
назначенным для программистов Автор рекомендует Visual Studio Code (https://
code.visualstudio.com/)
2.2. Упражнение.
Доступ к элементам веб-страницы
Помимо использования метода write о объекта HTMLDocument, есть и другой способ
создать содержимое страницы программно. Сейчас мы им воспользуемся, чтобы
снова сформировать на странице таблицу чисел, их квадратов и кубов.
I. Найдем в папке 2\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 2.2 html (пустая «заготовка» для создания страницы). Скопиру-
ем ее куда-либо на локальный диск.
2. Откроем копию страницы 22.html в текстовом редакторе и найдем в ее коде сле-
дующий фрагмент:
<bcdy>
<table>
<theaa>
<7thead>
<tbody>
</tbody>
</table>
</body>
11ачиная c Windows 11в одном экземпляре Блокнота можно открывать произвольное количество
файлов, которые будут выводиться на отдельных вкладках. Это существенно упрощает работу с боль-
шими проектами, включающими множество файлов с программным кодом.
Часть таблицы — ее шапка — уже сформирована в HTML-коде. Нам останется
вставить HTML-код, создающий остальные строки таблицы, в секцию ее тела —
пустой тег <tbody> (выделен подчеркиванием).
Для чего этот тег следует предварительно как-то пометить.
3. Укажем у тега <tbody> якорь output:
<tbody>
<tbody id="output">
</tbody>
Якорь — уникальная пометка, задаваемая у тега в агрибуте id. Часто исполь-
зуется для обращения к этому тегу из веб-сценария.
Теперь мы сможем получить доступ к этому элементу, чтобы вставить в него
сгенерированное содержимое.
4. Вставим тег <script>. в котором позже запишем сценарий:
<body>
<rable>
<Xhead>
</thead>
ctbody id="output">
</tbody>
</table>
<script>
</scnpt>
</body>
Чтобы получить доступ к какому-либо элементу страницы, необходимо, чтобы
этот элемент уже был выведен па экран. Поэтому сценарий, работающий с этим
элементом, следует помещать в HTML-коде после создающего его тега. Обычно
сценарий помещают в конце секции тела страницы, непосредственно перед
закрывающим тегом <body>.
5. Впишем в тег <script> сценарий, создающий таблицу с числами:
<script>
const output = document.getElementById('output'); // .............. 1
let s = ' 1 ; // ............................................... 2
for (let 1 = 1; i < 11; i++) // .................................. 3
s = s + '<tr>' + // ....................................... 4
'<td>’ + i 4 '</td>' +
'<td>' + i ** 2 + '</td>‘ +
•<td>’ + i ** 3 + '</td>' +
'</tr>';
output. innerHIML = s; / / ....................................... 5
</script>
В приведенном здесь примере присутствуют строчки, начинающиеся с двойного
символа слеша //. Это комментарии JavaScript.
Комментарий— фрагмент программного кода, который игнорируется испол-
няющей средой. Служит для вставки в код всевозможных пометок, пояснений,
напоминаний и пр.
Автор использовал комментарии для вставки в выражения числовых пометок,
с помощью которых он будет ссылаться на эти выражения из следующих далее
пояснений.
Класс HTMLDocument, представляющий веб-страницу, поддерживает метод
getEIementByld (), который ищет в составе страницы элемент с заданным в пара-
метре якорем и возвращает объект, представляющий найденный элемент. Мы
использовали этот метод, чтобы иайги секцию тела таблицы с якорем output
(поз. 1 в приведенном примере).
Далее мы объявили переменную s и присвоили ей пустую строку (поз. 2). В этой
переменной будет храниться HTML-код, формирующий строки создаваемой
программно таблицы.
Потом мы написали цикл, который будет генерировать один за другим фраг мен-
ты H TML-кода. формирующие отдельные строки таблицы (поз. 3). Тело этого
цикла создает очередной фрагмент HTML-кода и добавляет его к коду, сгенери-
рованному ранее и хранящемуся в переменной s (поз. 4).
Еще раз посмотрим на тело цикла:
s = s + *<tr>’ + . . . + '</tr>';
С точки зрения JavaScript это выражение совершенно корректно'. Оно извлекает
из переменной $ хранящийся гам HTML-код, добавляет к нему очередной сгене-
рированный фрагмент кода и присваивает результат той же переменной s.
Все классы, представляющие элементы страницы (включая класс
HTMLTableSectionElement, представляющий секцию таблицы), поддерживают
свойство innerHTML. Оно хранит HFML-код, формирующий содержимое текуще-
го элемента страницы, в виде строки. Если присвоить этом) свойству строку
с другим HTML-кодом, в элементе появится новое содержимое, сформирован-
ное на основе обработки веб-обозревателем присвоенного кода. Что мы с успе-
хом и сделали (поз. 5).
Откроем страницу 2.2 html в веб-обозревателе. Таблица с числами должна выглядеть
так же, как показано на рис. 2.1.
'Это выражение можно сделать чуть компактнее, применив оператор сложного присваивания. Но
пока отложим это до урока 6. Нам и так нужно многое изучить на этом уроке...
2. 3. Упражнение. Обработка событий
С помощью веб-сценариев часто реализуют всевозможные интерактивные эффек-
ты, наподобие подсвечивания изображения при наведении на него курсора мыши.
Давайте и мы сделаем нечто подобное'.
I. Найдем в папке 2\'sources сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 2 3.html («заготовка»— страница с изображением) и 2.3 jpg
(само это изображение). Скопируем их куда-либо на локальный диск.
2. Откроем копию страницы 2 3 html в текстовом редакторе, найдем в ее HTML-
коде тег <img>, выводящий изображение, и укажем у него якорь interactive:
<body>
<div>
<img src-"2.3.jpg">
<ung id="interactive" src="2.3. зрд">
</div>
</body>
Автор предпочитает ставить в тег атрибут id, задающий якорь, самым первым,
а остальные атрибуты — после него.
Страница 2 3.html содержит внутреннюю таблицу стилей, содержащую два стиля:
первый задает у изображения степень непрозрачности, равную 0.2, второй, со
стилевым классом hovered, делает изображение полностью непрозрачным. Для
указания степени непрозрачности применяется CSS-атрибут opacity.
Следовательно, чтобы подсветить изображение при наведении на него курсора
мыши, следует привязать к нему стилевой класс hovered. А чтобы после увода
курсора мыши снять подсветку, достаточно убрать привязку этого стилевого
класса.
Но как в JavaScript выполнить какой-либо программный код в момент, когда
пользователь совершает то или иное действие? Реализовать этот код в виде
обработчика события.
Событие— сигнал от веб-обозревателя, сообщающий о том, что в элементе
страницы или ином объекте что-то произошло. Каждое из поддерживаемых
событий имеет уникальное имя.
Как только пользователь наводит курсор мыши на какой-либо элемент страни-
цы, веб-обозреватель сигнализирует этому элементу о возникновении в нем
события с именем mouseover. А при уводе курсора мыши с элемента в нем воз-
никает событие mouseout. Это лишь два из многочисленных событий, поддержи-
ваемых веб-обозревателем (мы с ними обязательно познакомимся).
Разумеется, такой эффект можно сделать исключительно средствами CSS. Но ведь тогда мы не на-
учимся обрабатывать события ..
Обработчик события— функция, выполняющаяся при возникновении опреде-
ленного события в определенном элементе страницы (или ином объекте).
Таким образом, для реализации подсветки изображения нам понадобится напи-
сать обработчики собы гий mouseover и mouseout, возникающих в этом изображе-
нии. Первый обработчик привяжет к изображению стилевой класс hovered,
второй уберет его.
3. Напишем этот веб-сценарий:
<body>
<div>
cimg id="interactive" src="2.3.jpg">
</div>
<script>
const interactive =
document.getElementById('interactive'); // ........... 1
function onlnteractxveMouseOver() { // ..................... 2
interactive className = 'hovered1; // .................. 4
}
function onlnteractiveMouseOut() { // ...................... 3
interactive.className = ''; // ......................... 5
}
interactive.addEventListener('mouseover',
onlnteractivewouseOver); // ... 6
interactive.addEventListener('mouseout',
onlnteractiveMouseOut); // .... 7
</scnpt>
</body>
Сначала получаем объект, представляющий изображение с якорем interactive,
вызовом метода getElementByld () класса HTMLDocument, знакомого по упражне-
нию 2.2 (поз. 1 в приведенном здесь коде). Этот объект сохраняем в переменной
interactive.
Далее объявляем функции onlnteractiveMouseOver() и onlnteractiveMouseOut (),
которые станут обработчиками событий соответственно mouseover и mouseout
(поз. 2 и 3).
Все классы, представляющие элементы страниц, поддерживают свойство
className, задающее привязанные к элементу стилевые классы и аналогичное по
назначению атрибуту тега class. Чтобы привязать к изображению стилевой
класс hovered, мы присваиваем свойству строку с именем этого стилевого класса
(поз. 4). а чтобы удалить стилевой класс — пустую строку (поз. 5).
Опять же. все классы элементов страницы поддерживают метод addEventListener (),
который привязывает к возникающему в текущем элементе страницы событию
с заданным в первом параметре именем обработчик, указанный во втором
параметре. Мы используем этот метод для привязки к изображению interactive
объявленных ранее обработчиков событий onlnteractiveMouseOver () и
onlnteractiveMouseOut() (ПОЗ. 6 И 7).
4. Откроем в веб-обозревателе страницу 2 4 htmf. Наведем на изображение курсор
мыши и посмотрим, что получится (рис. 2.2).
Рис. 2 2. Результат выполнения упражнения 2 4 слева — изначальное изображение,
справа — после наведения курсора мыши
Код сценария мы можем немного сократить.
5. Исправим объявление функции-обработчика события onlnteractiveMouseOver ()
следующим образом:
function onlnteractiveMouseOver() {
interactive. classNurtlo - ' hovered1;
this. className = ' hovered ' ;
}
Переменная this автоматически создается самой исполняющей средой и хранит
контекст выполнения.
Контекст выполнения — объект, хранящийся в переменной this. В разных
ситуациях r качестве контекста выполнения могут выступать разные объекты
(подробности — на следующих уроках).
В теле функции, используемой в качестве обработчика события, контекстом
выполнения, хранящимся в переменной this, выступает объект элемента стра-
ницы, в котором возникло событие.
В нашем обработчике событ ия контекстом выполнения станет объект изображе-
ния с якорем interactive, хранящийся также в одноименной переменной. Так
что мы можем просто заменить в теле функции-обработчика interactive на this.
6. Исправим объявление функции onlnteractiveMcuseOut () аналогичным образом.
Обновим открытую в веб-обозревателе страницу 2 3 html и наведем на изображение
курсор мыши. Все должно работать, как прежде.
2. 4. Упражнение.
Взаимодействие с элементами управления
Львиная доля веб-сценариев обрабатывает данные, занесенные пользователями
в разнообразные элементы управления: поля ввода, списки, флажки, переключа-
тели. регуляторы и др. Поскольку мы собираемся стать веб-разработчиками, нам
следует научиться работать с элементами управления.
Напишем страницу с полем ввода для указания величины, выраженной в дюймах,
и абзацем, в котором будет выводиться эта же величина, но выраженная в милли-
метрах. Это будет наше первое клиентское веб-приложение.
Клиентское веб-приложение — полноценное приложение, реализованное в ви-
де веб-страницы и работающее без обращения к каким-либо интернет-ресу р-
сам.
I Найдем в папке 2\'sources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 2 4 html (пустая «заготовка»). Скопируем ее куда-либо на
локальный диск
2. Откроем копию страницы 2 4 html в текстовом редакторе, найдем в ней поле вво-
да (тег <input>) и абзац (тег <р>), расположенный под веб-формой (тег <form>),
и укажем у них якоря inches и millimeters соответственно:
<body>
<form>
<р>
<input>
<input id="inches">
</p>
<px/p>
<p id="millimeters"X/p>
</body>
3. Напишем веб-сценарий:
<body>
<p id="millimeters"x/p>
<script>
const inches = document getElementByld('inches1);
const millimeters = document.getElementByld('millimeters 1);
function onlncheslnput() {
const inchesS = this.value; // ........................ 2
const inchesN = parseFloat(inchesS); // ............... 3
const rams = inchesN * 25.4; // ....................... 4
millimeters. textContent = mms; // .................... 5
}
inches.addEventListener('input', onlnchesInput); // ..... 1
</scnpt>
</body>
Код этого примера подобен тому, что мы написали при выполнении упраж-
нения 2.3. Здесь мы аналогичным образом получаем доступ к объектам поля
ввода inches и абзаца millimeters, объявляем функцию-обработчик события
onlnchesinput () и привязываем его к событ ию. Гак что имеет смысл сосредото-
читься лишь на отличиях от кода из предыду mei о примера.
Прежде всего, обработчик мы привязываем к событию input поля ввода input
(поз. I в приведенном коде). Это событие возникает в поле ввода сразу же, как
только пользователь изменяет присутствующее в нем значение.
Величину в дюймах, занесенную пользователем в поле ввода input, мы извлека-
ем из свойства value, поддерживаемого классом HTMLInputElement, который
представляет поле ввода (поз. 2). Отметим, что это свойство хранит значение
в виде строки.
Перед выполнением вычислений величину в дюймах, представляющую собой
строку, следует преобразовать в число. Мы делаем это. передав строковую вели-
чину встроенной функции parseFloatO в качестве единственною параметра
(поз. 3). Функция преобразует переданную ей величину в дробное число и вер-
нет результат.
Переводим величину из дюймов в миллиметры, умножив ее на 25,4 (поз. 4).
Чтобы вывести результат в абзаце millimeters, мы присваиваем этот результат
свойству textcontent абзаца (поз. 5). Это свойство поддерживается всеми клас-
сами элементов страницы и хранит текстовое содержимое текущего элемента.
Значение, присвоенное этому свойству, не проходит обработку веб-обозре-
вателем с целью преобразовать присутствующие в нем HTML-теги в соответ-
ствующие элементы страницы (в отличие от свойства innerHTML, с которым мы
познакомились при выполнении упражнения 2.2). Благодаря этому вывод ново-
го значения на страницу производится быстрее (правда, если в значении присут-
ствуют HTML-теги, они будут выведены как есть).
Откроем страницу 2.4 htm! в веб-обозревагеле. Запишем в поле ввода какое-либо
целое число и посмотрим на результат (рис. 2.3). Далее занесем в поле дробное
число, использовав для разделения целой и дробной частей точку (не запятую),
и также посмотрим, что получится.
12
304.79999999999995
Рис. 2.3. Результат выполнения упражнения 2 4
2.5. Самостоятельные упражнения
1. Добавьте в выводимую таблицу квадратов и кубов чисел (примеры из упраж-
нений 2.] и 2.2) четвертый столбец — с числами, возведенными в четвертую
степень.
2. Реализуйте в примере из упражнения 2.4 округление результата до трет ьего зна-
ка после запятой
3. Напишите страницу, которая будет вычислять синус, косинус и тангенс угла,
занесенного в поле ввода и выраженного в градусах.
ЧАСТЬ II
Язык JavaScript
Ф Урок 3. Основные понятия JavaScript
Ф Урок 4 Логические величины и управляющие выражения
Ф Урок 5 Числа
Ф Урок 6 Строки
О Урок 7 Временные отметки
Ф Урок 8 Функции
Ф Урок 9 Массивы, множества и словари
Ф Урок 10 Объекты
Ф Урок 11 Классы
Ф Урок 12. Итераторы и генераторы
Ф Урок 13. Регулярные выражения
Ф Урок 14 Модули
Ф Урок 15 Обработка программных ошибок. Исключения
Ф Урок 16 Средства отладки
Урок 3
Основные понятия JavaScript
На уроке 1 мы познакомились с языком программирования JavaScript, что называ-
ется, шапочно. Настала пора изучить его более подробно.
Эта часть книги в основном теоретическая и описывает программные инструменты,
применяемые повсеместно. Практических упражнений будет немного (однако мы
обязательно наверстаем это в плане практики в последующих частях).
Начать следует с базовых принципов, лежащих в основе этого языка. И заодно
вспомнить изученное ранее.
3.1. Выражения, программы и веб-сценарии
Выражение— это указание компьютеру выполнить какое-либо законченное
действие, записанное на том или ином языке программирования. Выражение
может быть простым или сложным, состоящим из простых выражений.
Программа— это последовательность выражений, которые компьютер должен
выполнить для достижения нужного результата.
Веб-сценарий— это программа, которая манипулирует содержимым веб-
страницы с целью дополнить его или реализовать какие-либо интерактивные
эффекты.
3.1.1. Правила написания выражений
♦ Выражения языка JavaScripl записываются в виде обычного текста, в текстовой
кодировке UTF-8.
♦ Для наилучшего восприятия программного кода между отдельными командами
и языковыми конструкциями можно вставлять произвольное количество про-
бельных символов, к которым относятся собственно пробелы, символы табуля-
ции и разрывы строк Исполняющая среда игнорирует пробельные символы.
Например, следующие три выражения полностью корректны и при вычислении
выдадут одинаковый результат:
let а = 2 + 3;
let а=2+3;
Зет а = 2 + 3;
То же самое допускается при вызове функций, обращении к элементам масси-
вов. свойствам объектов и вызове их методов:
let b = [1, 2, 3, 4|;
let b = [1,2,3,4];
let b = [ 1, 2, 3, 4 ] ;
let c = parseFloat('1234.567?');
let c = parseFloat( '1234.5678' );
let pi = Math.PI;
let pi = Math . PI;
Однако следует помнить, что между некоторыми языковыми конструкциями
пробелы обязательны. Гак, между оператором let и именем объявляемой пере-
менной нужно поставить пробел — иначе мы получим ошибку.
Как говорилось ранее, к пробельным символам относятся также разрывы строк.
Вот пример выражения, создающего массив, которое занимает несколько строк:
const languages = [
'JavaScript',
'Python',
'PHP',
'Ruby'
];
Обычно на отдельные строки разбивают слишком длинные выражения.
♦ Как правило, одно выражение занимает одну строку текста:
let а = 10;
let b = 20;
let с = а + Ь;
♦ Короткие выражения можно набрать в одну строку, разделив их пробельными
символами:
let а = 10; let b = 20; let с = а + Ь;
Это позволит сократить код, однако сделает его менее наглядным.
♦ Отдельные части программного кода допускается разделять пустыми строками.
Исполняющей средой пустые строки игнорируются Пример:
const inches = document.getElementByld('inches');
const millimeters = document.getElementByld('millimeters');
function onlncheslnput() {
}
inches.addEventListener('input', onlncheslnput);
Обычно пустыми строками отделяют друг от друга части кода, выполняющие
какие-либо законченные операции: выражения, объявляющие множество пере-
менных, объявления функций, классов и т. п.
♦ Выражения, являющиеся частями сложных выражений {вложенные выраже-
ния), набирают'с отступами слева.
Пример условного выражения, в котором вложенные выражения, выполняемые
при истинности или ложности условия, набраны с отступами:
if (2.5 * 4 > 80 / 10)
console.log('Да, 2.5 * 4 больше, чем 80 / 10!');
else
console.log('Нет, 2.5 * 4 не больше, чем 80 / 10');
1 Тример цикла, в котором тело также набрано с отступом слева:
for (let i = 1; i < 11; 1++)
console.log(i * i);
Пример цикла с телом, содержащим условное выражение, в котором вложенные
выражения набраны с удвоенными отступами, чтобы показать их двойную вло-
женность (оператор % делит нацело поставленное слева делимое на делитель,
записанный справа, и возвращает остаток отделения):
for (let i = 1; i < 11; i++)
if (i % 2 = 0}
console.log('Число ' + i + ' четное');
else
console.log('Число ' + i + ' нечетное');
Отступ обычно создается пробелами в количестве, кратном 4 (единичный от-
ступ — 4 пробела, двойной — 8 и т. д.). Иногда ради компактности кода отступы
создают пробелами в количестве, кратном 2, но такой код хуже читается.
♦ Каждое выражение должно завершаться символом точки с запятой (;), включая
выражения, набранные в одну строку. Этот символ сообщает исполняющей сре-
де. что очередное выражение закончилось.
Нужно отметить, что JavaScript допускает отсутствие символа точки с запятой
в конце выражений. Но в этом случае исполняющей среде придется проводить
дополнительный анализ кода, чтобы понять, где заканчивается одно выражение
и начинается другое. Это отнимает дополнительное время и замедляет запуск
сценария.
Полезно знать
♦ Синтаксис языка JavaScript похож на таковой, используемый в языке С. Поэтому
JavaScript называют С-подобным языком. Другие С-подобные языки: C++, С#, PHP.
♦ JavaScript допускает написание пустых выражений, представляющих собой еди-
ничные символы точки с запятой:
г
Пустое выражение не выполняет никаких действий и является, скорее, курьезом,
нежели чем-то полезным.
3.1.2. Встраивание веб-сценариев в веб-страницы
Чтобы веб-сценарий смог работать с содержимым страницы, он должен быть тем
или иным образом встроен в эту страницу.
Для встраивания веб-сценария, написанного на JavaScript, в HTML-код страницы
применяется парный тег <script>.
Веб-сценарий может принадлежать к одному из двух видов, описанных далее, —
в зависимости от способа его встраивания.
♦ Внутренний веб-сценарий — полностью записывается непосредственно в H I ML-
коде страницы, внутри тега <script>. Пример:
<img id="interactive" src="2.3.jpg">
<script>
// Получаем доступ к элементу страницы
const interactive = document.getElementByld(’interactive’);
</script>
Такие веб-сценарии нам уже знакомы. При выполнении упражнений из урока 2
мы написали целых четыре.
В качестве вну треннего рекомендуется оформлять веб-сценарий, который имеет
небольшой объем (не более 20 строк) и должен находиться лишь в одной веб-
странице. В ряде случаев (в частности, при разработке простых сайтов) это
позволит немного упростить сопровождение (в основном за счет уменьшения
количества файлов, в которых хранится код сайта).
Также внутренние сценарии пишутся при обучении программированию (как
в нашем случае) и в процессе экспериментов с веб-технологиями.
Во всех остальных случаях настоятельно рекомендуется применять внешние
веб-сценарии.
♦ Внешний веб-сценарий— сохраняется в отдельном файле, который далее привя-
зывается к странице.
Привязка внешнего веб-сценария — указание в HTML-коде веб-страницы ссыл-
ки на файл с внешним веб-сценарием.
Файл с внешним веб-сценарием записывается в кодировке UTF-8, в обычном
текстовом формате1, с расширением js Программный код. сохраняемый в таком
файле, не должен заключаться в тег <script>.
Для привязки внешнего сценария в HTML-коде страницы ставится пустой тег
<script> с атрибутом src, в котором записывается интернет-адрес привязывае-
мого файла сценария-
В таком же формате сохраняются текстовые файлы с расширением txt
<!— Файл с веб-страницей —>
<img id="interactive" src=-"2.3.jpg">
<script src="script. js"x/script>
// Файл с внешним веб-сценарием script.js
const interactive = document.getElementByld('interactive');
Внешними делают веб-сценарии, которые имеют достаточно большой объем и
подлежат привязке к нескольким веб-страницам. Это существенно упрощает со-
провождение сайтов (так. если потребуется исправить ошибку в программном
коде сценария, достаточно внести правки в один файл — файл сценария).
Количество сценариев, встроенных в страниц}, не ограничено. Пример встраивания
грех сценариев’
<script src="scriptl. js"x/script>
<script src="scripc2.js"x/script>
<script>
</script>
Местоположение тегов <script> с веб-сценариями в HTML-коде страницы может
быть произвольным. Однако, как правило, эти теги располагают в одном из двух
мест HTML-кода:
♦ в конце секции тела страницы, перед закрывающим тегом <body>:
<html>
<body>
<script>
// Веб-сценарий располагается здесь
</script>
</body>
</html>
Такое расположение гарантирует, что перед запуском сценария весь HTML-код
будет прочитан и обработан, а все элементы страницы, описываемые им. —
успешно сформированы. В результате, если сценарий обратится к какому-либо
элементу страницы, он без проблем получит представляющий его объект;
♦ в секции заголовка страницы (в теге <head>):
<html>
<head>
<script src="script. js"x/script>
</head>
</html>
Однако в этом случае сценарий выполнится еще до того, как веб-обозреватель
сформирует все элементы страницы. И при попытке получить доступ к какому-
либо элементу страницы возникнет ошибка.
Устранить эту ошибку можно, предписав веб-обозревателю загружать сцена-
рии одновременно с обработкой страницы и выполнять их строго после завер-
шения ее обработки. Для этого в гег <script> следует вставить атрибут без зна-
чения defer. Пример:
<html>
<head>
<script src="script.js" deferx/script>
</head>
</htnd>
Внимание!
Атрибут без значения defer тега <script> принимается во внимание пишь в случае,
если встраиваемый веб-сценарий является внешним
3.1.3. Выполнение веб-сценариев
Встретив в HTML-коде тег <script> с веб-сценарием, веб-обозреватель поступает
следующим образом:
♦ если в теге <script> отсутствует атрибут defer, или сценарий является внут-
ренним, приостанавливает обработку HTML-кода на этом месте, загружает
(если сценарий является внешним) и выполняет сценарий:
♦ если в теге <script> присутствует атрибут defer — продолжает обработку
HTML-кода, параллельно запускает загрузку сценария и выполняет последний,
как только обработка HTML-кода закончится.
Сценарий выполняется лишь один раз. Чтобы исполнить его повторно, придется
перезагрузить открытую в веб-обозревателе страницу.
Все переменные, функции и классы, объявленные в коде сценария (объявлениями
классов мы займемся на уроке 77). по завершении его работы сохраняются в памя-
ти. Благодаря этому, в частности, функции, привязанные к событиям в качестве
обработчиков, будут успешно выполнены при возникновении этих событий.
Полезно знать
Исполняющая среда перед выполнением программного кода производит его компиля-
цию в машинный код.
Компиляция— преобразование программы, написанной на каком-либо языке
программирования, в представление, подходящее для выполнения (например,
в машинный код).
Машинный код— команды, непосредственно выполняемые центральным про-
цессором компьютера.
Это делается для повышения производительности выполнения сценария.
3.2, Ключевые слова JavaScript
Ключевое слово используется для написания различных языковых конструкций:
операторов, сложных выражений, объявлений и др.
JavaScript включает следующие ключевые слова: async, await, break, case, catch,
class, const, continue, debugger, default, delete, do, else, export, extends, false,
finally, for, function, if, import, in, instanced, let, new. null, return, static, super,
switch, this, throw, true, try, typeof, undefined, var, void, while, with1 и yield.
Часть ключевых слов нам уже знакома. Слова true и false используются для указа-
ний значений логического типа, if и else — при написании условных выражений,
for— циклов, a function — в объявлениях функций.
Внимание!
Ключевые слова следует набирать полностью в нижнем регистре. В противном случае
исполняющая среда не сочтет их ключевыми словами, и возникнет ошибка.
Давать объявляемым сущностям (переменным, функциям и классам) имена, совпа-
дающие с ключевыми словами, не допускается. При попытке сделать этс возникнет
ошибка типа syntaxError (ошибка - синтаксисе выражения)
Пример:
> let true = О
< VM679:1 Uncaught SyntaxError: Unexpected token ’true'
3.3. Комментарии
Комментарий— фрагмент программного кода, игнорируемый исполняющей сре-
дой. Служит для размещения в программном коде заметок, пояснений и напомина-
ний, предназначенных для программистов.
JavaScript поддерживает два вида комментариев:
♦ однострочный— начинается с последовательности символов // (два слеша)
и продолжается до конца строки. Может как занимать отдельную строку, так и
записываться в конце какого-либо выражения. Пример:
// Выводим в консоли квадраты чисел от 1 до 1G
for (let i = 1; i < 11; i++)
window.console.log(i * i) // Сократить этс выражение потом
Ключевое слово with применяется для написания языковой конструкции, которая объявлена уста-
ревшей и не рекомендованной к применению. В настоящей книге эта конструкция не рассматрива-
ется.
♦ многострочный — начинается с последовательности символов /* и заканчивает-
ся последовательностью */. Обычно занимает отдельные строки. Пример:
/*
Функция getDiv(content, className) возвращает HTML-код,
создающий блок (тег <div>).
Параметры:
content (строка) — содержимое создаваемого блока;
className (строка) — имя стилевого класса, привязываемого к блоку.
*/
function getDiv(content, className) {
}
Полезно знать
Иногда в процессе отладки сценария требуется сделать так. чтобы какой-либо фрагмент
кода не выполнялся исполняющей средой. Для этого следует превратить такой фраг-
мент в комментарий — выполнить его закомментирование. Пример:
const interactive— document.gotElomencBylat'interactive1};
// const interactive = document.getElementBy!d('interactive');
Впоследствии следует произвести обратное действие— раскомментирование:
// const interactive - document.gctElementByldi'interactive');
const interactive = document getElementByld('interactive');
3.4. Строгий режим
Строгий режим — режим выполнения веб-сценария, при котором в языке дей-
ствуют более жесткие правила и ограничения.
Так, в обычном режиме можно присвоить значение еще не объявленной перемен-
ной— в результате переменная будет объявлена неявно, и ей присвоено значение.
В строгом режиме попытка сделать это вызовет программную ошибку;
Чтобы переключить веб-обозрсвагель в строгий режим, достаточно самым первым
выражением в сценарии поставить строку 'use strict':
// Включаем строгий режим
'use strict';
const interactive = document.getElementByld!'interactive');
Будучи включенным, строгий режим будет действовать при выполнении всею по-
следующего кода без исключения. Отключить его нельзя.
Более подробно правила и ограничения, действующие при строгом режиме, мы
рассмотрим далее.
Строгий режим рекомендуется включать при разработке сложных решений— это
позволит избежать целого ряда ошибок.
3.5. Блоки
К сожалению, тело цикла может быть оформлено лишь в виде единичного выра-
жения:
for (let. i = 1; i < 11; i++)
console.log(i * i);
Если же понадобится написать тело цикла, состоящее из нескольких выражений,
например:
let q;
for (let. i = 1; i < 11; i++)
q = i * i;
console.log(q);
то исполняющая среда посчитает телом цикла лишь выражение, вычисляющее
квадрат очередного числа и присваивающее его переменной q, и многократно
выполнит только его. А выражение, выводящее результат в консоли, выполнится
всего один раз, поскольку оно уже не входит в состав тела цикла.
То же самое касается выражений, входящих в состав условных выражений и вы-
полняющихся при истинности или ложности заданного условия
Устранить эту проблему можно, применив блок.
Блок, или блочное выражение — объединяет произвольное количество других
выражений.
Формат написания блока:
{
<выражения, объединяемые в блок>
Пример:
for (let i = 1; i < 11; i++)
{
q = i * i;
console.log(q);
}
Вот теперь все выполнится как надо.
Сложное выражение, включающее блок, можно записать чуть короче, поставив
открывающую фигурную скобку в конце предыдущей строки:
for (let. i = 1; i < 11; i++) {
q = i * i;
console.log(q);
3.6. Операторы, их приоритет
и последовательность выполнения.
Круглые скобки
Оператор- команда языка программирования, выполняющая какое-либо элемен-
тарное действие над переданными ему значениями (операндами). Вот пример
выражения, использующего оператор математического сложения +:
let sum = 2 + 3;
В числе операторов, поддерживаемых JavaScript, есть:
♦ унарные— принимающими один операнд.
Так, оператор увеличения значения переменной на единицу ++ является унар-
ным;
♦ бинарные — принимающими два операнда.
К ним относятся почти все математические операторы;
♦ тернарный — принимающий три операнда.
Также есть специальные операторы, не принимающие операндов и принимающие
произвольное их количество.
Все операторы будут рассмотрены на следующих уроках.
Очередность выполнения разных операторов задается их приоритетом. Операторы
с большим приоритетом выполняются раньше операторов с меньшим приоритетом.
Очередность исполнения операторов с одинаковым приоритетом (равно как и не-
скольких экземпляров одного и того же оператора) задается последовательностью
их выполнения. Разные операторы выполняются в направлении либо слева направо,
либо справа налево.
Приоритет и последовательность выполнения операторов JavaScript приведены
в приложении 1.
Рассмотрим пример:
> 49 / 7 + 4*5
< 27
Операторы деления / и умножения * имеют больший приоритет, чем оператор сло-
жения +. Все эти операторы выполняются в порядке слева направо. Поэтому снача-
ла число 49 будет разделено 7. потом 4 будет умножено на 5, и затем полученные
частное и произведение будут сложены.
Повысить приоритет оператора можно, заключив его. вместе с операндами, в круг-
лые скобки.
Исправим приведенный пример следующим образом:
> 49 / (7+4) *5
< 22.27272727272727
Поскольку оператор + заключен в круглые скобки, ею приоритет повысился. Сле-
довательно, сначала будут сложены числа 7 и 4, далее 49 будет разделено на полу-
чившуюся сумму, а та. в свою очередь, окажется умноженной на 5. Результат полу-
чится совсем другим.
В круглые скобки можно брать не только отдельные операторы, но и части выра-
жений.
Рассмотрим такой пример:
> (20 + 12 / 3) /8
< 3
Сначала 12 делится на 3. потом 20 прибавляется к получившемуся частному, и. на-
конец, их сумма делится на 8.
Операторы, которые находятся в составе части выражения, уже находящейся
в круглых скобках, также можно заключать в круглые скобки, чтобы повысить их
приоритет в еще большей степени.
Исправим предыдущий пример:
> ((20 + 12) / 3) / 8
< 1.3333333333333333
Сначала складываются 20 и 12, потом сумма делится на 3, а затем полученное
частное делится на 3.
Вложенные круглые скобки встречаются в выражениях JavaScript довольно часто.
3.7. Типы данных
Тип данных определяет характер значения и набор выполняемых над ним опера-
ций.
JavaScript поддерживает восемь типов данных:
♦ числовой;
♦ тип больших целых чисел (не путать с числовым!):
♦ строковый;
♦ логический;
♦ функциональный (функции);
♦ объектный (объекты, к этому типу также относятся массивы);
♦ тип эмблем;
♦ неопределенный (undefined).
Числовой, строковый и логический типы нам уже немного знакомы. Их. а также
остальные типы мы подробно изучим позже.
3.7.1. Получение типа данных,
к которому принадлежит значение
Выяснить, к какому типу принадлежи! указанное значение, позволяет унарный опе-
ратор получения типа typeof, записываемый в одном из следующих форматов:
typeof <значенпе>
typeof(<значенпе>)
Какой из форматов использовать — дело вкуса.
Оператор typeof в качестве результата возвращает обозначение типа, представлен-
ное в виде одной из следующих строк:
♦ 'number' — числовой тип;
♦ 'bigint' —тип больших целых чисел;
♦ ' string' — строковый тип;
♦ 'boolean'—логический;
♦ ' function' — функциональны й;
♦ 'object'—объектный;
♦ 'symbol' — тип эмблем;
♦ 'undefined' — неопределенный.
Внимание!
В дальнейшем не представляющие интереса результаты вычисления некоторых вы-
ражений (например, значения undefined, возвращаемые выражениями циклов и
объявлениями функций), выводимые в консоли, в тексте примеров приводиться не
будут — для краткости
Несколько примеров:
> typeof 'HTML и CSS'
< 'string'
> typeof 1
< 'number'
> typeof false
< 'boolean'
> // Допускается объявить функцию с «пустым» телом
> function someFunc() {}
> typeof(someFunc)
< 'function'
> typeof new Date()
< 'obj ect'
Приоритет оператора typeof выше, чем у всех арифметических операторов. Поэто-
му, если потребуется выяснить тип результата каких-либо вычислений, фрагмент
выражения, выполняющий эти вычисления, следует взять в круглые скобки (как
рекомендуется в разд. 3.6). Пример:
> typeof (1+2)
< 'number'
> // Без круглых скобок оператор typeof вернет тип числа 1 — 'number',
> // поскольку имеет более высокий приоритет, чем оператор +.
> // Далее оператор + присоединит к возвращенной строке 'number' число 2,
> // преобразованное в строку.
> typeef 1+2
< 'number2'
Также можно использовать второй формат оператора получения типа — с круглы-
ми скобками:
> t ypeof(1 + 2)
< 'number'
3.7.2. Неопределенный тип {undefined)
Неопределенный тип— поддерживает единственное значение undefined, кото-
рое сигнализирует об отсутствии какого бы то ни было «значащего» значения.
Значение неопределенного типа undefined неявно присваивается любой перемен-
ной. которая была объявлена без явного присваиваний ей значения:
> Объявляем переменную а, не присвоив ей никакого значения
> let а
> // Обращаемся к этой переменной, чтобы получить ее значение
> а
< undefined
3.7.3. Значащие и ссылочные типы данных
Типы данных, поддерживаемые JavaScript, делятся на значащие и ссылочные.
Значение значащего (или примитивного, или элементарного) типа хранится
непосредственно в переменной.
При присваивании значения значащего типа, хранящегося в какой-либо пере-
менной, другой переменной ей присваивается копия значения.
К значащим типам относятся числовой, больших целых чисел, строковый,
логический, эмблем и неопределенный.
Пример-
> let а = 1
> // В переменной b окажется копия числа из переменной а
> let b = а
> // Для проверки присвоим переменной а другое значение
> а = 2
> // Значение переменной а изменилось...
> а
< 2
> // ...а в переменной b осталось старое значение
> Ь
< 1
Значение ссылочного типа хранится в отдельной области памяти, а переменной
присваивается ссылка на эту область.
При присваивании значения ссылочного типа, ссылка на которое хранится в ка-
кой-либо переменной, другой переменной ей присваивается копия ссылки.
В результате обе переменные станут ссылаться на одно и то же значение.
К ссылочным типам относятся функциональный и объектный.
Пример с массивом (массивы относятся к объектному типу):
> let с = [1, 2, 3, 4]
> // В переменной d окажется копия ссылки на массив из переменной с
> let d = с
> с[3]
< 4
> df3]
< 4
> // Изменим значение 4-го элемента массива, обратившись к переменной с
> с[3] = 123456Т8
> // Получим значение 4-го элемента, обратившись к переменной с
> с[3]
< 12345678
> // Получим значение 4-го элемента, обратившись к переменной d
> d[3]
< 12345678
> // Вывод: переменные end ссылаются на един и тот же массив
Внимание!
В дальнейшем ради простоты мы будем говорить, что какая-либо переменная хранит,
а какая-либо функция возвращает значение ссылочного типа, а не ссылку на него
3.8. Присваивание
Присваивание — это сохранение значения в переменной. Может быть простым,
комбинированным и условным.
3.8.1. Простое присваивание
Простое присваивание, пли, просто, присваивание — сохраняет заданное зна-
чение в указанной переменной.
Простое присваивание заданного значения указанной переменной выполняется опе-
ратором присваивания = (знак равенства), который записывается в формате:
<переменная> = <.значение>
В качестве результата оператор = возвращает присвоенное значение.
Переменная должна быт ь предварительно объявлена.
Значение может быть как записано непосредственно:
> let num
> num = ЗСО
< 100
гак и представлять собой результат каких-либо вычислений:
> num - 10 + 40 * 7
< 290
Приоритет оператора = ниже приоритетов практически всех операторов, поддержи-
ваемых JavaScript. Поэтому сначала выполняются вычисления, а уже потом их
результат присваивается переменной.
Порядок выполнения оператора присваивания — справа налево Благодаря этому
можно присвоить одно и то же значение сразу нескольким переменным, записав
единственное выражение:
nwnl = num2 = num3 = 90;
Сначала выполнится крайний правый оператор присваивания — он присвоит пере-
менной питЗ число 90 и вернет это же число в качестве результата. Далее выпол-
нится средний оператор присваивания — он присвоит результат, возвращенный
расположенным правее оператором = (число 90), переменной num2 и вернет это же
число. Наконец, выполнится крайний левый оператор присваивания, который со-
хранит результат выполнения предыдущего оператора = (то же число 90) в пере-
менной numl.
3.8.2. Комбинированное присваивание
Комбинированное присваивание— извлекает значение из заданной переменной,
выполняет над ним и указанным операндом какое-либо действие и сохраняет
результат в гой же переменной.
Такое присваивание выполняется одним из операторов комбинированного присваи-
вания (будут рассмотрены позже):
<переменная> <оператор комбинированного присваиваний <операнд>
Все операторы комбинированного присваивания возвращают результат произве-
денного действия.
Так, оператор =+ складывает значение переменной с операндом, заносит получив-
шуюся сумму в ту же переменную и возвращает эту же сумму:
> let num =100
> num += 200
< 300
> num
< 300
Любой оператор комбинированного присваивания выполняется быстрее, чем ком-
бинация оператора, выполняющего заданное вычисление, и оператора простого
присваивания.
3.8.3. Условное присваивание
Условное присваивание— присваивает заданное значение указанной переменной
только в том случае, если переменная хранит «пустое» значение: undefined или
null (нулевая ссылка, описана в разд. 10.6).
Оно производится операторам условного присваивания ??=:
<переменная> ??= <значение>
Пример:
> let langl
> let lang2 = 'JavaScript’
> langl ??= 'Python'
> langl
< 'Python'
> lang2 ??= 'Python'
> lang2
< 'JavaScript'
3.9. Оператор выбора
Оператор выбора ?? возвращает значение операнда 1, если оно не является «пус-
тым» (undefined или null), и значение операнда 2— в противном случае:
<операнд 1> ?? <операнд 2>
Пример:
> let langl
> let lang2 = 'JavaScript'
> langl ?? 'Python'
< 'Python'
> lang2 ?? 'Python'
< 'JavaScript'
Оператор ?? похож на оператор условного присваивания ??= (см. разд. 3.8.3). толь-
ко просто возвращает результат, а не выполняет присваивание.
3.10. Переменные и константы
Переменная — это ячейка памяти, имеющая уникальное имя и предназначенная для
хранения одного произвольного значения.
Если присвоить переменной другое значение, ранее хранившееся в ней значение
будет потеряно.
3.10.1. Объявление переменных. Область видимости
Объявление переменной— создание переменной с заданным именем, возможно,
с одновременным присваиванием ей указанного значения. Выполняется посредст-
вом одного из поддерживаемых операторов объявления переменных'.
<оператор объявление <описания объявляемых переменны:-, через запятухз>
Между оператором объявлениям описаниями переменны:: должен присутствовать хотя
бы один пробел.
Каждое описание объявляемой переменной может быть записано в одном из двух
форматов:
<имя объявляемой переменной>
<имя объявляемой переменной> = <изначальное значение>
Первый формат просто объявляет переменную с заданными именами заносит в нее
значение undefined. Второй формат сразу же присваивает объявленной переменной
указанное изначальное значение.
Имя объявляемой переменной должно включать только буквы, цифры, символы дол-
лара ($), подчеркивания (_) и не должно начинаться с цифры. Прочие символы,
в частности пробелы, в имени не допускаются. Также имя переменной не должно
совпадать ни с одним из ключевых слов JavaScript (см. разд. 3.2).
Примеры правильных имен переменных: а, Ь, с, languageName, private, $special.
Неправильные имена: 4abc, language name.
Имя переменной должно быть уникальным в пределах области видимост и этой пере-
менной1.
Область видимости— часть программного кода, в котором доступна объяв-
ленная переменная или иная сущность (функция или класс).
Различные области видимости будут описаны чуть позже.
Внимание!
Имена переменных чувствительны к регистру символов
Так, language. Language и LANGUAGE — ЭТО разные переменные.
Что вполне понятно — ведь обращение к переменной производится по ее имени.
Внимание!
Объявление переменной должно предшествовать первому обращению к ней.
Примеры:
// Просто объявляем переменную
let а;
// Объявляем переменную и сразу же заносим в нее значение
let Ь = 10;
// Можно объявить произвольное количество переменных,
// указав их описания через запятую
let с, flag = true, platform = 'Node.js';
Поддерживаемые операторы о&ьявленпя переменных:
♦ let — объявляет обычную переменную, которой в дальнейшем можно присваи-
вать другие значения Примеры применения этого оператора были приведены
ранее.
Область видимости переменной, объявленной с применением этого оператора,
зависит от того, где она была объявлена:
• в обычном коде, не являющемся блоком и телом функции.— переменная
доступна везде (глобальная область видимости}.
Переменная, имеющая глобальную область видимости, носит название гло-
бальной',
• внутри блока — только в этом блоке и вложенных в него блоках (область
видимости уровня блока, блочная переменная}.
Как только выполнение блока завершится, все объявленные в нем блочные
переменные будут уничтожены. Попытка обратиться к ним вне блока вызовет
ошибку типа ReferenceErr зг (ошибка обращения к переменной).
Пример:
> // Объявляем глобальную переменную
> let а = 1;
> а
< 1
> // Создаем блок с блочной переменной
> {
let b = 2;
console.log(Ь);
>
> // Блочная переменная доступна лишь в блоке...
> 2
> // ...но не вне его
> Ь
< VM171:1 Uncaught ReferenceError: b is not defined
at <anonymous>:1:1
> // В отличие от глобальной переменной
> а
< 1
Повторное объявление переменной с тем же именем недопустимо. Попытка сде-
лать это вызовет ошибку типа SyntaxError. Пример:
let b;
let b;
// Результат — ошибка:
// VM326:3 Uncaught SyntaxError: Identifier 'b' has already been declared
Оператор let применяется наиболее часто, поскольку позволяет предотвратить
конфликт имен.
Конфликт имен— объявление двух разных сущностей (переменных, функций
или классов) с одинаковыми именами.
♦ const — то же самое, что и let, только объявляет константу (переменную,
которой значение можно присвоить лишь единожды,— непосредственно при
объявлении):
> const languageName = 'JavaScript'
Попытка присвоить константе другое значение вызовет ошибку тина ТуреЕггог
(некорректное использование программного инструмента):
> languageNaire = ’Python’
< VM464:1 Uncaught TypeError: Assignment to constant variable,
at <anonymous>:1:14
Использование констант позволяет предотвратить ряд программных ошибок,
вызываемых случайным присваиванием какой-либо переменной другого значе-
ния. Кроме того, обращение к константам выполняется несколько быстрее, чем
к переменным.
♦ var — то же самое, что и let, за двумя исключениями:
• в любом случае создается глобальная переменная:
> {
var Ь = 2;
console.log(b);
)
> // Объявленная в блоке переменная доступна и в блоке...
> 2
> // ...и вне его
> Ь
< 2
• допустимо повт орное объявление переменной.
Если переменная была повторно объявлена без явного присваивания ей зна-
чения. она сохранит старое значение.
Пример:
> var с = 3
> с
< 3
> var с
> с
< 3
> var с = 333
> с
< 333
Полезно знать
Согласно существующим соглашениям имена переменных в JavaScript, состоящие из
одного слова, или представляющие собой сокращение, записывают полностью в ниж-
нем регистре:
а, Ь, с, obj, result, start, inches, millimeters, output
Если имя переменной состоит из нескольких слов, первые буквы всех слов, кроме пер-
вого, записывают в верхнем регистре:
inchesS, inchesN, isNew, valueCalculateResult
Такой стиль именования сущностей носит название camelCase.
3.10.2. Обращение к переменным
Обращение к переменной— это извлечение из нее значения Выполняется простым
указанием имени переменной. Пример:
> // Объявляем переменную
> let а = 1и0;
> // Выполняем обращение к ней, указав в выражении ее имя
> а / 5
< 20
В выражениях, содержащих оператор присваивания, обращение к переменной вы-
полняется только в том случае, если ее имя у казано правее этого оператора. Если
имя переменной указано левее, выполняется присваивание этой переменной нового
значения.
Обращение к несуществующей переменной, записанное:
♦ в операторе получения типа или условии условного выражения — вернет значе-
ние undefined;
♦ в остальном коде — вызовет ошибку типа ReferenceError.
3.10.3. Проверка существования переменной
Чтобы проверить, была ли переменная объявлена ранее, можно воспользоваться
оператором получения типа typeof (описан в разд. 3.7.1). Если правее его указать
имя несуществующей переменной, оператор вернет 'undefined'. Пример (перемен-
ная abc заведомо не существует):
> typeof abc
< 'undefined'
Еще пример (оператор = возвращает true, если значения операндов равны,
и false — в противном случае):
if (typeof abc == 'undefined')
// Переменная abc не существует. Предпринимаем какие-либо действия.
Код проверки существования переменной можно немного сократить, если знать
правила преобразования различных типов данных в логический, однако здесь воз-
можны проблемы (подробности — в разд. 4.1.4.3).
3.10.4. Присваивание значения
несуществующей переменной
При попытке присвоить значение еще не объявленной переменной:
♦ если строгий режим не включен — будет создана глобальная переменная (как
если бы для ее объявления использовался оператор var):
platform = 'HTML + CSS';
console.log(pratform);
// Результат: 'HTML + CSS'
♦ если строгий режим включен — возникнет ошибка типа ReferenceError:
'use strict';
platform = 'HTML + CSS';
// Результат — ошибка ReferenceError
Как говорилось в разд. 3.10.1. все переменные следует объявлять до первого обра-
щения к ним. Рекомендуется ставить выражения, объявляющие переменные, в на-
чало кода.
3.11. Глобальный объект, контекст выполнения
и переменная this
Весь программный код выполняется внутри глобального объекта. Таковым являет-
ся объект класса window, представляющий текущее окно веб-обозревателя и храня-
щийся в автоматически создаваемой переменной window.
Для обращения к свойствам глобального объекта и вызова его методов ссылаться
на сам этот объект необязательно — достаточно лишь указать нужное свойство или
метод.
Предположим, что нужно вызвать у объекта окна метод alert о, выводящий окно-
сообщение с текстом, заданным в параметре. Можно написать такое выражение,
указав переменную window, хранящую объект окна:
window.alert(’Здравствуйте!’);
Л можно не указывать этот объект, поскольку он является глобальным:
alert('Здравствуйте! ') ;
Кроме того, исполняющая среда автоматически создает переменную this, которой
присваивается объект, называемый контекстом выполнения. В разные моменты
выполнения кода в качестве контекста выполнения выступают разные объекты.
В обычном коде и блоках контекстом выполнения является объект текущего окна
веб-обозревателя - глобальный объект.
Следовательно, предыдущий пример можно записать и гак:
this.alert('Здравствуйте!');
В приведенном варианте от переменной this нет никакой пользы. Однако в других
случаях (в частности, при написании методов классов и обработчиков событий) без
нее не обойтись. Эти случаи мы обязательно изучим в дальнейшем.
Урок 4
Логические величины
и управляющие выражения
Управляющее выражение — это сложное выражение, управляющее порядком вы-
полнения выражений, которые входят в его состав
В состав управляющего выражения обязательно входит условие — выражение,
в качестве результата возвращающее логическую величину. Поэтому урок уместно
начать с рассмотрения логического типа данных.
4.1. Логические величины
4.1.1. Введение в логический тип данных
Логическая величина (или значение логического типа данных) может принимать
два значения: true (представляет истинность какого-либо утверждения) или false
(представляет ложность утверждения):
> // 2 больше 3?
> 2 > 3
< false
> // 4 меньше 1?
> 4 < 1
< false
Оператор typeof, будучи примененным к логической величине, возвращает строку
'boolean':
> typeof (2 > 3)
< 'boolean'
Логическая величина является объектом класса Boolean. Никаких полезных свойств
и методов этот класс не поддерживает.
4.1.2. Операторы сравнения
Операторы сравнения сравнивают заданные операнды и возвращают результат
сравнения в виде логической величины:
<операнд 1> <оператор сравнения> <операнд 2>
Все поддерживаемые JavaScript операторы сравнения приведены в табл. 4.1.
Таблица 4.1. Операторы сравнения
Оператор Описание Примеры Результаты
_ _1 Равно > 1 == 1.000 < true
>1 = 2 < false
> 1 = '1' < true
> 1 = '2' < false
I = Не равно > 1 != 2 < true
> 1 != 1 < false
> 1 != '2' < true
> 1 != '1' < false
< Меньше > 100 < 1000 < true
> 1000 < 100 < false
< 100 < 100 < false
<= Меньше или равнс > 100 <= 1000 < true
> 1000 <= 100 < false
< 100 <= 100 < true
> Больше > 1000 > 100 < true
< ICO > 1000 < false
< 100 > 100 < fal se
>- Больше или равно > 1000 >= 100 < true
< 100 >= 1000 < false
< 100 >= 100 < true
=== Строго равно > 1 = 1.000 < true
>1 = 2 < false
> 1 = '1' < false
> 1 = '2' < false
I == Строго не равно > 1 != 2 < true
> 1 != 1 < false
> 1 != '2' < true
> 1 != '1' < true
Операторы ==, != , <, <=, > и >=, получив операнды, относящиеея к разным типам
данных, преобразуют их к единому типу и уже потом выполняют сравнение.
Операторы === и !== в таком случае не выполняют преобразование операндов
к единому типу, а считают их не равными и возвращают в качестве результата
соответственно false и true (что и показано в табл. 4.1).
Два знака =. Один знак = — это оператор присваивания (см. разд. 3.8.1).
Приоритет операторов сравнения довольно низок. Поэтому в качестве операндов
можно указывать фрагменты кода, выполняющие какие-либо вычисления.— и эти
вычисления будут выполнены перед собственно сравнением. Пример:
>2.5 * 4 > 80 /10
< true
4,1.3. Логические операторы
Логические операторы выполняют соответствующую логическую операцию над
заданными операндами, имеющими логический тип, и возвращают результат также
в виде логической величины
В JavaScript имеются гри логических оператора:
♦ \<операнд> — унарный, возвращает false, если операнд имеет значение true,
и true — в противном случае (логическое отрицание, или логическое НЕ).
Приоритет оператора ! выше приоритета операторов сравнения, поэтому по-
следние следует брать в круглые скобки, чтобы они выполнились первыми.
I Тример:
> !(10 == 100)
< true
♦ && — возвращает true, если оба операнда равны true, и false в противном случае
(логическое умножение, или логическое И);
♦ II — возвращает true, если хотя бы один операнд равен true, и false в против-
ном случае (логическое сложение, или логическое ИЛИ).
Два последних оператора записываются в формате:
<операнд 1> <логическмй оператор? ^операнд 2?
Приоритет этих операторов ниже приоритета операторов сравнения, поэтому
операторы сравнения в выражениях заключать в круглые скобки необязательно.
I )римсры:
10 <= 100 true kb J != 1 > 10 <= 100 | < true 1 o 1= 1
> 10 >= 100 && 0 != 1 > 10 >= 100 | 1 o != 1
< false < true
> 10 <- 100 && J == 1 > 10 <= 100 | 1 c == 1
< false < true
> 10 >= 100 0 == 1 > 10 >= 100 | 1 0 == 1
< false < false
Также поддерживаются два оператора комбинированного присваивания (см. разд. 3.8.2),
приведенные в табл. 4.2.
Таблица 4.2. Логические операторы комбинированного присваивания
Оператор комбинированного присваивания Его аналог
V &£= ор V = V && ор
V ||= ор V - V II ор
Примеры:
> let а = 10 <= 100
> а &&= 0 == 1
> а
< false
> а = 10 <= 100
> а | |= 0 = 1
> а
< true
4.1.4. Преобразование нелогических значений
в логические
Преобразование может быть неявным (выполняемым без указания со стороны про-
граммиста) или явным (когда программист явно указывает выполнить его).
4.1.4.1. Неявное преобразование
Неявное преобразование производится при указании значения типа, отличного от
логическою, в качестве условия в управляющем выражении или в качестве операн-
да у логического оператора.
В значение false будут преобразованы:
♦ пустая строка ('');
♦ число 0;
♦ значение NaN (рассмотрено в разд. 5.1.1);
♦ null (нулевая ссылка, см. разд. 10.6);
♦ undefined.
В значение true будут преобразованы:
♦ любая непустая строка;
♦ любое ненулевое число, включая значения infinity и -infinity (см. разд. 5.1.1);
♦ любая функция;
♦ любой объект;
♦ любая эмблема.
Пример'
let abc = ' ';
// Пустая строка будет преобразована в false
if (abc)
console.log('Этот текст не будет выведен');
else
console.log('Переменная abc хранит пустую строку');
4,1,4.2. Явное преобразование
Явное преобразование нелогического значения в логический тип можно выполнить
двумя способами:
♦ использовав двойной оператор I:
> ! !5
< true
> ! ! "
< false
< // Любая непустая строка преобразуется в true
> !!'false'
< true
♦ вызвав функцию Boolean(<преобразуемое значение?):
> Boolean(5)
< true
> Boolean(' ')
< false
Первый способ компактнее, зато второй нагляднее.
4.1.4.3. Еще раз о проверке существования переменной
В разд. 3 10.3 описывался способ проверки существования переменной с помощью
оператора получения типа typeof. Этот способ дает хорошую гарантию (кроме слу-
чая, когда проверяемая переменная объявлена, но хранит значение undefined), но
реализующий его код несколько громоздок.
Код, производящий проверку на существование переменной, можно сократить. Для
этого достаточно воспользоваться тем, что нелогическая величина, указанная
в качестве условия в условном выражении, преобразуется к логическому типу.
В таком случае, если проверяемая переменная:
♦ существует— хранящееся в ней значение будет неявно преобразовано в true,
и условие выполнится;
♦ не существует— обращение к переменной вызовет выдач} в качестве результа-
та значения undefined, которое будет неявно преобразовано в false.
Пример:
if (abc)
Il Переменная abc существует!
else
// Переменная не abc существует. Надо что-то делать...
Если нужно лишь проверить факт отсутствия переменной, можно использовать
такой код:
if (labc)
// Переменная не abc существует. Надо что-то делать...
Здесь используется оператор логической инверсии !, преобразующий значение
false В true, a true В false (СМ. разд. 4.1.3).
Однако здесь кроется «подводный камень». Если проверяемая переменная сущест-
вует, но хранит значение, которое преобразуется в false (пустую строку, число О
и т. д.. см. разд. 4.1.4.1), то это значение преобразуется в false и написанный нами
веб-сценарий «решит», что проверяемая переменная не существует. При програм-
мировании либо придется учитывать этот факт, либо для проверки существования
переменной все же пользоваться способом, описанным в разд. 3.10.3.
4.2. Управляющие выражения
4.2.1. Условные выражения
Условное выражение выполняет выражение true, если заданное условие истинно,
и выражение false—если условие ложно:
if (<условие>')
< выражение true>
[else
^выражение false>]
Условие обязательно следует указывать в круглых скобках.
Пример:
if (languageName = 'JavaScript')
console.log('Лучший в мире язык!');
else
console.log ’'А почему не JavaScript?');
Ключевое слово else и выражение false можно нс указывать. В этом случае, если
условие ложно, ничего не произойдет. Пример:
if (languageName = 'JavaScript')
console, log ('Лулпий в мире язык! ');
В качестве любого из выражении можно указать блок (см. разд. 3.5)\
if (languageName == 'JavaScript') {
console.log('Лучший в мире язык!');
console.log('В этом нет ни малейших сомнений.’);
} else {
console.log('А почему не JavaScript?’);
console.log('Впрочем, это тоже неплохой выбор.’);
J
4.2.1.1. Множественные условные выражения
Множественное условное выражение объединяет произвольное количество услов-
ных выражений:
if (,<уаловие
(выражение true 1>
else if (<уаловие 2>)
(выражение true 2>
else if ((условие п-1>)
(выражение true п-1>
else if {(условие п>)
<выражение true п>
[else
(выражение false>]
Если условие 1 истинно, выполняется выражение true 1, если истинно условие 2 —
выражение true 2 и т. д. Если все условия ложны, будет выполнено выражение false
в случае, когда оно присутствует в коде. Количество условий не ограничено.
Пример:
if (languageName — 'JavaScript')
console.log('Лучший в мире язык!');
else if (languageName = 'Python')
console.log('Тоже хороший язык.');
else
console.log('А почему не JavaScript или Python?');
4.2.2. Условный оператор
Условный оператор — возвращает результат true, если указанное условие истинно,
и результат false— если оно ложно:
(<условие>) 2 (результат true> : (результат false>
Условный оператор часто применяется в качестве более компактной альтернативы
условному выражению.
Пример:
> let s = ''
> let result
> result = (s == '') ? 'Строка пуста' : 'Строка не пуста'
> result
< 'Строка пуста'
> s = 'Условный оператор'
> result = (s = ’’) ? 'Строка пуста' : 'Строка не пуста'
> result
< 'Строка не пуста'
Условный оператор— единственный тернарный (принимающий три операнда)
оператор в JavaScript.
4.2.3. Выражение выбора
Выражение выбора последовательно сравнивает заданное значенье с набором
указанных величин и при совпадении с какой-либо величиной выполняет соот-
ветствующий фрагмент кода.
Выражение выбора проще рассмотреть, сравнивая его с множественным условным
выражением, при исполнении которого будет достигнут аналогичный результат
(табл. 4.3).
Таблица 4.3 Сравнение выражения выбора с множественным условным выражением
Формат выражения выбора Аналогичное условное выражение
switch (<эначение>) { case * величина 1>: <фрагмент 1> break; case <величина 2>: <фрапленг 2> break; case <величина п-1>: <фрагмент п-1> break; case <величина л>: •^фрагмент п> break; [default: <фрагм. default^] } if (<значение> === <величина 1>) (фрагмент 1> else if {<значен.> <вел. 2>) -фрагмент 2> else if (<знач.> ==== <вел. п-Т>) < фрагмент п-1> else if (<знач.> ==== <велич. л>) 'Фрагмент п> else <фрагмент default>
Несмотря на то что выражение выбора может оказаться более громоздким, оно
выполняется быстрее аналогичного множественного условного выражения.
Рассмотрим пример (предполагается, что переменная screensize уже объявлена
и хранит значение размера экрана в дюймах):
let s;
switch(screenSize) {
case 3:
s = 'Маленький экран';
break;
case 4:
s = 'Маленький экран';
break;
case 5:
s = 'Экран средних размеров';
break;
case 6:
s = 'Большей экран';
break;
default:
s = 'Таких экранов в телефонах не бывает';
}
Здесь, если в переменной screensize записано число 4, будет выполнен второй по
счету фрагмент кода (записанный после строки case 4:), и переменная s в качестве
значения получит строку 'маленький экран'.
При использовании выражений выбора следует иметь в виду два момента:
♦ при сравнивании заданного значения с величинами JavaScript применяет опера-
тор = (строго равно). Это значит, что преобразование типов при сравнении не
проводится
В нашем случае, если в переменной screensize записана строка '4', она не сов-
падет ни с одной из четырех величин, присутствующих в этом выражении выбо-
ра. В результате будет выполнен фрагмент default, и в переменной s окажется
строка 'Таких экранов в телефонах не бывает';
♦ в конце каждого фрагмента (за исключением самого последнею) ставится опера-
тор прерывания break. Он немедленно прерывает выполнение выражения вы-
бора.
Если после какого-либо фрагмента не указать оператор break, после выполнения
этого фрагмента выполнится следующий фрагмент. Понятно, что результат ис-
полнения кода будет не тем, на который мы рассчитывали.
Однако эту особенность выражения выбора можно использовать на практике,
чтобы сократить код.
Вот как это можно сделать на приведенном ранее примере (исправленный код
выделен полужирным шрифтом):
let s;
switch(screensize) {
case 3:
case 4:
s = 'Маленький экран';
break;
case 5:
s = 'Экран средних размере! ';
break;
case 6:
s = 'Большой экран';
break;
default:
s = 'Таких экранов в телефонах не бывает’;
}
При совпадении значения переменной screensize с величинами 3 и 4 будет вы-
полнен один и тот же фрагмент кода— присваивающий переменной s строку
'Маленький экран'.
4.2.4. Циклы
Цикл — сложное выражение, выполняющее заданное выражение многократно, пока
истинно заданное условие. Поддерживаются несколько разновидностей циклов.
4.2.4.1. Цикл со счетчиком
Эта разновидность цикла знакома нам по упражнению 1.4.
Цикл со счетчиком — выполняется, пока значение, хранящееся в особой пере-
менной (счетчике г{икла), не превысит заданную граничную величину:
for ([<установка>]; [<условие>]; [<приращение>])
[<тело цикла>]
В составе цикла со счетчиком присутствуют четыре следующих выражения:
♦ установка— выполняется однократно в начале исполнения цикла, перед его
первой итерацией, и присваивает счетчику начальное значение.
|| Итерация, или проход, цикла— однократное выполнение цикла.
Установка может как использовать в качестве счетчика уже объявленную пере-
менную, так и объявлять новую. В последнем случае, если объявление перемен-
ной было выполнено оператором let, переменная будет доступна только в теле
цикла (подробности об объявлении переменных — в разд. 3.10.1).
Далее начинается выполнение итераций цикла. В составе каждой итерации вы-
полняются условие, тело цикла И приращение',
♦ условие— выполняется в начале очередной итерации цикла. В зависимости от
возвращенного им результата:
• true—выполняется тело цикла',
• false — исполнение цикла завершается;
♦ тело цикла— простое выражение или блок, который и будет выполняться мно-
гократно;
♦ приращение— выполняется в начале очередной итерации никла и изменяет зна-
чение счетчика.
После выполнения приращения начинается следующая итерация цикла.
Пример:
for (let i = 1; i < 11; i++)
console.log(i * i) ;
Поскольку здесь переменная-счетчик i была объявлена в установке цикла с приме-
нением оператора let, OHti будет доступна только в теле цикла и по завершении
всех его итераций уничтожится.
Если требуется, чтобы счетчик был доступен и вне тела цикла, счетчик следует
объявить в отдельном выражении перед циклом;
let 1;
for (i = 1; i < 11; i++)
console.log(i * i);
const numberOfLastiteration = i - 1;
В качестве тела цикла можно использовать блок:
for (let i = 1; i < 11; i++) {
const q = i * i;
console.log(q);
}
В предыдущих примерах значение счетчика при каждой итерации увеличивалось
на единицу. В следующем примере значение счетчика, напротив, уменьшается на
единицу:
for (let i = 10; i > 0; i—)
console.log(i * i);
В установке и приращении допускается указывать несколько выражений, разделяя их
запятой (не точкой с запятой!). Пример цикла с двумя счетчиками, для объявления
которых применяются два выражения, записанные в составе установки, а для изме-
нения — два выражения в составе приращения:
for (let i = 1, j = 10; i < 11 && j > 0; i++, j —)
console.log(i+’*’+j+'=,+i*j);
И установка, И условие, и приращение, И даже тело ицлкла не ЯВЛЯЮТСЯ обязательными
для указания:
♦ цикл без установки— счетчик объявляется перед циклом:
let i = 1;
for (; i <= 10; ++i) {
}
♦ цикл без условия— выяснение, следует ли прервать цикл, выполняется в начале
тела цикла, а прерывание цикла производится оператором break (описан
в разд. 4.2.3):
for (i = 1; ; ++i) {
if (i > 10)
break;
}
♦ цикл без приращения— значение счетчика изменяется в конце тела цикла:
for (i = 1; i <= 10;) {
}
♦ цикл без тела— необходимые действия выполняются в приращении, перед из-
менением значения счетчика. В этом случае выражение цикла следует завер-
шить точкой с запятой. Пример:
for (let i = 1; i < 11; console.log(i * i), i++);
4.2.4.2. Цикл с предусловием
Цикл с предусловием выполняется, пока заданное условие остается истинным.
Условие проверяется перед каждой итерацией цикла.
Форма! цикла с предусловием:
while (<уоловие>)
<тело цикла>
Пример вывода квадратов чисел от I до 10 с помощью цикла с предусловием:
let i = 1;
while (i < 11) {
console.log(i * i);
}
4.2.4.3. Цикл с постусловием
Цикл с постусловием аналогичен циклу с предусловием (см. разд. 4.2.4.2), но
условие проверяется после каждой итерации цикла. Такой цикл выполнится хотя
бы один раз, даже если условие изначально ложно.
Формат цикла с постусловием:
de
<тело цикла>
while (<условие>);
Пример вывода квадратов чисел от 1 до 10 посредством цикла с постусловием:
let i = 1;
do {
console.log(i * i);
i++;
} while (i < 11);
4.2.4.4. Цикл перебора последовательности
Последовательностью называется набор упорядоченных значений {элементов).
К последовательностям относятся, в частности, массивы.
Цикл перебора последовательности— перебирает один за другим элементы
из заданной последовательности, присваивая очередной элемент заданной пере-
менной'.
for ([<оператор объявления:»] <переменная> of <.последсвательность>)
<тело цикла>
Если переменная ранее не была объявлена, следует поставить какой-либо оператор
объявления переменной: let или var. Переменная, объявленная оператором let,
будет досту пна только в теле цикла.
Пример перебора массива:
> const languages = ['HTML', 'CSS', ’JavaScript’];
> for (let language of languages)
console.log(language);
< HTML
< CSS
< JavaScript
4.2.4.5. Цикл по свойствам объекта
Цикл no свойствам объекта последовательно перебирает все свойства, что
присутствуют в указанном объекте, присваивая имя очередного свойства ука-
занной переменной'.
for ([<опера тор объявления^ <переменная> in <сбъект>)
<тело цикла>
Если переменная ранее не была объявлена, следует поставить какой-либо оператор
объявления переменной: let или var. Переменная, объявленная оператором let,
будет доступна только в теле цикла.
Перебираем все свойства объекта window, представляющего текущее окно веб-
обозревателя. и выводим их в консоли в формате <имя свойствам <значение свой-
ствам.
> for (let р in window)
console.log(p + + window(pj);
< . . . Часть вывода пропущена . . .
< closed: false
< innerWidth: 746
< innerHeight: 567
< scrollX: 0
< pageXOffset: 0
< scrollY: 0
< pageYOffset: 0
< . . . Часть вывода пропущена . . .
Поскольку имя очередного свойства хранится в переменной, для доступа к свойст-
ву с этим именем следует применить квадратные скобки (подробно работа с объек-
тами рассмотрена налоге 10).
4.2.4,6. Прерывание цикла
Оператор прерывания break прерывает выполнение цикла (а также выражения
выбора—см. разд. 4.2.3).
Оператор прерывания не принимает операндов и не возвращает результата.
Пример вывода квадратов чисел от 1 до 10:
let i = 1;
while (true) {
if (i > IC)
break;
console.log(i * i) ;
i++;
}
В этом цикле в качестве условия поставлено значение true, в результате чею такой
цикл станет работать бесконечно, если не будет прерван каком-либо способом. Мы
прерываем его оператором break, который выполнится после исполнения десяти
итераций цикла.
4,2,4,7. Прерывание итерации цикла
Оператор прерывания итерации continue прерывает выполнение текущей ите-
рации цикла и начинает выполнение следующей.
Этот оператор не принимает операндов и нс возвращает результата.
Пример вывода только четных квадратов чисел от I до 10:
for (let i = 1; i < 11; i++) {
const q = i * i;
if (q % 2 1= 0)
continue;
console.log iq);
}
Оператор делит нацело операнд слева на операнд справа и возвращает остаток
от деления. Чтобы выяснить, является ли число нечетным, достаточно разделить
нацело его на 2 и удостовериться, что полученный остаток не равен 0. Если это так.
выполняется оператор continue, прерывающий текущую итерацию цикла.
4.2.4.8, Бесконечный цикл
Бесконечный циклг — цикл, выполняемый бесконечно. Вызывает «зависание»
веб-обозревателя и является одной из серьезнейших ошибок в программиро-
вании.
Пример бесконечного цикла:
let i = 1;
while (true) {
console.log(i * i);
i++;
}
Это лишь пример. Никогда так не делайте!
Урок 5
Числа
JavaScript может манипулировать числами, относящимися к двум различным
типам.
5.1. Обычные числа
Обычные числа относятся к числовому типу данных. Они могу-т быть как целыми,
так и дробными (их еще называют вещественными и числами с плавающей
точкой).
В качестве обычных чисел могут быть представлены величины от
-1,7976931348623157x1030* до 1,797693134 8623157х 1О308.
Обычные числа являются объектами класса Number. Этот класс поддерживает ряд
полезных свойств и методов, которые мы рассмотрим позже.
Оператор получения типа typeof, будучи примененным к обычному числу, возвра-
щает строку ' number'.
Это наиболее универсальный из двух числовых типов, поддерживаемых языком.
Поэтому, если в программе требуется выполнить какое-либо вычисления, практи-
чески всегда применяют обычные числа.
5.1.1. Запись обычных чисел
Числа записываются по обычным (школьным) правилам:
10, 20, 1234567
В вещественных числах в качестве десятичного разделителя применяется точка (не
запятая!):
1.5, 23.67, 0.053
У отрицательных чисел в начале ставится знак - (минус):
-10, -20, -0.32, -0.053
Для записи чисел в экспоненциальной форме: <мантисса> х 10 — применя-
ется формат:
<мантисса>е<порядок>
Буква е может записываться в любом pet истре. Примеры:
1еб (1000000), 1. Зе-5 (0,000013), -1ЕЗ (-1000).
Целые числа могут указываться в других системах счисления, помимо десятичной:
♦ шестнадцатеричной — с префиксом 0х или их (цифра о и буква х или х):
0x10 (16). 0X53 (83), -0x321 (-801)
♦ восьмеричной — с префиксом 0о или ОО (цифра С и буква о или о):
ОоЮ (8). UO123 (83). -0о45 (-37)
Если не задействован строгий режим, допускается предварять восьмеричные
числа нулем (0). — при условии, что число содержит лишь цифры от 0 до 7:
010, 0123, -0о45
Если же число включает хотя бы одну цифр\ 8 или 9. оно трактуется как деся-
тичное.
Однако при включенном строгом режиме такая запись приводит к ошибке типа
SyntaxError;
♦ двоичной — с префиксом оь или зв (цифра о и буква ь или в):
oblllO (14). 0B100110L01 (309), -ОЬООИОЮ (-26)
Для удобства восприятия группы цифр в числах допускается разделять символами
(подчеркивание):
1 ооо_оог-( 1000000), 1 234 5б7_890 (1234567890)
К числам также относятся три специальных значения:
♦ NaN (Not a Number, не число)— значение, получаемое при попытке, например,
умножить число на строку или преобразовать в число строку, не содержащую
числа (о преобразовании нечисловых значений в числовой тип рассказано
в разд. 5.1.6):
> 20 * ’JavaScript'
< NaN
♦ infinity— математическая «бесконечностью: оо. Это значение можно получить
делением положительного числа на 0. Пример:
>10/0
< Infinity
♦ -infinity— математическая «минус бесконечность»: -ос. Может быть получена
делением отрицательного числа на ).
5.1.2. Арифметические операторы
Арифметические операторы выполняют различные арифметические действия над
заданными операндами и возвращают результат. Поддерживаются бинарные и
унарные арифметические операторы.
Бинарные арифметические операторы записываются в формате:
<операнд 1> -^.арифметический оператор? <.сперанд 2>
Все бинарные арифметические операторы приведены в табл. 5.1.
Таблица 5.1. Бинарные арифметические операторы
Оператор Описание Пример Результат
+ (плюс) Сложение >27+14 < 41
- (минус) Вычитание > 40 - 100 < -60
♦ (звездочка) Умножение > 12 * 32 < 384
/ (слеш) Деление >3/4 < 0.75
(процент) Остаток от деления > 4 % 3 < 1
* * Возведение в степень Операнд 1 задает основание, операнд 2—показатель > 2 ** 10 < 1024
Также поддерживаются соответствующие им операторы комбинированного при-
сваивания, приведенные в табл. 5.2.
Таблица 5.2. Арифметические операторы комбинированного присваивания
Оператор комбинированного присваивания Его аналог
V += ор V = V + ср
V -= ор V = V - ср
V *= ор V = V * ор
V /= ор V = V / ор
V %= ор V = V % ор
V **= ор V = V * * ор
Пример:
> let а = 27
> а += 13
> а
< 40
> а /= 5
> а
< 8
Унарные арифметические операторы:
♦ -<операнд> (минус) — изменяет знак операнда (унарный минус):
> let п = 100
> -п
< -100
♦ ++ (два плюса)— оператор инкремента. Увеличивает значение заданной пере-
менней на 1. Результат заносится в ту же переменную. Поддерживает два форма-
та записи:
• ++<переменная>—сначала выполняет инкремент, потом возвращает получен-
ный результат и затем присваивает его переменной:
> let i = 1
> ++i
< 2
> ++i
< 3
• <переменная>++— сначала возвращает значение переменной, потом выполняет
инкремент и затем присваивает результат переменной:
> i = 1
> i++
< 1
< 2
♦ — (два минуса) — оператор декремента. Уменьшает значение переменной на 1.
В остальном аналогичен оператору инкремента.
Операторы инкремента и декремента выполняются быстрее, чем операторы сложе-
ния и вычитания, поэтому рекомендуются к применению везде, где возможно.
5.1.3. Методы и свойства класса Number
Класс Number, представляющий обычные числа, поддерживает ряд полезных стати-
ческих свойств, объектных и статических методов.
Объектные методы.
♦ tostring ([<основ<эние>=10]) — возвращает текущее число, преобразованное в стро-
ковый формат, в системе счисления с заданным основанием. При указании
основания, отличного от 10, число возвращается без соответствующего префикса
(см. разд. 5.1.1). Примеры.
> let а = 123
> a.toString()
< '123'
> а.toString(8)
< '173'
> a.toString(16)
< ’7Ь’
> а = 123.321
> a. toString ()
< '123.321'
♦ t^Exponentiai ([^количество цифр после точки>]) — возвращает текущее число,
преобразованное в строковый формат, в экспоненциальном виде. Если количест-
во цифр после десятичной точки не указано, будет выведено столько цифр,
сколько нужно для представления этого числа. Примеры:
> а = 123456.789123
> а.toExponential()
< '1.23456789123е+5'
> а.toExponential(31
< Ч.235е+5'
♦ toFixed ([количество цифр после точки>]) — возвращает текущее число, преоб-
разованное в строковый формат и округленное до заданного количества цифр.
Если количество цифр не указано, число будет округлено до целых. Примеры:
> a.toFixed()
< '123457'
> a.toFixed(2)
< '123456.79'
♦ toPrecision([<длина числа в цифрах>])— возвращает текущее число, преобра-
зованное в строковый формат и урезанное до заданной длины. Если длина не ука-
зана, число не урезается. Примеры:
< a.toPrecision()
> '123456.789123'
< a.toPrecision(7)
> '123456.8'
> a.toPrecision(4)
< '1.235е+5'
Статические методы
♦ isinteger (<число>) — возвращает true, если заданное число целое, и false —
в противном случае:
> Number.islnteger(123)
< true
> Number,islnteger(1.23)
< false
♦ isFinite {<число>) — возвращает true, если заданное число конечное, и false —
если это математическая бесконечность:
> Number.IsFinite(2)
< true
> Number. isFinite (2 / 10?
< true
> Number.isFinite(2 / 0)
< false
♦ isNaN (< числе») — возвращает true, если заданное число является значением NaN,
и false — если это действительно число:
> Number.isNaN(2)
< false
> Number.isNan(2 / 10)
< false
> Number.isNaN(2 / 0)
< false
> Number.isNaN(2 / 'abcde')
< true
♦ isSafelnteger(<число>) — возвращает true, если заданное число укладывается
в диапазон oi -9007199254740991 до 9007199254740991 и соответственно может
быть представлено без потери точности (подробности — в разд. 5.1.7),
и false— в противном случае:
> Number.isSafelnteger(1)
< true
> Number.isSafelnteger(9007199254740991)
< true
> Number.isSafelnteger(9007199254340991 + 1)
< false
Статические свойства’
♦ min value — наиболее близкое к нулю число, которое способен обработать
JavaScript:
> Number.MIN VALUE
< 5e-324
♦ max value— наибольшее число, которое способен обработать JavaScript:
> Number.MAX VALUE
< 1.7976931348623157e+308
♦ epsilon — наименьшая допустимая разница между двумя числами:
> Number.EPSILON
< 2.220446049250313е-16
♦ min safe integer— наименьшее допустимое целое число, которое может быть
сохранено без потери точности:
> Number. MIN_SAFE_INTEGER
< -9007199254740991
♦ max safe integer— наибольшее допустимое целое число, которое может быть
сохранено без потери точности:
> Number.MAX_SAFE_1NTEGER
< 9007199254740991
5.1.3.1. Форматированный вывод чисел
Метод testring () класса Number (см. разд. 5.1.3) выводит текущее число в том виде,
в котором оно записывается в программном коде на языке JavaScript:
> let а = 1234567.3210123
> a.tostring()
< '1234567.3210123'
Однако часто бывает необходимо вывести число отформатированным согласно
текущей локали.
Локаль— совокупность параметров, задающих региональные настройки про-
граммы: язык, страну, принятые форматы вывода чисел, временных отметок,
денежных сумм и др.
В таком случае удобно применя ть объектный метод toLocaleString() класса Number:
toLocaleString([<обозначение локали>[, дополнительные настройки>]])
Обозначение локали задается в виде строки. Поддерживаемые на текущий момент
обозначения локалей можно найти на странице: http://tinyurl.com/yeydhuza. Если
обозначение локалинс указано, используется локаль веб-обозревагеля.
Примеры:
> // Поскольку обозначение локали мы не указали, будет использована
> // локаль веб-обозревателя - русская
> a.toLocaleString()
< '1 234 567,321'
> // Указываем американскую английскую локаль
> a.toLocaleString('en-US')
< '1,234,567.321'
> // Указываем немецкую локаль
> a.toLocaleString('de-DE')
< '1.234.567,321'
Дополнительные настройки, задаются в виде простого объекта (см. разд. 1.6). свойства
которого соответствуют поддерживаемым настройкам:
♦ notation— обозначение нотации, используемой для вывода числа, в виде одной
из строк:
• 'standard' —обычная нотация;
• 'scientific' —экспоненциальная нотация;
• 'engineering' — аналогична экспоненциальной нотации, но использует толь-
ко степени 10. кратные 3:
• ’compact’ — нотация со строковым представлением степеней 10.
Значение настройки по умолчанию: 'standard'.
Примеры:
> a.toLocaleString('ru-RU', { notation: ’scientific’ })
< '1,235E6'
> a.toLocaleString('ru-RU', { notation: 'compact' })
< '1,2 эдн'
> let c = 1 000^000
> c.toLocaleString('ru-RU', { notation: 'scientific' })
< '1E7'
> c.toLocaleString('ru-RU', { notation: 'engineering' })
< '10E6'
♦ compactDisplay— обозначение формата вывода числа, используемого в случае
выбора нотации 'compact', в виде одной из строк: 'short' (сокращенный фор-
мат) или 'long' (полный формат). Значение по умолчанию: 'short'. Пример.
> а.toLocaleString('ru-RU', { notation: ’compact',
compactDisplay: 'long' })
< '1,2 миллиона'
♦ useGrouping— если true, цифры н выводимом числе будут группироваться, если
false — не будут (по умолчанию: true):
> a.toLocaleString('ru-RU')
< '1 234 567,321'
> a.toLocaleString('ru-RU', { useGrouping: false })
< '1234567,321'
♦ signDisplay — обозначение режима отображения знака у выводимого числа
в виде одной из строк:
• 'auto' — выводить знак только у отрицательных чисел, включая отрицатель-
ный 0:
• 'negative' — выводить знак только у отрицательных чисел, за исключением
отрицательного 0:
• 'always* выводить знак у всех чисел;
• 'exceptzero' — выводить знак у всех чисел, за исключением отрицательного
нуля;
• 'never' — никогда не выводить знак.
Значение по умолчанию: 'auto';
♦ style — обозначение стиля выводимого числа в виде одной из строк: 'decimal'
(в виде обычного числа), 'currency' (в виде денежной суммы в указанной валю-
те), 'percent' (в виде процентов) или 'unit' (в виде величины в заданной еди-
нице измерения). Значение по умолчанию: 'decimal'. Пример:
> a.toLocaleString('ru-RU', { style: 'percent' })
< '123 456 732 %'
Для вывода числа в виде денежной с\ммы требуется указать валюту, а для вы-
вода в виде величины в какой-либо единице измерения — единицу измерения
(их задание будет описано чуть позже). Если этого не сделать, возникнет ошибка
с типом ТуреЕггог;
♦ currency— обозначение валюты для выводимой денежной суммы в виде строки.
Обозначения всех существующих в мире валют можно найти на странице:
http://tinyurl.com/26f7n8xr. Пример:
> // Выводим сумму в российских рублях
> a.toLocaleString('ru-RU', { style: 'currency', currency: 'RUB' })
< '1 234 567,32 ₽'
♦ currencyDisplay— обозначение формата вывода валюты в виде одной из строк:
'symbol' (обычное обозначение), 'narrowsymbol' (аббревиатура, поддерживается
не всеми валютами), 'name' (название) или 'code' (код стандарта ISO 4217). Зна-
чение по умолчанию: 'symbol'. Примеры:
> a.toLocaleString('ru-RU', { style: 'currency', currency: 'RUB',
currencyDisplay: 'name' })
< '1 234 567,32 российского рубля'
> a.toLocaleString('ru-RU', { style: 'currency', currency: 'RUB',
currencyDisplay: 'code' })
< '1 234 567,32 RUB'
♦ currencysign — обозначение формата вывода отрицательных денежных сумм
в виде одной из строк: 'standard' (вывод со знаком -) или 'accounting' (заклю-
чение в круглые скобки). Значение по умолчанию: 'standard'. В ряде локалей не
принимается во внимание, и отрицательные денежные суммы всегда выводятся
со знаком
♦ unit — обозначение единицы измерения в виде строки. Перечень обозначений
поддерживаемых единиц измерения приведен на странице http://tinyurl.com/
wcxh9skv. Пример:
let Ь = 10
> // Выводим расстояние в метрах
> b.toLocaleString('ru-RU', { style: 'unit', unit: 'meter' })
< '10 m'
> // В километрах
> b.toLocaleString('ru-RU', ( style: 'unit', unit: 'kilometer' })
< '10 km'
♦ uniteisplay — обозначение формата вывода единицы измерения в виде одной из
строк: 'short' (сокращенное название единицы измерения отделяется от значе-
ния пробелом), 'narrow' (то же самое, что и 'short', только единица измерения
выводится вплотную к значению, поддерживается не всеми локалями) или
'long' (полное название). Значение по умолчанию: 'short'. Примеры:
> b.toLocaleString('ru-RU', { style: 'unit', unit: 'meter',
unitL'isplay: 'long' })
< '10 метров'
♦ minimumintegeri. igits — минимальное количество цифр в целой части числа.
Если текущее число содержит меньше цифр в целой части, лишние позиции сле-
ва будут заполнены нулями. Допускаются значения от I до 21. Значение по
умолчанию: I;
♦ minimumFractionDigits — минимальное количество цифр в дробной части числа.
Если текущее число содержит меньше цифр в дробной части, лишние позиции
справа будут заполнены нулями. Допускаются значения от 0 до 20. Значение по
умолчанию: 2 (при выводе денежных сумм) или 0 (в остальных случаях).
Примеры:
> а.toLocaleString('ru-RU *,
< '0 001 234 567,321'
> a.toLocaleString('ru-RU',
< '1 234 567,321'
> a.toLocaleString('ru-RU',
< '1 234 567,32101'
> a.toLocaleString('ru-RU',
< '1 234 567,3210123000'
{ minimumlntegerDigits: 10 })
{ minimumFractionDigits: 2 })
{ minimumFractionDigits: 5 })
{ minimumFractionDigits: IC })
♦ maximumFractionDigits — максимальное количество цифр в дробной части числа.
Если текущее число содержит больше цифр в дробной части, оно будет соответ-
ственно округлено. Допускаются значения от 0 до 20. Значение по умолчанию:
• значение свойства minimunFractionDigits, но не менее 2 — при выводе де-
нежных сумм;
• 0 — при выводе процентов;
• значение свойства minimunFractionDigits, но не менее 3 — при выводе обыч-
ных чисел.
Примеры:
> a.toLocaleString ('ru-RU', { maxiiriumFracti^nDigits: 0 })
< '1 234 567'
> a.toLocaleString('ru-RU', { maximumFractionDigits: 2 })
< '1 234 567,32'
♦ minimumSignificantDigits — минимальное количество значащих цифр. Если те-
кущее число содержит меньше значащих цифр, лишние позиции справа от дроб-
ной части будут заполнены нулями. Допускаются значения от 1 до 21. Значение
по умолчанию: I;
♦ maximumsignificant Digits — максимальное количество значащих цифр. Если
текущее число содержит больше значащих цифр, оно будет соответственно
округлено. Допускаются значения от 1 до 21. Значение по умолчанию: 21.
11римеры:
> a.toLocaleString('ru-RU’, { minimumSignificantDigits: 21 })
< '1 234 567,32101230000000'
> a.toLocaleString('ru-RU', { maximumSignificantDigits: 6 })
< '1 234 570'
> a.toLocaleString('ru-RU', { maximumSignificantDigits: 2 })
< '1 200 000'
Для форматированного вывода множества чисел вместо метода toLocaleString ()
л>чше использовать объект класса intl.NumberFormat, т. к. вывод с его помощью
выполняется быстрее. Объект этого класса создается с помощью выражения фор-
мата:
new Inti.NumberFormat([<обозначение локали>[,
дополнительные настройки>] ])
Здесь задаются ге же параметры, что и у метода toLocaleString(). Созданный объ-
ект следует присвоить какой-либо переменной.
Форматированный вывод заданного числа выполняется методом format (<число>)
класса Intl.NuruberFormat.
Пример:
> const formatter = new Inti.NumberFormat(’ru-RU',
{ style: 'currency', currency: 'RUB' })
> formatter.format(1234567.321)
< '1 234 567,32 ₽'
> formatter.format(0.00000001)
< '0,00 ₽'
5.1.4. Класс Math
Класс Math содержит только статические свойства, хранящие математические
константы, и статические методы, реализующие действия математ ики и тригоно-
метрии.
Статические свойства класса Math:
♦ pi — число л:
> Math.PI
< 3.141592653589793
♦ е — число е (основание нагх рального логарифма);
♦ SQRT2 — квадрат ный корень от 2;
♦ sqrt1_2 — квадратный корень от 0.5;
♦ LN2 — In 2 (натуральный логарифм от 2);
♦ LN10— In 10 (натуральный логарифм от 10);
♦ LOG2E — 1g е (десятичный логарифм от числа е);
♦ LOG2E — 1Ь е (двоичный логарифм от числа е).
Mei оды класса Math и возвращаемые ими результаты:
♦ sqrt (<число>) — квадратный корень от числа:
> Math.sqrt(20)
< 4.47213595499958
♦ cbrt. (<число>) — кубический корень от числа;
♦ hypot(<длина катета 1>, <ллина катета 2>} —длина гипотенузы, вычисленная
на основе заданных длин катетов:
> Math.hypot(2, 3)
< 3.6055512754639896
♦ min (<число 1>, <число 2>, . . . < число д»—наименьшее число из указанных:
> Math.min(2, 56, 900, 425, -87)
< -87
♦ шах(<число 1>, <число 2>, . . . <число N>) —наибольшее числе из указанных;
♦ abs (<число>) — модуль (абсолютная величина) числа:
♦ sign(<число>)----1, если число отрицательное, 1 — если положительное, 0 —
если равно нулю, -0 — если равно -0;
♦ round (<число>) — число, округленное до ближайшего целого;
♦ floor (<число>) — число, округленное до ближайшего меньшего целого;
♦ ceil (<число) — число, округленное до ближайшего большего целого:
♦ trunc(<число>) — целая часть числа',
♦ sin (<утол>) — синус угла, заданного в радианах.
Пояснение
Формула для пересчета углов из градусов в радианы приведена в разд. 1.8
♦ cos (<угол>) — косинус угла, заданного в радианах;
♦ tan (<угол>) — тангенс угла, задатки о в радианах.
♦ asin(<число>) —арксинус в радианах от числа:
♦ acos (<число>) — арккосинус в радианах от числа:
♦ atan (<число>) —арктангенс в радианах от числа.
Пояснение
Пересчитать угол из радианов н градусы можно по формуле
a(rpj = а(рад) х 180 / и.
Здесь фрад) — угол в радианах, а(гР) — угол в градусах
Пример вычисления арктангенса от числа 1 и получения результата в градусах
> let at = Matn.atan(1)
> at * 180 / Math.PI
< 45
♦ sinh (<число) — гиперболический синус от числа;
♦ cosh «число» — гиперболический косинус от числа;
♦ tanh(<чиало>) — гиперболический тангенс от числа;
♦ asinh (<чиало>) —гиперболический арксинус от числа;
♦ acosh(<число» —г иперболический арккосинус от числа;
♦ atanh (<число) — гиперболический арктангенс от числа;
♦ atan2 (<у>, <х>) — угол в радианах между осью абсцисс и точкой с координата-
ми [х, у], отсчитанный против часовой стрелки;
♦ random () — псевдослучайное вещественное число от 0 до I:
> Mar.h. random ()
< 0.2220291778805139
> Math.random ()
< 0.5721614375670632
> Mach.random()
< 0.19212452555545023
♦ log «число» —натуральный логарифм числа;
♦ 1од1р(<число>) — натуральный логарифм суммы числа и 1;
♦ 1од10 «число» —десятичный логарифм числа;
♦ 1од2 «число» —двоичный логарифм числа;
♦ ехр(<число>) — значение е'^0;
♦ exmpl «число» — разность значения ечисло и 1;
♦ pow(<a>, <b>) —значение аь. Метод остался от старых версий JavaScript и может
встретиться в старом коде.
5.1.5. Двоичные операторы
Двоичные операторы по очереди обрабатывают отдельные биты заданных чис-
ловых операндов, находящиеся в одинаковых позициях, и возвращают результат
в виде числа.
JavaScript предоставляет следующие двоичные операторы:
♦ ~<операнд>— унарный, преобразует каждый нулевой бит операнда в единичный,
а единичный в нулевой (двоичное отрицание, или двоичное НЕ):
> -оьоон
< -4 и 0Ы100
♦ &— выдает единичный бит, если биты из обоих операндов являются единичны-
ми, и нулевой — в противном случае (двоичноеумножение, или двоичное И);
♦ | — выдает единичный бит. если бит хотя бы из одного операнда является еди-
ничным, и нулевой — в противном случае (двоичное сложение, или двоичное
ИЛИ);
♦ Л— выдает единичный бит, если бит из какого-либо одного операнда является
единичным, и нулевой — в противном случае (двоичное исключающее ИЛИ).
Эти гри оператора являются бинарными и имеют следующий формат записи:
< операнд 1> <двоичный оператору <операнд 2>
) Тримеры:
> ObOOll & ObOOlO
< 2 // ObOOlO
> ObOOll & оьоюо
< 0 // ObOOOO
> ObOOll I ObOOlO
< 3 // ObOOll
> ObOOll I UbOlOO
< 7 // ObOlll
> ObOOll Л ubOOlO
< 1 // ObOOOl
> ObOOll л оьоюо
< 7 // ObOlll
♦ « — сдвигает операнд на заданное количество позиций влево с заполнением
освободившихся позиций справа нулями (сдвиг влево);
♦ » — сдвигает операнд на заданное количество позиций вправо с заполнением
освободившихся позиций слева значениями самого левого бита (сдвиг вправо
знаковый);
♦ »>— сдвигает операнд на заданное количество позиций вправо с заполнением
освободившихся позиций слева нулями (сдвиг вправо беззнаковый).
Эти три оператора также бинарные, носят название операторов сдвига и запи-
сываются в формате:
<.операнд> < оператор сдвита> <колмчество позицийу
I Тримеры:
> ОЬООООООООООООООООООООООООООООООН « 2
< 12 // оьооооооооооооооооооооооооооооноо
> ObOlOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO » 2
< 26843545b // ОЬОООЮООООООООООООООСОООООООООООО
> ОЫООООООООООООООООООООООООООООООО » 2
< -536870912 // Obi1100000000000000000000000000000
> ObOlOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO »> 2
< 268435456 // оьоооюооооаоооооооооооооооооооооо
> OblOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO »> 2
< 536870912 H ObOOlOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
Также поддерживаются операторы комбинированного присваивания (см. разд. 3.8.2),
приведенные в табл. 5.3.
Таблица 5 3. Двоичные операторы комбинированного присваивания
Оператор комбинированного присваивания Его аналог
V &= ор v = v & ор
V |= ор v = v | ор
V Л- ор V = V Л ор
V «= ор v = v « ор
V »= ор V = V » ор
V »>= ор V = V »> ор
Двоичные операторы находят весьма ограниченное применение — в случаях, когда
требуется манипулировать отдельными битами чисел.
В качестве примера можно привести использование битовых полей — чисел, в ко-
торых отдельные биты хранят состояния каких-либо утверждений: единичный бит
обозначает истинность утверждения, а нулевой — ложность. Такое битовое поле
можно получить двоичным сложением нужных битовых масок— чисел, в которых
один из бигов является единичным и задает истинность одного из признаков, а
остальные — нулевыми. Пример:
// Создаем битовые маски, соответствующие трем утверждениям
const BI7MASK1 = ObOOl;
const BITMASK2 = ОЬОЮ;
const BITMASK3 = 0Ы00;
// Создаем битовое поле, в котором истинными станут первое и третье
// утверждения
const bitfield = BITMASK1 | B1TMASK3;
Чтобы проверить, является ли нужный бит, присутствующий в битовом поле, ис-
тинным. достаточно выполнить двоичное умножение битового поля и соответст-
вующей битовой маски и проверить, является ли результат ненулевым:
// Проверяем, является ли единичным третий бит
if (bitfield & BITMASK3)
// Третий бит является единичным
Битовые поля занимают в памяти меньше места, чем аналогичные наборы логи-
ческих величин, что в ряде случаев может оказаться критичным.
Операторы сдвига « и »> применяются для выполнения соответственно умноже-
ния и деления нацело чисел на 2. Эти операторы выполняются быстрее, чем опера-
торы умножения и деления.
5.1.6. Преобразование значений других типов
в числовые
5.1.6.1. Неявное преобразование
Неявное преобразование выполняется, если в выражении требуется указать число,
но задано значение, не являющееся числом.
Исключением является лишь арифметическое сложение числа и строки — в этом
случае исполняющая среда преобразует число в строку и произведет объединение
получившихся строк.
♦ Строка:
• содержащая только цифры, точку, символ + или - в начале, — преобразуется
в соответствующее число. Начальные и конечные пробелы игнорируются
Примеры:
> 2 * '3'
< б
> 2 * '3.3'
< б.б
> 2 * '-3.3'
< -6.6
• содержащая какие-либо другие символы, помимо приведенных ранее, —
в значение NaN:
> 2 * ' 1234abcde'
< NaN
• пустая или содержащая только пробелы — в 0;
♦ логическая величина:
• true — в I;
• false — в 0;
♦ пил — в 0;
♦ undefined — В NaN;
♦ объект— из объекта извлекается его значащее представление (подробности —
в разд. 11.8). и выполняется попытка преобразовать его в число;
♦ большое целое число, функция или эмблема— при попытке преобразования
возникает ошибка типа ТуреЕггог.
5.1.6.2. Явное преобразование
Явное преобразование можно выполнить с помощью:
♦ оператора ±<операнд> — он возвращает операнд, преобразованный в числовой
тип согласно приведенным ранее правилам (унарный плюс):
> +'1234.5'
< 1234.5
> +true
< 1
> +'1234abcdef'
< NaN
♦ встроенной функции Number (<знатение>) — она возвращает значение, преобразо-
ванное в обычное число.
Преобразование производится согласно приведенным ранее правилам. Кроме
того, эта функция позволяет преобразовывать в числовой тип большие целые
числа (см. разд. 5.2).
Примеры:
> Numoer('1234.5')
< 1234.5
> Number(true)
< 1
> Number('1234abcdef')
< NaN
♦ встроенной функции parselnt (<значгение>[, <основание>=10]) —она возвращает
значение, преобразованное в целое число. Значение должно быть строковым. Оно
может быть записано в виде строки в системе счисления, отличной от десятич-
ной.— в этом случае необходимо указать основание используемой системы
счисления.
Если заданное значение не удается преобразовать в обычное число, возвращается
значение NaN.
Преобразование производится согласно приведенным ранее правилам. Кроме
того, значение может после собственно числа содержать произвольное количест-
во символов, не являющихся цифрами, точкой, знаками + и — они будут про-
игнорированы.
Примеры:
> parselnt('1234')
< 1234
> parselnt('1234.5')
< 1234
> parselnt('1234abcdef')
< 1234
> parselnt('abcdef')
< NaN
♦ встроенной функции parseFIoat (<значение>) — она возвращает значение, преоб-
разованное в вещественное число. Значение должно быть строковым. Преобразо-
вание выполняется согласно тем же правилам, что и в случае функции
parselnt().
Примеры:
> parseFloat('1234')
< 1234
> parseFloat('1234.5’)
< 1234.5
> parseFloat('1234.5abcdef')
< 1234.5
5.1.7. Точность представления обычных чисел.
Потеря точности
JavaScript выделяет под хранение каждого обычного числа ячейку памяти ограни-
ченного объема. В такой ячейке может быть полностью сохранено целое число,
укладывающееся в диапазон от-9007199254740991 до 9007199254740991. Кстати,
граничные значения этого диапазона хранятся в статических свойствах
min safe_integer и max safe integer класса Number (см. разд. 5.1.3). Пример:
> let а = 9007199254740991
> // Присвоенное переменной а число записалось полностью
> а
< 9007199254740991
При попытке записать в такую ячейку целое число, выходящее за пределы указан-
ного диапазона, оно будет преобразовано в вещественное число с отбрасыванием
части значащих цифр, располагающихся справа. В результате число исказится, или,
как говорят программисты, возникнет потеря точности. Пример
> // Результат умножения числа из переменной а на себя слишком велик,
> // чтобы поместиться в ячейку памяти, поэтому он преобразуется
> // е вещественное число — с неизбежной потерей точности
> а * а
< 8.112963841460666е+31
Ячейка памяти, выделяемая под хранение обычного числа, позволяет хранить числа
от-1,7976931348623157x1 о’08 до 1,7976931348623157x10308. Если попытаться зане-
сти в нее число, выходящее за этот диапазон, будет занесено значение соответст-
венно -Infinity или Infinity (-со или ос).
Кроме того, при выполнении некоторых операций над вещественными числами,
в частности вычитании, результат получается неточным. Это связано с особенно-
стями обработки таких чисел исполняющей средой JavaScript. Примеры
> // Сложение чисел дает правильный результат
> 2.3 + 5.9
< 8.2
> 11 Умножение — тоже
> 2.3 * 5.9
< 13.57
> // А вот вычитание — нет...
> 2.3 + 5.9 - 6.4
< 1.799999999999999
В таких случаях можно посоветовать лишь округлять получившийся результат до
нужного знака после запятой. Для этого достаточно умножить округляемое значе-
ние на число передать получившееся
произведение методу round о класса Math (см. разе). 5.1.4) и разделить возвращен-
ный результат на то же число. Пример округления до четвертого знака после запя-
той:
> let Ь = 2.3 + 5.9 - 6.4
> Math.round(Ь * 10000) / 10000
< 1.8
5.2. Упражнение.
Реализуем форматированный вывод чисел
При выполнении упражнения 2.4 мы написали небольшое приложение, переводя-
щее величины, заданные в дюймах, в миллиметры. Вот только выводятся преобра-
зованные величины не очень красиво...
Сделаем так, чтобы преобразованная величина выводилась округленной до целых
(вряд ли кому-то нужны доли миллиметра...) и с обозначением единицы измере-
ния — миллиметров.
1. Найдем в панке 2\ех 2 4 сопровождающего книгу файлового архива (см. прило-
жение 5) файл 2 4html (результат выполнения упражнения 2.4). Скопируем ее
куда-либо на локальный диск.
Сначала следует создать простой объект с настройками вывода преобразованной
величины. Этот объект мы далее передадим методу toLocaleString}) класса
N'iniber.
2. Откроем копию страницы 2 4.html в текстовом редакторе и добавим код, соз-
дающий объект с настройками
const format = {
style- 'unit',
unit: 'millimeter',
minimumFractionDigits 0,
maximumFractionDigits: 0
};
const inches = document.getE^ementByldi'incnes');
const millimeters = document.getElementByld('millimeters');
Этот код мы поместили в самое начало веб-сценария. Именно там обычно по-
мещаются объявления всевозможных «фундаментальных» констант, задающих
различные настройки для остального кода.
В объекте мы указали вывод числа в виде величины, выраженной в миллимет-
рах. а также значения минимального и максимального количества цифр в дроб-
ной части числа, равные 0, — чтобы число при выводе округлялось до целого.
Теперь можно добавит ь код, реализующий сам форматированный вывод.
3. Исправим код функции crunches input О (обработчика события input поля для
ввода величины в дюймах) следующим образом:
function onlncheslnput() {
const nuns = inchesN * 25.4;
mi 11 imor.ore. t cxtConront ~ nuns;
millimeters.textContent = rams.toLocaleString(’ru-RU', format);
}
Проверим страниц} в работе. Теперь вывод результата выполняется гораздо краси-
вее: с округлением, указанием единицы измерения и даже группировкой цифр
в числе (рис. 5.1).
120
3 048 мм
Рис. 5.1. Результат выполнения упражнения 5 2
5.3. Большие целые числа
Большие целые числи могут иметь произвольное количество значащих цифр и при
этом будут сохранены в памяти без какой бы то ни было потери точности. Они от-
носятся к типу больших целых чисел.
Большие целые числа являются объектами класса Biglnt. Оператор получения типа
typeof, примененный к большому целому числу, возвращает строку 'bigint'.
Большие целые числа записываются по тем же правилам, что и обычные целые
числа (см. разд. 5.7.7). только с добавлением суффикса п:
9371n, ICOOUOCOOOOOOOOOGOOOOOOOOCOOOOOOOOOOOOOOOOCOOn, 0xf67da09b34n
Применительно к большим целым числам поддерживаются
♦ все арифметические операторы (см. разд. 5.1.2).
Следует иметь в виду, что большие целые числа делятся исключительно нацело.
Примеры:
> 9371п + 137830П
< 147201П
> 9371г. - 137830п
< -128459П
> 9371П * 137830п
< 1291604930л
> 137830л / 9371л
< 14п
> 9371г. / 137830л
< Оп
♦ двоичные операторы (см. разд. 5.1.5), за исключением оператора беззнакового
сдвига вправо »>:
> 9371л | 137830л
< 147199л
> 9371л « 45л
< 329712750844444672л
Внимание!
Оба операнда бинарных арифметических и двоичных операторов должны представ-
лять собой большие целые числа Если указать обычное число, возникнет ошибка
с типом I уреЕггог.
♦ операторы сравнения (см. разд. 4.1.2):
> 9371л < 137830л
< true
Большие целые числа можно сравнивать с обычными.
> 9371л > 7695645.57
< false
Преобразовать большое целое число в обычное можно с помощью функции
Number () (см. разд. 5.1.6):
> Number(9371л)
< 9371
> Number(ЮОООООООООООООООООООООООООООООООООООООООООООл)
< 1е+44
Класс Biglnt поддерживает объектные методы toString() и toLocaleString (), опи-
санные в разд. 5.1.3 и 5.7 3.1:
> (9371л).toString()
< '9371'
> (9371л).toString(2)
< '10010010011011'
> (9371n).toLocaleString()
< '9 371'
Неявное преобразование значений других типов в большие целые числа никогда не
выполняется. Попытка сделать это вызовет ошибку типа TypeError.
Явное преобразование заданного значения другого типа в большое целое число
производится вызовом функции Biglnt (<значею!е>). При этом:
♦ строка:
• содержащая цифры, символ + или - в начале, — преобразуется в соответст-
вующее число. Начальные и конечные пробелы игнорируются. Пример:
> Biglntr 123456 ')
< 123456л
• содержащая любые символы, помимо приведенных ранее, — вызывает
ошибку типа SyntaxError;
• пустая или содержащая только пробелы — в On;
♦ логическая величина:
• true — в In;
• false — в On;
♦ объект — из объекта извлекается его значащее представление (подробности —
в разд. /7Л), и выполняется попытка преобразовать его в большое целое число;
♦ обычное число, функция, эмблема, null или undefined— вызывают ошибку типа
ТуреЕггог.
5.4. Самостоятельное упражнение
Если в поле ввода приложения преобразования величин в дюймы (упражнение 5.2)
ввести значение, не являющееся числом, результатом станет вывод строки NaN
Сделайте так, чтобы в таком случае на экране вообще ничего не выводилось. Под-
сказка: вспомните, что значение NaN при преобразовании в логический тип дает
false, и используйте условное выражение.
Урок 6
Строки
Строки — это наборы текстовых символов неограниченной длины . Они могут со-
держать любые символы, поддерживаемые кодировкой Unicode (за некоторыми
исключениями, описанными далее). Примеры:
'JavaScript', 'строковый тип', 'tunog Srdopcvcjv оирЗоХооеip&g'
Строки относятся к строковому типу данных и представляют собой объекты класса
String. Оператор получения типа typeof, примененный к значению строкового
типа, возвращает строку 'string'.
Полезно знать
♦ Каждый текстовый символ, хранимый и выводимый компьютером, имеет уникаль-
ный числовой номер, называемый кодом символа. Строка фактически представляет
собой последовательность чисел — кодов входящих в нее символов.
♦ На уроке 1.7 мы познакомились с последовательностями — значениями, представ-
ляющими собой наборы других значений (элел/емиов). Строка — это последова-
тельность, неизменяемая, пронумерованная и способная хранить значения только
одного типа.
6.1. Запись строк
Строка записывается в виде последовательности входящих в нее символов, заклю-
ченной в одинарные или двойные кавычки:
'JavaScript1, "JavaScript"
Какую разновидность кавычек использовать — дело вкуса2.
Если строка заключена в одинарные кавычки, в ней не допускаются символы оди-
нарной кавычки. Если строка заключена в двойные кавычки, в ней не допускаются
символы двойной кавычки. В любом случае в строках не допускаются символы
обратного слеща.
♦ Правильно: "Шеймус О'Брайен", 'Суши-бар "Йокогама"'.
♦ Неправильно: 'Шеймус О'Брайен', "Суши-бар "Йокогама"".
1 Точнее, длины, ограниченной лишь объемом оперативной памяти компьютера.
Автор книги предпочитает одинарные— их проще вводить.
Указанные запреты можно обойти, применив специальные символы.
Специальный символ— символ или последовательность символов, имеющая
специальное назначение.
Наиболее актуальные специальные символы, доступные для применения в строках,
приведены в табл. 6.1.
Таблица 6.1. Специальные символы, используемые в строках
Специальный символ Описание
X' Одинарная кавычка
X" Дз энная кавычка
XX Обратный слеш
\г Возврат каретки
\п Перевод строки
\х<код> Символ с кодом, заданным в виде шестнадцатеричного числа от 00 до ff. Позволяет вывести любой из первых 255 символов кодировки Unicode
\и< код> Символ с кодом, заданным в виде шестнадцатеричного числа от 00 ЭС до tfff. Позволяет вывести любой из первых 65535 символов кодировки Unicode
Хи{<код>} Символ с кодом, заданным в чиде шестнадцатеричного числа от 00000. до lOffff. Позволяет вывести любой символ кодировки Unicode
Разрыв строк обозначается либо специальным символом \п, либо (реже) последова-
тельностью символов \г\п.
Примеры.
'Шеймус ОХ'Брайен', "Суши-бар \"Йокогама\"". 'Хха73' («§7»), '2 \u2260 3' («2^3»),
’ЕХВ\и{2122)' («БХВ™»)
Слишком длинные значения строкового типа допускается разрывать на отдельные
строки, поставив перед каждым разрывом обратный слеш:
> const language = 'JavaScript - это лучшим в мире X
> язык программирования!'
> language
< 'JavaScript - это лучший в мире язык программирования!'
Отметим, что последующие строки, содержащие остальные части строкового зна-
чения, следует набирать без отступов слева — в противном случае эти отступы
будут помещены в состав строкового значения:
> const language = 'JavaScript - это лучший в мире X
> язык программирования!'
> language
< 'JavaScript - это лучший в мире язык программирования!'
И наконец, допускается создавать пустые строки, не содержащие ни единого сим-
вола. Для этого пару одинарных или двойных кавычек следует записать вплотную
друг к другу. 11римеры: '',
6.1.1. Шаблонные строки
Шаблонная строка— строка, содержащая какие-либо выражения JavaScript.
Результат, возвращенный таким выражением, будет вставлен строку вместо
этого выражения.
Шаблонная строка заключается в символы обратного апострофа ( ).
Для вставки в шаблонную ст року заданного выражения применяется языковая кон-
струкция формата:
$ {< выражение>}
Выражение должно быть простым. Использовать сложные выражения (наподобие
условных выражений и циклов) не допускается. Количество выражений, помещае-
мых в шаблонную строку, не ограничено.
Пример.
> let cagName = 'span'
> let tagContent = 'Веб-программирснание'
> <${tagName}{tagContent} <7${tagNnme}>'
< '<зрап>Веб-программирование</зрап>'
Шаблонные строковые значения можно разрывать на отдельные строки, не встав-
ляя перед разрывами обратные слеши. При обработке такой строки в местах разры-
вов будут помещены литералы \п, обозначающие разрыв строки (см. табл. 6.1).
Пример:
> const language = 'JavaScript - это лучший в мире
язык программирования!
> language2
< 'JavaScript - это лучший в мире Хпязык программирования!'
Чтобы создать пустую шаблонную строку, пару обратных апострофов следует за-
писать вплотную друг к дру гу: ' .
6.1.1.1. Помеченные строки
Помеченная строка— шаблонная строка, которая обрабатывается заданной
функцией-пометкой'.
Помеченная строка записывается в следующем формате:
<функция-пометка?<шаблонная строка>
Шаблонная строка записывается согласно правилам, приведенным в разд. 6.1.1
Функция-пометка должна принимать следующие параметры:
♦ первый — массив, содержащий все строковые фрагменты шаблонной строки,
в которых все имеющиеся специальные символы уже обработаны.
Если в шаблонной строке два выражения записаны вплотную, без строковых
фрагментов между ними, этот «пустой» промежуток в массиве, получаемом
в первом параметре, представляется пустой строкой. То же самое происходит,
если выражение записано в самом начале или самом конце шаблонной строки;
♦ вторым и последующими параметрами—значения выражений, присутствую-
щих В шаблонной строке.
Результат, возвращенный функцией, которая реализует пометку, и станет результа-
том обработки помеченной строки.
Для примера объявим функцию-пометку getSumO, реализующую обработку поме-
ченных строк следутощего формата:
[«произвольный строковый фрагмент 1>]<выражение 2>'Ъ
[<лро713йольны;7 строковый фрагмент 2>]<выражение 2>’Ъ
[<.про77звольньл7 строковый фрагмент 3>]
В качестве результата функция-пометка вернет исходную строку с добавленной
к ней суммой значений выражений 1 и 2,
Объявление функции-пометки g«tSum():
> function getSunHstrings, opl, op2) {
console.log(strings);
console.log(opl);
console.log(op2);
return '$(strings[0]}${opl}${strings[1]}${op2}. ' +
'Результат: ${opl + op2} ;
)
Пример № 1 использования этой функции-пометки:
> console.log(getSum'Сложим ${1} и ${2). Каков результат?')
. . . Вывод, выполненный самой функцией-пометкой . . .
. . . Содержимое массива из первого параметра (strings) . . .
< (3) [’Сложим ’, ’ и ’, ’. Каков результат?’, raw: Array(3)]
. . . Значение выражения 1 (второй параметр - opl) . . .
< 1
. . . Значение выражения 2 (второй параметр — ор2) . . .
< 2
. . . Результат, возвращенный функцией-пометкой . . .
< Сложим 1 и 2. Результат: 3
Пример № 2:
> console.log (getSuni'${756} + ${187}')
< (3) [’’, ’ + ’, ’’, raw: Array(3)J
< 756
< 187
< 756 + 187. Результат: 943
Здесь в массиве из первого параметра, переданного функции-пометке, первый и
последний элементы содержат пустые строки — поскольку мы в шаблонной строке
записали первое выражение в самом ее начале, а второе— в самом ее конце. Пус-
тая строка представляет «пустой» строковый фрагмент.
Массив, передаваемый функции-пометке первым параметром, поддерживает свой-
ство raw, хранящее массив строковых фрагментов заданной шаблонной строки
с необработанными специальными символами.
6.2. Простейшие операции со строками
Со строкой можно выполнять следующие простейшие операции:
♦ получение символа с указанным индексом (порядковым номером в строке). Для
этого используется оператор доступа к элементу последовательности [], запи-
сываемый в формате:
<последовательности?[индекс элементамJ
Индекс должен быть целочисленным, равным 0 или положительным.
Внимание!
Нумерация символов в строке начинается с О
I Тримеры:
> const str = 'Веб-разработка'
> str L4]
< 'р'
> str [12]
< 'к'
Ранее говорилось, что строки являются неизменяемыми последовательностями.
I Тоэтому попытка изменить символ в строке по его индексу ни к чему не приве-
дет. Пример:
> str[4| = 1т'
> str
< 'Веб-разработка’
Л если включен строгий режим, попытка сделать это приведет к ошибке типа
ТуреЕггог;
♦ перебор с помощью цикла перебора последовательности (см. разд. 4.2.4.4Y
> for (let 1 of str)
console.log(1);
< В
< e
< 6
// . . . Остальной вывод пропущен . .
С тем же результатом можно использовать цикл со счетчиком (см. разд. 4.2.4.1 у
for (let i = 0; i < str.length; i++)
console.log(str[i]);
Пример перебора строки с конца:
> for (let i = str.length - 1; i >= 0; i—)
console.log(str[i]);
< a
< к
< T
< о
♦ объединение, или конкатенацию, заданных строковых операндов— посредством
оператора конкатенации строк +:
<операнд 1> + <операнд 2>
Пример:
> 'Java' + 'Script'
< 'JavaScript'
Объединение успешно выполняется и в том случае, если один из операндов име-
ет тип, отличный от строкового, — исполняющая среда предварительно преоб-
разует его в строку:
> 'JavaScript' + 2017
< 'JavaScript2017’
Если же оба операнда не являются строками, выполняется арифметическое сло-
жение:
> 2017 + 10
< 2027
♦ сравнение строк — в этом случае последовательно сравниваются коды содер-
жащихся в них символов: сначала коды первых символов сравниваемых строк,
потом — коды вторых и т. д.:
> // Коди обоих сравниваемых символов равны
> 'а' = 'а'
< true
> // Коды обоих сравниваемых символов не равны
> 'а' = 'Ь'
< false
> // Код символа а меньше кода символа b
> 'а' < 'Ь'
< true
> // Коды первых двух символов в обеих строках равны, но код третьего
> II символа первой строки (с) меньше кода третьего символа второй
> // строки (d; , и условие становится ложным
> 'abc' > 'abd'
< false
6.3. Свойства и методы класса String
Класс string поддерживает полезное свойство и ряд методов, как объектных, так и
статических.
Свойство length этого класса хранит длину строки в символах:
> 'JavaScript'.length
< 10
Объектные методы:
♦ toLowerCase() — возвращает копию текущей строки, в которой все символы
приведены к нижнему pei истру:
> 'JavaScript'.toLowerCase()
< 'javascript'
> 'ВеБ-рАзРаБоТкА’.toLowerCase()
< 'веб-разработка'
♦ toLocaleLowerCase () — то же самое, что и toLowerCase ();
♦ toUpperCaseO — возвращает копию текущей строки, в которой все символы
приведены к верхнему регистру:
> 'JavaScript'.toLocaleUpperCase()
< 'JAVASCRIPT'
> 'ВеБ-рАзРаБоТкА'.toUpperCase()
< 'ВЕБ-РАЗ РАБОТКА'
♦ toLocaleUpperCase() — то же самое, что и toUpperCase ();
♦ trim() — возвращает копию текущей строки, у которой пробелы в начале и кон-
це удалены:
> ' HTML и CSS '. trim()
< 'HTML и CSS'
♦ trimstart() — возвращает копию текущей строки, у которой пробелы в начале
удалены:
> ' HTML и CSS • .trimO
< 'HTML и CSS
♦ trimEndO — возвращает копию текущей строки, у которой пробелы в конце
удалены:
> ' HTML и CSS '.trim()
< ' HTML и CSS'
♦ includes (<подстрока>) — возвращает true, если текущая строка содержит ука-
занную подстроку, и false — в противном случае:
> 'HTML и CSS'.includes(’CSS*)
< true
> 'HTML и CSS'.includes(’Python')
< false
♦ startswith(<лодсгрока>) — возвращает true, если текущая строка начинается
с указанной подстроки, и false— в противном случае:
> 'HTML и CSS'.scarcsWithf'PHP')
< false
♦ endswith(<подстрока>) — возвращает true, если текущая строка оканчивается
указанной подстрокой, и false— в противном случае:
> 'HTML и CSS'.endsWith('CSS')
< true
♦ indexOf (<подстрока> [, <начальная позиция>^0]) — возвращает номер позиции,
на которой в текущей строке находится заданная подстрока. Поиск начинается
с указанной начальной позиции. Если подстрока не найдена, возвращается -1.
1 Тримеры:
> 'JavaScript'.indexOf('а')
< 1
> 'JavaScript'.indexOf('а', 2)
< 3
> 'JavaScript'.indexOf('z')
< -1
♦ lastlndexOf (<подс,грока>[, <начальная лозиция^-Infinity]) —то же самое, что
indexOf (), но поиск начинается с конца строки:
> 'JavaScript'.lastlndexOf('а')
< 3
> 'JavaScript'.lastlndexOf('а', 2)
< 1
♦ si ice () — возвращает фрагмент теку щей строки:
sli се (<номер первого символа фрагмента> [,
<номер символа, следующего за фрагментом?])
Если номер символа, следующего за фрагментом, не указан, возвращаемый фраг-
мент будет содержать остаток строки.
Примеры:
> 'JavaScript'.slice(4)
< 'Script'
> 'JavaScript'.slice(4, 7)
< 'Scr'
Допускается указание отрицательных номеров— тогда отечет символов будет
производиться с конца строки (последний символ будет иметь номер -I, пред-
последний ----2 и г. д.):
> 'JavaScript'.slice(-5, -1)
< 'crip'
♦ substring() — то же самое, что и slice о, только не позволяет указывать отри-
цательные номера символов:
substring(<номер первого символа фрагментам,
сномер символа, следующего за фрагментов])
Этот метод остался от ранних версий JavaScript и до сих пор встречается в ста-
ром коде;
♦ replace (<заменяемая подстрокам Оаменяющая подстрокам —возвращает копию
текущей строки, в которой первое вхождение заменяемой подстроки заменено на
заменяющую:
> 'HTML и Python'.replace('Python', 'CSS')
< 'HTML и CSS'
> 'да да да'.replace('да', 'нет')
< 'нет да да'
♦ replaceAll ^заменяемая подстрокам <заменяющая подстрокам — ТО же самое,
что и replace (), только заменяет все вхождения заменяемой подстроки:
> 'да да да1.replace('да', 'нет')
< 'нет нет нет'
♦ split (<разделитель>[, <предельный размер массивам) — разбивает текущую
строку на подстроки по заданному сим волу-разделителю, формирует из подстрок
массив и возвращает его в качестве результата. Можно указать предельный раз-
мер массива — тогда избыточные подстроки в него не попадут. Примеры:
> 'HTML,CSS,JavaScript'.split(',')
< (3) ['HTML', 'CSS', 'JavaScript']
> 'HTML,CSS,JavaScript'.split(',', 2)
< (2) ['HTML', 'CSS']
♦ concat (<строка 1>, <строка 2>, . . . <строка N>)—возвращает результат объ-
единения заданных строке текущей:
> 'HTML'.concat(' ', 'и', ' ', 'CSS')
< 'HTML и CSS'
Если требуется объединить более двух строк, рекомендуется использовать этот
метод, т. к. он выполняется быстрее, чем соответствующее количество операто-
ров объединения строк +;
♦ padstart (<длина>[, <строка>=' ']) —добавляет в начало текущей строки за-
данную строку для достижения указанной длины (в символах) и возвращает
результат. Если строка слишком короткая, она дублируется нужное количество
раз, если слишком длинная —урезается. Пример:
> '12345.6'.padstart(10;
< ' 12345.6'
> '12345.6'.padStart(10, '.’)
< '...12345.6'
♦ padEnd (<длмна> I, <стоока>=' ' ]) — то же самое, что и padstart (), только добав-
ляет заданную строку в конец текущей;
♦ repeat (<количество повторен!д5>) — возвращает строку, которая представляет
собой текущую строку, повторенную заданное количество раз:
> .repeat(20)
< •--------------------.
> 'Привет, мир! '.repeat(3)
< 'Привет, мир! Привет, мир! Привет, мир! '
♦ charCodeAt (<номер символа» — возвращает числовой Unicode-код символа
текущей строки с указанным номером:
> 'HTML и CSS'.charCodeAt (5)
< 1080 // Код буквы «и»
♦ at (<номер символа» — возвращает символ текущей строки, имеющий заданный
номер:
> 'HTML и CSS'.at(3)
< 'L'
Допускается указание отрицательного номера — тогда символы будут отсчиты-
ваться с конца текущей строки:
> // Получаем последний символ строки
> 'HTML и CSS'.at(-l)
< 'S'
> // Получаем третий с конца символ строки
> 'HTML и CSS*.at(-3)
< 'С
♦ charAt (<нсмер символа» — то же самое, что и at(), только поддерживает лишь
положительные номера:
> 'HTML и CSS’.charAt(2)
< 'М'
Этот метод остался от предыдущих версий JavaScript и может встретиться в ста-
ром коде.
Статический метод fromCharCodef) возвращает строку, составленную из символов
с заданными Unicode-кодами:
fromCharCode(<код 1>, <код 2» . . . <код N»
Пример:
> String.fromCharCode(0x42f, .х437, 0x44b, 3x43а, 0x20, 0x43, 0x53, 0x53)
< 'Язык CSS'
6.3.1. Сравнение строк с учетом текущей локали
В разд. 6.2 говорилось, что при сравнении строк реально сравниваются коды вхо-
дящих в них символов. Строки, набранные латиницей, сравниваются корректно,
поскольку буквы латиницы от «а» до «z» имеют последовательно увеличиваю-
щиеся шестнадцатеричные коды от jx61 до 0x7а. Пример:
> 'а' < 'f
< true
Однако с буквами кириллицы все сложнее. Если буквы от «а» до «ям имеют также
последовательно увеличивающиеся коды от 0x430 до 0x44f и также сравниваются
корректно:
> 'а' < 'я'
< true
то буква «ё» имеет код 0x45], т. е. с точки зрения исполняющей среды в алфавите
располагается за буквой «я»:
> 'ё' < 'л*
< false
Выполнить корректное с точки зрения текущей локали сравнение текущей и задан-
ной строк может метод localeCompare () класса string:
localeCompare(<сравниваемая строка>)
Метод возвращает:
♦ -1 — если текущая строка «меньше» сравниваемой",
♦ о — если обе строки равны:
♦ 1 — если текущая строка «больше» сравниваемой.
Пример, показывающий, что буква «ё» все же находится в алфавите перед буквой
«л»:
> 'ё'.localeCompare('л')
< -1
6.4. Преобразование значений других типов
в строки
6.4.1. Неявное преобразование
Неявное преобразование производится, если в выражении требуется указать стро-
ку, но задано значение, не являющееся строкой. В частности, это происходит с не-
строковым операндом оператора +, если другой оператор является строкой.
♦ Число, обычное или большое целое, — преобразуется как есть:
> '23' + 23
< '2323'
> '23' + 23п
< '2323'
♦ логическое значение:
• true — В строку ' true';
• false — в строку ’ false';
♦ из объекта — извлекается его значащее представление (подробности —
а разд. 11.8);
♦ null — в строк} ' null';
♦ undefined—В строку 'undefined';
♦ функция — в строку, содержащую код функции;
♦ эмблема — при попытке преобразования возникает ошибка типа ТуреЕггог.
6.4.2. Явное преобразование
Явное преобразование можно выполнить:
♦ шаблонной строкой вида '${<преобразуемое значенме>}':
> '$(2 > 3}'
< 'false'
♦ объединив преобразуемое значение с пустой строкой:
> (2 > 3) + "
< 'false'
♦ функцией String (<значение>) — возвращающей заданное значение в строковом
виде:
> String(2 > 3)
< 'false'
6.5. Упражнение. Использование средств
для обработки строк при программном
генерировании содержимого веб-страницы
Владелец упомянутого в разд. 6.1 суши-бара «Йокогама» узнал, что мы изучаем
JavaScript (и весьма успешно), и предложил нам подработку. Требуется на основе
данных, записанных в массиве, сформировать меню суши-бара и вывести его на
веб-страницу. При этом в процессе вывода необходимо удалить из названий блюд
слово «Новинка:» и пометить соответствующую позицию меню заданным стилем.
К счастью, владелец суши-бара предоставил нам «заготовку» для работы: пустую
страницу, таблицу стилей, веб-сценарий, создающий исходный массив с содержи-
мым меню, и еще один веб-сценарий, пустой — в котором мы и будем писать необ-
ходимый код.
I. Найдем в папке 6Vsources сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 6 5 html (пустая страница), 6.5.css (внешняя таблица стилей),
6.5 1 .js (сценарий с кодом исходного массива), 6.5.2 js (пустой сценарий, где мы
будем писать код) и папку 6 5 (графические файлы с иллюстрациями блюд).
Скопируем их куда-либо на локальный диск.
2. Откроем копию файла 6.5 html в текстовом редакторе и найдем следующий
фрагмент кода:
<Ы>Суши-бар «Йокогама» </hl>
стаin id=''menu">
</mai,n>
^script src-="6.5,l. js"x/scripr>
<script src="6.5.2. js"x/script>
Сгенерированное меню мы поместим в тег основного содержимого (<main>)
с «говорящим» якорем menu (создающий его HTML-код подчеркнут).
3. Откроем копию файла 6 5.1 js и посмотрим на код. создающий исходный массив
для генерирования меню:
const data = [
{
title: 'Чукка',
image: ’6.5/s ushi_chukka.jpg',
desc: 'Диетические суши, приготовляемые из . . .
price: ' 55 ₽'
},
{
title: ’Новинка: Магуро',
image: ’6.5/sushi maguro.jpg',
desc: 'Классические суши. Готовятся из тунца и риса.',
price: '65 ₽'
},
];
Массив с исходными данными хранится в переменной data. В качестве элемен-
тов он содержит простые объекты, каждый из которых описывает одно блюдо.
Простые объекты содержат свойства title (название блюда), image (интернет-
адрес графического файла с иллюстрацией), desc (описание) и price (цена).
Владелец суши-бара выставил следующие требования:
• Отдельная позиция меню должна помещаться в тег статьи (<article>).
• Если в начале названия блюда присутствует подстрока ’Новинка:', тег статьи
должен содержать стилевой класс new.
• В теге статьи должны располагаться следующие элементы:
название блюда — в теге заголовка второго уровня (<h2>);
иллюстрация — в теге изображения (<img>);
описание блюда — в теге абзаца (<р>);
цена — в геге блока (<div>).
Для формирования HTML-кода, создающего меню, мы используем шаблонные
строки (см. разд. 6.1.1} — это упростит код.
Нам потребуется создавать строки, представляющие собой Н1 ML-код какого-
либо парного тега с содержимым (например. '<Ь2>Чукка</Ь2>'). Мы применим
для этого помеченные строки (см. разд. 6.1.1.1) формата:
<амя тегаХвыражение, возвращающее содержимое тега>
Пример: 'h2${dishTitle)
Для обработки таких помеченных строк мы объявим функцию-пометку
getTag(<массив строк>, <содержимое тега>).
4. Откроем копию сценария 6 5 2 js в текстовом редакторе и запишем код, объяв-
ляющий функцию-пометку getTag ():
function getTag(strings, content) {
return '<${strings[0]}>${content}</$(strings(0]}>';
I
Имя тега мы берем из первого элемента массива, передаваемого функции-
пометке первым параметром (strings).
5. Добавим код, который получает объект, представляющий секцию основного со-
держимого menu, и объявляет еще три переменные:
const newTitle = ’Новинка:'; // ................................... 2
const menu = document.getElementByld(’menu’); // .................. 1
let s = ”, t; // .................................................. 3
Для получения объекта, представляющего элемент страницы, по его якорю при-
меняем знакомый по уроку 2 метод getElementByld() объекта страницы (поз. I
в приведенном коде).
Для хранения подстроки, идентифицирующей новинку, объявим отдельную
константу newTitle (поз. 2). Если владелец суши-бара в дальнейшем решит из-
менить эту строку, чтобы сценарий продолжал работать, нам будет достаточно
исправить лишь это выражение.
В переменной s будем хранить постепенно формируемый HTML-код меню,
а переменную t позже применим для временного хранения данных (поз. 3).
6. Добавим код, непосредственно формирующий HTML-код меню:
for (let el of data) { // .......................................... 1
let isNew = el.title.startsWith(newTitle); // .................. 2
if (isNew)
t = el.title.replace(newTitle, ’').trimStart(); // ........... 3
else
t = el.title; // .............................................. 4
s += '<article${isNew ? 1 class="new'" : ••}>' + // ............... 5
getTag'h2${t}' + // ......................................... 6
'<img src="$ {el. image} +
getTag p$( el. descp +
getTag div${el.price} +
1</article>';
}
Для работы с массивом data применим цикл перебора последовательности
(поз. 1 в приведенном коде).
В теле этого цикла сначала проверяем, содержит ли название блюда (свойство
title очередного объекта из массива data) подстроку-признак, ранее сохранен-
ную в константе newTitle (т. е. является ли блюдо новинкой, поз. 2). Для этого
используем метод startswithО класса string (см. разд. 6.3).
Если блюдо является новинкой, удаляем из его названия подстроку-признак. Для
этого применим метод replaced класса string, указав в качестве его второго
параметра пустую строку. Если подстрока-признак находится в начале названия
блюда, после ее удаления останется начальный пробел, который также следует
удалить вызовом метода trimstart О. Результат присваиваем переменной t,
объявленной ранее (поз. 3).
Если же блюдо не является новинкой, присваиваем той же переменной t назва-
ние блюда (поз. 4).
Далее начинаем формировать HTML-код, создающий меню, с активным приме-
нением шаблонных и помеченных строк.
В шаблонную строку, создающую открывающий тег <article>, мы вставили вы-
ражение, которое добавит в этот тег атрибут class со стилевым классом new,
если текущее блюдо является новинкой. В этом выражении мы применили
условный оператор (см. разд. 4.2.2)— поскольку полноценные условные выра-
жения в шаблонных строках недопустимы (поз. 5).
Для формирования тега <h2> с названием блюда используем объявленную ранее
функцию-пометку getTag() (поз. 6).
Остальные выражения из тела цикла аналогичны таковым, представленным
в поз. 5 и 6.
7. Добавим код, выводящий сформированный HTML-код в элементе страницы
menu:
menu.innerHTML = s;
Откроем страницу 6 5 html в веб-обозревателе. Мы должны увидеть то, что показано
на рис. 6.1. А симпатичное у нас получилось меню, не так ли?
Суши-бар «Йокогама»
Ь^аинкэ'
Унаги
Диетические суши,
приготовляемые иэ
одноименного
салага, риса,
кунжута и
водорослей нори
Магуро
Тобико
Хамами
Готовится И1
желтохвоста,
лакедры. аеленого
лука, рисе и
дайконе
Классические суши
Готрвип>из-унцаи
ржа
Икра летучей рыбы
рис, мори
Копченый угоре,
свежий салат.
кунжут и
одноименный соус
65 ₽
Э5руО
81* р (МО
руб)
Рис. 6.1. Результат выполнения упражнения 6 5
Цены здесь пока выводятся не очень красиво: с разными обозначениями рублей
(«₽» и «руб», с точкой в конце и без точки) и с одновременным выводом старой
и новой цены в последней позиции. Мы исправим это на уроке 13.
6.6. Самостоятельное упражнение
Доработайте приложение преобразователя величин в дюймы (упражнение 5.3),
чтобы в нем можно было задавать величины не только в миллиметрах, но и в сан-
тиметрах и метрах. Величины в сантиметрах пусть помечаются суффиксом «см»
или «см.», величины в меграх — суффиксом «м» или «м.».
Урок 7
Временные отметки
Временная отметка — это значение, представляющее собой комбинацию даты и
времени.
Временные отметки представляются в виде объектов класса Date. Они имеют
объектный тип. Оператор получения типа typeof в случае применения к временной
отметке возвращает строку 'object'.
Дата и время в объекте временной отметки хранятся в виде количества миллисе-
кунд. прошедших с полуночи I января 1970 года .
7.1. Создание временных отметок
Временные отметки создаются выражениями следующих форматов:
♦ new Date() — возвращает временную отметку, хранящую текущие дату и время:
> new Date ()
< Tue Oct 10 2023 14:17:14 GMT+G300 (Волгоград, стандартное время)
> // 10 октября 2023 года, вторник, 14:17:14 по времени Волгограда
> // (отстоит от гринвичского на 3:00)
♦ new Date(<год>, <номер месяца>[, <число>=1[, <часы>=0[,
<минуть£>=$[, <секунды>=0 [, <mi 1ллисекунды>=и] ] | ] ])
Все параметры указываются в виде целых чисел:
• год — четырехзначного или двухзначного от 0 до 99. в последнем случае
заданное число интерпретируется как год от 1900 до 1999;
• номер месяца — от 0 до 11:0 — январь, 1 — февраль и т. д.;
• число — от 1 до 31;
• часы—от 0 до 23;
• минуты— от 0 до 59:
• секунды— от 0 до 59;
• миллисекунды— от 0 до 999.
Задаваемые параметры рассматриваются как принадлежащие местной времен-
ной зоне, заданной текущей локалью.
1 ')тот момент времени носит название начала эпохи UNIX.
Примеры:
> new Date (2023, 9, 10, 15, 32, 9)
< Tue Oct 10 2023 15:32:09 GMT+0300
> new Date(2023, 9, 10)
< Tue Oct 10 2023 00:00:00 GMT+030C
(Волгоград, стандартное время)
(Волгоград, стандартное время)
♦ new Date (<строка с датой>) — строка с датой указывается в формате:
<год>[~<номер месяца>=1 [~<число>=1 [7<часЫ>=0 [: <минуты>=0 [: <секувдь£>=0^>
|.<миллисекундЫ>=С‘[<обозначение временной зоны>]]]]]]]
Здесь номер месяца задается в виде числа от 1 до 12: 1 — январь, 2 — февраль
и т. д.
Если обозначение временной зоны не указано, заданные составные части строки
с датой будут трактоваться как принадлежащие универсальному координиро-
ванному времени (UTC).
В качестве обозначения временной зоны можно у казать;
• букву z —тогда составные части строки будут трактоват ься как принадлежа-
щие UTC (как если бы обозначение временной зоны вообще не было указано);
• подстроку формата <+ или -><чась£>[:]<минуты> — тогда составные части
строки будут трактоваться как принадлежащие временной зоне и отстоящие
от UTC на заданное количество часов и минут, в зависимости от поставленно-
го впереди знака: + или
11римеры:
> new Date('2023-10-10Т15:32:09.C00+03GC’)
< Tue Oct 10 2023 15:32:09 GMT+ОЗОО (Волгоград, стандартное время)
> new Date('2023-10-10Т15:32:09.000+09:00')
< Tue Oct 10 2023 09:32:09 GMT+0300 (Волгоград, стандартное время)
> new Date( '2023-10-3 0T15:32:09. C’UOZ')
< Tue Oct 10 2023 18:32:09 GMT4-0300 (Волгоград, стандартное время)
> new Date('2023-10-10')
< Tue Oct 10 2023 03:00:00 GMT-4 0300 (Волгоград, стандартное время)
♦ new Date(<значение времени>} —значение времени задается в виде целочислен-
ного количества миллисекунд, прошедших с полуночи I января 1970 года:
> new Date(1699619529000)
< Fri Nov 10 2023 15:32:09 GMT+0300 (Волгоград, стандартное время)
7.2. Методы класса Date
Методы класса Date, возвращающие и изменяющие одну из составляющих текущей
временной отметки (год, номер месяца, часы и др.) и работающие в текущей вре-
менной зоне, приведены в габл. 7.1.
Таблица 7.1. Методы класса Date, работающие в текущей временной зоне
Составляющая Получение Изменение
Год getFullYear() setFullYear(<под>)
Номер месяца getMonth() setMonth(<намер месяцам)
Число getDate() setDate(< числе»)
Часы getHours() setHcurs (<часы>)
Минуты getMinutes() setMinutes(<ммнуты>)
Секунды getSeconds() setSeconds(<секуноы>)
Миллисекунды getMi11i seconds() setMilliseconds(<мсек>)
Дата и время целиком getTime() setTime (< да та и время'-1
Нс мер месяца здесь возвращается и задается также в виде числа от 0 до 11:0 — ян-
варь. 1 — февраль и т. д.
Дата н время здесь возвращаются и задаются в виде целочисленного количества
миллисекунд, прошедших с полуночи 1 января 1970 года.
Примеры:
> let d = new Date()
> // Какой сейчас год?
> d.getFullYear()
< 2023
> // А месяц?
> d.getMonthO
< 9 // Десятый по счету месяц — октябрь
> // Меняем месяц на июль, а число — на 24-е
> d.setMonthI6)
> d.setDate(24)
Также поддерживается набор методов, аналогичных приведенным в табл. 7.1 (кро-
ме getTimeO), но работающих во всемирном координированном времени. В их име-
нах после слова дес или set поставлена аббревиатура итс. Например, методу
getFullYear() соответствует метод getUTCFullYear(), методу setHoursO — метод
setUTCHours().
Еще несколько полезных методов:
♦ getDayO — возвращает порядковый номер дня недели от 0 до 6: 0— воскресе-
нье. 1 — понедельник и т. д. в текущей временной зоне:
> d.getDayO
< 2
♦ getUTCDay () — то же самое, что и getDay (), только работает в UTC;
♦ getTlmezoneoffset() — возвращает смещение текущей временной зоны отно-
сительно UTC в виде целого числа в минутах:
> d.getTimezoneOffset()
< -180
♦ toString () — возвращает строку с текущей временной отметкой в американском
формате и в текущей временной зоне:
> d.toString()
< 'Tue Oct 10 2023 17:43:16 GMT*0300 (Волгсград, стандартнее время)'
В этом же формате временная отметка выводится в консоли веб-обозревателя;
♦ toDateStringO — возвращает строк) с датой, извлеченной из текущей времен-
ной отметки, в американском формате:
> d.toDateString()
< 'Tue Oct IC 2023'
♦ toTimeStringO — возвращает строку с временем, извлеченным из текущей
временной отметки, в американском формате:
> d.toTimeString()
< '17:43:16 GMT-гОЗОО (Волгоград, стандартное время)'
♦ coisoscring() — возвращает строку с текущей временной отметкой в формате
ISO 8601
> d.tcISOString()
< '2023-10-10Т14:43:16.053Z'
На основе строки такого формага можно воссоздать изначальную временную
отметку (см. разд. 7.1):
const d = new Date('2023-10-10T14:43:16.053Z’);
♦ toUTCString () — то же самое, что и toString (), только работает в UTC:
> d.toUTCString()
< 'Tue, 10 Oct 2023 14:43:16 GMT'
7.2.1. Форматированный вывод временных отметок
Для форматированного вывода временных отметок сообразно указанной локали
следует использовать методы:
♦ toLocaleString () — возвращает строку с текущей временной отметкой;
♦ toLocaleDateString () — возвращает строку с датой, извлеченной из текущей
временной отметки;
♦ toLocaleTimeString() — возвращает строку с временем, извлеченным из теку-
щей временной отметки.
Формат выюва у этих трех методов одинаков:
<метод> ([<обозначение локали? [, Дополнительные настройки>] ])
Обозначение локали задается в том же формате, что и у метода toLocaleString()
класса Number (см. разд. 5.1.3.1):
> let d = new Date()
> // Поскольку обозначение локали мы не указали, будет использована
> // локаль веб-обозревателя - русская
> d.toLocaleString()
< '06.11.2023, 18:11:04'
> d.toLocaleDateStringO
< '06.11.2023'
> d.toLocaleTimeStringO
< '18:11:04'
> // Указываем американскую английскую локаль
> d.toLocaleString('en-US')
< '11/6/2023, 6:11:04 PM’
> // Указываем немецкую локаль
> d.toLocaleString('de-DE')
< '6.11.2023, 18:11:04'
допынительные настройки также задаются в виде простого объекта. Поддерживают-
ся следующие настройки:
♦ timezone — обозначение временной зоны для вывода времени в виде строки.
Список всех существующих временных зон можно найти по интернет-адресу:
http://tinyurl.com/mM3rut2a Пример:
> d. toLocaleString ('ru-RU', { timeZone: 'Asia/Sakhalir.' })
< '07.11.2023, 02:11:04'
♦ hourl2— если true, время будет выводиться в 12-часовом формате, если
false — в 24-часовом (значение по умолчанию зависит от локали);
♦ настройки, которые предписывают выводить соответствующие составляющие
даты и времени:
• era — название эры: 'long' (полное), 'short' (сокращенное) или 'narrow'
(аббревиатура);
• year— год: 'numeric' (из четырех цифр) или '2-digit' (из двух цифр);
* month — номер или название месяца: 'numeric' (номер из одной или двух
цифр). '2-digit' (номер строго из двух цифр), 'long' (полное название),
'short' (сокращенное название) или 'narrow' (аббревиатура);
• day— число: 'numeric' (из одной или двух цифр) или '2-digit' (строго из
двух цифр);
• weekday— название дня недели: 'long' (полное), 'short' (сокращенное) или
'narrow' (аббревиатура);
• dayPeriod—название половины дня: 'long' (полное), 'short' (сокращенное)
или 'narrow' (аббревиатура). Принимается во внимание, только если время
выводится в 12-часовом формате;
• hour— часы: 'numeric' (из одной или двух цифр) или '2-digit' (строго из
двух цифр);
• minute— минуты: 'numeric' (из ОДНОЙ или двух цифр) ИЛИ '2-digit' (строю
из двух цифр);
• second— секунды: 'numeric' (ИЗ ОДНОЙ ИЛИ двух цифр) ИЛИ '2-digit' (строго
ИЗ двух цифр);
• fractionalSecondDigits — количество цифр, отводимых для представления
дробной части секунд, от 1 до 3;
• timeZoneName— название временной зоны: 'long' (полное локализованное),
'longGeneric' (полное), ’short' (короткое локализованное), 'shortGeneric'
(короткое), 'longOffset' (в виде смещения в часах и минутах) и 'shortoffset'
(в виде смещения в часах).
Если какая-либо из приведенных настроек не указана, соответствующий компо-
нент временной отметки выведен не будет.
Примеры:
> d.toLocaleString('ru-RU', { era: 'long', year: 'numeric',
month: 'long', day: 'numeric',
hour: 'numeric', minute: 'numeric',
second: 'numeric', weekday: 'long',
timeZoneName: 'long' })
< 'понедельник, 6 ноября 2023 г. от Рождества Христова в 18:11:04 'Ъ
Волгоград, стандартное время'
> d.tcLocaleString('ru-RU', { era: 'narrow', year: '2-digit',
month: 'narrow', day: 'numeric',
hour: 'numeric', minute: 'numeric',
second: 'numeric', weekday: 'narrow',
timeZoneName: 'short' })
< 'П, 6 H 23 г. н.э., 18:11:04 GMT+3'
> d.toLocaleString('ru-RU', { year: 'numeric', month: 'numeric',
day: 'numeric',
timeZoneName: 'shcrtOffset' })
< '06.11.2023, GMT+3'
Если же стоит задача отформатировать множество временных отметок, лучше ис-
пользовать объект класса Inti.DateTimeFormat— это повысит производительность.
Объект этого класса создается выражением формата:
new Inti.DateTimeFormat([<обозначение локали>[,
<дополнительные настройки>]])
Здесь задаются те же параметры, что и у описанных ранее методов. Созданный
объект следует присвоить какой-либо переменной.
Форматированный вывод заданной временной отметки выполняется методом
format (<временная отметка>} класса Intl.DateTimeFormat.
Пример:
> const formatter = new Inti.DateTimeFormat(’ru-RU',
{ year: 'numeric', month: 'numeric',
day: 'numeric', timeZoneName: 'shortoffset' })
> fonnatter2.format(d)
< '06.11.2023, GMT+3'
Урок 8
Функции
Функция — набор выражений, выполняющий заданные вычисления и рассчитан-
ный для многократного использования. Функция может принимать определенный
набор значений {параметров) и возвращать результат. К функции .можно обра-
щаться (выполнять ее вызов) из произвольных мест кода, каждый раз передавая ей
разные параметры.
Мы можем как создать свои собственные функции, выполнив их объявление, так
и свободно использовать встроенные, реализованные в исполняющей среде
JavaScript.
8.1. Вызов функций
Оператор вызова функции () (пара круглых скобок) записывается в следующем
формате:
<вызываемая функц1я> ([<значенмя параметров, передаваемых функции, через заяягую>])
Если вызываемая функция не принимает параметров, следует указать пустые круглые
скобки (не забываем, что это не просто скобки — а самый настоящий оператор).
Если вызванная функция возвращает результат, его можно присвоить переменной,
передать в качестве параметра другой функции или использовать в вычислениях.
Если результат не нужен, его можно вообще никак не использовать — тогда он
будет потерян.
Примеры:
> // Вызываем встроенную функцию parselnt() (см. разд. 5.1.6)
> parselnt('12345', 10)
< 12345
> // Поскольку второй параметр функции parselnt() является необязательным
>// и имеет значение по умолчанию 10, при необходимости передать
> // значение 10 этим параметром его можно не указывать
> parselnt(*12345’)
< 12345
> parselnt('12345’, 8)
< 534 9
Оператор вызова функции имеет очень высокий приоритет, поэтому выполняется
раньше, чем большинство остальных операторов, включая арифметические:
> // Сначала выполняется вызов функции, а уже потом — сложение
> parselntt'12345') + 1
< 12346
Вызов несуществующей функции приведет к программной ошибке типа
ReferenceError.
Полезно знать
В JavaScript любой функции можно передать больше параметров, чем ей нужно. «Лиш-
ние» параметры будут проигнорированы, и функция успешно выполнится. 11ример:
> parselntC 12345', 10, 589776, 'abcdef')
< 12345
Также любой функции можно передать меньше параметров, чем ей требуется. В гаком
случае параметрам функции, не получившим значения при вызове, будет присвоено
значение undefined. Пример:
> parselnt()
< NaN
1 (ервый. обязательный параметр функции parselnt () получил значение undefined, кото-
рое было преобразовано в NaN.
Однако такими «фокусами» лучше не увлекаться. В лучшем случае результат выполне-
ния функции окажется не таким, на какой мы рассчитывали, а в худшем— возникнет
программная ошибка.
8.2. Объявление функций
Объявление функции— это выражение, описывающее создаваемую функцию: ее
имя, набор принимаемых ею параметров и тело функции (набор выражений, ради
которого и создается функция).
Объявление функции записывается в формате:
function <имя функшяй> ((отмена параметров, принимаемых функцией, Ъ
через запятую>]) {
<тело функции>
Имя функции должно включать только буквы, цифры, символы доллара ($), подчер-
кивания (_), не должно начинаться с цифры, равно как и совпадать с каким-либо из
ключевых слов языка {см. разд. 3.2). Можно сказать, что имя функции подчиняется
тем же правилам, что и имя переменной {см.разд. 3 10.1).
Имя функции должно быть уникальным в пределах области видимости функций
(о ней — чуть позже).
Имена функций чувствительны к регистру символов (как и имена переменных).
Имена параметров также должны удовлетворять тем же требованиям, что предъяв-
ляются к именам переменных.
Тело функции всегда оформляется в виде блока, т. е. берется в фигурные скобки,
даже если оно состоит всего из одного выражения.
При вызове функции исполняющая среда создает локальные переменные, одно-
именные указанным в объявлении функции параметрам, и присваивает им значения
параметров, переданные при вызове. К этим переменным можно обращаться в теле
функции.
Локальная переменная— переменная, область видимости которой ограничена
телом функции, в которой была создана эта переменная {область видимости
уровня функции). Переменная, объявленная в теле функции, всегда остается
локальной вне зависимости от оператора, которым было выполнено ее объяв-
ление: let, const или var.
Для возврата результата выполнения функции (если функция должна возвращать
результат) в нужном месте ее тела следует поместить оператор возврата резуль-
тата return, записываемый в формате:
return [<возвращаемое значениё>]
Внимание!
Оператор возврата результата return немедленно прерывает выполнение тела функ-
ции Код, располагающийся после этого оператора, выполнен не будет
Если в операторе возврата результата неуказан возвращаемый результат, из функции
будет неявно возвращено значение undefined. Оператор возврата результата без
указания возвращаемого результата применяется, если нужно прервать выполнение
функции в заданном месте.
Объявление функции может располагаться в произвольном месте кода. Однако
традиционно объявления всех используемых функций помещают в начало сцена-
рия.
Будучи объявленной в обычном коде, функция доступна везде, включая тела дру-
гих функций (имеющая глобальную область видимости, или глобальная, функция).
Для примера объявим функцию getTagO, принимающую два параметра: имя и со-
держимое тега — и возвращающую строку с готовым HTML-кодом:
> function getTag(tagName, content) {
return '<${ragName}>${content}</${tagNanie}> ;
)
Проверим функцию в деле:
> getTag('h2', 'Хамачи')
< ' <Д2>Х.амачи</Н2> ’
> getTag('div', 'Цена: 65 руб.’)
< '<div>UeHa: 65 py6.</div>’
Еще нужно отметить, что в теле функции доступны все глобальные переменные
и переменные уровня блока (если вызов функции производится в блоке):
> // Объявляем глобальную переменную
> let а = 1;
> // Объявляем функцию, присваивающую глобальной переменной новое значение
> function f() {
а = 10;
}
> // Вызываем функцию
> f О
> // Значение глобальной переменной изменилось
> а
< 10
Объявим еще одну функцию—vowelCount (), принимающую строку и возвращаю-
щую количество гласных в ней:
> function vowelCount(str) {
let ent = 0;
for (let 1 of str) {
if ((1 = 'a') || (1= 'y') || (1 = 'o') I| (1 = 'ы') ||
(1 = 'э') I I (1 == 'я') I I (1 — 'ю') || (1 — 'e') ||
(1 = 'и') I I (1 == 'e'))
cnt++;
}
return ent;
}
> vowelCount('веб-разработка')
< 5
> vowelCount('функциональный тип данных’)
< 8
Третья функция— randomiooo, не принимающая параметров и возвращающая
псевдослучайное целое число от 0 до 100;
> function randomlOOO {
return Math.floor(Math.random() * 100);
}
> randoml00()
< 66
> randomlCOO
< 45
> randomlOOi)
< 79
Последняя функция— greeting О, принимающая имя, выводящая в консоли при-
ветствие и не возвращающая результата:
> function greeting(name) {
console.log('Привет, ${name}!');
}
> greeting(’Владимир')
< Привет, Владимир!
> greeting('Билл Гейтс')
< Привет, Билл Гейтс!
ПОЛЕЗНО ЗНАТЬ
♦ Имена объявляемым функциям даются согласно стилю canielCase, применяемому
при именовании переменных (см. разг). 3 10.1).
♦ Ранее говорилось, что оператор возврата результата return прерывает выполнение
тела функции. Этим часто пользуются. Рассмотрим такой пример:
function someFunct . . . ) {
if (<условие 1>)
return <значение 1>;
if (<условие 2>)
return <значение 2>;
return означение 3>;
return ^значение 4>;
)
Если истинно условие 1, будет возвращено значение 1, и выполнение функции пре-
рвется. Если истинно условие 2, будет возвращено значение 2, и выполнение функ-
ции также прервется. Если же оба условия ложны, функция вернет значение з.
А выражение, возвращающее значение 4, никогда не выполнится и фактически бес-
полезно1.
♦ Если функция не возвращает никакого результата явно, ее вызов приведет к неявно-
му возврату значения undefined.
♦ Допустимо объявлять функции повторно. При этом предыдущее объявление будет
заменено последующим. Пример:
> // Объявляем функцию
> function greeting(name) {
console.log('Привет, ${name)1');
}
> greeting('Владимир')
< Привет, Владимир!
> // Объявляем ту же функцию повторно
> function greeting(name) {
console.log('Здравствуйте, ${name}!4);
> }
> greeting('Владимир')
< Здравствуйте, Владимир!
♦ JavaScript допускает объявлять пуст ые функции, не содержащие тела:
function scmeFunc() {}
Подобного рода код, не выполняющийся никогда, называют мертвым.
8.2.1. Объявление локальных переменных
В разд. 8.2 говорилось, что при выполнении функции на каждый параметр, указан-
ный в ее объявлении, создается локальная (доступная только в теле функции) пере-
менная, и ей присваивается значение параметра, заданное при вызове.
Однако мы для своих н\жд можем объявлять в теле функции собственные локаль-
ные переменные— средствами, описанными в разд. 3.10.1.
Локальные переменные существуют лишь в теле функции вне зависимости от опе-
ратора. примененного для их объявления: let, const или var. По завершении вы-
полнения функции все объявленные в ней локальные переменные уничтожаются
(если функция не является хранящей — см. разд. 8.5.3).
Пример:
function greeting(name) {
let s = Привет, ${name}!';
console.log(s);
}
Если в теле функции объявить локальную переменную с именем, совпадающим
с именем какой-либо глобальной переменной, то при указании этого имени будет
выполняться обращение к локальной переменной. Обратиться к одноименной гло-
бальной переменной в теле функции будет невозможно. Пример
// Объявляем глобальную переменную
let а = 1
// Объявляем функцию, создающую одноименную локальную переменную
//и выводящую ее значение в консоли
function f() {
let а = 10;
// Будет выведено значение локальной переменной
console.log(а);
}
8.2.2. Необязательные параметры функций
В разд. 8.1 было сказано, что при вызове функции ей можно передать меньшее
количество параметров, чем указано в ее объявлении, при этом оставшиеся пара-
метры автоматически получат значения undefined.
Однако при объявлении функции у любого параметра можно указать значение по
умолчанию, которое он получит, если не будет указан при вызове функции (необя-
зательный параметр). Дгя этого в объявлении функции в круглых скобках следует
поместить конструкцию формата:
<;1мя парамегра>=<значение по умолчанпю>
Внимание!
Необязательные параметры в объявлении функции должны располагаться строго по-
сле всех обязательных
Пример функции getTagо, принимающей два параметра: обязательное содержимое
тега и необязательное имя тега со значением по умолчанию 'div':
> function getTag(content, tagName='div') {
return '<${tagName}>$(content}</${tagNanie}>' ;
> getTag('Хамачи', ’h2*)
< '<Ь2>Хамачи</Ь2>'
> getTag('Цена: 65 py&.')
< '<div>LleHd: 65 pyO.</div>'
8.2.3. Функции
с произвольным количеством параметров
Есть возможность объявить функцию, принимающую произвольное количество
параметров. Для этого в объявлении функции в круглых скобках следует записать
конструкцию формата:
. . . <лбот параметра>
Троеточие ... — это оператор упаковки-распаковки. При вызове функции он «со-
бирает» все переданные ей параметры, «упаковывает» их в массив1 и присваивает
его параметру с заданным именем.
Пример функции average(). принимающей произвольное количество чисел и вы-
числяющей их среднее арифметическое (свойство length массива хранит его раз-
мер):
> function average(...nums) {
let sum = 0;
for (let n of nums)
sum += n;
return sum / nums.length;
}
> average(1, 2, 3, 4, 5)
< 3
> average(465, 87, 3, 9045, 82)
< 1936.4
Параметр, объявленный с применением оператора упаковки-распаковки, можно
совмещать с обычными, обязательными к указанию параметрами. В этом случае
функция сможет принимать заданное количество обязательных параметров и про-
извольное — необязательных.
Внимание!
Параметр, объявленный с применением оператора упаковки-распаковки, должен рас-
полагаться строго после обязательных параметров и быть единственным параметром
такого рода
1 ')тот оператор также может «распаковывать» массивы, о чем мы узнаем на троке 9.
Пример функции getTag(). принимающей обязательное имя тега и произвольное
количество строк, объединяющей их и возвращающей строку с готовым HTML-
кодом (метод join() массива объединяет элементы текущего массива в строку, раз-
деляя их заданным в параметре разделителем, и возвращает полученную строку):
> function getTag(tagName, ...strings) {
return '<${tagName}>${strings.join(' •)}</${tagName}> ;
> }
> getTag('div', 'Цена1, *65’, 'руб')
< '<div>LleHa 65 py6</div>'
> getTag('p', 'Заходите', 'в', 'наш', 'суши-бар’, 'почаще 1')
< '<р>3аходите в наш суши-бар почаще!</р>'
8.2.4. Вложенные функции
Вложенная функция — функция, обьявленная в зеле другой функции. Доступна
лишь в теле функции, в которой объявлена (т. е. имеет область видимости
уровня функции).
Вложенной функции доступны все глобальные переменные, переменные уровня
блока (если вызов функции, в которой она объявлена, производится в блоке),
а также все локальные переменные из «внешней» функции.
Пример функции, содержащей вложенную функцию:
> function getTag(tagName, ...strings) {
function concateStrings(strs) {
let s = '';
for (let str of strings)
s += ' ' + str;
return s.trim();
)
return '<${tagName}>$(concateStrings(strings)}</${tagName}>';
> getTag('p', 'Заходите', 'в', 'наш', 'суши-бар', 'почаще!')
< '<р>Заходите в наш суши-бар почаще!</р>'
Не забываем, что вне «внешней» функции вложенная функция недоступна.
8.3. Функция как значение.
Функциональный тип данных
Функция в JavaScript — это значение, такое же как число или строка. Оно является
объектом класса Function и относится к функциональному типу данных. Оператор
получения типа typeof в случае функции возвращает строку 'function'. Пример:
> function someFuncf) {}
> typeof someFunс
< 'function'
8.3.1. Присваивание функций
Поскольку функция суть значение, ее можно:
♦ присвоить переменной:
> // Объявим функцию
> function getTag(tagName, content) {
return '<$ <tagName}>${content}</${tagName}>';
}
> getTag (1h21, ’Хамачи*)
< '<Ь2>Хамачи</Ь2>'
> // И присвоим ее переменной
> const func = getTag
При присваивании функция указывается без круглых скобок (если их указать,
функция будет вызвана, и в переменной окажется возвращенный ею результат).
После этого мы можем вызвать функцию, использовав синтаксис, описанный
в разд. 8.1, только вместо имени функции указав имя переменной, которой была
присвоена вызываемая функция
> func(’h2', 'Хамачи')
< ' <Ь2>Хамачи<../Ь2> ’
♦ передать функцию с параметром другой функции.
Вернемся к упражнению 2.4 и посмотрим на код, объявляющий обработчик и
привязывающий его к событию:
function onlncheslnput() {
}
inches.addEventListener('input', onlncheslnput);
Здесь вторым параметром методу (который суть частный случай функции)
addEventListener () передается объявленная ранее функция onlncheslnput ().
Отметим, что и здесь функция записывается без круглых скобок.
8.3.2. Проверка существования функции
Проверить, объявлена ли какая-либо функция, можно одним из способов, описан-
ных в разд. 3.10.3 и 4.1.4.3. Только результат, возвращенный оператором получения
типа typeof, следует сравнивать со строкой 'function'.
В качестве примера проверим существование функции getTag():
// Более надежный способ
if (typeof getTag == 'function) {
// Функция существует
else {
// Функция не существует. Можем объявить ее.
// Функция, объявленная в блоке, лоступна отовсюду (см. разд. 8.2) .
function getTag(...){...}
}
// Менее надежный способ
if (getTag)
// Функция существует
else
// Функция не существует
8.4. Упражнение. Спойлер, часть 1
Спойпер— панель, разворачивающаяся и сворачивающаяся при щелчке на ее
заголовке.
Наша слава как веб-программислов существенно выросла после успешного созда-
ния меню для суши-бара «Иокогама» (см. упражнение 6.5). И к нам потянулись
другие клиенты.
Очередной клиент попросил сделать ему спойлер. Этим сейчас и займемся
I. Найдем в папке SVsources сопровождающею книгу файлового архива (см. при-
ложение 5) файлы 8 4.html (страница с «заготовкой» для спойлера) и 8 4 css
(внешняя таблица стилей с необходимым оформлением). Скопируем их куда-
либо на локальный диск.
2. Откроем копию файла 8 4 html в текстовом редакторе и посмотрим на HTML-код
«заготовки» для спойлера:
<div id="spoilerl">
<Н6>0бъявление функции</Ь6>
<div>
<div>
<р>Объявление функции записывается в формате:</р>
</div>
</div>
</div>
Сам спойлер создается блоком (тегом <div>) с якорем spoilerl. В него вложен
заголовок шестого уровня (<h6>), формирующий заголовок спойлера, и два по-
следовательно вложенных друг в друга блока с содержимым спойлера.
Теперь вспомним, что сказал заказчик...
К «внешнему» блоку, создающему спойлер, следует привязать стилевой класс
spoiler, который задаст необходимое оформление (изначально этот стилевой
класс не привязан). Все необходимые стили уже записаны в таблице стилей
8 4 css.
Изначально спойлер свернут. Чтобы развернуть его. следует дополнительно
привязать к «внешнему» блоку стилевой класс opened, а для сворачивания доста-
точно убрать этот стилевой класс.
Следует предусмотреть следующие возможности:
• делать спойлер изначально открытым;
• задавать у него дополнительное оформление в виде еще одного стилевого
класса.
В таблице стилей уже имеется стиль со стилевым классом highlighted, кото-
рый делает спойлер более заметным.
Код, создающий спойлер, нужно поместить в отдельный файл, тем самым создав
внешний сценарий. Заказчик хочет использовать его на других страницах.
Поместим этот код в файл 8 4 js.
3. Вставим в конце секции тела страницы тег, привязывающий к странице файл
внешнего веб-сценария 8.4 js:
<body>
<div id="spoilerl">
</div>
<scnpt src="S . 4 js"X/scnpt>
</body>
4. Создадим в папке, где хранится копия страницы 8 4htmL файл 8 4js и откроем
его в текстовом редакторе.
Нам понадобится хранить состояние спойлера: свернут ли он или развернут.
Объявим для этого переменную spoilerstate, а состояние спойлера обозначим
логическими величинами false и true соответственно. Не забываем, что изна-
чально спойлер должен быть свернут.
5. Запишем в файл 8 4 is код, объявляющий переменную spoilerstate:
let spoilerState = false;
Наш спойлер будет менять состояние в ответ на щелчки мышью на заголовке —
теге <h6>. Для этого потребуется обрабатывать событие щелчка мышью, нося-
щее название click. Обработчик этого события будет менять состояние спойлера
на противоположное и либо привязывать к нему стилевой класс opened, либо
убирать его привязку. Назовем функцию-обработчик onHeaderClick().
6. Добавим туда же объявление функции onHeaderClick() — обработчика события
click, возникающего в заголовке спойлера:
function onHeaderClick() {
spoilerState = !spoilerState; // .............................. 1
if (spoilerState)
this.parentElement.cIassList.add('opened'); // .......... 2
else
this.parentElement.classList.remove(’opened'); // ............ 3
}
В теле этой функции мы сначала выполняем операции» логического отрицания
над состоянием спойлера, применив оператор !, описанный в разд. 4.1.3 (поз. I
в приведенном коде). Если полученный результат равен true, разворачиваем
спойлер, привязав к нему стилевой класс opened (поз. 2), в противном случае —
сворачиваем, убрав привязку этого стилевого класса (поз. 3).
Обработчик события click мы привяжем к заголовку спойлера. Но привязывать
стилевой класс opened требуется к «внешнему» блоку, формирующему спойлер
и являющемуся родителем заголовка. Получить родитель текущего элемента мы
можем, обратившись к свойству parentElement, которое поддерживается всеми
классами-элементами страницы.
Еще все классы, представляющие элементы страницы, поддерживают свойство
classList. Оно хранит объект особого класса, представляющего перечень привя-
занных к элементу стилевых классов (подробности— на уроке 17). Метод add()
этого класса привязывает к элементу стилевой класс с указанным в параметре
именем, метод remove () удаляет привязку заданного стилевого класса.
Далее объявим функцию createSpoiler(), которая превратит заданный элемент
страницы в полнофункциональный спойлер с указанными изначальным состоянием
И дополнительным стилевым классом:
createSpoiler (Отемент страницЫ> [, <изначальное состоятie>=false [,
Дополнительный стилевой класс>=1 ’ ] ])
7. Добавим туда же объявление функции createSpoiler ():
function createSpoiler(element, state=false, decoration=’') {
const header = element.querySelector('h6'); // .................. 1
element.classList.add('spoiler'); // ............................ 2
spoilerScate = state; // ....................................... 3
if (spoilerState) // ............................................ 4
element.classList.add('opened’);
if (decoration) // .............................................. 5
element.classList.add(decoration);
header.addF.ventListener ('click', DnHeaderClick); // ........... 6
}
Каждый класс-элемент страницы поддерживает метод queryselector (), возвра-
щающий первый найденный потомок текущего элемента, который удовлетворя-
ет заданному CSS-селектору. Мы используем этот метод для поиска заголовка
спойлера, вложенного во «внешний» блок (поз. 1 в приведенном коде).
Дальше привязываем к «внешнему» блоку стилевой класс spoiler, задающий
оформление спойлера (поз. 2), заносим заданное во втором параметре изначаль-
ное состояние в переменную spoilerstate (поз. 3), если спойлер должен быть
изначально развернутым — дополнительно привязываем к «внешнему» блоку
стилевой класс opened (поз. 4), если в третьем параметре указан дополни тельный
стилевой класс — привязываем также и его (поз. 5). Напоследок указываем в ка-
честве обработчика события click (щелчок мышью), возникающего в заголовке
спойлера, ранее объявленную функцию onHeaderdickO (поз. 6).
Осталось добавить код, превращающий блок с якорем spoilerl в спойлер.
Оформим его как внутренний веб-сценарий.
8. Добавим в страницу 8.4 html сценарий, инициализирующий спойлер:
<body>
<div id="spoilerl">
</div>
<script src="8.4 . js"x/script>
<script>
const spoilerl = document.getElenjentById('spoilerl‘);
createSpoiler(spoilerl);
</script>
</body>
Откроем страницу 8 4 html в веб-обозревателе. Развернем и свернем спойлер,
щелкая на его заголовке (рис. 8.1).
+ Объявление функции
- Объявление функции
Ойыш.тение функции ипткинзется формате
function <«МД фрЛЩ*И>([<ИМ*Н4 параметре#, приммм««мм функцией, •
«тело функции?
>
11мя фукмши ДОЛЖНО aK.W4.il» ТОЛЫ» цифры. sIMBaiU ДоЛЛВра
it I. подчоркшання (_). не должно шппются с цифры, ровни как и
scans топ. с каким- пню in к мочевых стоя ятыкэ (см ртв f ?) Можно
спить, что имя фхлкшш Подчиняем, я тем же прли им. что и имя
переменной <си./»М 3.10.Г).
Имя фуимиш ДОЛЖНО иЫТЬ УНИМЛЫШМ 1 Пределах ООТКП1 ВИДИМО, П|
функции (о ней • чутьпотжк).
Имена функций чух.т>1пеикы к pern,тру игмтюло» (как и шш
переменных)
Рис. 8.1. Результат выполнения упражнения 8 4 спойлер в свернутом (слева)
и развернутом (справа) состоянии
Попробуем сделать спойлер изначально развернутым и указать у него дополни-
тельный стилевой класс highlighted.
9. Исправим ранее написанный код инициализации спойлера:
const spoilerl = document.getElementByld('spoilerl');
craatospoilortspoilcrii;
createSpoiler(spoilerl, true, 'highlighted');
Обновим страницу и посмотрим на спойлер (рис. 8.2).
- Объявление функции
4- Объявление функции
ЦбЫЫЛПКг фГИКШШ МПИОЛТСТСХ * форипг
function имя *умн^ии>([<аикя« пярочтм*. пранимм>«<г *уи,ик*и. •
-•0«> 1>лят>« )) (
<’♦« ♦гИЯцМ»
)
Имя jnidUHU ЛМ.ТКИМ як ЧОПЯП толы» бумы, цифры. < ПМИ-1ЛЫ датырэ
(Я. пазч£рытмнпя<_1. ие доъкиокачивлия I цифры. р»яо как п
<овп.иаисыив1пг6о1пкл>и'1сьы\с1ов»>ы».1(ск1 рчх) .<?) Можно
сшить. что имя фу1ИЛ1Ш1 посптистеж тем же прашььиь что и п>ш
перемятой (см /»ги> И.1О.Ц
11ч» 4vxMiiiii ютжио быть уника тьным я прстетм О&1КТП N! lllMocnt
фуиьшШ <о иеП — чуть пыже).
Имена фуНкциП чукпипе.тЫ1Ы к регистру сныватоя iuib и имена
переменных-
Рис. 8.2. Результат выполнения упражнения 8 4 спойлер с дополнительным оформлением
в развернутом (слева) и свернутом (справа) состоянии
Однако, когда мы создадим на странице два подобных спойлера, возникнет про-
блема. Если мы раскроем один спойлер, то при попытке раскрыть второй ничего не
произойдет. Второй спойлер раскроется лишь после второго щелчка.
Дело в том, что состояние обоих спойлеров хранится в одной переменной —
spoilerState. При раскрытии первого спойлера ей присвоится значение true. При
попытке раскрыть второй спойлер обработчик события onHeaderClick() инвертиру-
ет ее значение, получит в результате false — и свернет уже свернутый спойлер.
Мы исправим это при выполнении упражнения 8.6.
8.5. Специальные разновидности функций
В разд. 8.2 описывалось объявление нехранящих именованных функций.
Нехранящая функция— функция, после выполнения которой все созданные
в ней локальные переменные удаляются из памяти.
Именованная функция— функция, имя которой было указано непосредственно
при объявлении — после ключевого слова function.
JavaScripi поддерживает объявление функций специальных разновидностей, кото-
рые мы сейчас рассмотрим.
8.5.1. Анонимные функции
|| Анонимная функция — функция, объявленная без указания имени.
Формат объявления анонимной функции:
function([Симена параметров, принимаемых функцией, через запятую>]) {
<-тело функции>
Объявленную анонимную функцию следует присвоить какой-либо переменной или
передать в качестве параметра другой функции— иначе она будет сразу же унич-
тожена исполняющей средой.
Пример:
> const func = function(tagName, content) {
return '<${tagName }>${content}</${tagName}>';
> func(’h6’, ’Объявление функции’)
< ’<Ь6>0бъявление функции</Ь6>’
Анонимными обычно делают функции, которые требуется передавать в качестве
параметров другим функциям в каком-либо одном месте кода. Например, аноним-
ной можно сделать функцию-обработчик события, привязываемую к единствен-
ному элементу страницы, записав ее объявление непосредственно в вызове метода
addEventListener():
inches.addEventListener(’input’, function() {
Il Код обработчика события
});
8.5.2. Стрелочные функции
Стрелочная функция - разновидност ь анонимной функции. Ее объявление бо-
лее компактно и содержит последовательность символов =>, напоминающую
ст релку.
Стрелочная функция также является анонимной, поэтому после объявления должна
быть присвоена переменной или передана другой функции с параметром.
Стрелочная функция может быть объявлена в одном из четырех форматов:
♦ ([<имена параметров через запятую?]) => { <тело функции? }
В теле функции можно записать код любой сложности с использованием любых
выражений JavaScript.
Пример:
> const func = (tagName, content) => {
return '<${tagName}>$(content}</${tagName)>';
}
> func(’h6’, 1 Объявление функции’)
< '<Ь6>0бъявление функции</Ь6>'
♦ ([<имена параметров через запятую?1) => <выражеяие>
Результат вычисления заданного выражения будет возвращен функцией.
Выражение может быть только простым. Сложные выражения в таких функциях
не допускаются
Пример:
> const func = (tag, content) => '<${tag}>${content}</${tag}>';
> func(’p1, •Объявление функции записывается в формате:1)
< ’<р>Объявление функции записывается в фермате:</р>'
♦ <имя единственного параметра? => { Стало функций? }
Разновидность первого формата, применяемая для объявления функций, кото-
рые принимают лишь один параметр.
Пример:
? const func = name => { return Привет, ${name}! ; }
> func(’Билл Гейтс')
< 'Привет, Билл Гейтс!'
♦ <имя единственного параметра? => <выражение?
Разновидность второго формата, применяемая для объявления функций, кото-
рые принимают лишь один параметр.
1 Тример:
> const func = name => 'Привет, $(name>!’
> func(’Марк Цукерберг')
< 'Привет, Марк Цукерберг!'
Стрелочные функции более компактны, чем обычные, и вызываются несколько бы-
стрее. Однако в случае их применения в качестве обработчиков событий следует
иметь в виду, что контекст выполнения у них отличается от такового у обычных
функций (подробност и — в разд. 8.9}.
Стрелочные функции применяются в тех же случаях, что и анонимные. Вследствие
своей компактности в настоящее время они используются чаще обычных.
8.5.3. Функции однократного вызова
Функция однократного вызова— вызывается сразу после объявления —
и лишь однократно, после чего уничтожается
Функция однократного вызова представляет собой комбинацию объявления ано-
нимной или стрелочной функции с ее вызовом. Оператор вызова функции о
указывается непосредственно после ее объявления. Поскольку он имеет больший
приоритет, нежели объявление функции, последнее следует заключить в круглые
скобки.
Пример функции однократного вызова:
> (function () {
const sum = 10 + 30;
console.log('Сумма чисел 10 и 2U равна ' + sum);
}) О
< Сумма чисел 10 и 20 равна 40
Функции однократного вызова ранее часто применялись для предотвращения воз-
можных конфликтов имен и сокрытия критических данных от внешнего кода. 11о-
скольку локальные переменные любой функции недоступны извне, внешний код
добраться до них и повредить хранящиеся в них значения не может.
В настоящее время для той же цели обычно используются более удобные инстру-
менты: классы (см. урок 11) и модули (см.урок 14).
8.5.4. Замыкания
Замыкание, или хранящая функция — функция, после выполнения которой не-
которые из созданных в ней локальных переменных (или все они) сохраняются
в памяти.
Чтобы превратить обычную функцию в замыкание, следует объявить в ней
вложенную функцию, использующую значения всех локальных переменных,
которые требуется сохранить, после чего либо вернуть эту функцию в качестве
результата (для последующего сохранения в переменной), либо сохранить
ссылку на эту функцию в каком-либо внешнем хранилище (например, глобаль-
ной переменной).
Пока ссылка на возвращенную замыканием функцию существует, будут сохра-
няться в памяти все локальные переменные, объявленные в теле замыкания и
используемые в теле возвращенной функции.
Пример замыкания counter(), объявляющего локальную переменную ent и возвра-
щающего стрелочную функцию, которая возвращает предыдущее значение пере-
менной ent и инкрементирует его:
> function counter() {
let ent = 0;
return () => cnt++;
> )
Вызовем это замыкание и сохраним возвращенную им функцию в переменной
counterInc:
> const counterlnc = co:mter()
Пока существует переменная counterlnc, будет существовать и хранящаяся в ней
функция, возвращенная замыканием counter О, и соответственно будет сохраняться
в памяти локальная переменная ent, объявленная в замыкании и используемая
в теле возвращенной функции.
Убедимся, что это гак:
> counterlnc()
< 0
> counterlnc()
< 1
> counterlnc()
< 2
Мы можем вызвать замыкание counter() еще раз, создав другой экземпляр самого
замыкания, возвращаемой функции и локальной переменной ent:
> const counterlnc2 = counter()
> counterlnc2()
< 0
> counterlnc2()
< 1
Как видим, оба экземпляра замыкания функционируют независимо друг от друга.
Замыкание может создать и вернуть не только стрелочную, но и анонимную и даже
именованную функцию.
Следует отметить, что все локальные переменные (например, ent в приведенном
примере), объявленные в замыкании, недоступны извне. Поэтому замыканиями
часто пользуются, чтобы скрыть какие-либо важные данные от внешнего кода,
который может их повредить.
Вместо функции замыкание может объявить и вернуть класс, методы которого ис-
пользуют подлежащие сохранению локагьные переменные (объявлению классов
посвящен урок 11).
Полезно знать
Ранее замыкания повсеместно использовались для сокрытия критических данных от
внешнего кода. В настоящее время этою можно достичь, применяя более удобные ин-
струменты— классы и модули (рассмотрены на уроках 11 и 14).
Тем не менее замыкания до сих пор часто встречаются в старом коде.
8.5.4.1. Фабрики функций
Фабрика функций — замыкание, объявляющее и возвращающее функцию, ко-
торая нас троена сообразно значениям полученных замыканием параметров.
Пример фабрики функций, которая возвращает функции, настроенные на формиро-
вание строк HTML-кода с использованием заданных тегов:
> // Объявляем фабрику функций
> function getTagFactory(tagName) {
return function(content) {
return '<${tagName}>${content}</${tagNamej> ;
}
}
> // Создаем с ее помощью две функции
> const getDiv = getTagFactory('div')
> const getHl = getTagFactory('hl')
> // Используем созданные функции
> getDiv('Цена 65 руб.')
< '<div>UeHa 65 py6.</div>'
> getHl('Хамачи')
< '<Ь1>Хамачи</Н1>'
8.5.4.2. Замыкания однократного вызова
Замыкание однократного вызова— реализуется в виде функции однократного
вызова.
Пример замыкания однократного вызова, аналогичного рассмотренному и разг). 8.5.4:
> let counterlnc;
> (function () {
let ent = 0;
counterInc = () => cnt++;
}) 0
> counterlnc()
< 0
> counterlnc()
< 1
> counterlnc()
< 2
Замыкания однократного вызова применяются для тех же целей, что и функции
однократного вызова.
8.6. Упражнение. Спойлер, часть 2
Последний клиент недоволен, поскольку заказанный им спойлер можно поместить
на страницу лишь в одном экземпляре. Исправим этот недостаток.
1. Найдем в нанке 8\ех8 4 сопровождающего книгу файлового архива (см. прило-
жение 5) файлы 8 4.html (страница со спойлером), 8 4.js (внешний сценарий
с кодом спойлера) и 84css (таблица стилей с оформлением). Скопируем их
куда-либо на локальный диск.
2. Откроем копию файла 8 4.js в текстовом редакторе. И подумаем.
В настоящий момент состояние всех спойлеров хранится в единственном экзем-
пляре в переменной spoilerstare— что и является источником проблемы.
Решением могло бы быть создание отдельного экземпляра этой переменной на
каждый отдельный спойлер.
Это можно сделать, превратив функцию createSpoilerо, создающую спойлер,
в замыкание и включив в него объявления переменной spoilerstate и обработ-
чика события onHeaderClick(). Поскольку обработчик у нас привязывается к со-
бытию, ссылка на него сохраняется в объекте, представляющем заголовок спой-
лера (этот объект создается веб-обозревагелем, о чем мы узнаем в подробностях
на уроке 77). А пока существует объект заголовка спойлера, существует и функ-
ция-обработчик событий, объявленная в замыкании, и соответственно локальная
переменная spoilerState, хранящая состояние спойлера
3. Исправим код сценария следующим образом:
function createSpoiler(element, state=false, deccration=1') {
let spoilerState = false;
function onHeaderClick() {
}
function croateSpoiler(ol<3nent/ statc-falso/ decoration^11)—{
const header = element.queryselector('h6');
header.addEventListener('click', onHeaderClick);
}
4. Откроем копию файла 8 4 html в текстовом редакторе и создадим в ней еще один
спойлер (можно просто скопировать существующий, указав у него другой
якорь):
<div id="spoilerl">
<Ь6>0Съявление функции</У16>
</div>
<div id="spoiler2">
<НбХ>уъяЕление фуакции</Пб>
</div>
5. Добавим во внутренний сценарий в составе страницы 8 4 html код. инициализи-
рующий второй спойлер:
const spoilerl = document.gecElemencByld('.spoilerl');
createSpoiler(spoilerl);
const spoiler2 = document.getElementByld('spoiler2');
createSpoxler(spoiler2);
+ Объявление функции
- Объявление функции
Объявление функции записывается в формате:
I function «имя функции>([«имена параме-ров, принимаемых функцией, ®
через запятую/]) {
«тело функции»
1
Имя функции дозжно включать только буквы, цифры, символы доллара <$), подчеркивания (_),
не должно начинаться с цифры, равно как и совпадать с каким-либо из ключевых слов языка (см.
разд. 3.2). Можно сказать, что имя функции подчиняется гем же правилам, что и имя
переменной (см разд 3.10.1).
Имя функции должно бьпь уникальным в пределах области видимости функции (о ней — чу ть
позже).
Имена функции чувствительны к регистру символов (как и имена переменных!.
Рис. 8.3 Результат выполнения упражнения 8 6
Откроем страницу 8 4 html в веб-обозревателе и опробуем в работе оба спойлера.
Теперь они должны разворачиваться и сворачиваться независимо друг от друга
(рис. 8.3).
8.7. Упражнение. Рекурсия и ее использование
Пока довольный клиент любуется на готовые и полностью работающие спойлеры,
напишем небольшое приложение для вычисления факториалов чисел. Для этого
используем рекурсию.
Рекурсия— вызов функцией самой себя.
Рекурсивная функция — функция, вызывающая саму себя.
1. Найдем в папке SVsources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 8 7 html (страница с «заготовкой»). Скопируем его куда-либо на
локальный диск.
2. Откроем копию страницы 8 7 html в текстовом редакторе и найдем следующий
HTML-код:
<form>
<input id="num">
<input type="button" id="calculate" уа1ие="Рассчита1|ь">
</fornu>
<p id="output"></p>
В поле ввода с якорем num будет заноситься исходное число. Кнопка calculate
запустит процесс вычисления. В абзаце output будет выведен результат.
Объявление функции factorial (<чиолс>), вычисляющей факториал заданного
числа, поместим во внешний веб-сценарий 8 7 html — на случай, если эту функ-
цию придется использовать в других проектах. Код, запускающий вычисление и
выводящий результат, оформим как внутренний сценарий.
3. Вставим теги сценариев:
<form>
</form>
<р id="output"x/p>
<scnpt src="8.7.js"X/script>
<script>
</script>
4. Создадим в папке, где хранится страница 8 7 html, файл 8 7 js и запишем в него
код, объявляющий функцию factorial() (листинг 8.1).
Листинг 8 1. Функция factorial () (файл 8,7.js)
function factorial(val) {
if (val — 1)
return 1;
else
return val * factorial(val - 1);
}
Эта функция последовательно вызывает сама себя, передавая себе с параметром
заданное число, уменьшенное па I. Так продолжается, пока при очередном
вызове она не получит в качестве параметра число 1 — тогда функция вернет
результат, и цикл последовательных вызовов прекратится.
5. Запишем в файл 8 7 html. во втором, пустом теге <script> код сценария, выпол-
няющего вычисление факториала с применением объявленной ранее функции:
const num = document.getElementByld('num');
const calculate = document.getElementByld('calculate');
const output = document.getElementByld('output');
calculate.addEventListener('click', () => {
const input = parselnt(num.value);
output.textContent = factorial(input);
});
Сначала получаем объекты, представляющие поле ввода num, кнопку calculate
и абзац output. Далее привязываем к кнопке обработчик события click, реализо-
ванный в виде стрелочной функции. Обработчик извлечет из поля ввода число,
воспользовавшись свойством value, преобразует число в числовой тип вызовом
функции parselnt (), вызовет функцию factorial(), передав ей полученное чис-
ло, и выведет результат в абзаце, присвоив его свойству textcontent.
Откроем страницу 8.7 html в веб-обозревателе, введем какое-либо целое число в по-
ле ввода, нажмем кнопку Рассчитать и дождемся вывода результата (рис. 8.4).
12
Рассчитать
479001600
Рис. 8.4. Результат выполнения упражнения 8 7
Рекурсивная функция в заданный момент должна прерывать цикл вызовов самой
себя, вернув какой-либо результат. У нашей функции такой момент наступает при
получении сю с параметром числа 1. Если же функция продолжит вызывать саму
себя до бесконечности, возникнет бесконечная рекурсия, которая со временем при-
ведет к краху веб-обозревателя.
Внимание!
Не допускайте бесконечной рекурсии
8.8. Контекст выполнения функции
и его указание
Контекст выполнения — это объект, который хранится в переменной this, созда-
ваемой самой исполняющей средой. В обычном коде это объект класса window,
представляющий текущее окно веб-обозревателя, он же— глобальный объект
(см.разд. 3.11).
8.8.1. Контекст выполнения
обычных и стрелочных функций
У обычных и стрелочных функций контекст выполнения различается:
♦ у обычной функции — создается собственная локальная переменная this, кото-
рой присваивается контекст выполнения;
• при обычном вызове функции — глобальный объект (как и в обычном коде);
• если функция является обработчиком события — объект элемента страницы,
к которому привязан этот обработчик (см. разд. 2.3);
• если функция реализует метод — текущий объект (подробности — на уро-
ках 10 и //);
♦ у стрелочной функции — собственная локальная переменная this не создается,
и функция использует глобальную переменную this, которая всегда хранит
ссылку на глобальный объект.
В качестве примера рассмотрим две кнопки с привязанными к ними обработчиками
события click, которые выводят контекст выполнения к консоли веб-обозревателя.
Обработчик события щелчка на первой кнопке реализован в виде обычной функ-
ции, обработчик щелчка на второй кнопке — в виде стрелочной функции:
<input type="button" id="btnl" уа1ие="0бычная функция"?
<input type="button" id="btn2" уа1ие="Стрелочная функция"?
const btnl = document.getElementByld{’btnl’);
const btn2 = document.getElementById('bcn2');
btnl.addEventListener('click', function () {
console.log(this);
});
btn2.addEventListener('click', () => {
console.log(this);
});
При щелчке на первой кнопке в консоли будет выведено следующее:
cinput type="button" id="btnl" value="06H4HaH функция"?
Переменной this, существу тощей в теле обычной функции-обработчика, при ее вы-
зове присваивается объект, к которому привязан обработчик, в нашем случае —
кнопка. Этот объект и будет выведен в консоли в виде создающего его HTML-кода.
При щелчке на второй кнопке мы увидим:
Window (...)
В теле стрелочной функции-обработчика при вызове переменная this не создается,
и будет выведено значение глобальной переменной this — глобальный объект
класса window, представляющий текущее окно веб-обозревателя. Этот объект
в консоли представляется именем класса, на основе которого он был создан,
и. в фигурных скобках, значениями его свойств (в приведенном примере не пока-
заны).
Поэтому при использовании стрелочной функции в качестве обработчика события
в ее теле не следует обращаться к элементу страницы, к которой привязан этот
обработчик, через переменную this. Вместо этого следует пользоваться ранее объ-
явленной переменной, хранящей этот элемент (в приведенном далее коде она под-
черкнута):
btn2.addEventListener('click', () => {
console.log'btnz);
});
Также к этому элемент}' страницы можно обращаться посредством свойств объекта
события, передаваемого функции-обработчику (рассмотрен на уроке 17).
8.8.2. Явное указание контекста выполнения
Есть возможность явно указать у функции произвольный контекст выполнения
либо перед ее вызовом, либо в процессе вызова.
Внимание!
Это работает только с обычными функциями У стрелочной функции контекст выпол-
нения — глобальный объект— изменить невозможно
Для этого применяются следующие методы класса Function, представляющего
функцию:
♦ call о — вызывает текущую функцию, указывая у нее заданный контекст вы-
полнения и передавая ей заданные параметры:
са11 (< контекст выполнен! гя> (,
<параметр 1>, <параметр 2>, . . . <параметр N>1)
I Тример № I:
> function getDayName() {
const days = ('воскресенье', 'понедельник', 'вторник', 'среда',
'четверг', 'пятница', 'суббота'];
return days[rhis.getDay() ];
)
> gecDayName.call(new Date());
< 'пятница'
Пример № 2:
> function getTag(content, className) {
return ’<${this} class="${className}">$(content}' +
</${this}> ;
}
> getTag.call('hl', 'Marypo', 'dish-name')
< '<hl class="dish-name">Marypo</hl>1
♦ apply () — то же самое, что и call О, только параметры, передаваемые вызывае-
мой функции, указываются в виде массива:
apply (<контекст выполнения»[, <массив параметров'»])
1 Тример:
> getTag.apply('hl', ('Marypo', 'dish-name'])
< '<hl class="dish-name">Marypo</hl>'
♦ bindf) — возвращает копию текущей функции с указанным контекстом выполне-
ния. Если заданы параметры, они при вызове возвращенной функции будут пере-
даны ей перед параметрами, заданными при вызове. Формат:
bind(<контекст выполнения»[,
спараметр 1>, спараметр 2>, . . . спараметр W>])
1 Тример:
> // Создаем простой объект со свойством tagMame, хранящим имя тега
> const props = { tagName: 'div' }
> // Объявляем функцию, возвращающую HTML-код
> function getTag(className, content) {
return '<${this.tagName} class="${className}">${content}' +
</${this.tagName }> ;
}
> // Получаем копию этой функции, у которой в качестве контекста
> // выполнения указан объект props
> const getTag = getTag.bind(props, 'aish-name')
> // Вызываем полученную копию функции
> getTag3('Marypo'}
< '<div class="dish-name">Marypo</div>'
8.9. Самостоятельные упражнения
I. Объявите в консоли веб-обозревателя две функции, принимающие произвольное
количество строк и возвращающие строку с наименьшей и наибольшей длиной
соответственно. Проверьте обе функции.
2. Модифицируйте спойлер (см. упражнение 8.6), использовав в качестве обработ-
чика события щелчка на заголовке стрелочную функцию.
Урок 9
Массивы, множества и словари
Ранее мы уже сталкивались с последовательностями — значениями, представ-
ляющими собой наборы других значений (элементов). Так, последовательностью
является любая строка (см. урок 6).
Настала пора рассмотреть другие последовательности, поддерживаемые JavaScript:
массивы, обычные и типизированные, множества и словари.
9.1. Массивы
Массив — это пронумерованная, изменяемая последовательность Массивы бывают
обычными и типизированными.
9.1.1. Обычные массивы
Обычные массивы могут хранить элементы произвольных типов. Вследствие чего
из всех разновидностей последовательностей они наиболее часто применяются на
практике.
9.1.1.1. Создание обычных массивов
Обычный массив, хранящий заданные элементы. создается записью следующего
формата:
[ <злемент 1>, <злемент 2>, . . . <элемент N> ]
Созданный массив сразу же следует присвоить какой-либо переменной или пере-
дать функции с параметром — в противном следует он будет уничтожен.
Элементы в созданном массиве хранятся в том же порядке, в котором они были
записаны в квадратных скобках, и этот порядок никогда не меняется.
Пример массива, хранящего строки:
> const languages = ['JavaScript', 'Python', 'Ruby', 'PHP'I
> languages
< (4) ['JavaScript', 'Python', 'Ruby', 'PHP']
Обратим внимание, как массив выводится в консоли веб-обозревателя: сначала
в круглых скобках количество содержащихся в нем элементов (размер), потом,
в квадратных скобках, сами элементы.
Пример массива, хранящего значения разных типов:
> const data = ['JavaScript’, ’веб-разработка', 1995, true, 22]
> data
< (5) ['JavaScript', 'веб-разработка', 1995, true, 22]
После последнего элемента массива допускается поставить запятую:
> const data = ['JavaScript', 'веб-разработка', 1995, true, 22,]
При создании массива некоторые элементы можно пропустить— они станут пус-
тыми.
Пустой элемент массива— элемент, не содержащий никакого значения, даже
undefined.
Пример массива с пустым третьим, чет вертым и седьмым элементами:
> const arr = [1, 2, , , 5, 6, , 8]
> arr
< (8) [1, 2, пусто х 2, 5, 6, пусто, 8]
При выводе массива в консоли пустой элемент обозначается словом пусто, а не-
сколько идущих подряд пустых элементов— надписью формата пусто х количе-
ство пустых элементов>.
Пустым элементам в дальнейшем можно присвоить любое значение.
Допускается создавать массивы, содержащие лишь один элемент:
> const singleElementArray = [123]
> singleElementArray
< [123]
При выводе таких массивов в консоли их размер не отображается.
Можно создать и пустой массив, не содержащий ни единого элемента, записав
квадратные скобки вплотную:
> const emptyArray = []
> emptyArray
< []
Есть еще один способ создания массива — написанием выражения одного из сле-
дующих форматов:
♦ new Array (Олемент 1>, <элемент 2>, . . . Олемент N>)
Возвращает массив, содержащий заданные элементы. Аналог рассмотренной
ранее записи, в которой элементы создаваемого массива заключаются в квад-
ратные скобки.
Пример;
> const companies = new Array('Intel', 'AMD', 'Qualcomm')
♦ new Array(<размер массива>)
Возвращает массив заданного размера, все элементы которого являются пус-
тыми:
> const sevenAbcents = new Array(7)
> sevenAbcents
< (7) [пусто x 7]
> // Также можно использовать выражение:
> // const sevenAbcents =
> // Однако оно неудобно и менее наглядно
Выражение этого формата применяется, если требуется заранее создать массив
известного размера, элементы в который будут заноситься потом;
♦ new Array () — возвращает пустой массив.
Массив — это объект класса Array. Он имеет объектный тип, и оператор получения
типа typeof, примененный к массиву, вернет строку 'object'.
9.1.1.2. Простейшие операции с обычными массивами
♦ Обращение к элементу массива с заданным индексом — посредством оператора
доступа к элементу последовательности [] (см. разд. 6.2).
Внимание!
Нумерация элементов массива (как и любой другой последовательности) начинает-
ся с О
Если оператор доступа к элементу последовательности в выражении, содержа-
щем оператор присваивания, стоит правее этого оператора, выполняется извле-
чение элемента из массива:
> const arr = [1, 2, 3, 4, 5, 6, 7,]
> arr
< (7) [1, 2, 3, 4, 5, 6, 7]
> // Извлекаем четвертый элемент массива arr
> let а = arr[3]
> а
< 4
При попытке обратиться к несуществующему элементу возвращается значение
undefined:
> arr[lll]
< undefined
Если же оператор доступа к элементу последовательности стоит.чевее оператора
присваивания, выполняется присваивание элементу массива нового значения:
> // Присваиваем последнему элементу массива arr новое значение
> агг[6] = 4321
> arr
< (7) (1, 2, 3, 4, 5, 6, 4321]
♦ добавление нового элемента в массив — присвоив нужное значение элементу
массива, индекс которого на I больше индекса последнего элемента. Новый
элемент будет создан автоматически. Пример добавления в массив arr нового,
восьмого, элемента:
> arr[7J = 'Array’
> arc
< (8) [1, 2, 3, 4, 5, b, 4321, ’Array’]
У добавляемого элемента можно указать индекс, отличающийся от индекса по-
следнего элемента больше чем на 1 — в этом случае между последним и созда-
ваемым элементами будет создано нужное количество пустых элементов:
> агг[12] = ’Массив'
> агг
< (13) [1, 2, 3, 4, 5, 6, 4321, 'Array', пусто х 4, 'Массив']
♦ перебор элементов с помощью цикла перебора последовательности (см.разд, 4.2.4.4):
> for (let el of arr)
console.log(el);
< 1
< 2
. . . Остальной вывод пропущен . . .
9.1.1.3. Вложенные массивы
Вложенный массив— массив, хранящийся в качестве элемента в другом мас-
сиве.
Пример:
> // Создаем массив с вложенными массивами
> let arr = [[1, 2, 3, 4], ['а', 'Ь', 'с']]
> агг
< (2) [Array(4], Аггау(3}]
При выводе в консоли вложенный массив представляется словом Array (массив),
после которого в фигурных скобках указан размер массива.
Для обращения к элементу вложенного массива оператор доступа к элементу по-
следовательности следует записать дважды: первый оператор выполнит обращение
к элементу «внешнего» массива, второй — к элементу' вложенного массива:
> агг[0][3]
< 4
> arr[l][1]
< 'Ь'
> arr [1] 12] = 'def
> arr[1]
< {3} ('a', 'b', 'def'J
Вложенные массивы могут содержать другие вложенные массивы.
> let arr = [
(1, 2, 3, 4],
['а', 'Ь', 'с'],
[[45, 67], ['веб', 'разработка']]
]
> arr[2][1][0]
< 'веб'
9.U.4. Свойство и методы класса Array
Свойство length этого класса храни 1 размер массива, т. е. количество элементов
в нем:
> let arrl = [1, 2, 3, 4]
> arrl.length
< 4
Если присвоить этому свойству значение, превышающее размер массива, в массиве
будет создано нужное количество пустых элементов:
> arrl.length = 7
> arrl
< (7) [1, 2, 3, 4, пусто х 3]
Если же присвоить свойству значение, меньшее размера массива, лишние элементы
массива будут удалены:
> arrl.length = 3
> arrl
< (3) [1, 2, 3]
Объектные методы класса Array:
♦ at(<индекс элемента:») — возвращает элемент текущего массива, имеющий за-
данный целочисленный индекс:
> arrl = [1, 2, 3, 4]
> arrl.at(1)
< 2
Допускается задавать отрицательные индексы. В этом случае отсчет элементов
выполняется с конца массива: -1 — последний элемент, -2 — предпоследний
и т. д. Пример:
> arrl.at(-1)
< 4
Если элемент с заданным индексом отсутствует, возвращается undefined:
> arrl.at(12)
< undefined
♦ with(<MKflerc>, <значение>) — возвращает новый массив, являющийся копией
текущего, в котором элементу с заданным индексом присвоено указанное значе-
ние. Текущий массив не меняется. Пример:
> arrl.with(l, 222)
< (4) [1, 222, 3, 4]
> arrl
< (4) [1, 2, 3, 4)
♦ сопсаг (<массив 1>, <массив 2>, . . . <массив N>) —возвращает новый массив,
содержащий все элементы из текущего и всех заданных массивов'.
> arrl.concat([5, б], [10, 11, 12])
< (9) [1, 2, 3, 4, 5, 6, 10, 11, 12]
♦ push (<значение 1>, «значение 2>, . . . Оначение N>j —добавляет в конец те-
кущего массива указанные значения и возвращает новый размер массива:
> let arr2 = [10, 11]
> arr2.push(12, 13)
< 4
> arr2
< (4) [10, 11, 12, 13]
♦ unshift («значение 1>, «значение 2>, . . . «значение N>) —добавляет в начало
текущего массива указанные значения и возвращает новый размер массива:
> arr2.unshift(8, 9)
< 4
> arr2
< (6) [8, 9, 10, 11, 12, 13]
♦ pop () — удаляет последний элемент текущего массива и возвращает его в каче-
стве результата:
> агг2.рор()
< 13
> агг4
< (5) [8, 9, 10, 11, 12]
♦ shift () — удаляет первый элемент текущего массива и возвращает его в качест-
ве результата:
> arr4.shift ()
< 8
> arr4
< (4) [9, 10, 11, 12]
♦ splice() — удаляет из текущего массива заданное количество элементов и
вставляет на их место указанные элементы?.
splice («индекс первою удаляемого элементам [,
«количество удаляемых элементов>=1[,
«элемент 1>, «элемент 2>, . . . «элемент N>] ])
Если индекс первого удаляемого элемента отрицательный, он отсчитывается
с конца массива. Если количество удаляемых элементов равно 0, никакие элемен-
ты удалены не будут. В качестве результата возвращается массив с удаленными
элементами. Примеры:
> let аггЗ = ['HTML', 'C++', 'Python']
> аггЗ.splice(1, 1, 'CSS')
< ["C++"]
> аггЗ
< (3) ["HTML", "CSS", "Python"]
> аггЗ.splice(2, 0, 'JavaScript', 'PHP')
< []
> аггЗ
< (5) ["HTML", "CSS", "JavaScript", "PHP", "Python"]
♦ toSplicedO — то же самое, что и splice(), только возвращает новый массив
с удаленными и вставленными элементами, не затрагивая текущий массив;
♦ includes (<значение> [, <индекс первого элемента>=й'\) — возвращает true, если
массив содержит хотя бы один элемент с заданным значением. и false — в про-
тивном случае. Поиск ведется, начиная с элемента с указанным индексом. При-
меры:
> let arr4 = [1, 1, 2, 3, 3, 3, 4]
> агг4.includes(1)
< true
> arr4.includes(1, 3)
< false
Метод includes() для сравнения указанного значения с элементами текущего
массива использует оператор = (строго равно). Следовательно, типы значения
и элементов массива должны совпадать, в противном случае поиск не увенчает-
ся успехом. Пример:
> arr4.includes('3')
< false
♦ indexOf (<значение> [, <индекс первого элемента>=0]) —возвращает индекс эле-
мента текущего массива, хранящего заданное значение. Поиск начинается с эле-
мента с указанным индексом. Если индекс первого элемента отрицательный, он
отсчитывается от конца массива. Если подходящий элемент не найден, возвра-
щается -1. Пример:
> arr4.indexOf(3)
< 3
> arr4.indexOf(3, 4)
< 4
> arr4.indexOf(3, -2)
< 5
Этот метод для сравнения указанного значения с элементами текущего массива
также использует оператор === (строго равно);
♦ lastlndexOf (<значение> [, <индекс первого элемента>=1п£ inity]) —ТО же самое,
что и indexOf О, только поиск начинается с конца массива’
> arr4.lastlndexOf(3)
< 5
♦ every (<функция проверки>[, <контекст выпопнения>]) — возвращает true, если
все элементы текущего массива проходят проверку. Если хоть один элемент
массива не проходит проверку, возвращается false.
11роверку осу ществляет указанная функция, принимающая три параметра: оче-
редной элемент массива, индекс этого элемента и сам массив. Функция должна
возвращать логическое значение: true (если элемент прошел проверку) или
false (в противном случае). Пример'
> // Проверяем, все ли элементы массива arrl больше О
> arrl.every((el, ind, ar) «=> el > 0)
< true
> // Проверяем, все ли элементы массива arrl больше 2
> arrl.every((el) => el > 2)
< false
Можно указать контекст выполнения, который будет доступен в теле функции
проверки через локальную переменную this;
♦ some (<функция проверки>[, <контекст выполнения>]) — то же самое, что и
every О, только возвращает true, если проверку проходит хотя бы один элемент
текущего массива:
> arrl.some((el, ind, ar) => el > 2)
< true
♦ find (<функция проверки>[, <контекст выполнения:?]) — возвращает первый эле-
мент текущего массива, который прошел проверку. Если ни один элемент не
прошел проверку, возвращается undefined. Параметры те же, что и у метода
every(). Пример:
> arrl.find((el) => el > 2)
< 3
> arrl.find((el) => el > 5)
< undefined
♦ findLast (<функция проверки:? [, <контекст выполнения:?)) — ТО же самое. ЧТО и
findo, только возвращает последний элемент текущего массива, который про-
шел проверку:
> arrl.find((el) => el > 2)
< 4
♦ findindex(<функция проверки>[, <контекст выполнения^]) —то же самое, что и
find(), но возвращает индекс первого прошедшего проверку элемента. Если ни
один элемент не прошел проверку, возвращается -1;
♦ findLastlndex(<. функция проверки>[г <контекст выполнения>]) — ТО же самое,
что и findindex(), только возвращает индекс последнего прошедшего проверку
элемента;
♦ filter (<функцня проверки>[, <контекст выполнения^) — возвращает массив,
составленный из элементов текущего массива, которые прошли проверку. Пара-
метры те же, что и у метода every(). Пример:
> arrl.filter((el) > el > 2)
< (2) (3, 4]
♦ flat ((<уровень>=1]) — применяется к массивам, содержащим вложенные друг
в друга массивы. Возвращает копию текущего массива, в которой элементы
вложенных массивов рекурсивно «подняты» на заданный уровень. Примеры:
let arr5 = [1, 2, 3, [4, 5, [6, 7, [8, 9] ] ] ] ;
let rl = arr5.flat(); // [1, 2, 3, 4, 5, [6, 7, [8, 9]]]
let r2 = arr5.flat(2) // [1, 2, 3, 4, 5, б, 7, [8, 9] ]
let r3 = arr5.flat(3) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
♦ forEach (<.функция> [, <контекст выполнения^)—перебирает элементы текущего
массива и передает каждый элемент заданной функции. Результата не возвращает.
Функция должна принимать те же параметры, что и аналогичная функция у мето-
да every (), но не должна возвращать результат. Пример:
arrl.forEach((el) => { console.log(el); });
♦ map (<функция> [, <контекст выполнения>]) — перебирает элементы текущего
массива, передает каждый элемент заданной функции, помещает возвращенные
функцией результаты в новый массив и возвращает его. функция должна прини-
мать те же параметры, что и аналогичная функция у метода every(), и возвра-
щать результат действий с очередным элементом массива. Примеры:
rl = arrl.map((el) => el ** 2); // [1, 4, 9, 16]
r2 = arrl.mapl(el) => [el, el ** 2]); // [[1, 1], [2, 4], [3, 9], [4, 16]]
♦ f latMap(<функция> [, <контекст выполнения^ ) — то же самое, что и тар (), только
дополнительно рекурсивно «поднимает» в результирующем массиве элементы
вложенных массивов на уровень, равный 1:
r2 = arrl.flatMap((el) => [el, el ** 2]) // [1, 1, 2, 4, 3, 9, 4, 16]
♦ reduce(<функция>[, <начальное значение>])— перебирает элементы текущего
массива и передает каждый элемент указанной функции. Функция должна прини-
мать следующие параметры
• результат, возвращенный предыдущим вызовом этой функции. Если это пер-
вый вызов (обрабатывается первый элемент массива), будет передано началь-
ное значение, если же оно не указано — значение первого элемента массива:
• значение очередного элемента теку щего массива;
• индекс очередного элемента текущего массива;
• ссылка на текущий массив.
Функция должна возвращать какой-либо результат, который будет передан ей при
следующем вызове.
В качестве результата метод reduce () возвращает значение, возвращенное по-
следним вызовом функции.
Элементы текущего массива перебираются в направлении от начала к концу.
I Тримеры:
> // Подсчитываем сумму элементов массива
> arrl.reduce((sum, el) => sum + el)
< 10
> // Создаем строку из пронумерованных элементов массива,
> // перечисленных через запятую
> аггЗ.reduce((str, el, i) => str + ++i + ': 1 + el + ', ', '')
< '1: HTML, 2: CSS, 3: JavaScript, 4: PHP, 5: Python, '
♦ reduceRight (<функциям[, «начальное значение?-]) —то же самое, что и reduce (),
только элементы теку щего массива перебираются от конца к началу:
> аггЗ.reduceRight((str, el, i) => str + ++i + ’: ' + el + ', ', '')
< '5: Python, 4: PHP, 3: JavaScript, 2: CSS, 1: HTML, '
♦ join([«разделитель>=',']) — объединяет все элементы текущего массива в
строку, разделяя их заданным символом-разделителем, и возвращает полученную
строку в качестве результата:
> arr2.jorn()
< 'HTML,CSS,JavaScript'
> arr2.joint', ')
< 'HTML, CSS, JavaScript’
♦ slice() — возвращает новый массив, сформированный из элементов текущего
массива, которые расположены между заданными индексами:
si ice([«индекс первого элемента?[,
<индекс элемента, следующего за последним?])
Если индекс элемента, следующего за последним, не указан, в новый массив по-
мещаются все оставшиеся элементы. Если и индекс первого элемента не указан,
создается копия текущего массива. Примеры:
> arrl.slice(1, 3)
< (2) [2, 3]
> arrl.slice(1)
< (3) [2, 3, 4]
> arrl.slicet)
< (4) [1, 2, 3, 4]
♦ filit) — заполняет элементы текущего массива, находящиеся между задан-
ными индексами, указанным значением.
fill(<значение>[, «индекс первого элемента?[,
<индекс элемента, следующего за конечный] ])
Если индекс элемента, следующего за конечным, не указан, заполнение ведется до
конца массива. Если индекс первого элемента не указан, заполнение ведется,
начиная с первого элемента массива. Результата метод не возвращает. Примеры:
> [10, 11, 12, 13, 14].fill(0)
< (5) [0, 0, 0, 0, 0]
> (10, 11, 12, 13, 14].fill(0, 2)
< (5) [10, 11, 0, 0, 0]
> [10, 11, 12, 13, 14].fill(0, 2, 4)
< (5) [10, 11, 0, 0, 14]
♦ copywithinO — копирует указанную последовательность элементов текущего
массива по заданному местоположению и возвращает ссылку на текущий массив:
с?pyWithin{<индекс местоположения коппи>[,
<индекс первого элемента копируемой последовательности>=0[,
<индекс элемента, следующего за последним элементом 'Ъ
копируемой последовательности?] ])
Если индекс элемента, следующего за последним элементом копируемой последо-
вательности, не указан, в копируемую последовательность будут включены все
оставшиеся элементы массива.
11римеры:
> arrl = [1, 2, 3, 4, 5, 6]
> arrl.copyWithin(0, 3)
< (6) [4, 5, 6, 4, 5, 6]
> arrl.copyWithin(l, 3, 5)
< (6) [4, 4, 5, 4, 5, 6]
> arrl.copyWithin(-4, -5, -1)
< (6) [4, 4, 4, 5, 4, 5]
♦ entries () — возвращает последовательность из вложенных массивов, каждый из
которых содержит два элемента: индекс очередного элемента текущего массива
и его значение:
> аггЗ = ['HTML', 'CSS', 'JavaScript']
> for (let elData of arr3.entries())
console.log('${elData[0] + 1}: ${elData[1]}');
< 1: HTML
< 2: CSS
< 3: JavaScript
♦ keys() — возвращает последовательность из индексов элементов текущего мас-
сива;
♦ values()— возвращает последовательность из значений элементов текущею
массива:
♦ toLocaleStringо — возвращает строку, которая составлена из элементов теку-
щего массива, разделенных запятыми. Элементы массива форматируются со-
гласно локали с заданным обозначением и указанным дополнительным настройкам
(описаны в разд. 5.1.3.1 и 7.2.1). Формат:
toLocaleString(^обозначение локали>[, <дополнительные настройки>]])
Пример:
> arrl.toLocaleString('ru-RU', { style: 'unit', unit: 'meter' })
< '1 m, 2 м, 3 m, 4 m'
Форматируются только числа и временные отметки. Значения остальных типов
помещаются в результирующую строку без каких бы то ни было преобразова-
ний.
Статические методы класса Array:
♦ isArray(<объект>) — возвращает true, если объект является массивом, и false —
в противном случае:
> Array.isArray(arrl)
< true
> Array. isAr ray (1234)
< false
♦ of ([олемент 1>, олемент 2>, . . . олемент N>]) — возвращает новый мас-
сив, содержащий заданные элементы. Если указан один элемент, будет создан
массив из одного элемента. Если ни один элемент не указан, будет создан пустой
массив;
♦ from () — возвращает новый массив, созданный из элементов заданной последо-
вательности':
f rom (<последовательность> [, <афункция> [, <контекст выполнена 1я>] ])
Если задана функция, то каждый элемент исходной последовательности будет
«пропущен» через нее, а в результирующий массив добавятся результаты, воз-
вращенные этой функцией. В качестве параметров функция должна принимать
значение очередного элемента массива и его индекс.
Можно указать контекст выполнения, который будет доступен в теле функции
через локальную переменную this. Примеры:
> Array.from('abode')
< (5) ['a', 'b', 'o', 'd’, ’e’J
> Array.from('abode', (el, ind) => ${ind + 1}: ${el.toUpperCase()}4)
< (5) ['1: A', '2: B', '3: C, '4: D', '5: E’]
9.1.1.5. Сортировка массивов
Метод sort ([<функция сравнения*]) класса Array сортирует текущий массив и воз-
вращает ссылку на него.
Если функция сравнения не указана, метод sort() сортирует элементы по возрас-
танию:
> let arrl = [32, 7, 90, -4, 15, 900, 64]
> arrl.sort()
< (7) [-4, 15, 32, 64, 7, 90, 900]
Строки сортируются по возрастанию сумм кодов содержащихся в них символов —
r результате выполняется сортировка в алфавитном порядке:
> let arr2 = ['HTML', 'Ruby', 'CSS', 'PHP', 'Python', 'C++']
> arr2.sort()
< (6) ["C++", "CSS", "HTML", "PHP", "Python", "Ruby"]
Если нужно отсортировать массив в другом порядке, в вызове метода sort() следу-
ет задать функщпо сравнения. Она должна принимать два параметра: два сравнивае-
мых элемента массива — и возвращать:
♦ любое отрицательное число — если первый элемент меньше второго;
♦ 0 — если элементы равны;
♦ любое положительное число — если первый элемент больше второго.
Пример сортировки массива с числами по убыванию:
> arr.sort((ell, е12) => e!2 - ell)
< (7) [900, 90, 64, 32, 15, 7, -4]
Для сортировки строк удобно применять метод localeCompare() (см.ршд. 6.3.1).
Пример сортировки массива со строками в порядке, обратном алфавитному:
> arr2.sort((ell, е12) => е12.localeCompare(ell))
< (6) ["Ruby", "Python", "PHP", "HTML", "CSS", "C++"]
Аналогичный метод toSorted( [«функция сравнения»]) возвращает новый массив
с отсортированными элементами, не затрагивая текущий массив.
Метод reverseO класса Array меняет порядок следования элементов текущего мас-
сива на противоположный и возвращает ссылку на текущий массив:
> arr2. reverse ()
< (6) ["C++", "CSS", "HTML", "PHP", "Python", "Ruby"]
Аналогичный метод toReversedO возвращает новый массив с противоположным
порядком следования элементов, не меняя текущий массив.
9.1.1.6. Деструктурирование массивов
|| Деструктурирование массива — разделение массива на отдельные элемент ы.
Деструктурирование может выполняться с целью:
♦ присваивания отдельных элементов массива разным переменным — посредст-
вом выражения формата:
\<оператор объявления переменных») 'Ъ
[ <переменная 1>, <переменная 2>, . . . <переменная N> ] = <массив>
Переменной 1 будет присвоен первый элемент указанного массива, перемен-
ной 2— второй элемент и т. д. Если переменные не были объявлены ранее, следу-
ет указать какой-либо оператор о&ъявления переменных: let, const или var.
В качестве отдельной переменной можно указать:
• имя переменной:
> let arr = [1, 2, 3]
> let la, b, c] = arr;
> a
< 1
> b
< 2
> c
< 3
В выражении дес груктурирования можно указать переменных меньше, чем
элементов в массиве, — тогда «лишние» элементы останутся ненрисвоен-
ными:
> let lx, у] = arr;
Можно не указывать какую-либо переменную внутри перечня, поставив, одна-
ко, запятую,— тогда соответствующий элемент массива останется неприсво-
енным:
> [а, , b] = arr;
> а
< 1
> Ь
< 3
Еще можно указать переменных больше, чем элементов в массиве, — тогда
лишние переменные получат значение undefined:
> [а, Ь, с, х] = arr;
> а
< 1
> х
< undefined
• конструкцию формата:
< имя переменной?=<значение по умолчанию?
В этом случае, если переменной с указанным именем не «хватит» элемента
заданного массива, она получит указанное значение по умолчанию:
> [а, Ь, с, х=1С0] = arr;
> х
< 100
• конструкцию формата ... <переменная> — тогда заданной переменной будет
присвоен массив со всеми оставшимися элементами деструктурируемого мас-
сива. Такая конструкция ставится в самом конце перечня переменных. Пример:
> [х, . . .у] = агг;
> х
< 1
> У
< (2) [2, 3]
Деструктурирование массивов с присваиванием часто применяется для обмена
значениями между двумя переменными:
> а = ’HTML';
> Ь = 'CSS';
> [Ь, а] = [а, Ы;
> а
< 'CSS'
> ь
< ’HTML’
♦ передачи элементов массива функции в отдельных параметрах— с помощью
оператора упаковки-распаковки (см. разд. 8.2.3):
...<массив>
Этот оператор ставится в перечень параметров, передаваемых вызываемой
функции.
1 Тример:
> function getTag(content, tagName, className) {
return '<${tagName} ' +
class="${className}">${content} </$ {tagName} >' ;
> arr = ['Чукка', 'h2', 'discount']
> getTag(...arr)
< '<h2 class="discount">4yKKa</h2>'
> arr = ['hl', 'dish-name']
> getTag('Marypo', ...arr)
< '<hl class="dish-name">Marypc</hl>'
♦ добавления элементов деструктурируемого массива в другой, вновь создавае-
мый массив— также с помощью оператора упаковки-распаковки, который на
этот раз ставится в перечень элементов создаваемого массива:
> arr = ['HTML', 'CSS']
> lee arr2 = [...arr, 'JavaScript']
> arr2
< (3) ['HTML', 'CSS', 'JavaScript']
9,1.1,7. Копирование массивов
Массивы относятся к объектному типу, являющемуся ссылочным. Поэтому, если
присвоить массив, хранящийся в какой-либо переменной, другой переменной, то ей
будет присвоена копия ссылки, указывающая на тот же массив.
Но что сделать, если требуется создать копию самого массива?
Можно прибегнуть к деструктурированию копируемого массива:
> // Создаем исходный массив
> let arrl = [1, 2, 3, 4]
> // Создаем его копию путем деструктурирования
> let arr2 = (...arrl]
> // Изменяем исходный массив
> arrl[3] = 444
> arrl
< (4) [1, 2, 3, 444]
> // Его копия не изменилась
> агг2
< (4) [1, 2, 3, 4]
Однако, если копируемый массив содержит вложенные массивы или иные объекты,
будут скопированы ссылки на эти объекты (программисты называют такое копиро-
вание неглубоким):
> // Создаем исходный массив со вложенными массивами
> arrl = [[1, 2], [3, 4]]
> // Создаем его копию путем деструктурирования
> агг2 = [...arrl]
> // Изменяем второй вложенный массив, содержащийся в исходном массиве
> aril(l][1] = 444
> arrl Г1]
< (2) [3, 444]
> // Выводим второй вложенный массив, содержащийся в копии
> агг2[1]
< (2) (3, 444]
> // И исходный массив, и его копия содержат ссылки на одни и те же
> 11 вложенные массивы
Чтобы выполнить глубокое копирование заданного массива — вместе со вложен-
ными в него объектами, следует воспользоваться встроенной функцией
structuredClone(<массив>):
> arrl = [[1, 2], [3, 4]]
> arr2 = structuredClone(arrl)
> arrlLI][1] = 444
> arrl[1]
< (2) [3, 444]
> arr2[l]
< (2) [3, 4]
9.1.2. Типизированные массивы
Типизированный массив — массив, способный хранить элементы только одного
типа.
Типизированные массивы занимают меньше памяти, чем обычные массивы, и об-
рабатываются быстрее.
Классы типизированных массивов, предоставляемые JavaScript, приведены
в табл. 9.1.
Таблица 9.1. Классы типизированных массивов
Класс Тип элементов
Int8Array Целые числа от -128 до 127 — знаковые байты
UintSArray Uint8СlampedArray Целые числа от 0 до 255 — беззнаковые байты
Intl6Array Целые числа от -32768 до 32767 — знаковые короткие (2 байта)
Uintl6Array Целые числа от 0 до 65535 — беззнаковые короткие (2 байта)
Tnt32Array Целые числа от -2147433648 до 2147483647 — знаковые длинные (4 байта)
Uint32Ariay Целые числа от 0 до 4294967295 — беззнаковые длинные (4 байта)
Biglnt64Array Целые числа от -203 до 2е3-1 — знаковые большие (8 байтов)
BigUint64Array Целые числа от 0 до г64-! — беззнаковые большие (8 байтов)
Float 32Array Вещественные числа от -3.4*10 до 3.4* 10 1 —>бычнои точности (4 байта)
Float64Array Вещественные числа от -1,8* 10 до 1 8* 10 ‘ — двойной точности (8 байтов)
Типизированный массив создается выражением одного из следующих форматов:
♦ new <класс массмва> (<исходный массив>) —создает типизированный массив ука-
занного класса из элементов заданного исходного массива'.
> new UintfeArray(fl, 2, 3, 4])
< Uint8Array(4) [1, 2, 3, 4, . . . ]
При выводе типизированного массива в консоли веб-обозревателя показывается
имя класса, к которому относится массив, его размер в круглых скобках, эле-
менты массива и его свойства — в квадратных скобках (в приведенном примере
не показаны).
При попытке поместить в создаваемый массив число, не укладывающееся в до-
пустимый диапазон (см. табл. 9.1):
• у всех классов массивов, кроме Uint8ClampedArray, — будут отброшены пра-
вые (старшие) байты недопустимого значения:
> new Uint8Array ([1, 2, 3, 1334])
< Uint8Array(4) [1, 2, 3, 54, ... ]
• у класса uintSciampedArray — вместо значения, меньшего 0, будет подстав-
лен 0, вместо значения, большего 255, будет подставлено 255:
> new UintSClampedArray([3, 2, 3, 1334])
< Uint8CldinpedArray (4) [1, 2, 3, 255, . . . ]
♦ new скласс массива> (<размер масспва>) — создает массив указанного класса
и заданного размера, элементы которого хранят значения 0:
> new lnt32Array (10)
Int32Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, . . . . ]
♦ new <класс массива> () — создает пустой массив указанного класса.
Типизированные массивы позволяют выполнять те же операции, что и обычные
массивы (см. разд. 9.1.1.2 и 9.7.7.6). а их классы поддерживают свойство length,
методы at (), with(), includes (), indexOf(), lastlndexOf (), every (), some(), find(),
findLastO, findindex(), findLastlndex(), filter (), forEach(). map(). reduce (),
reduceRight(), join(), slice (), fill(), copyWithin(), entries(), keys(), values(),
toLocaleString(), sort(), toSortedO, reverseO, toReversed(), знакомые по классу
обычного массива Array (см. разд. 9.1.1.4 и 9.7.7.5).
Допол нительно поддержи ваются:
♦ объектное свойство byteLength — размер массива в байтах:
♦ статическое свойство bytes^pef element - размер одного элемента массива
в байтах.
9.2. Множества
Множество— непронумерованная изменяемая последовательность, хранящая
лишь уникальные значения произвольного типа.
9.2.1. Создание множеств
Создать новое множество можно, записав выражение в одном из следующих фор-
матов:
♦ new set (последовательности) —создает и возвращает множество, содержащее
все уникальные элементы из указанной последовательности. Повторяющиеся
элементы будут отброшены. Примеры:
> new Set([1, 2, 3, 4])
< Set(4) {1, 2, 3, 4}
> new Set([l, 1, 2, 2, 3])
< Set(3) {1, 2, 3}
> new Set('JavaScript1)
< Set (9) {'J', 'a', 'v', 'S', 'c', 'r', 'i', 'p', 't'}
При выводе множества в консоли веб-обозревателя показывается слово Set. раз-
мер множества в круглых скобках и его элементы в фигурных скобках;
♦ new Set () — создает и возвращает пустое множество.
Элементы множества всегда хранятся в том порядке, в котором они были добавле-
ны.
Множество — это объект класса set. Оно имеет объектный тип. и оператор полу-
чения типа typeof, примененный к множеству, возвращает строку 'object'.
9.2.2. Простейшие операции со множествами
Множество является непронумерованной последовательностью, поэтому получить
элемент множества по индексу мы не можем. При попытке сделать это будет воз-
вращено значение undefined. Пример:
> let setl = new Set([l, 2, 3, 4J)
> setl[2]
< undefined
Также не увенчается успехом попытка дать элементу множества с указанным
индексом новое значение:
> setl[l] = 5
> setl
< Set(4) {1, 2, 3, 4}
Мы можем:
♦ перебрать множество— в цикле перебора элементов последовательности
(см. разд. 4.2.4.4):
> for (let el of setl)
console.log(el)
< 1
< 2
< 3
< 4
♦ деструктурировать множество — так же как и массив (см. разд. 9. /. /.б):
> let (а, b] = setl
> а
< 1
> Ь
< 2
Множества часто применяются для удаления из массива повторяющихся эле-
ментов:
> // Создаем исходный массив с дублирующимися элементами
> let arr = [1, 2, 1, 5, 4, 5, 7, 5, 2, 1, 5]
> // Создаем множество на основе этого массива.
> // Дублирующиеся значения автоматически отбрасываются.
> let set2 = new Set(arr)
> // Создаем окончательный массив на основе множества —
> // уже без дублирующихся элементов
> let arrUnique = new Array(...set2)
> arrUnique
< (5) [1, 2, 5, 4, 7]
9.2.3. Свойство и методы класса Set
Свойство size класса Set хранит размер множества, т. е. количество элементов
в нем:
> let setl = new Set([l, 2, 3, 4])
> setl.size
< 4
Объектные методы класса Sec:
♦ add(<значение>) — добавляет в текущее множество заданное значение. Возвра-
щает ссылку на текущее множество. Пример:
> setl.add(5)
< Set(5) {1, 2, 3, 4, 5}
При попытке добавить в множество значение, уже существующее в нем. ничего
не произойдет:
> setl.add(3)
< Set(5) {1, 2, 3, 4, 5}
♦ has (<значение>) — возвращает true, если заданное значение присутствует в те-
кущем множестве, и false— в противном случае:
> setl.has(3)
< true
> setl.has(2)
< false
♦ forEach (<функция> [, <контекст выполнения^) —перебирает элементы текущего
множества и передает каждый элемент заданной функции. Результата не возвра-
щает. Функция получит первыми двумя параметрами значение очередного
элемента множества, а третьим— ссылку на текущее множество. Результата
функция возвращать не должна. Пример:
> let set2 = new Set([’HTML’, 'CSS', 'JavaScript'])
> setl.forEach((el) => { console.log(el); });
< HTML
< CSS
< JavaScript
Можно указать контекст выполнения, который будет доступен в теле функции
через локальную переменную this;
♦ delete «значение^ — удаляет из текущего множества заданное значение. Воз-
вращает true, если значение было удалено, и false— в противном случае (на-
пример, если значение в множестве отсутствует). Пример:
> setl.delete(2)
< t rue
> setl
< Set(4) {1, 3, 4, 5}
> setl.delete(222)
< false
> setl
< Set(4) {1, 3, 4, 5}
♦ clear() —удаляет из текущего множества все элементы. Результата не возвра-
щает;
♦ entries О — возвращает последовательность из вложенных массивов, каждый из
которых содержит два одинаковых элемента— значения очередного элемента
текущего множества:
for (let [el,] of set2.entries())
console.log(el)
♦ keys() — возвращает последовательность из значений элементов текущего мно-
жества;
♦ values () — то же самое, что и keys ().
9.3. Словари
Словарь— непронумерованная изменяемая последовательность, хранящая эле-
менты произвольного типа, в которой каждый элемент связан с определенным
ключом.
Ключ — уникальная пометка, которая связывается с каждым элементом словаря
и по которой впоследствии можно обратиться к этому элементу. Может быть
любого типа, однако наиболее часто применяются с троковые ключи.
9.3.1. Создание словарей
Создать новый словарь можно с помощью выражения одного из следующих фор-
матов:
♦ new мар (.< последовательность^ — создает и возвращает словарь с элементами из
указанной последовательности. Последовательность должна содержать вложенные
массивы с двумя элементами: ключом и значением элемента будущего словаря
11ример:
> new Map([
['platform', 'Node.js’],
[’language', 'JavaScript'],
('databa se', 'MongoDB']
])
> Map(3) {'platform' => 'Node.js', 'language' => 'JavaScript', ’b
'database' => 'MongoDB'}
Словарь в консоли веб-обозревателя представляется в виде слова Мар, количе-
ства элементов словаря в круглых скобках и, в фигурных скобках, перечня эле-
ментов в виде набора пар формата <ключ> -> <значениё>\
♦ new Мар () — создает и возвращает пустой словарь.
Элементы словаря всегда хранятся в том порядке, в котором они были добавлены.
Словарь — это объект класса Мар. Он имеет объектный тип, и оператор получения
типа typeof, примененный к словарю, возвращает строку 'object'.
9,3.2, Простейшие операции со словарями
♦ I lepeoop словаря — в цикле перебора элементов последовательности
(см. разд. 4.2.4.4).
На каждой итерации цикл выдает массив из двух элементов: ключа очередного
элемента словаря и его значения.
I [ример:
> let mapl = new Map([
['platform', 'Node.js'],
['language', 'JavaScript'],
['database', 'MongoDB']
])
> for (let [key, val] of mapl)
console.log('${key}: ${val}')
< platform: Node.js
< language: JavaScript
< database: MongoDB
♦ Деструктурирование словаря— поддерживается только при создании массива
(см. разд. 9.1.1.6). Создаваемый массив будет хранить вложенные массивы
с двумя элементами: ключом и значением элемента словаря Пример:
> let arr = [...mapl]
> arr[0]
< (2) ['platform', 'Node.js']
> arr[l]
< (2) ['language', 'JavaScript']
> arr[2]
< (2) ['database', 'MongoDB']
Пример объединения двух словарей-
> let map2 = new Мар([['client’, 'Lit']])
> let mapMerged = new Map([...mapl, ...map2])
> mapMerged
< Map(4) {'platform' => 'Node.js', 'language' => 'JavaScript', Ъ
'database' => 'McngoCB', 'client' => 'Lit'}
9.3.3. Свойство и методы класса Map
Свойство size класса Мар хранит размер словаря, т. с. количество элементов в нем:
> let mapl = new Map([
['platform', 'Node.js'],
['language', 'JavaScript'],
[’database', 'dBase']
])
> mapl.size
< 3
Объектные методы класса мар:
♦ set(<ключ>, <значение>) — присваивает элементу текущего словаря с указан-
ным ключом заданное значение. Если элемент с таким ключом не существует, он
будет создан. Возвращает ссылку на текущий словарь. Примеры:
> mapl.set('database', 'MongoDB')
< Map(3) {'platform' => 'Node.js', 'language' => 'JavaScript', 'b
'database' => 'MongoDB'}
> mapl.set('client', 'Lit')
< Map(4) {'platform' => 'Node.js', 'language' => 'JavaScript', |Ъ
'database' => 'MongoDB', 'client' => 'Lit'}
♦ get(<KHK>v>) — возвращает значение элемента текущего словаря с указанным
ключом. Если такого элемента нет. возвращается значение undefined. Примеры:
> mapl.get('platform')
< 'Node.j s'
> mapl.get('backup')
< undefined
♦ has (<ключ>) — возвращает true, если элемент с заданным ключом присутствует
в текущем словаре, и false — в противном случае:
> mapl.has('language')
< true
> mapl.has('backup')
< false
♦ forEach(<функция>\, <контекст выполнения^ ) —перебирает элементы текущего
словаря и передает каждый элемент заданной функции. Результата не возвращает.
Функция получит первым параметром значение очередного элемента словаря,
вторым — ключ элемента, третьим — ссылку на текущий словарь. Результата
функция возвращать не должна. 11ример:
> mapl.forEach((va1, key) => { console.log('${key): ${val}'); })
< platform: Node.js
< language: JavaScript
< database: MongoDB
< client: Lit
Можно указать контекст выполнения, который будет доступен в теле функции
через локальную переменную this;
♦ delete(<ключ>) — удаляет из текущего словаря элемент с заданным ключом. Воз-
вращает true, если значение было удалено, и false— в противном случае (на-
пример. если значение с таким ключомв словаре отсутствует). Пример:
> mapl.delete('database')
< true
> mapl
< Map(3) {'platform' => 'Node.js', 'language' => 'JavaScript*, ’b
'client' => 'Lit'}
> mapl.delete('backup')
< false
> mapl
> Map(3) {'platform' => 'Node.js', 'language' => 'JavaScript',
'client' => 'Lit'}
♦ clear () —удаляет из текущего словаря все элементы. Результата не возвращает;
♦ entries () — возвращает последовательность из вложенных массивов, каждый из
которых содержит два элемента: ключ и значение очередного элемента текущего
словаря:
for (let [key, val] of map].entries())
console.log('${key}: ${val}')
♦ keys() — возвращает последовательность из ключей элементов текущего сло-
варя;
♦ values ()— возвращает последовательность из значений элементов текущего
словаря.
Словарь можно использовать для хранения набора произвольных значений под за-
данными ключами. Словари обрабатываются несколько быстрее простых объектов
(см. разд. 1.6), традиционно использующихся для той же цели.
9.4. Самостоятельные упражнения
1. Напишите функцию createSpcilersO, создающую сразу множество спойлеров
(см. упражнение 8.6). Пусть эта функция принимает произвольное количество
параметров, которыми могут быть как ссылки на объекты элементов-спойлеров,
так и якоря этих элементов. Поместите объявление этой функции в файл сцена-
рия. где содержится объявление функции createspoiler(), создающей единич-
ный спойлер.
2. Модифицируйте написанную ранее функцию createSpoilers() гак, чтобы она
принимала либо произвольное количество параметров с отдельными элемента-
ми, которые станут спойлерами, либо один параметр с массивом таких эле-
ментов.
Урок 10
Объекты
Объект— это значение, представляющее какую-либо сложную сущность. Объект
содержит свойства (переменные, хранящие отдельные части или характеристики
этой сущности) и методы (функции, выполняющие какие-либо действия над сущ-
ностью или возвращающие ее параметры).
|| Элемент объекта — это его свойство или метод.
Так, массив JavaScript (см. разд. 9.1.1)— это объект. Он содержит свойство length,
хранящее одну из характеристик массива— его размер, метод includes О, возвра-
щающий характеристик} массива — наличие в нем элемента с указанным значени-
ем, и метод push(), выполняющий над массивом действие — добавляющий в него
новый элемент.
Более того, значение любого типа, поддерживаемого JavaScript,— это объект. На-
пример. объект строки (см.урок 6) поддерживает свойство length и метод trim(), а
объект числа (см. урок 5) — методы toExponential () и toFixed ().
Объекты создаются на основе классов — своего рода образцов, задающих наборы
поддерживаемых свойств и методов. Например, массив — это объект класса Array,
строка — объект класса string, а число — объект класса Number.
JavaScript поддерживает ряд встроенных классов, реализованных в исполняющей
среде. Также программист может объявлять собственные классы для своих нужд
(как это делается, описано на уроке 77).
Класс может включать свойства и методы, как объявленные в нем самом, так и
унаследованные от базового класса.
Наследование— создание одного класса (производного, или подкiacca) на
основе другого (базового, или суперкласса). Производный класс получает все
общедоступные элементы (свойства и методы) базового класса.
Например, класс HTMLimageEiement, представляющий графический тег <img>, являет-
ся производным от класса HTMLElement. Он объявляет собственное свойство sre,
хранящее интернет-адрес файла с выводимым изображением, и наследует от базо-
вого класса свойство style, содержащее объект встроенного CSS-стиля. Л класс
HTMLElement, в свою очередь, является производным от класса Element.
Все классы JavaScript наследуются, непосредственно или опосредованно, от класса
Object.
10.1. Создание объектов
Для создания объекга на основе класса с указанным именем применяется оператор
создания объекта new, записываемый в формате1:
new <имя класса>([<набор параметров, передаваемых создаваемому объекту, 'Ъ
через запятук»])
Параметры, передаваемые объекту при его создании, описаны в документации по
классу, на основе которого создается объект.
Оператор new возвращает в качестве результата вновь созданный объект.
Возвращенный объект будет сразу же уничтожен, если его не зафиксировать в па-
мяти. присвоив какой-либо переменной (или свойству другого объекга), передав
в качестве параметра функции (или методу другого объекта) или иным способом
(например, описанным ^упражнении 17.2).
Примеры:
> // Создаем объект класса Date — временную отметку (см. разд. 7.1} —
> 11 без передачи параметров. Будет создана временная отметка,
> // хранящая текущие дату и время.
> let currentDate = new Date()
> // Создаем временную отметку с передачей параметров
let birthday = new DateCl^O, 9, 27)
> // Создаем обычный массив из 12 пустых элементов (см. разд. 9.1.1.1}
< let arr = new Array(12)
> // Создаем типизированный массив lnt!6Array (см. разд. 9.1.2}
> ley arrlntl6 = new Intl6Array((1024, 2048, 4096, 8192])
Практика показывает, что, если при создании объекга ему не требуется передавать
параметры, круглые скобки после имени класса можно опустить:
> let currentDate = new Date
< let arrEmpty = new Array
Все объекты принадлежат к объектному типу данных. Оператор получения тина
typeof при применении его к объекту возвращает строку 'object'.
Объектный тип данных является ссылочным, т. е. в переменных хранятся не сами
объекты, а ссылки на них.
10.2. Работа с объектными свойствами
и методами
Объектные свойства и методы принадлежат конкретному объекту, называемому
текущим.
1 ')тот оператор нам уже неплохо знаком по предыдущим урокам.
10.2.1. Доступ к объектным свойствам и методам
Для обращения к объектному свойству заданного о&ъекта применяется оператор
доступа к элементу объекта, обозначаемый обычной точкой (.):
<сбъект>.<свойство>
Если такая языковая конструкция в выражении, содержащем оператор присваива-
ния, стоит правее этого оператора, выполняется извлечение значения свойства:
> let arr = new Array(1, 2, 3, 4)
> let 1 = arr.length
> 1
< 4
При попытке обратиться к несуществующему свойству возвращается значение
undefined:
> arr.square
< undefined
Если же оператор доступа к элементу объекта стоит левее оператора присваивания,
выполняется присваивание свойству нового значения:
> arr.length = 3
> arr
< (3) [1, 2, 3]
Для вызова объектного метода также применяется оператор доступа к элементу
объекта, который в этом случае записывается в формате, схожем с форматом вызо-
ва функции (см.разд. 8.1):
<объект>,<метод>{[<значенпя параметров, передаваемы?: методу, через запятую>\)
Пример-
> arr.push(5)
Попытка вызвать несу шествующий метод приведет к ошибке типа TypeError.
10.2.1.1. Вложенные объекты. Цепочки операторов доступа
|| Вложенный объект — объект, хранящийся в свойстве другого объекта.
Для обращения к свойствам вложенных объектов и вызовов у них методов опера-
торы доступа . записываются цепочками. Операторы доступа выполняются в по-
рядке слева направо, и каждый последующий оператор работает с результатом,
возвращенным предыдущим оператором.
Пример цепочки из операторов доступа № 1:
window.document.write('<th>4wcno</th>’);
Первый по счету оператор доступа . обращается к объекту window, чтобы извлечь из
его свойства document хранящийся там вложенный объект. Второй по счету опера-
гор доступа . вызывает у вложенного объекта, извлеченного предыдущим операто-
ром доступа, метод write ().
Пример № 2:
const si = s.trim().length;
Первый оператор доступа . вызывает метод trim о у строки ив переменной s, вто-
рой — обращается к свойству length строки, возвращенной этим методом.
Использование подобных цепочек обращений и вызовов позволяет существенно
сократить код. Однако слишком длинные цепочки плохо воспринимаются при чте-
нии программного кода.
10.2.1.2. Условный доступ
Если обратиться к свойству или вызвать метод у несуществующего вложенного
объекта, возникнет программная ошибка типа ТуреЕггог. Избежать ее можно, про-
веряя перед обращением (вызовом) существование вложенного объекта и выполняя
обращение (вызов) лишь в случае, если объект существует. Пример:
// Проверяем существование вложенного объекта t свойстве innerObject
if (outerObject.innerObject)
/I Вызываем метод method() лишь в том случае, если вложенный объект
// существует
outerObject.innerObject.method();
Подобный код можно сократить, применив оператор условного доступа ?.:
<свойство со вложенным объектом??. <свойство или метод вложенного объекта?
Если заданное свойство содержит вложенный объект, оператор обращается к ука-
занному свойству и возвращает его значение или вызывает указанный метод вло-
женного объекта и возвращает выданный им результат. В противном случае опера-
тор сразу же возвращает значение undefined.
Перепишем приведенный ранее код с применением оператора условного доступа:
outerObject.innerObject?.methodd ;
Если требуется вызвать метод, который, возможно, не существует, оператор услов-
ного доступа следует поставить между именем вызываемого метода и круглыми
скобками Если указанный метод существует, он будет вызван, и оператор вернет
выданный им результат (если метод его выдаст). Если же метод не существует,
оператор сразу вернет значение undefined.
Проверим, так ли это. попытавшись вызвать заведомо несуществующий метод
toMoneyO класса Number:
> (1_000_000 ООО).toMoney?.()
> undefined
10,2.1.3. Альтернативный формат доступа
к объектным свойствам и методам
Для доступа к объектным свойствам и методам также можно использовать опера-
тор доступа к элементу последовательности [] (квадратные скобки):
<объект>[ < свойство ]
<объект>[ <метод> ]([<значения параметров, передаваемых методу, через запятую>])
Этот формат применяется, если требуется выполнить доступ к свойству или мето-
ду, имя которого хранится в какой-либо переменной в виде строки, если имя свой-
ства или метода содержит пробелы или иные недопустимые в именах символы или
вообще не является строкой (подробности — на уроке 11).
Примеры:
> // Объявляем переменную с объектом - строкой
> let s = 'веб-разработка'
> // Объявляем переменную с именем свойства
> const propNane = 'length'
> // Объявляем переменную с именем метода
> const methodName = 'toUpperCase'
> // Обращаемся к свойству, имя которого хранится в переменной
> slpropNaine]
< 14
> // Обращаемся к методу’, имя которого хранится в переменной
> s[methodName]()
< 'ВЕБ-РАЗРАБОТКА'
10.2.2. Проверка существования свойства или метода
Существует несколько способов проверить существование в объекте указанного
свойства:
♦ способ, описанный в разд. 3.10.3:
if (typeof object.property == 'undefined')
// Свойство property в объекте object не существует
else
// Свойство property в объекте object существует
♦ применение оператора проверки существования свойства in:
<лмя свойство in <объект>
Оператор возвращает true, если свойство с заданным именем поддерживается
указанным объектом, и false— в противном случае. В расчет принимаются не
только собственные свойства, объявленные непосредственно в классе, на основе
которого был создан объект, но и унаследованные от базовых классов.
1 Тримеры:
> let arr = []
> 'length' in arr
< true
> 'size' in arr
< false
Проверить наличие заданною свойства в значении, относящегося к значащему
типу, этим оператором нельзя — возникнет ошибка ТуреЕггог;
♦ вызов метода hasOwnProperty(<пмя свойствам. унаследованного от общего базо-
вого класса Object. Метод возвращает true, если текущий объект поддерживает
собственное свойство с заданным именем, и false — если такое свойство не под-
держивается или если оно существует, но унаследовано от базовых классов.
Пример:
> arr.hasOwnProperty(1 length')
< true
Этот, а также следующий метод позволяют проверить наличие заданного свой-
ства в значении, относящемся к значащему типу:
> let s = 'веб-разработка'
> s.hasOwnProperty(’length')
< true
> s.hasOwnProperty('size')
< false
♦ вызов статического метода hasOwn (<обьект>, <имя свойствам класса Object. Ме-
тод возвращает true, если заданный объект поддерживает собственное свойство
с заданным именем, и false— если такое свойство не поддерживается или если
оно унаследовано от базовых классов. Примеры:
> Object.hasOwn(arr, 'length')
< true
> Object.hasOwn(s, 'length')
< true
Проверить наличие метода в объекте можно способом, описанным в разд. 3.10.3,
или с помощью оператора in. Только имя метода следует записать без круглых
скобок (если их указать, метод будет вызван, в выражение проверки подставлен
возвращенный им результат, и результат проверки окажется не тем, какой мы ожи-
дали). I Тримеры:
if (typeof object.method — 'undefined')
// Метод method() в объекте object не существует
else
// Метод method() в объекте object существует
> 'indexOf in arr
< true
> 'search' in arr
< false
К сожалению, метод hasOwnProperty о и статический метод hasOwn() ДЛЯ проверки
наличия метода в объекте использовать не получится. Даже при указании имени
заведомо существующего метода оба этих метода вернут значение false1.
10.2.3. Добавление свойств к объектам.
Добавленные свойства
Если присвоить значение свойству, не существующему в объекте, будет создано
добавленное свойство.
Добавленное свойство — произвольное свойство, созданное в объекте простым
присваиванием значения и существующее наряду со свойствами, которые были
получены объектом от класса.
Пример создания добавленного свойства description у массива с названиями язы-
ков программирования:
> let languages = ['JavaScript', 'Python', 'Ruby', 'PHP']
> languages.description = 'Языки программирования’
> languages.descr iption
< 'Языки программирования’
Добавленные свойства принадлежат лишь тем объектам, к которым они были
добавлены. Убедимся в этом, создав еще один массив и попытавшись обратиться
к его свойс’1 ву description:
> let arr = [1, 2, 3, 4]
> arr.description
< undefined
Внимание!
Добавленные свойства можно создавать только у значений ссылочных типсн
При попытке создать добавленное свойство у значения значащего типа в обычном
режиме ничего не произойдет:
> let s = 'Веб-разработка'
> s.description = 'Род занятий’
> // Свойство не было создано
> s.description
< undefined
Если же активизирован строгий режим, возникнет ошибка типа ТуреЕггог.
')то связано с особенностями реализации объектов в JavaScript. Своего рота тяжелое наследие,
оставшееся от ранних реализаций языка и до сих пор отравляющее жизнь программистам.
10.2.4. Удаление свойств
Оператор удаления указанного свойства из заданного объекта delete записывается
в следующем формате:
delete <обрашение к удаляемому' свойству>
Обращение к удаляемому свойству можно записать как с оператором . (см. разд. 10.2.1),
так и с оператором [] (см разд. 10.2.1.3).
Пример:
> let languages = ['JavaScript’, 'Python', 'Ruby', 'PHP']
> languages.description = 'Языки программирования'
> languages.description
< 'Языки программирования'
> delete languages.description
> // Все, свойство languages в массиве больше не существует
> languages.description
< undefined
10.3. Простые объекты
Простой объект— объект, содержащий произвольный набор свойств и созда-
ваемый посредством упрощенного синтаксиса. Создается на основе класса
object.
Для создания простого объекта применяется выражение формата:
<объявление свойства 1>,
<объявление свойства 2>,
^.объявление свойства N>
}
Созданный простой объект следует сразу же присвоить какой-либо переменной или
передать с параметром какой-либо функции — иначе он будет уничтожен.
Каждое объявление свойства записывается в одном из следующих форматов:
♦ универсальном:
<ммя свойства>: <значение, переменная или выражение>
Создает свойство с указанным именем и присваивает ему заданное значение,
содержимое указанной переменной или результ ат вычисления выражения.
Имя свойства должно удовлетворять тем же требованиям, что и имя переменной
(см. разд. 3.10.1). Имена свойств чувствительны к регистру символов.
1Тример:
> const color = 'black’
> // Создаем простой объект с тремя свойствами
> let point = {
х: 100,
у: 100 * 2,
color: color
}
> point
< {х: 10U, у: 200, color: 'black'}
В консоли веб-обозревателя простой объект представляется в виде набора его
свойств со значениями, заключенного в фигурные скобки:
♦ сокращенном— используется в случае, если существует переменная и необхо-
димо создать в простом объекте свойство с гшенем, совпадающим с именем этой
переменной, и хранящее значение этой переменной:
<-имя свойства (оно же имя переменной)>
Приведенный ранее пример можно записать чуть короче (сокращенный фраг-
мент кода подчеркнут):
> point = {
х: 100,
у: 300 * 2,
color
}
♦ альтернативном:
[<имя свойства>] : <значение, переменная или выражение>
11рименяется, если имя свойства не удовлетворяет предъявляемым требованиям
(например, содержит пробелы), хранится в какой-либо переменной в виде стро-
ки или вообще не является строкой.
I Тример:
> const xPropName = 'х'
> const yPropName = 'у'
> let point2 = {
[xPropName]: ICO,
[yPropName]: 100 * 2,
['color in CSS format']: color
)
> point2.x
< 100
> point2['color in CSS format']
< 'black'
После последнего объявления свойства в простом объекте допускается ставить за-
пятую:
> let point = {
color: color,
Можно дублировать объявление одного и того же свойства — в этом случае свой-
ство получит значение, указанное в последнем объявлении:
> point = {
х: 'координата х',
х: 100,
у: 100 * 2,
}
> point.х
< 100
Допускается создавать простые объекты с одним свойством:
> let verySimpleObject = { language: 'JavaScript' }
А также пустые — не содержащие ни одного свойства. — записав фигурные скоб-
ки вплотную:
> let erupt yObject = {}
Простые объекты применяются для сохранения каких-либо наборов значений, час-
то— с целью передачи их какой-либо функции или методу. Так, в виде простых
объектов методу toLocaleString () класса Number передаются дополнительные пара-
метры (см. разд. 5.1.3.7). В этом простые объекты схожи со словарями (описаны
в разд. 9.3).
Код, создающий простой объект, нагляднее и компактнее, чем код создания слова-
ря. Однако словари, особенно содержащие много элементов, обрабатываются
быстрее. Тем не менее простые объекты до сих пор применяются намного чаще
словарей.
10.3.1. Методы в простых объектах
В простых объектах можно создавать методы. Для этого используется конструкция,
записываемая в фигурных скобках и имеющая следующий формат:
< :1мя метода>([<имена параметров, принимаемых методом, через запятую>]) {
<тело метода>
}
При вызове метода в его теле создается локальная переменная this, хранящая
ссылку на текущий объект, который станет контекстом выполнения. Эту перемен-
ную можно использовать для доступа к свойствам и другим методам текущего объ-
екга.
Пример простого объекта с методом:
> let point = {
х: 100,
у: 100 * 2,
color: 'black',
getDescriptionO {
return 'Точка [${this.x}, ${this.y}], ${this.color}';
}
}
> point.getDescriptionO
< 'Точка [100, 200], black'
Для создания метода в простом объекте также можно объявить обычное свойство
и присвоить ему функцию, именованную или анонимную:
let point = {
getDescription: finction() {
return 'Точка [${this.x}, ${this.y}], ${this.color}';
}
}
В стрелочных функциях локальная переменная this не создается, и они используют
глобальную переменную this, содержащую ссылку на глобальный объект — окно
веб-обозревателя. Поэтому использовать для реализации методов в простых объек-
тах стрелочные функции нельзя.
Полезно знать
«За кулисами» методы всех объектов представляются в виде свойств, которым при-
своены функции, реализующие эти методы.
10.3.2. Копирование простых объектов
Объекты, как и массивы, относятся к ссылочному типу. При присваивании объекта
другой переменной последняя получит копию ссылки на этот же объект.
Для неглубокого копирования объектов применяется деструктурирование:
> let objl = { propl: 1, prop2: 2 }
> let obj2 = { ...objl }
> objl.propl = 111
> obj1.propl
< 111
> obj 2.propl
< 1
Однако не забываем о том, что при неглубоком копировании вложенные объекты
не копируются — копируются лишь ссылки на них:
> objl = { propl: 1, prop2: { embpropl: 2, embprop2: 3 } }
> onj 2 = { ...obj1 }
> obj1.prop2.embprcpl = 222
> objl.prop2
< {embpropl: 222, embprop2: 3}
> obj2.prop2
< {embpropl: 222, embprop2: 3}
Выполнить глубокое копирование объектов позволит встроенная функция
structuredcione (), описанная в разд. 9.1.1.7:
> objl = { propl: 1, prop2: { embpropl: 2, embprop2: 3 } }
> obj2 = structuredcione(objl)
> objl.prop2.embpropl = 222
> objl.prop2
< {embpropl: 222, embprop2: 3}
> obj2.prop2
< {embpropl: 2, embprop2: 3}
10.3,3 Фабрики объектов
Фабрика объектов— замыкание, создающее и возвращающее простой объект,
который настроен сообразно значениям полученных замыканием параметров.
Как правило, простой объект, возвращаемый фабрикой объектов, содержит методы,
в телах которых используются параметры, полученные замыканием при вызове,
и объявленные в нем локальные переменные.
Пример фабрики объектов, которая возвращает простые объекты, аналогичные рас-
смотренным в разд. 10.3. Г.
> function getPointObject(х, у, color) {
return point = {
х,
У/
getDescription() {
return 'Точка [${this.x], ${this.y}], ${color} ;
}
}
> let redPoint = getPointObject(100, 200, ’red')
> redPoint.getDescription()
< 'Точка [100, 200], red'
> let bluePoint = getPointObject(200, 100, 'blue')
> bluePoint.getDescripr.ion ()
< 'Точка [200, 100], blue'
10.4. Простейшие действия с объектами
♦ Выяснение, принадлежит ли заданный объект указанному классу или одному из
классов, производных от него, — с помощью оператора принадлежности клас-
су instanceof:
<обьект> instanceof <класс>
Оператор возвращает true, если объект принадлежит классу или одному из про-
изводных от нею классов, и false — в противном случае.
Примеры:
> let arr = []
> // Массив принадлежит классу Array
> arr instanceof Array
< true
> // Массив не принадлежит классу String
> arr instanceof String
< false
> // Массив принадлежит классу, производному от класса Object
> arr instanceof Object
< true
♦ сравнение объектов — поскольку объекты являются ссылочным типом, сравни-
ваются ссылки на объекты. Фактически выполняется проверка, ссылаются ли
две переменные на один и тот же объект. Пример:
> let datel = new Date(2023, 9, 27)
> let date2 = datel
> // Переменные datel и date2 ссылаются на один и тот же объект
> datel == date2
< true
> let date3 = new Date(2023, 9, 27)
> // А переменные datel и date3 ссылаются на разные объекты, несмотря
> // на то, что оба объекта хранят одинаковую временную отметку
> datel == date3
< false
♦ перебор свойств — как собственных, так и унаследованных — с помощью цикла
по свойствам объекта (см. разд. 4.2.4.5):
> let point = {
х: 100,
у: 100 * 2,
color: 'black',
> for (let p in point)
console.log('$(p): $ipoint[pl} );
< x: 100
< y: 200
< color: black
♦ получение перечня собственных свойств заданного с&ьекта и их значений — вы-
зовом статического метода entries (<о&ьект>) класса Object.
Метод возвращает массив, содержащий вложенные массивы из двух элементов:
имени очередного свойства в виде строки и его значения.
Пример:
> arr = Object.entries(point)
> arr[0]
< (2) ['x', 100]
> arr[l]
< (2) ['y', 200]
> arr[2]
< (2) ['color', 'black']
♦ получение массива из имен собственных свойств указанного объекта— вызовом
статического метода keys (<объект>) или getOwnPrcpertyNames(<объект>) класса
object;
♦ получение массива из значений собственных свойств указанного объекта— вы-
зовом статического метода values (<объект>) класса Object;
♦ превращение заданного объекта в нерасширяемый вызовом статического ме-
тода preventExtensions(<объект>) класса Object.
Нерасширяемый объект— объект, в котором нельзя создавать добавленные
свойства.
Попытка создать в нерасширяемом объекте добавленное свойство в обычном
режиме ни к чему не приведет, а в строгом режиме вызовет ошибку типа
ТуреЕггог. Однако удалять свойства нерасширяемого объекта все же можно.
В качестве результата метод preventExtensions () возвращает ссылку на задан-
ный объект.
Пример:
> // Делаем объект point нерасширяемкм
> Object.preventExtensions(point)
> // Пытаемся создать в нем добавленное свойство
> point.h = '9'
> // Свойство создано не было
> p^int.h
< undefined
Выяснить, является ли заданный объект нерасширяемым, позволяет статический
метод isExtensible (<объект>) класса Object. Он возвращает true, если в объекте
можно создавать добавленные свойства, и false— если объект нерасширяем.
Пример:
> Obj ect.isExtensible(point)
< false
♦ превращение заданного объекта в запечатанный— вызовом статического мето-
да seal (<объект>} класса Object.
Запечатанный объект — нерасширяемый объект, у которого, ко всему про-
чему. нельзя удалять существующие свойства.
Попытка удалить какое-либо свойство в запечатанном объекте в обычном режи-
ме ни к чему не приведет, а в строгом режиме вызовет ошибку типа ТуреЕггог.
В качестве результата метод seal о возвращает ссылку на заданный объект.
Выяснить, является ли заданный объект запечатанным, позволяет статический
метод isSealed(<объект>) класса Object. Он возвращает true, если объект запеча-
тан, и false — в противном случае;
♦ превращение заданного объекта в замороженный — вызовом статического ме-
тода freeze (<объект>) класса Object.
Замороженный объект — запечатанный объект, свойствам которого, ко всему
прочему, нельзя присваивать новые значения.
Попытка присвоить новое значение свойству замороженного объекта в обычном
режиме ни к чему не приведет, а в строгом режиме вызовет ошибку типа
ТуреЕггог.
В качестве результата метод freeze () возвращает ссылку на заданный объект.
Выяснить, является ли заданный объект замороженным, позволяет статический
метод isFrozen(<объект>) класса Object. Он возвращает true, если объект замо-
рожен. и false— в противном случае.
10.5. Деструктурирование объектов
|| Деструктурирование объекта — разделение объекта на отдельные свойства.
Деструктурирование может выполняться с целью:
♦ присваивания отдельных свойств объекта разным переменным — посредством
выражения, записанного в одном из следующих форматов:
<опера тор объявления переменных> 'Ъ
{< переменна я 1>, <переменная 2>, . . . ^.переменная W>} = <объект>
({<переменная 1>, <переменная 2>, , . . <переменная N>} = <объект>)
Если переменные не были объявлены ранее, следует указать какой-либо оператор
объявления переменных: let, const или var. В противном случае оператор объявле-
ния переменных не указывается, а все выражение берется в круглые скобки.
В качестве отдельной переменной можно указать:
• имя переменной— тогда этой переменной будет присвоено значение одно-
именного свойства объекта:
> let point = { х: 100, у: 200, z: 20 }
> let { х, у, z, color } = point
> X
< 100
> У
< 200
> z
< 20
Переменные, чьи имена не совпадают с именами свойств объекта, получат
значения undefined:
> color
< undefined
• конструкцию формата:
< 'имя свойства о&ьекта>: <:ш переменной>
Значение свойства объекта с указанным именем будет присвоено переменной
с заданным именем.
> let { х: xCoord, у: yCoord } = point
> xCoord
< 100
> yCoord
< 200
• конструкцию формата:
<имя переменной>=<значение по умолчанию>
Если в объекте отсутствует свойство с именем, совпадающим с указанным
именем переменной, переменная ПОЛУЧИТ заданное значение по умолчанию:
> ({ х, у, color='green' } = point)
> color
< ’green’
• конструкцию формата:
<имя свойства объектам. <имя переменной>=<значение по умолчанию»
Это комбинация двух приведенных ранее конструкций. Пример
> let cssCclor
> ({ х, у, color: cssColor=’green1 } = point)
> cssColor
< 'green'
• конструкцию формата ... <переменная> — тогда заданной переменной будет
присвоен простой объект со всеми оставшимися свойствами дестрх курируе-
мого объекта. Такая конструкция ставится в самом конце перечня переменных.
Пример:
> let props
> ({ х, ...props } = point)
> props
< {y: 200, z: 20}
♦ присваивания отдельных свойств объекта разным локальным переменным в теле
функции — с помощью конструкции, описанной ранее.
Конструкция, выполняющая деструктурирование объекта, записывается в объ-
явлении функции в качестве одного из принимаемых ею параметров (см. разд. 8.2).
Свойства объекта, передаваемого с этим параметром, будут присвоены локаль-
ным переменным с заданными именами.
11ример:
> function getTag({ content, tag: tagName, className='dish-name' }) {
return '<${tagName} ' +
'ciass="${className}">${content}</${tagName}>';
)
> let tag = { content: 'Чукка', tag: 'h2', className: 'discount' }
> getTag(tag)
< '<h2 class="discount">Чукка</Ь2>'
> tag = { content: 'Marypo', tag: 'hl' }
> getTag(tag)
< '<hl class="dish-name">Marypo</hl>'
♦ добавления свойств деструкту рируемого объекта в другой, вновь создаваемый
объект —с помощью оператора упаковки-распаковки (см. разд. 8.2.3):
...^о&ьект>
Оператор ставится в перечень объявлений свойств создаваемого объекта.
I Тример:
> tag = { content: 'Marypo' }
> let tag2 = { ...tag, tag: 'h2', className: 'dish-name' }
> tag2
< {content: 'Marypo', tag: 'h2', className: 'dish-name'}
10.6. Нулевая ссылка
Нулевая ссылка — ссылка, не указывающая ни на один объект. Обозначается
ключевым словом null.
Пример:
let someObject = null;
Нулевая ссылка является пустым значением, как и undefined (см. разд. 3.7.2). Одна-
ко она имеет объектный тип (undefined— неопределенный).
Если пишется функция, которая, в зависимости от каких-либо условий, должна
возвращать заданный объект или не возвращать ничего, в последнем случае из
функции следует вернуть значение null— это послужит сигналом тому, что под-
ходящий объект отсутствует1.
Кстати, так делает знакомый нам метод getEl emencByld () класса HTMLDocument.
10.7. Объектная природа значащих типов
Ранее неоднократно говорилось, что все типы данных, включая значащие (в том
числе числа, строки и логические величины), являются классами, а значения ти-
пов— объектами соответствующих классов. Отсюда следует, что число является
объектом класса Number, строка— объектом класса String, а логическая величи-
на — объектом класса Boolean.
Но это не совсем так. На самом деле значения значащих типов сразу при их созда-
нии хранятся в переменных в виде собственно значений, а не объектов. Гак. число
хранится в виде числа, а строка в виде наборов кодов символов, входящих в ее
состав. Это сделано для экономии памяти.
В таком же виде значения значащих типов обрабатываются при проведении над
ними простейших операций: арифметических действий — над числами, объедине-
ния — над строками, операций сравнения и др.
Однако при необходимости обратиться к свойству какого-либо значения значащего
типа или вызвать у него метод это значение предварительно преобразуется в объект
соответствующего класса. Для этого применяется оператор создания объекта new,
описанный в разд. 10.1.
Например, для преобразования числа в объект класса Number исполняющая среда «за
кулисами» выполняет выражение:
new Number(<число>)
Получив объект, исполняющая среда выполняет обращения к его свойствам и вы-
зовы его методов.
А как только снова потребуется выполнить какое-либо простейшее действие,
исполняющая среда извлекает из объекта значение вызовом одного из особых ме-
тодов (описаны в разд. 11.8) и сохраняет его в той же переменной. Не нужный
больше объект уничтожается.
Процесс превращения значения в объект и извлечения из него значения выполняет-
ся очень быстро и не вызывает значительного снижения производительности.
Вместе с тем, поскольку значение занимает меньше места, чем объект, экономится
память.
Полезно знать
Ничто не мешает нам создавать объекты классов Numoer, string и Boolean самостоятель-
но, записав в коде выражения наподобие:
> let а = new Number(1);
> let b = new Boolean(false);
Однако при таком подходе возникает ряд сложностей. Во-первых, созданные таким
образом значения будут иметь объектный тип:
> typeof а
< 'object'
Во-вторых, сравнение двух объектов, содержащих одинаковое значение, покажет, что
они не равны — поскольку это два разных объекта:
> Let с = new Number(1)
> а = с
< false
В-третьих, не забываем, что при преобразовании любого объекта в логический тип
всегда 6} дет получаться значение true:
> // Поскольку объект b хранит значение false, по идее, должно быть выведено:
> // b: false
> if (b)
console.log('b: true')
else
console.log('b: false')
. . . Однако будет выведено..........
< b: true
Поэтому значения значащих типов лучше не представлять в виде объектов.
10.8. Работа со статическими свойствами
и методами
Статические (или классовые) свойства и методы принадлежат не отдельному
объекту какого-либо класса, а самому этому классу.
Для доступа к статическим свойствам и методам можно использовать как основной
(см. разд. 10.2.1), так и альтернативный форматы (см. разд. 10.2.1.3), указав вместо
объекта нужный класс.
Примеры:
> Number.MAX VALUE
< 1.7976931348623157е+308
> Number ('MIN_VALUE1 ]
< 5е-324
> Math.sqrt(2)
< 1.4142135623730951
> Math['round'](1234.5678)
< 1235
10.9. Эмблемы
|| Эмблема— заведомо унпкатьный идентификатор.
Эмблемы часто применяются в качестве имен свойств, создаваемых в простых объ-
ектах (см.разд. 10.3). Поскольку эмблемы всегда уникальны, можно не опасаться
случайно создать в объекте свойство с уже существующим именем.
Эмблема создается вызовом встроенной функции Symbol ():
Symbol ([<олисание>])
Необязательное описание задается в виде строки.
Созданную эмблему следует сразу же присвоить какой-либо переменной, передать
в качестве параметра функции или использовать иным способом — иначе она будет
уничтожена.
Пример создания двух эмблем и использования их для указания свойств у простого
объекта:
> const xSym = Symbol('Координата X')
> const ySym = Symbol('Координата Y')
> let point = {
[xSym]: 100,
(ySymJ: 100 * 2
}
> point[xSym]
< 100
> point[ySym]
< 200
Отметим, что для доступа к свойствам, в качестве имен которых используются эмб-
лемы. следует применять выражения альтернативного формата (см. разд. 10.2.1.3).
> xSym
< Symbol(Координата X)
В консоли веб-обозревателя эмблема отображается как слово Symbol с круглыми
скобками, в которых выводится описание эмблемы (если оно было задано).
Эмблемы — это объекты класса symbol. Они относятся к типу эмблем, являющему-
ся значащим, и оператор получения типа typeof, примененный к эмблеме, возвра-
щает строку ' symbol.'.
Класс Symbol поддерживает свойство description, хранящее описание эмблемы, ко-
торое было указано при ее создании, или значение undefined, если эмблема не име-
ет описания. Это свойство доступно только для чтения. Пример:
> xSym.description
< 'Координата X'
Оператор проверки существования свойства in, метод hasOwnProperty(), статиче-
ский метол hasOwnO класса Object успешно работают со свойствами, созданными
с применением эмблем:
> ySym in point
< true
> point .hasOwriProperty (xSym)
< true
Однако цикл по свойствам объекта (см. разд. 4.2.4.5\ статические методы
entries (), keys (), getOwnPropertyNames () и values () класса Object игнориру ют такие
свойства:
> Object.keys(point)
< []
Получить массив с именами свойств заданного объекта, созданных с помощью эмб-
лем, можно вызовом статического метода getOwnPropertySymbols (<о&ъект>) класса
Object:
> Object.getOwnPropertySymbols(point)
< (2) [Symbol(Координата X), Symbol(Координата Y)]
Урок 11
Классы
Класс— это образец для создания объектов. Класс определяет набор объектных
свойств и методов, принадлежащих объектам, равно как и набор статических
свойств и методов, принадлежащих ему самому.
В исполняющей среде JavaScript реализован ряд встроенных классов, готовых
к использованию. Также мы можем объявлять собственные классы.
11.1. Объявление классов
Объявление класса — выражение, описывающее класс, включая набор поддер-
живаемых им свойств и методов, как объектных, так и статических.
Объявление класса записывается в следующем формате:
class <имя класса> {
< объявления элементов класса>
}
ймя класса должно удовлетворять тем же требованиям, что и имя переменной
{см. разд. 3.10.1).
Объявления элементов класса— свойств, методов (включая конструктор) и динами-
ческих свойств — записываются в фигурных скобках. Формат их объявлений будет
рассмот реп в следующих разделах.
Внимание!
Код объявления класса всегда выполняется в строгом режиме, даже если таковой
явно не был задействован
Область видимости объявленных классов такая же, как и у переменных, объявлен-
ных с помощью операторов let и const {см. разд. 3.10.1). В частности, класс, объ-
явленный в блоке, доступен только в этом блоке, а класс, объявленный в теле
функции, может использоваться только в теле функции.
Первое обращение к классу (например, с целью создать его объект) должно быть
выполнено строго после его объявления. Попытка обратиться к еще не объявлен-
ному классу вызовет появление ошибки типа ReferenceError. Обычно объявления
классов помещают в самом начале сценария.
Допускаются повторные объявления одного и того же класса. При этом более позд-
нее объявление заменит собой более раннее.
Полезно знать
Согласно существующим соглашениям имена классов в JavaScript, состоящие из одно-
го слова или представляющие собой сокращение, записывают полностью в нижнем
регистре, за исключением первой буквы, которая пишется в верхнем регистре:
Point. Square. Spoi ier. Slider
Если имя класса состоит из нескольких слов, первые буквы всех слов, включая первое,
записывают в верхнем регистре:
SomeClass, OtherClass, AdvancedSpoiler
Такой стиль именования сущностей носит название PascalCase.
11.1.1. Объявление объектных свойств класса
Объявление объектного свойства класса записывается в формате:
<имя свойства>[ = <изначальное значение свойства>]
Имена свойств должны быть уникальны в пределах объявляемого класса. Объяв-
лять свойство и метод с одинаковыми именами нельзя, попытка сделать это приве-
дет к ошибке типа SyntaxError.
В качестве изначального значения также можно указать переменную (тогда изна-
чальным значением объявляемого свойства станет значение этой переменной) или
выражение (значением свойства станет результат, возвращенный этим выражени-
ем). В последнем случае выражение, задающее изначальное значение свойства,
будет вычисляться при каждом создании объекта объявляемого класса.
В объявлении класса создается своя собственная переменная this, которая в каче-
стве контекста выполнения хранит ссылку на текущий объект объявляемого класса.
Эту переменную можно использовать в выражениях, задающих имена свойств, для
обращения к ранее объявленным свойствам.
Если изначальное значение не указано, объявляемое свойство получит значение
undefined.
Объявления свойств записываются построчно. После объявления свойства допус-
кается ставить точку с занятой (но обычно этого не делают).
Пример:
> class Point {
х = 100
// Через переменную this обращаемся к ранее объявленному свойству,
// чтобы получить его значение
у = this.x * 2
color
}
> new Point()
< Point {x: 100, у: 200, color: undefined}
Можно создавать свойства, в качестве имен которых используются значения, от-
личные от строк (например, эмблемы), или имена которых вычисляются в резуль-
тате выполнения каких-либо выражений. Однако следует помнить, что выражение,
возвращающее имя объявляемого свойства, вычисляется лишь один раз — в мо-
мент, когда исполняющая среда обрабатывает объявление класса. Таким образом,
все объекты этого класса получат свойство с одним и тем же именем. Пример:
> let name = 'language' + 'Name'
> class Language {
[name |
)
> new Language()
< Language {languageName: undefined}
> name = 'name' + 'Of + 'Language'
> new Language()
< Language {languageName: undefined}
11.1.2, Объявление объектных методов класса
Объявление объектного метода класса записывается в формате, аналогичном фор-
мату объявления функции:
<имя метода>((<имена параметров, принимаемых. методом, через запятую>]) {
<тело метода>
}
Имена методов должны быть уникальны в пределах объявляемого класса. Объяв-
лять свойство и метод с одинаковыми именами нельзя, попытка сделать это приве-
дет К ошибке типа SyntaxError.
В теле метода создается переменная this, хранящая контекст выполнения — теку-
щий объект объявляемого класса. Эту переменную можно использовать для досту-
па к свойствам и другим методам текущего объекта.
Пример:
> class Point {
х = 100
у = this.x * 2
get.Pointf) {
return 'Точка [${this.x}, ${this.y}} ;
}
> let point = new Point()
> point.getPoint()
< 'Точка [100, 200]'
Также можно объявить свойство (см. разд. 11.1) и присвоить ему функцию, которая
и реализует метод:
class Point {
getPoint = function() {
return 'Точка (${this.x), ${this.y}]';
}
В разд. 11.1 говорилось, что в объявлении класса присутствует своя собственная
переменная this (помимо создаваемой в теле вызываемого метода), хранящая
текущий объект. Поэтому для объявления методов описанным ранее способом
можно использовать и стрелочные функции. Пример:
class Point {
getPoint = () => Точка [${this.x}, ${this.y}]'
}
Можно создавать методы, в качестве имен которых используются значения, отлич-
ные от строк (например, эмблемы), или имена которых вычисляются в результате
выполнения указанных выражений. Однако помним, что выражение, возвращаю-
щее имя объявляемого метода, вычисляется лишь один раз в момент, когда ис-
полняющая среда обрабатывает объявление класса. Следовательно, все объекты
этого класса получат метод с одним и тем же именем.
11.1.3. Объявление конструктора
Конструктор— метод, автоматически вызываемый при создании каждого
объект класса.
Конструктор обычно используется для занесения в свойства создаваемого объекта
значений, переданных ему с параметрами.
Формат объявления конструктора:
constructor([<имена параметров, принимаемых конструктором, 'Ъ
через запятую? ]) {
<тело конструктора>
)
Конструктору передаются значения параметров, задаваемые при создании объекта
оператором new в круглых скобках (см. разд. 10.1).
Конструктор не должен возвращать никакого результата.
Конструктор вызывается после создания объявленных в классе свойств (см. разд. 11.1).
В его теле можно обратиться к этим свойствам для получения их значений и при
необходимости присвоить этим свойствам другие значения.
Пример:
> class Point {
х
У
constructor(х, у) {
this.x = х;
this.у = у;
}
}
> let point = new Point(100, 200)
> point
< Point {x: 100, y: 200}
В теле конструктора допускается создавать у объекта новые свойства, просто при-
сваивая им значения:
class Point {
х
У
constructor(х, у, color) {
// Присваиваем значения ранее объявленным свойствам
this.x = х;
this.у = у;
// Создаем новое свойство, просто присвоив ему значение
this.color = color;
}
Однако настоятельно рекомендуется обьявлять все необходимые свойства явно —
это сделает код класса более наглядным.
11.1.4. Объявление объектных динамических свойств
Динамическое свойство— имитация обычного свойства. Представляет собой
геттер, сеттер или их комбинацию.
Геттер— метод, вызываемый при попытке получить значение динамического
свойства. Не принимает параметров и возвращает результат, который и станет
значением динамического свойства.
Геттер может извлекать возвращаемое значение из другого, обычного свойства или
получать это значение в результате каких-либо вычислений.
Сеттер—метод, вызываемый при попытке присвоить динамическом} свойст-
ву новое значение. Принимает единственным параметром новое значение
динамического свойства и не возвращает результата.
Сеттер может заносить новое значение в другое, обычное свойство или выполнять
какие-либо вычисления с его участием.
Формат объявления геттера:
get <имя динамического свойство () {
<тело геттера>
}
Формат обьявления сеттера:
set <имя динамического свойства?(<имя параметра?) {
<тело сеттера?
У геттера и сеттера, относящихся к одному динамическому свойству, должны быть
одинаковые имена.
Если в динамическом свойстве присутствуют:
♦ только геттер — свойство доступно лишь для чтения.
Попытка присвоить такому свойству новое значение в обычном режиме ни к че-
му не приведет, а в строгом режиме вызовет ошибк} типа ТуреЕггог;
♦ только сеттер — свойство доступно лишь для записи.
Попытка получить значение такого свойства приведет к выдаче значения
undefined;
♦ и гетгер. и сетгер — свойство доступно и для чтения, и для записи.
Пример класса, выполняющего форматированный вывод чисел с указанной едини-
цей измерения (форматированный вывод чисел описан в разд. 5.1.3.1):
> class NumberFomatter {
// О&ычные свойства для хранения внутренних данных
__unit
value
// Динамическое свойство, доступное для чтения и записи
get unit О { return this, unit; }
set unit(val) { this._ unit = val; }
// Динамическое свойство, доступное только для записи
set value(val) { this, value = val; }
// Динамическое свойство, доступное лишь для чтения
get formatted() {
return this, value.toLocaleString('ru-RU',
{ style: 'unit', unit: this, unit });
}
}
> let nf = new NumberFomatter ()
> nf.unit = 'meter'
> nf.unit
< 'meter'
> nf.value = 1000
> nf.formatted
< '1 000 m'
11.1.5. Объявление статических свойств и методов
Статические свойства и методы, в отличие от объектных, принадлежат классу,
а не объекту.
Объявления статических элементов класса записываются в следующих форматах:
static <имя свойства>[ = <изначальное значение свойствах
static <имя метода>([<имена параметров, принимаемых методом, через запятуюХ) {
<тело метсда>
}
static get <.имя динамического свойствах.) {
<тело геттерам
}
static set <имя динамического свойства>(<имя параметрах {
<.тело сеттера>
Выражения, задающие изначальные значения статических свойств, вычисляются
всего один раз — когда исполняющая среда обрабатывает объявление класса.
В теле статического метода при его вызове создается переменная this, которая
в качестве контекста выполнения хранит ссылку на текущий класс (не объект).
Этой переменной можно пользоваться для доступа к другим статическим свойствам
и методам класса.
Пример:
> class NumberFormatter {
static unit
static ________value
static get unit() { return this, unit; }
static set 'unit (val) { this, unit = val; }
static set value(val) { this.__value = val; }
static get formatted() {
return this.__value.tohocaleString('ru-RU',
{ style: 'unit', unit: this, unit });
}
)
> NumberFormatter.unit = 'Celsius'
> NuitiberFormatter.value = 100
> NumberFormatter.formatted
< '100 °C
Чтобы получить доступ к статическому свойству или методу из объектного метода
(включая конструктор), следует воспользоваться свойством constructor текущего
объекта (доступного из переменной this). Это свойство хранит объект, представ-
ляющий текущий класс и содержащий все статические свойства и методы текущего
класса. Пример
> class SomeClass {
static prop
constructor(val) {
// Присваиваем значение статическому свойству в конструкторе
this.constructor.prop = val;
static getPropO {
return '!!! $(this.prop} !!!';
}
output() {
// Вызываем статический метод из обычного
console.log(this.constructor.getProp());
}
}
> let so = new SomeClass(’Static’)
> so.output()
< !!1 Static !!!
Как показывает практика, таким же способом можно получать доступ к статиче-
ским свойствам и методам класса через объект этого класса в обычном коде:
> so.constructor.getProp()
< ’!!! Static I!!1
Поскольку переменная this в теле статического метода хранит ссылку на текущий
класс, этой переменной можно пользоваться для создания объектов текущего клас-
са, использовав ее вместе с оператором new:
> class SomeClass {
static create() {
//В статическом методе создаем и возвращаем
// объект текущего класса
return new this();
}
}
> SomeClass.create()
< SomeClass {}
Выражения, вычисляющие значения статических свойств, должны быть простыми.
Сложные выражения (например, условные или циклы) применять для этого нельзя.
Однако если код, вычисляющий значения статических свойств, настолько сложен,
что его нельзя реализовать в виде простых выражений, в объявлении класса можно
создать блок статической инициализации, в котором и записать необходимый код.
Такой блок записывается в формате:
static {
<код, вычислят# at значения статических свойству
}
Пример:
class SomeClass {
static propl = 'Static*
static prop2
static ргорЗ
static (
if (IsOtherClassDefined) {
this.prop2 = new OtherClass(123);
this.prop3 = new OtherClass(321);
}
)
11.1.6. Закрытые свойства и методы
Ранее мы создавали классы с открытыми свойствами и методами.
Открытое (или общедоступное') свойство и открытый (или общедоступный)
метод— доступны как внутри класса, в котором они объявлены, гак и из
внешнего кода.
JavaScript позволяет объявлять также закрытые свойства и методы.
Закрытое (или частное) свойство и закрытый (или частный) метод— дос-
тупны только внутри класса, в котором они объявлены.
Закрытые элементы класса объявляются так же. как и открытые, только их имена
должны начинаться с символа # («решетка»).
Закрытым можно сделать объектное или статическое свойство, метод, геттер и сет-
тер динамического свойства.
Попытка получить доступ к закрытому свойству или методу извне вызовет возник-
новение ошибки типа SyntaxError.
Пример класса с двумя закрытыми свойствами и закрытым методом:
class NumberFormatter {
# unit
# value
#getParams() { return { style: 'unit', unit: this.#__unit }; }
get unit() { return this.# unit; }
set unit(val) { this.# _unit = val; }
set value(val) { this.#_value = val; }
get formatted() {
return this.#__value.toLocaleString('ru-RU1, this.#getParams() );
}
)
Закрытыми делают свойства, хранящие критически важные данные, несанкцио-
нированное изменение которых может нарушить работу объекта, и методы, вызов
которых «извне» по каким-либо причинам необходимо исключить.
11.2. Упражнение. Спойлер, часть 3
Переделаем спойлер, созданный при выполнении упражнений 8.7 и 8.6. реализовав
его в виде класса Spoiler. Клиент, заказавший спойлер, очень любит новые техно-
логии вообще и классы в частности...
Констриктор класса Spoiler будет принимать два параметра:
♦ элемент страницы, который требуется превратить в спойлер. в виде ссылки на
объект, представляющий этот элемент;
♦ дополнительные настройки (необязательный) — в виде простого объекта. Он
будет содержать необязательные свойства state (состояние спойлера в виде ло-
гической величины, по умолчанию: false) и decoration (имя дополнительного
стилевого класса, привязываемого к спойлеру, по умолчанию: пустая строка).
Еще клиент попросил реализовать в спойлере следующие открытые элементы.
♦ объектное динамическое свойство state, доступное для чтения и записи,— со-
стояние спойлера;
♦ объектный метод open о — разворачивающий спойлер:
♦ объектный метод closet) —сворачивающий спойлер:
♦ статическое свойство spoilerCiass — имя стилевого класса, который привязы-
вается к элементу, формирующему спойлер (по умолчанию: 'spoiler’);
♦ статическое свойство openedClass — имя стилевого класса, который привязыва-
ется к разворачиваемому спойлеру (по умолчанию: ’opened’);
♦ статический метод create о — принимает произвольное количество ссылок на
элементы страницы и создает на их основе спойлеры.
I. Найдем в папке 8\ех8 6 сопровождающего книгу файлового архива (см. прило-
жение 5) файлы 8 4 html (страница со спойлерами), 8 4 js (код спойлера) и 8 4 css
(таблица стилей с оформлением). Скопируем их куда-либо на локальный диск.
2. Откроем файл 8 4 js в текстовом редакторе и удалим все его содержимое.
3. Запишем в файл 8 4 js код из листинга 11.1.
Листинг 11.1. Класс Spoiler (файл 8.4.js)
class Spoiler{
static spoilerCiass = 'spoiler' // .............................. 1
static openedClass = 'opened'
ttspoilerState = false // ........................................ 2
#element
header
ttonHeaderClick = () => { // ....................................... 7
this.state = !this.state; If .................................. 8
constructor(element, { state=false, decoration='' }={}) { // ... 9
this.^element = element;
this.header = element.querySelector('h6');
element.classList.add(this.constructor.spoilerClass);
if (state) // ............................................. 1U
this.open();
if (decoration)
element.classList.add(decoration);
this.header.addEventListener(’click’, this.#onHeaderClick);
}
get state() { return this.#spoilerState; } // .................... 5
set state (val) { // ............................................. 6
if (val)
this.open();
else
thi s.close();
)
open () { // ..................................................... 3
if (!this.#spoilerState) {
this.#spoilerState = true;
this.ttelement.classList.add(thi s.constructor.openedclass);
)
}
closet') { // ..................................................... 4
if (thisЛspoilerState) {
thisЛspoilerState = false;
this.ftelement.classList.
remove(this.constructor.openedclass);
}
}
static create (... elements) { // ............................... 11
elements.forEach(el => ( new this(el); });
В классе мы объявили статические свойства spoilerclass и openedclass, как
условились ранее (поз. 1 в листинге 111).
Еще мы объявили три служебных свойства (поз. 2): spoilerState (состояние
спойлера в виде ло1ической величины, изначально false— т. е. спойлер свер-
нут), element (будет хранить ссылку на элемент, создающий спойлер) и header
(получит ссылку на заголовок спойлера). Первые два свойства мы сделали
закрытыми, поскольку несанкционированное изменение их значений нежела-
тельно и может нарушить работу спойлера. Третье свойство сделано откры-
тым — на будущее.
Метод open о разворачивает спойлер Сначала он проверяет, хранит ли свойство
spoilerstate значение false (т. е. свернут ли спойлер в настоящий момент), если
это так. присваивает свойству spoilerstate значение true и привязывает к эле-
менту. создающему спойлер (ссылка на него хранится в свойстве element), сти-
левой класс из статического свойства openedClass (поз. 3).
Метод close () разворачивает спойлер. Сначала он проверяет, хранит ли свойст-
во spoilerstate значение true (т. е развернут ли сейчас спойлер), если это так.
присваивает свойству spoilerstate значение false и убирает привязку к элемен-
ту. создающему спойлер, стилевого класса из статического свойства openedClass
(поз. 4)
Геттер динамического свойства state просто возвращает состояние спойлера,
хранящееся в свойстве spoilerstate (поз. 5). Сеттер этого динамического свой-
ства. в зависимости от переданного ему значения, разворачивает или сворачива-
ет спойлер, вызывая методы ореп() или close() соответственно (поз. 6).
Обработчик события щелчка на заголовке спойлера мы реализовали в виде стре-
лочной функции, присвоенной закрытому свойству onHeaderClick (поз. 7). В теле
обработчика нам понадобится обращаться к свойству state объекта спойлера,
и удобнее всего делать это через переменную this. Стрелочная функция исполь-
зует переменную this, создаваемую в объявлении класса и хранящую ссылку на
текущий объект этого класса. — что нам и надо. Если бы мы использовали
обычную функцию, то переменная this в ее теле ссылалась бы на элемент стра-
ницы, создающий спойлер, и получить доступ к объекту самого спойлера было
бы проблематично.
В теле функции-обработчика события мы извлекаем из динамического свойства
state состояние спойлера, меняем его на противоположное с помощью операто-
ра ! и присваиваем тому же динамическому свойству (поз. 8).
В объявлении конструктора класса второй ггараметр. с которым передается про-
стой объект с настройками спойлера, мы сделали необязательным, указав у него
пустой объект в качестве значения по умолчанию. Одновременно мы произвели
деструктурирование этого объекта, также задав значения по умолчанию у пере-
менных, которым будут присвоены значения свойств этого объекта (поз. 9).
Деструктурирование позволяет значительно упростить код функций и методов,
принимающих простые объекты с заранее известным набором свойств.
Если при создании спойлера было предписано сделать его развернутым, разво-
рачиваем его, вызвав метод ореп() (поз. 10).
Остальной код конструктора аналогичен тому, что мы написали при выполне-
нии упражнений 8.4 и 8.6.
Статический метод create() перебирает элементы массива, содержащего эле-
менты страницы, на основе которых следует создать спойлеры, и создает эти
спойлеры (поз. 11).
4. Откроем файл 8 4 html в текстовом редакторе и исправим код, инициализирую-
щий оба спойлера:
const spoilerl = document.getElementByld('spoilerl;
eroateSpoij:er-(spQi4er-l-H
new Spoiler(spoilerl);
const spuiler2 = document.getElementByld('spoiler2');
croatoSpoilor(spoilers;
new Spoiler(spoiler2);
В разд. 10.1 говорилось, что созданные объекты следует фиксировать в памяти,
например, присвоив их переменным. Однако созданные объекты спойлеров мы
явно нигде не фиксируем. Казалось бы, они будут уничтожены сразу после соз-
дания, и наши спойлеры не заработают.
Но это не так. Каждый объект содержит метод onHeaderClick(), который мы
привязываем к событию click заголовка спойлера в качестве обработчика и та-
ким образом фиксируем в памяти. В теле этого метода выполняется обращение
к переменной this, созданной в объявлении класса и хранящей ссылку на теку-
щий объект спойлера. Следовательно, пока метод onHeaderClick () привязан
к событию click в качестве обработчика, в его теле будет существовать пере-
менная this и соответственно существовать и функционировать объект спойле-
ра, на который ссылается эта переменная
А как только элемент, формирующий спойлер, будет удален (например, другим
веб-сценарием), будет удалено все его содержимое, включая заголовок. Удале-
ние заголовка повлечет за собой удаление привязанного к нему обработчика
события onHeaderClick () вместе с переменной this и объектом спойлера, на ко-
торый она ссылается,
5. Откроем страницу 8.4 html в веб-обозревателе и проверим оба спойлера в работе.
Теперь опробуем программные инструменты для управления спойлером. Пер-
вый спойлер оформим стилевым классом highlighted и программно развернем,
второй — сделаем изначально развернутым и потом программно свернем.
6. Исправим код инициализации спойлеров следующим образом:
const spcilerl = document.getElementByld('spoilerl’);
now Spoiler(spoilerl>)
const spl = new Spoiler(spoilerl, { decoration; 'highlighted' });
spl.open <);
const spoiler2 = document.getElementByld('spoiler2');
now Spoiier(spoiiork);
const sp2 = new Spoiler(spoiler2, { state: true }) ;
sp2 . close () ;
Обновим открытую в веб-обозревагеле страницу. Первый спойлер должен иметь
более «жирное» оформление и изначально быть развернутым, а второй — быть
свернутым.
Проверим, как работает статический метод create О класса спойлера.
7. Закомментируем часть ранее написанного кода инициализации спойлеров и до-
бавим вызов упомянутого ранее метода:
const spoilerl = document.getElementByld('spoilerl');
const opj - now Spoiler (spoi jor 1 —i—decoration!—'high! ightod'—H-t
spl.opent1;
// const spl = new Spoiler(spoilerl, { decoration; 'highlighted' });
// spl.open() ;
const spoiler2 = document.getElementByld('spoiler2');
const яри— now Spoiler i spoil-or2,—i state: tug ]! ;
op2.close J /
// const sp2 = new Spoiler(spotler2, { state: true });
// sp2.close ();
Spoiler.create(spoilerl, spoiler2);
Обновим открытую в веб-обозревателе страницу и проверим работоспособность
обоих спойлеров.
8. Закомментируем выражение, вызывающее статический метод create о, и рас-
комментируем ранее закомментированные выражения. Гак мы подготовим сце-
нарий к выполнению следующего упражнения.
11.3. Наследование классов
Наследование— это создание одного класса (производного, или подкласса) на
основе другого (базового, или суперкласса). Производный класс получит все от-
крытые объектные и статические элементы базовою класса: свойства (как обыч-
ные. так и динамические) и методы.
Закрытые элементы класса не наследуются, и получить к ним доступ из кода про-
изводного класса невозможно.
Объявление класса, являющегося производным от заданного базового класса, запи-
сывается в формате:
class <пмя класса> extends <базовый класс> {
<объявлен! 1я элементов класса>
}
Пример:
> // Объявляем базовый класс
> class А {
propl = 1
methodi() { console.log('Класс А, метод methodi()'); }
}
> // Объявляем производный класс
> class В extends А {
prop2 = 111
method2() { consolе.log('Класс В, метод method2()'); }
> // Создаем объект производного класса
> let Ь = new В
> // Обращаемся к свойству и вызываем метод производного класса
> Ь.ргор2
< 111
> b.method2()
< Класс В, метод method?()
> // Обращаемся к свойству и вызываем метод, унаследованные от
> // базового класса
> b.propl
< 1
> b. methodic)
< Класс А, метод methodi()
11.3.1. Перекрытие свойств и методов
Перекрытие свойства— изменение в производном классе значения свойства,
унаследованного от базового класса, путем повторного объявления этого свой-
ства.
Перекрытие метода— изменение н производном классе реализации метода,
унаследованного от базового класса, путем повторного объявления этого ме-
тода.
Пример:
> // Объявляем базовый класс со свойством и методом
> class А {
propl = 1
methodic) { console.log(’Класс А, метод methodi С)'); }
)
> // Объявляем производный класс, в котором выполняется перекрытие
> // унаследованных свойства и метода
> class В exrends А {
propl = 111
methodic) { console.log(’Класс В, метод methodi()'); }
> // Создаем объект базового класса, обращаемся к его свойству и вызываем
> // его метод
> let а = new А
> a.propl
< 1
> a.methodic)
< Класс А, метод method]()
> // Создаем объект производного класса, обращаемся к его свойству и
> // вызываем его метол
> let b = new В
> Ь.propl
< 111
> b.methodi()
< Класс В, метод methodi()
Перекрыть можно любое свойство и любой метод класса. включая конструктор.
Внимание!
Метод базового класса, вызванный из производного класса при попытке обращения
к какому-либо свойству получит значение, заданное у этого свойства в базовом а не
в производном классе Также метод базового класса не сможет обратиться к свойст-
вам, объявленным в производном классе
То же самое случится с перекрытым методом производного класса, вызванным из ме-
тода базового класса.
Пример-
> class А {
propl =10
methodi() {
console.log(this.propl);
this.met.hod2 ();
}
method2() { }
}
> class В extends A {
prop2 = 100
method2() {
console.log(this.propl);
}
}
> const b = new В
> b.methodlO
> // И метод methodi () базового класса А, и вызванный им метод mezhod2()
> // производного класса В получат старое значение свойства propl
< 10
< 10
Избежать этой проблемы можно, задавая новые значения унаследованных свойств
и создавая новые свойства в переопределенном конструкторе производного класса
(см. далее).
11.3.2. Переопределение методов
Переопределение метода— то же самое, что и перекрытие, только новая реа-
лизация метода дополняет его старою реализацию.
При переопределении в объявлении метода производного класса записывается код,
реализующий дополнительную функциональность, и в нужном месте кода вставля-
ется вызов того же метода, только реализованного в базовом классе. Для этого
применяется следующая конструкция:
♦ для вызова конструктора базового класса:
super ([< лере тень параметров через запятукР])
♦ для вызова метода, принадлежащего базовому классу:
super.<вызываемый метод>([<перечень параметров через запятую>])
Пример.
> // Объявляем базовый класс с конструктором и методом
> class А {
constructor() { console.log('Класс А, конструктор'); }
methodi() { console. log ('Класс А, метод methodi О'); }
}
> // Объявляем производный класс, в котором выполняется переопределение
> // унаследованных кснструктора и метода
> class В extends А {
constructor() {
super();
console.log('Класс 3, конструктор');
}
methodi() {
super.methodi();
console.log('Класс В, метод methodlО');
}
}
> // Создаем объект базового класса и вызываем его метод
> let а = new А
< Класс А, конструктор
> a.methodi()
< Класс А, метод methodi()
> // Создаем объект производного класса и вызываем его метод
> let Ь = new В
< Класс А, конструктор
< Класс В, конструктор
> Ь.methodi()
< Класс А, метод methodi {)
< Класс В, метод methodi О
Посредством описанной здесь языковой конструкции можно вызывать и статиче-
ские методы базового класса:
> class А {
static methodiO { console, log ('Класс А, метод methodi O'); }
> class В extends A {
static methodi() {
super.methodi();
console.log('Класс В, метод methodi О ');
> A.methodi()
< Класс А, метод methodi ()
> B.methodl()
< Класс А, метод method!()
< Класс В, метод methods()
Переопределение обычных свойств не поддерживается.
В разд. 11.3.1 было сказано, что метод базового класса или перекрытый метод про-
изводного класса, вызванный из метода базового класса, при обращении к свойству
получит его значение, заданное в базовом, а не в производном классе. Решить эту
проблему можно, переопределив в производном классе конструктор, задавая новые
значения унаследованных свойств и создавая новые свойства в нем. Пример:
> class А {
propl = 10
methodi() {
console.log(this.propl);
this.method?();
}
methods() { }
}
> class В extends A {
constructor() {
super();
this.propl = 100;
}
method?() {
console.log(this.propl);
)
> const b = new В
> b.methodi()
> // Теперь и метод methodi () базового класса А, и вызванный им метод
> // method2() производного класса В получают новое значение свойства
> // propl
< 100
< 100
11.3.3. Переопределение динамических свойств
Чтобы переопределить динамическое свойство, придется выполнить переопределе-
ние его геттера и сегтера.
♦ В теле геттера — следует обратиться к тому же динамическому свойству, только
реализованному в базовом классе, чтобы извлечь из него значение.
♦ В теле сеттера присвоить полученное с параметром значение тому же дина-
мическому свойству, реализованному в базовом классе.
Для обращения к заданному динамическому свойству базовою класса в обоих случа-
ях применяется конструкция формата:
super.<динамическое свойство>
Пример переопределения динамического свойства будет рассмотрен при выполне-
нии упражнения 11.7.
Если в производном классе переопределить только гетгер или только сеттер, пере-
определенное свойство станет доступным соответственно только для чтения или
только для записи.
11.3.4. Неявное наследование от класса Object
При обычном объявлении класса, без явно заданного наследования (см, разд. 77.7),
объявленный класс станет производным от класса Object. Как говорилось ранее,
этот класс является базовым для всех классов, предоставляемых JavaScript.
От класса object остальные классы получают поддержку свойств constructor,
prototype, объектных методов toString (), toLocaleString!), valueOff) И
hasOwnProperty() (часть из них нам уже знакома, остальные будут описаны позже).
Практически все производные классы перекрывают эти методы.
11.4. Классы как значения
Объявление класса в JavaScript — полноценное значение, принадлежащее к функ-
циональному типу данных (как и функция)':
> class А { . . . }
> typeof А
< 'function'
11.4.1. Присваивание классов
Поскольку любой класс является значением, его можно присвоить какой-либо
переменной и использовать ее для создания объектов этого класса:
> const clsA = А
> let obj = new A()
Также любой класс можно передать какой-либо функции с одним из параметров.
11.4.2. Проверка существования класса,
его свойств и методов
Проверить существование класса можно таким же способом, какой применяется
для проверки существования функции:
1 Это одна из особенностей JavaScript.
if (typeof SomeClass = ’function) {
// Класс существует
else
// Класс не существует
Проверить существование в классе статического свойства или метода можно, при-
менив оператор проверки существования свойства in, описанный в разд. 10.2.2, или
один из рассмотренных там же методов. Только вместо объекта при их использова-
нии следует указать класс. Пример:
> class А {
propl
static prop2
methodi() {}
static method?() {}
}
> 'prop2' in A
< true
> Object.hasOwn(A, 'method?')
< true
К сожалению, проверить существование объектного свойства или метода таким же
образом нельзя — и оператор, и методы покажут, что такого свойства нет. Придет-
ся создать объект этого класса и проверить наличие нужного свойства или метода
у него. 11ример:
> let а = new А
> 'prop!' in а
< true
> 'methodi' in a
< true
11 .5. Анонимные классы
|| Анонимный класс— класс, объявленный без указания имени.
Формат объявления анонимного класса:
class [extends <базовыи класс>] {
<о&ьявленля элементов класса>
}
Объявленный анонимный класс следует присвоить какой-либо переменной или
передать в качестве параметра функции — иначе он будет потерян.
Пример:
> const Point = class {
х
У
сinstructor(х, у) {
this.x - х;
this.у = у;
}
> new Point(ICO, 200)
< Point (x: ICO, y: 200}
Анонимными обычно делают классы, которые требуется передавать в качестве
параметров каким-либо функциям в одном месте кода.
11.6. Фабрики классов
Фабрика классов — замыкание, объявляющее и возвращающее класс, который
настроен сообразно значениям полученных замыканием параметров.
Фабрика классов может объявлять как обычный, именованный, так и анонимный
класс.
Пример:
> function pointFactory(color) {
return class {
constructor(x, y) {
this.x = x;
this.у = у;
}
getPoint() {
return 'Точка [$|this.x], ${this.y}], цвет: ${color}';
}
> const BlackPoint = pointFactory('black’)
> const RedPoint = pointFactory('red')
> let bp = new BlackPoint(100, 200)
> let rp = new RedPoint(20C, 100)
> bp.getPoint()
< 'Точка [10C, 200], цвет: black'
> rp.getPoint()
< 'Точка [200, 100], цвет: red'
11.7- Упражнение.
Усовершенствованный спойлер
Клиент, ранее заказавший нам спойлер, остался чрезвычайно доволен результатом.
И попросил усовершенствовать наше изделие, добавив в класс следующие откры-
тые объектные элементы:
♦ свойство toggleiiandler— хранящее ссылку на функцию, которая будет вызы-
ваться при каждом изменении состояния спойлера. В качестве параметра эта
функция должна принимать ссылку на объект спойлера.
♦ метод toggle () — переключающий состояние спойлера на противоположное.
Еще клиент попросил реализовать в статическом методе create о возможность ука-
зывать в параметрах не только ссылки на объекты элементов страниц, но и их яко-
ря. представленные в виде строк.
Объявим новый класс Spoiler2, производный от класса Spoiler, и реализуем все
нововведения в нем. Код нового класса сохраним в новом файле 11.7.js.
I, Найдем в папке 11\ех11 2 сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 8 4.html (страница со спойлерами), 8 4js (код спойлера)
и 8 4css (таблица стилей с оформлением). Скопируем их куда-либо на локаль-
ный диск.
2. Создадим в папке, куда были скопированы эти файлы, файл 11 7.js, откроем его
в текстовом редакторе и наберем код из листинга 11.2.
Листинг 11.2. Класс Spciler2 (файл 11.7.js)
class Spoiler2 extends Spoiler {
togg1eHandler
get state () { return super .state; } // ........................ 1
set state(val) {
const oldState = this.state; // ............................ 2
super.state = val; // ....................................... 3
if ((this.state != oldState) && (this.flnoggleHandler)) // .. 4
this.#toggleHandler(this);
}
toggle () { // ................................................... 5
this.state = (this.state;
static create(...elements) {
elements = elements .map (el -> { // ........................ 6
if (typeof el == ’string') // ........................... 1
return document.getElementByld(el); // .............. 8
else
return el; // ....................................... 9
});
super.create(...elements); // ............................... 10
)
}
В геттере переопределенного динамического свойства state обращаемся к тому
же свойству базового класса и возвращаем его значение (поз. I).
В сеттере переопределенного динамического свойства state сначала сохраняем
предыдущее состояние— оно нам позже понадобится (поз. 2). Далее заносим
новое значение состояния в то же свойство базового класса — чтобы спойлер
соответственно развернулся или свернулся (поз. 3). Напоследок, если новое со-
стояние отличается от предыдущего и если была указана функция, вызываемая
при изменении состояния спойлера, вызываем эту функцию, передав ей в каче-
стве параметра ссылку на текущий объект (поз. 4).
Объявляем метод toggle(), который просто изменяет состояние спойлера на
противоположное (поз. 5).
В переопределенном статическом методе create() перебираем элементы полу-
ченного массива элементов страницы, используя метод тар() класса Array
(поз. 6). В функции, указанной в вызове этого метода, проверяем тип очередного
элемента (поз. 7). Если этот тип — строковый, предполагаем, что был передан
якорь, ищем элемент страницы по этому якорю и возвращаем из функции най-
денный элемент (поз. 8). Если же элемент массива имеет иной тип, предполага-
ем, что была передана ссылка на элемент страницы, и просто возвращаем ее
(поз. 9). Массив элементов, выданный методом тар(), передаем методу create()
базового класса (поз. Ю).
3. Откроем файл 8 4 html в текстовом редакторе и исправим код, инициализирую-
щий оба спойлера:
const spoilerl = document.getElementByld('spoilerl');
const tipi - now Spoiler (spoilerl,—| do co rot ion:—’ highlight od'—H-t
spi.open।। /
const spl = new Spoiler2 (spoilerl, { decoration 'highlighted' });
spl.toggle();
const spoiler2 = document.getElementByld('spoiler2');
const sp2 ~ now Spoilor-(-6pe4-le-g2-7—[ stator true ))/
sp2.close(,}
const sp2 = new Spoiler2(spoiler2, { state: true });
sp2.toggleHandler = (spoiler) => {
if (spoiler.state)
console.log(1Спойлер развернут');
else
console.log(’Спойлер свернут');
};
sp2 .toggleO ;
Откроем страницу 8 4js в веб-обозревагеле. Первый спойлер должен иметь бо-
лее «жирное» оформление и изначально быть развернутым, второй — быть
свернутым. Л при разворачивании и сворачивании второго спойлера в консоли
веб-обозревателя должны выводиться сообщения соответственно Спойлер раз-
вернут и Спойлер свернут.
4. Проверим работ}' переопределенного статического метода create() самостоя-
тельно.
11.8. Управление преобразованием объектов
в значения значащих типов
Если в выражении требуется указать значение какого-либо значащего типа (строк},
число или логическую величину), но задан объект, исполняющая среда выполнит
попытку преобразовать его в значение требуемого типа. Этот процесс носит назва-
ние преобразования типа, и он нам давно знаком.
JavaScript предоставляет инструменты, позволяющие управлять преобразованием
объекта в значение значащего типа, который требуется исполняющей среде (зна-
чащее представление объекта). Для этого достаточно объявить в классе, на основе
которого создается этот объект, ряд волшебных методов.
Волшебный, или магический,метод— метод, вызываемый самой исполняющей
средой в определенные моменты выполнения кода'.
Волшебные методы должны иметь строго заданные имена и наборы прини-
маемых параметров. В остальном же они не отличаются от обычных методов
(в частности, могут быть вызваны явно).
Вот эти волшебные методы:
♦ LSymboi. toPrimitiveJ (собозначение типа>) —должен возвращать значащее пред-
ставление текущего объекта, относящееся к типу с указанным обозначением.
Именем этого метода является эмблема, хранящаяся в статическом свойстве
toPrimirive класса symbol. Поэтому при объявлении метода следует взять его
имя в квадратные скобки.
11оддерживаются следующие обозначения типов:
• 'string’ —требуется значащее представление объекта строго в виде строки.
Такое случается при явном преобразовании объекта в строку (например, вы-
зовом функции stringO —см. разд. 6.4);
• ’number’ — требуется число. Случается, если объект явно преобразуется
в число (например, с помощью оператора унарного плюса +, подробности —
в разд. 5.1.6) или если в выражении требуется значение строго числового ти-
па (например, в операторе арифмет ического умножения);
• 'default' — на усмотрение класса. Случается, если исполняющая среда не
может определить достоверно, какой тип ей требуется.
Этот метод вызывается самым первым из трех, описываемых здесь;
♦ valueOfO — должен возвращать значащее представление текущего объекта,
которое может принадлежать к произвольному значащему типу.
Вероятно, авторы терминов считали, что такие методы добавляют в классы толику волшебства...
Вызывается, если метод [Symbol.toPrimitive] О отсутствует в классе, возвраща-
ет объект, значение null или undefined;
♦ toString О — должен возвращать значащее представление текущего объекта
в виде строки.
Вызывается, если методы (Symbol.toPrimitive] О и valueOfO отсутствуют
в классе, возвращают объект, значение null или undefined.
Во вновь создаваемых классах достаточно объявить лишь метод [Symbol.
toPrimitive] (). Методы valueOfO и toStringO объявляются разве что в крайне
специфических случаях.
Пример класса, реализующего все три волшебных метода, приведенных ранее:
> class SomeClass {
prop
constructor(р) { this.prop = p; }
|Symbol.toPrimitive](hint) {
if (hint = 'string')
return this. prop. toStringO ;
else if (hint == 'number')
return this.prop;
else
return 'prop: ${this.prop)';
}
valueOfO { return 100000; }
toStringO { return 'SomeClass'; }
}
> let obj = new SomeClass(10;
> // Явно преобразуем объект в строку — будет вызван метод
> // [Symbol. toPrimitive()] с передачей обозначения типа 'string'
> String(obj)
< ’10'
> // Явно преобразуем объект в число — будет вызван метод
> // [Symbol.toPrimitive()] с передачей обозначения типа 'number'
> -robj
< 10
> // Используем объект в качестве операнда оператора арифметического
> // деления — то же самое, поскольку исполняющая среда точно «знает»,
> // какою типа операнд ей нужен
> obj / 10
< 1
> // Используем объект в качестве операнда оператора + — будет вызван
> // метод [Symbol. toPrimitive () ]с передачей обозначения типа 'default',
> // поскольку исполняющая среда не «знает» точно, какого типа
> // операнд ей нужен
> obj + ''
< 'prop: 10'
> obj + 1
< 'prop: 101'
> // Ничто не мешает нам вызывать волшебные методы явно
> onj.toString()
< 'SomeClass'
> obj.valueOf()
< 100000
Полезно знать
Поддержка волшебного метода (symbol.toPrimitive] о в JavaScript появилась лишь не-
давно. Напротив, методы valueOf о и toString о поддерживаются с самых первых вер-
сий языка, и реализацию этих методов в классах можно встретить н старом коде.
11.9. Пространства имен
Пространство имен — это простой объект, свойства которого хранят ссылки
на различные классы.
Пример двух пространств имен:
const Figures = {
Point: class {...},
Square: class {...},
Circle: class { . . . }
};
const Elements = {
Spoiler: class {...},
Slider: class {...},
Square: class { . . . }
};
const point = new Figures.Point();
const figSquare = new Figures.Square();
const slider = new Elements.Slider();
const el Square = new Elements. Squared ;
Пространства имен позволяют, во-первых, объединить родственные классы в одной
сущности, а во-вторых, предотвратить конфликты имен — разные пространства
имен могут содержать одноименные классы (так. в приведенном примере про-
странства имен содержат два класса с одинаковыми именами — Square).
Пространства имен активно использовались ранее и до сих пор часто встречаются
в старом коде. Однако в настоящее время им есть более удобная и наглядная заме-
на— модули (рассмотрены на уроке 14).
ПОЛЕЗНО ЗНАТЬ
В исполняющей среде JavaScript реализовано пространство имен inti, содержащее
КЛаССЫ NumberFormat (СМ. разд. 5.1.3.1) И DateTimeFormat (CM./?4Z3t). 7.2.1).
11.10. Расширение функциональности
встроенных классов. Полифиллы
|! Полифим — метод, добавленный во встроенный класс.
JavaScript позволяет добавлять произвольные методы во встроенные классы.
Каждый встроенный класс поддерживает статическое свойство prototype, унасле-
дованное от класса object. Оно хранит простой объект со всеми методами, реализо-
ванными в этом классе.
Вновь реализуемые методы следует добавлять именно в этот объект. Только пред-
варительно следует проверить, не реализованы ли уже в классе эти методы.
Пример добавления в класс string, представляющий строку, метода
changeAt(<иадекс>, <заменяквдии символ>), который возвращает копию текущей
строки, в которой символ с заданным индексом заменен на указанный:
> // Сначала удостоверяемся, что в классе String отсутствует метод
> // с именем changeAt ()
> if ( (’ ').changeAt == undefined)
// Если это так, добавляем метод changeAt() в класс String
String.prototype.changeAt = function(index, char) {
return this. s Lice (0, index) + char + this.slice(index + 1);
}
> /I Проверяем метод ь работе
> let str = ’Javascript'
> str.changeAt (4, 'S')
< 'JavaScript'
Пример добавления в класс Number, реализующий число, метода sqrto, возвра-
щающего квадратный корень текущего числа:
> if ((123).sqrt == undefined)
Number.prototype.sqrt = function() {
return Math.sqrt(this);
}
> (123).sqrt()
< 11.090536506409418
Получить значение текущего числа в теле метода можно, просто обратившись
к переменной this. Метод [Symbol .toPrimitive] О в классе Number не реализован,
поэтому сразу будет вызван метод vaiueOf (). возвращающий текущее число.
Пример добавления в класс Array метода fc.rEachBack(), аналогичного методу
forEachO (см.ршд. 9.1.1.4), только перебирающего элементы текущего массива
с конца:
> if ([].forEachBack = undefined)
Array.prototype.forEachBack = function(fn, context) {
context ??= window;
for (let i = this.length - 1; i >= 0; i—)
fn.call(context, this[r], i, this);
}
> [1, 2, 3, 4].forEachBack((el) => { console.log(el); })
< 4
< 3
< 2
< 1
11.11. Самостоятельное упражнение
Объявите класс Spoiler3, производный от Spoiler2. Реализуйте в нем вывод знача-
щего представления объекта:
♦ если исполняющая среда запросит строку — в формате:
Спойлер "<текст заголовка спойлера>" (свернут|развернут)
♦ при запросе значения иного типа — в виде числа I, если спойлер развернут, или
0 — если свернут.
Для этого объявите в классе spoiler3 метод [Symbol.toPrimitive] (). Ссылка на
элемент, представляющий заголовок спойлера, хранится в открытом свойстве
header класса. Текстовое содержимое заголовка можно получить из свойства
textcontent, поддерживаемого всеми элементами страницы.
Также реализу йге в новом классе передачу в составе объекта параметров (указыва-
ется при вызове конструктора во втором параметре) ссылки на функцию, которая
будет заноситься в свойство toggleHandler и впоследствии вызываться при измене-
нии состояния спойлера.
Урок 12
Итераторы и генераторы
Последовательность — это значение, представляющее собой упорядоченный
набор других значений. Типичные примеры последовательностей — строки
и массивы.
JavaScript позволяет программисту наделить любой объявляемый класс функцио-
нальностью последовательности. В частности, элементы, содержащиеся в таком
классе, можно будет перебрать в цикле перебора последовательности1.
Более того, функциональностью последовательности можно наделить даже функ-
цию.
Для создания последовательностей JavaScript предоставляет два инструмента.
Какой из них использовать, дело предпочтений.
12.1. Итераторы
Итератор— программный механизм, реализующий последовательную выдачу
значений из заданного набора.
Итератор реализуется в виде объекга, простого или основанного на каком-либо
классе, содержащего три метода. Все эти методы вызываются самой исполняющей
средой при выполнении цикла перебора последовательности (т. е. являются вол-
шебными). Вот эти методы:
♦ next О — вызывается на очередной итерации цикла перебора последовательно-
сти. Должен возвращать простой объект с двумя свойствами:
* value— очередное выдаваемое значение из заданного набора Может отсут-
ствовать, если значений в наборе больше нет;
♦ done— false, если в наборе есть еще значения, подлежащие выдаче, true —
если значений больше нет. Может отсутствовать, если в наборе еще есть зна-
чения.
Метод next о может как извлекать выдаваемые значения из какого-либо храни-
лища (переменных, массива и др.), так и вычислять их самостоятельно.
Это единственный обязательный для реализации метод. Остальные два метода
реализовывать необязательно;
К сожалению. создавать пронумерованные последовательности, элементы которых доступны но
целочисленным индексам. JavaScript пока еще не позволяет.
♦ ret urn о — вызывается, если цикл перебора последовательное ! и намеревается
завершить перебор, не дожидаясь исчерпания имеющихся в наборе значений
(например, в результате прерывания цикла оператором break). Должен возвра-
щать просл ой объекг со свойством done, равным true.
В теле этого метода обычно реализуют какие-либо завершающие действия (на-
пример. удаление ненужного больше объекта);
♦ throw(<11сключение>) — вызывается при возникновении ошибки в процессе
выполнения цикла перебора последовательности. В качестве параметра методу
передаелся объект исключения, описывающий возникшую ошибку (исключения
и их обработка рассмотрен!»! на уроке 15).
В теле этого метода могут выполняться какие-либо действия, призванные устра-
нить возникшую ошибку.
Итератор может как возвращаться функцией, так и выдаваться объектом.
1 2.1.1. Итерируемые функции и методы
|| Итерируемая функция — функция, выдающая итератор.
Итерируемая функция должна возвращать простой объект с методом [symbol,
iterator] о, который, в свою очередь, должен возвращать итератор. В качестве
имени этого метода используется значение статического свойства iterator класса
Symbol.
Пример итерируемой функции, выдающей раз за разом названия языков, приме-
няемых в веб-разработке:
> function languages() (
const langl = 'HTML';
const lang2 = ’CSS’;
const lang3 = 'JavaScript';
let i = -1;
return {
[Symbol.iterator]() {
return {
next: function() {
i++;
if (i = 0)
return { value: langl };
else if (i — 1)
return { value: lanq2 };
else if (i = 2)
return { value: lang3 };
else
return { done: true };
return: function () {
i = 0;
return { done: true };
}
}
}
};
}
> for (let 1 of languages())
console.log(l);
< HTML
< CSS
< JavaScript
> // Мы можем деструктурировать последовательность,
> // выдаваемую итерируемой функцией, и создать на ее основе массив
> [...languages()]
< (3) (’HTML', 'CSS', 'JavaScript']
Отметим, что в приведенной итерируемой функции для хранения элементов выда-
ваемой последовательности и индекса выданного элемента мы использовали
локальные переменные. Поскольку методы итерагора используют эти переменные,
их значения будут храниться в памяти. Так что итерируемая функция здесь высту-
пает также в качестве замыкания, а точнее, фабрики объектов (см./?шд. 10.3.3).
Итерируемый метод—метод, обладающий функциональностью итерируемой
функции.
Итерируемый метод объявляется так же, как и итерируемая функция:
class SomeClass {
languages() {
return {
[Symbol.iterator]() {
}
}
}
1 2.1.2. Итерируемые объекты и классы
Итерируемый объект — объект, выдающий итератор.
Итерируемый класс— класс, на основе которого создаются итерируемые объ-
екты.
Итерируемый класс должен поддерживать метод [Symbol.iterator] (), возвращаю-
щий итератор.
Пример итерируемого класса, аналогичного итерируемой функции из разд. 12.1. Г.
> class Languages {
(Symbol.iterator]() {
const langl = 'HTML';
const lang2 = 'CSS';
const lang3 = ’JavaScript’;
let i = -1;
return {
next: function() {
if (i == 0)
return { value: langl };
else if (i = 1)
return { value: lang2 );
else if (i « 2)
return { value: lang3 };
else
return { done: true };
}
}
}
}
> for (let 1 of new Languages!))
console.log(1);
< HTML
< CSS
< JavaScript
12.2. Упражнение. Числа Фибоначчи
Напишем простое клиентское веб-приложение для вычисления заданного количе-
ства чисел из последовательности Фибоначчи.
Числа Фибоначчи — элементы числовой последовательности, в которой первые
два числа равны соответственно Он I, а каждое из последующих — сумме двух
предыдущих чисел.
Для вычисления чисел объявим итерируемую функцию fibonacci():
fibonacci(^количество вычисляемых чисел'?}
I. Найдем в папке 12\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 12 1 html (страница с «заготовкой» для приложения). Скопируем
его куда-либо на локальный диск.
2. Откроем копию файла 12 1 html в текстовом редакторе и найдем следующий
фрагмент кода:
<form>
<1аЬе1Жсличество чисел: </label>
<input type="number" id="cnt">
<input type="buttcn" id="start" value="Вычислить">
</form>
<p id="output"></p>
Количество вычисляемых чисел будет заноситься в поле ввода с якорем ent, вы-
числение запустится после щелчка на кнопке start, а результаты— выведены
в абзаце output.
Код объявления функции fibonacciO поместим в отдельный файл 12 1 js
3. Создадим в той же папке файл 12 1 js и запишем в него код из листинга 12.1.
Листинг 12.1. Итерируемая функция Fibonacci() (файл 12 1.js)
function fibonacci(ent) {
return {
(Symbol.iterator] () { // ............................................. 1
let i = 0, current, pprev, prev; // ...................... 2
return {
next() {
if (i < ent) { // ........................................ 3
if (i <= 1)
current = i; // ......................... 4
else
current = pprev + prev; // .............. 5
[pprev, prev] = [prev, current]; // ......... 6
i++; // .................................... 7
return { value: current]; // ................ 8
} else
return { done: true }; // ................... 9
}
};
}
);
Эта функция аналогична рассмотренной в разд. 12.1.1. Она возвращает простой
объект с методом [Symbol.iterator] (), который, в свою очередь, создает и воз-
вращает объект итератора (поз. I в листинге 12.1).
В теле упомянутого метода мы объявили ряд переменных (поз. 2): i (счетчик
уже вычисленных чисел), current (текущее вычисленное число), prev (предыду-
щее вычисленное число) и pprev («пред-предыдущее» число).
В теле метода next о итератора ( поз. 3) сначала проверяем, не превысило ли
значение счетчика уже вычисленных чисел Фибоначчи (хранится в перемен-
ной i) требуемое количество чисел, заданное при вызове функции (параметр
ent). Если это так. то:
* если значение счетчика чисел не превышает 1 (т. е. вычисляется первое или
второе число)— используем значение счетчика в качестве очередного вы-
численного числа, присвоив его переменной current (поз. 4);
* в противном случае вычисляем очередное число как сумму «пред-
предыдущего» и предыдущего чисел, взяв их из переменных pprev и prev. Ре-
зультат также заносим в переменную current (поз. 5).
Далее (поз. 6) заносим значение переменной prev в переменную pprev, а значе-
ние переменной current — в переменную prev (для чего применяем деструкту-
рирование массива, описанное в разд. 9.1.1.6). Наконец, инкрементируем счет-
чик (поз. 7) и возвращаем объект с вычисленным числом (поз. 8).
Если же значение счетчика превысило требуемое количество чисел, возвращаем
объект с признаком того, что числа закончились (поз. 9).
Теперь привяжем файл 12 1 js к странице 12.1 html. И напишем непосредственно
в HIMI.-коде страницы сценарий, извлекающий введенное количество чисел,
запускающий вычисление и выводящий результат.
4. Добавим в файл 12 1 html необходимый код:
<р id="output"></р>
<scrxpt src="12.1. js"X/scrxpt>
<scrxpt>
const ent = document.getElementByld(*ent');
const output = document.getElementById('output’);
const start = document.getElementByld(1 start');
start.addEventListener('click', () => { // ..................... 1
const fCount = +cnt.value; // .............................. 2
const fibs = [...fibonacci(fCount)]; // .................... 3
output.textcontent = fibs.join(' | '); // .................. 4
});
</scrxpt>
Вычисления будут выполняться в обработчике события click кнопки start
(поз. 1 в приведенном коде).
Занесенное в поле ввода ent требуемое количество чисел Фибоначчи получаем
из свойства value и преобразуем его в строку посредством оператора «унарный
плюс» (поз. 2).
Далее вызываем функцию fibonacci(), передав ей полученное ранее количество
чисел, производим деструктурирование последовательности, возвращенной этой
функцией, и создаем на ее основе массив (поз. 3).
Наконец, объединяем все элементы этого массива в строку через заданный раз-
делитель. воспользовавшись методом join о класса Array, и выводим получен-
ную строку в абзаце output (поз. 4).
Откроем страницу 12 1 html в веб-обозревателе, введем в поле ввода Количество
чисел значение 30. нажмем кнопку Вычислить и посмотрим на результат
(рис. 12.1). Проверим готовое веб-приложение на других значениях количества
чисел.
Количество чисел 3(> Вычислить
U| 1 | 1 2|3 5 8| 13 21 34 | 55 | 89 | 144|233 377 | 610 | 987 | 1597
I 2584 | 4181 | 6765 10946 | 17711 28657 46368 | 75025 121393 j
196418 j 317811 | 514229
Рис. 12.1. Результат выполнения упражнения 12 2
12.3. Генераторы
Итераторы — инструмент, достаточно сложный в использовании, а код, написан-
ный с их применением, не отличается наглядностью. Поэтому итераторы применя-
ются нечасто и лишь в специфических случаях. Для более простых случаев удобнее
применять генераторы.
Генератор — программный механизм, аналогичный итератору, но более про-
стой в использовании.
Генератор может быть реализован в виде функции (метода) или объекга.
12.3.1. Генераторы-функции и генераторы-методы
|| Г'енератор-функция — аналог итерируемой функции.
Формат объявления генератора-функции:
function* <имя генератора-функцми> ([<.ммена параметров, 'Ъ
принимаемых генератором-функцией, через запятую>]) {
< тело генера тора - функции>
1
Символ звездочки (’) после ключевого слова function обязателен и является при-
знаком того, что объявляется генератор.
Для возврага очередного вычисленного значения из тела генератора-функции при-
меняется оператор возврата с приостановкой yield:
yield <возвращаемое значение>
Он возвращает заданное значение и приостанавливает (не прерывает!) выполнение
тела генератора-функции на том месте, где он находится. При последующем вызове
генератора-функции его выполнение продолжится с того места, на котором оно
ранее было приостановлено.
Пример простого генератора-функции, аналогичного рассмотренной в разд. 12.1.1
итерируемой функции:
> function* languages() {
yield ’HTML’;
yield ’CSS';
yield 'JavaScript';
}
> for (let 1 of languages())
console.log(1);
< HTML
< CSS
< JavaScript
Пример более сложного генератора-функции, который выдает последовательность
целых чисел, находящихся в заданном диапазоне, с указанным шагом:
> function* integers(min, max, step = 1) {
for (let i = min; i <= max; i += step)
yield i;
> [...integers(0, 100, 15)]
< (7) [0, 15, 30, 45, 60, 75, 90]
В теле генератора-функции можно вызвать другой генератор, применив оператор
вызова генератора yield*:
yield* < вызываемым генератор>
Он вызывает заданный генератор и побуждает вызывающий генератор последова-
тельно вернуть все значения, выданные вызываемым генератором.
Пример-
> function* languagesAndFrameworks() {
yield* languages();
yield 'React';
yield 'Lit';
}
> for (let 1 of languagesAndFrameworks())
console.log(1);
< HTML
< CSS
< JavaScript
< React
< Lit
Генератор-метод — метод, обладающий функциональностью генератора-
функции.
При объявлении генератора-метода в начале его имени следует поставить символ
звездочки (*). В объявлении закрытого метода символ * должен стоять перед сим-
волом #. Пример:
class SomeClass {
// Открытый генератор-метод
*methodl() { . . . }
// Открытый статический генератор-метод
static *scaticMethodl () { . . . }
// Закрытый генератор-метод
*#method2() { , . . }
// Закрытый статический генератор-метод
static *#staticMethod2() { . . . }
}
Вызываются генераторы-методы так же, как и обычные методы:
const sc = new SomeClass();
for (let el of sc.methodi()) {
}
for (let el of SomeClass.staticMethodl()) {
}
12.3.2. Генераторы-объекты и генераторы-классы
Генератор-объект — аналог итерируемого объекта.
Генератор-класс — аналог итерируемого класса.
Генератор-класс должен содержать генератор-метод с именем * [Symbol.iterator] ().
Не забываем указать символ звездочки перед именем этого метода.
Пример:
> clas.s Languages {
*[Symbol.iterator]() {
yield 'HTML';
yield 'CSS';
yield 'JavaScript';
}
}
> [...new Languages()]
< (3) ['HTML', 'CSS', 'JavaScript']
12.4. Самостоятельное упражнение
Перепишите веб-приложение для вычисления чисел Фибоначчи, превратив функ-
цию fibonacci() в генератор
Урок 13
Регулярные выражения
Многие, если не большинство, веб-сценариев занимаются обработкой строк. В том
числе ищут в них фрагменты, совпадающие с заданными шаблонами, или проверя-
ют совпадение с шаблонами введенных пользователем данных (выполняют валида-
цию). Такие шаблоны оформляются в виде регулярных выражений.
Регулярное выражение — шаблон, применяемый для поиска совпадающих
с ним фрагментов текста и проверки на совпадение с ним заданных величин.
Регулярное выражение может включать в себя как обычные символы (которые об-
рабатываются как есть), так и специальные символы, имеющие особое значение.
Специальный символ может обозначать знак определенной категории (например,
любую букву, любую цифру, пробельный символ, букву из заданного набора и пр.),
указывать количество повторений символа и др. Существует несколько разновид-
ностей специальных символов.
13.1. Введение в регулярные выражения
Регулярное выражение представляет собой объект класса RegExp и имеет объектный
тип.
13.1.1. Создание регулярных выражений
Создать регулярное выражение можно двумя способами:
♦ записав его между символами / (прямой слеш) в формате:
/<собственно шаблону/ ; <.моднфикаторы> j.
Модификатор— обозначение специфического режима обработки регулярного
выражения, присутствие которого активирует этот режим. Имеет вид буквы
латиницы.
Созданное регулярное выражение присваивается какой-либо переменной:
let rex = /CSS/;
Шаблон этого регулярного выражения содержит только обычные символы и
совпадет с любой последовательностью символов css. Поиск совпадающих
фрагментов ведется с учетом регистра символов
Внимание!
Здесь и далее фрагменты строк, совпадающие с указанным регулярным выражением,
выделены рамками
I Тример:
// Результат поиска: 73и |CS3|3 3733 css HTML
Вот еще одно регулярное выражение
rex = /CSS\d/;
Литерал \d, присутствующий в нем, совпадает с любой цифрой от 0 до 9. Следо-
вательно, весь шаблон совпадет с любой строкой вида: css<mi<ppa>.
// Результат: CSS |CSS3| SCSS css HTML
Модификатор i включает поиск без учеши регистра символов
rex = /CSS/i;
// Результат: CSS CSS3 SCSS css HTML
♦ создав объект класса RegExp посредством оператора new:
new RegExp (<шаблон> L, <модиф1-псаторы> ]).
Шаблон указывается в виде обычной строки без символов /. модификаторы также
записываются в виде строки. Пример:
rex = new RegExp(’CSS\d', 'i*);
Этот способ имеет смысл применять лишь тогда, когда шаблон и (или) модифика-
торы создаются программно.
Для тестирования шаблонов регулярных выражений можно использовать интернет-
службу «Regular expression» (рис. 13.1). доступную по адресу https://regex101.com/.
В поле ввода Regular expression заносится шаблон регулярного выражения, а в об-
ласть редактирования Test string — обрабатываемая строка. Совпадающие фраг-
Рис. 13.1. Интернет-служба «Regular expression»
менты, найденные в обрабатываемой строке, подсвечиваются. Щелкнув на моди-
фикаторах регулярного выражения, находящихся в правой части поля ввода
Regular expression, можно вызвать меню для указания этих модификаторов,
а щелкнув на расположенной еще правее кнопке Copy to clipboard — скопировать
регулярное выражение в буфер обмена.
13.1.2. Использование регулярных выражений:
простые случаи
Проверить совпадение какой-либо строки с регулярным выражением или выпол-
нить в строке поиск совпадающего фрагмента можно посредством двух методов:
♦ метод test(<сзрока>) класса RegExp— возвращает true, если указанная строка
совпадает с текущим регулярным выражением, и false — в прот ивном случае:
let rex = /CSS/;
let result = rex,test('CSS3*); // true
result = rex.test('HTML5’); // false
♦ метод search (<регулярное выражением класса String— ищет в текущей строке
первый фрагмент, совпадающий с заданным регулярным выражением. Если совпа-
дающий фрагмент найден, возвращает номер его первого символа (нумерация
символов начинается с нуля), в противном случае----1. Пример:
result = 'Язык CSS’.search(rex); //5
result = 'Язык HTML'.search(rex); // -1
Метод search() позволяет указывать в качестве параметра и обычную строку,
которая автоматически преобразуется в регулярное выражение.
13.2. Специальные символы,
применяемые в регулярных выражениях
13.2.1. Метасимволы
Метасимвол— обозначает любой символ из какой-либо группы или символ
с указанным кодом.
В табл. I3.1 приведен список поддерживаемых метасимволов.
Таблица 13.1. Метасимволы
Метасимвол Описание Пример Результат
. (точка) Любой символ, кроме возврата каретки (\г) и перевода строки (\п) /./ @00000
\w Алфавитно-цифровой символ (буква, цифра или подчеркивание) Aw/ @0000!
\W Не алфавитно-цифровой символ /W Basic0
Таблица 13.1 (окончание)
Метасимвол Описание Пример Результат
\d Цифра Ad/ 0Р* Ю
\D Не цифра /\D/
\s Пробельный символ (пробел, табуляция, возврат каретки или перевод строки) Ав/ Ьрх[]20%
\s Не пробельный символ AS/ 000 000
\b Начале или конец слова /\ЬЗ/ 0px 0% W3C
\B Не начало и не конец слова /\вз/ Зрх 3% и{з|с
\х<код> Символ с указанным двухзначным шестна- дцатеричным Unicode-кодом Ах42/ |p|asic
\и<код> Символ с указанным четырехзначным шест- надцатеричным Unicode-кодом /\u043c/ Код ствола
Примеры:
// Ищем отдельное слово вида 'С35<цифра>'
let rex = /\bCSS\d\b/;
let result = rex.test('Язык CSS3'); result = rex.test(’Язык CSS’); // true // false
result = 'Пакет SCSS'.search(rex); // -1
// Ищем обозначение цвета в формате RGB без учета регистра символов
rex = /#\w\w\w\w\w\w/i;
result = rex.test('color: #cb458a;’); // true
Метасимволы \w, \w, \d и \d изначально принимают в расчет лишь буквы основной
латиницы и арабские цифры. Чтобы они учитывали также буквы других алфавитов
и цифры других культур, следует указать у регулярного выражения модификатор и.
Примеры.
rex = /\w/;
// Результат: Язык |Г][2]0|3
rex = /\w/u;
// Результат: [я][з||ы|[к| [][7]|й][1|
rex = /W;
// Результат: 0U00Lhtml
rex = /\W/u;
// Результат: Язык |J HTML
13.2.2. Поднаборы
|| Поднабор — обозначает любой символ из заданного набора.
В табл. 13.2 приведен список поддерживаемых поднаборов.
Таблица 13.2. Поднаборы
Поднабор Описание Пример Результат
[< СИМВОЛЫ?] Любой из символов, приведенных в скобках /[aeiuy]/
[л<СИМВОЛЫ?] Любой из символов, не приведенных в скобках /[Aaeiuy]/ |a|s|i@
[<с1>-<с2>] Любой из символов из диапазона от cl до с2 /[a-i]/
1Л<с1>-<с2>] Любой из символов не из диапазона от с! до с2 /Га-i]/
Примеры:
rex = / [RSTJ [i-m] Га-w]/;
result = rex.test('Sky');
result = rex.test('Tin');
// true
// false
13.2.3, Вариант
| Вариант — обозначает любой фрагмент из перечисленных.
Вариант записывается в формате:
<фрагмент 1>\ «фрагмент 2> | . . . |<фрагмент N>
В составе фрагментов допускается указывать как обычные, так и специальные сим-
волы.
Примеры:
// Ищем строки 'jpg', 'jpeg' или 'jpe' без учета регистра
rex = /jpg I jpeg I jpe/a ;
result = 'logo.jpg'.search(rex); // 5
result = 'background.jpeg'.search(rex); // 11
// Ищем либо *НТМЬ<цифра>', либо 'JavaScript'
rex = /HTML\dI JavaScript/;
result = 'Язык HTML5'.search(rex); // 5
result = 'Учите JavaScript!'.search(rex); // 6
result = 'Язык HTML'.search(rex); // -1
13.2.4. Квантификаторы
Квантификатор— указывает количество повторений или местоположение
символа.
Вот все поддерживаемые квантификаторы.
♦ <символ>+ — символ может присутствовать в произвольном количестве экземп-
ляров.
rex = /\bCS+S\b/i;
/7 Результат: CS |CSS| |CSSS| |CSSSSSSSS
♦ <спмвап>* — символ может присутствовать в произвольном количестве экземп-
ляров или вообще отсутствовать:
rex = /\bCS*S\b/i; // Результат: |csj [CSS] |CSSS| [CSSSSSSSS
♦ <символ>? — символ может присутствовать в одном экземпляре или отсутство-
вать:
rex = AbCS?S\b/i; // Результат: |Ucj [сзз| CSSS CSSSSSSSS
♦ <символ>{<п>} — символ должен присутствовать в количестве п экземпляров:
rex = /\bCS{2}S\b/i; // Результат: CS CSS |CSSS| CSSSSSSSS
♦ <символ>{<п>,} — символ должен присутствовать в количестве не менее п экзем-
пляров:
rex = /\bCS{2,}S\b/i; // Результат: CS CSS |CSSS| [CSSSSSSSS
♦ <символ>{<п1>,<п2>} — символ должен присутствовать в количестве от п1 до п2
экземпляров:
rex = /\bCS(l,2}S\b/i; // Результат: CS [CSS| |CSSS| CSSSSSSSS
♦ л<символ> — символ должен находиться в начале текста:
гех = /Л[0-9]+../; // Результат: Opt) ЗОрх 4% 200псп
Если в регулярном выражении указан модификатор m (будет рассмотрен далее),
этот квантификатор также обозначает начало отдельной строки текста;
♦ <символ>$ — символ должен находиться в конце т екста:
гех = /(0-9] + ..$/; // Результат: 20pt ЗОрх 4% |200тгнт
Если в регулярном выражении указан модификатор m (будет рассмотрен далее),
этот квантификатор также обозначает конец отдельной строки текста;
♦ <симеол 1>{?=<символ 2>) — за символом 1 должен следовать символ 2, причем
последний не включается в состав фрагмента, совпадающего с регулярным вы-
ражением:
// Ишем последовательность цифр, запятых и точек, за которой следует
// произвольное количество пробелов и строка 'руб', т. е. цену в рублях.
// Чтобы поместить в регулярное выражение символ точки, мы предварим
// его обратным слешем. Если этого не сделать, веб-обозреватель
// посчитает точку за специальный символ ., совпадающий с любым символом.
rex = /[\d,\. ] + (?=\s*py6)/; // Результат: 12 [12| руб |34, 67[руб |89.8[ руб 89.8
♦ <символ 1>(?1<символ 2>) —за символом 1 не должен следовать символ 2, причем
последний не включается в состав фрагмента, совпадающего с регулярным вы-
ражением:
// Ищем произвольное количество цифр, за которыми не следует
// комбинация из произвольного количества цифр и символов 'рх',
// т. е. величины, указанные не в пикселах
rex = /\d+(?!\d*px)/; // Результат: [Гс|рг ЗОрх II] |200|мг.
♦ (?<=<символ 1~>) <символ 2> — перед символом 2 должен находиться символ 1,
причем последний не включается в состав фрагмента, совпадающего с регуляр-
ным выражением:
// Ищем обозначение единицы измерения CSS 'pt',
// перед которым присутствует цифра
rex = /(?<=\d)pt/; // Результат: 2C)|pt] Зи{рс] pt 20C'|pt
♦ (?<!<символ 1>)<символ 2>— перед символом 2 не должен находиться символ 1,
причем последний не включается в состав фрагмента, совпадающего с регуляр-
ным выражением:
// Ищем обозначение единицы измерения CSS 'рс',
// перед которым отсутствует цифра
тех = /(?<!\d)pt/; // Результат: 20pt 30pt И 200pt
13.2.5 . Подквантификатор
По умолчанию квантификаторы + и * работают так, чтобы совпавшей с регулярным
выражением оказалась строка максимальной длины {«жадный» режим поиска).
В качестве примера рассмотрим регулярное выражение, которое совпадает с после-
довательностью любых символов, взятых в апострофы:
гех =
// Результат: C++ 'html' 'css* 'javascript’ python
Видно, что регулярное выражение, работая в «жадном» режиме, «захватило» самый
длинный фрагмент— расположенный между самым первым и самым последним
апострофами в строке. Чтобы оно «захватывало», напротив, наиболее короткий
фрагмент, его следует переключить в «щедрый»режим поиска.
Подквантификатор— специальный символ, указываемый после квантифика-
тора и изменяющий режим его работы.
Подквантификатор ?, указанный после квантификатора + и *, переключает его
в «щедрый» режим:
гех = /’.*?'/;
// Результат: с++ phtml1 |'css1 р javascript'| python
Будучи указанным в другом месте регулярного выражения, символ вопросительно-
го знака считается квантификатором ?, описанным ранее.
13.2.6 . Группы и обратные ссылки
Группа— независимая часть регулярного выражения, ведущая себя как один
символ. Фрагмент, совпавший с группой, запоминается исполняющей средой
и может быть использован в дальнейших вычислениях.
JavaScript поддерживает две разновидности групп:
♦ нумерованные — записываются в формате:
(<обычные и специальные символы, входящие в группу>)
Примеры:
// Регулярнее выражение, совпадающее со строками 'jpg', 'jpeg' и 'jpe'.
// Группа не использовалась.
rex = /jpgljpeg Ijpe/;
// To же самое регулярное выражение, но записанное с применением группы.
// Получилось короче.
rex = /jp(g|eg|e)/;
// Регулярное выражение, совпадающее с последовательностью цифр,
// после которой может присутствовать строка 'руб'
rex = /\d+(py6)?Z;
// Фрагмент, совпавший с группой и запомненный исполняющей средой,
// здесь и долее выделен черной заливкой
rex = /(\d+)px/; // Результат: Црх] 4Opt
♦ именованные — записываются в формате:
(?< <имя группьО хобычные и специальные символы, входящие в группу>}
имя группы должно быть уникальным в пределах текущего регулярного выраже-
ния.
Пример:
rex = /(?<value>\d+)px/;
Обратная ссылка— специальный символ, который обозначает ссылку на
фрагмент, совпавший с определенной группой.
Обратная ссылка:
♦ на нумерованную группу — записывается в формате:
\<порядковый номер группы>
Внимание!
Группы в регулярном выражении нумеруются в порядке слева направо, начиная с 1
Далее приведен пример регулярного выражения, совпадающего с любым пар-
ным тегом. Внутри него созданы две группы: первая — для имени тега, вто-
рая — для его содержимого. В записи закрывающего тега применяется обратная
ссылка, хранящая имя тега из первой группы. Чтобы поместить в регулярное
выражение символ прямого слеша, мы предварили его обратным слешем:
гех = /<(\w+)>(.*?)<\/\1>/;
// Результат:
js- г: - q
♦ на именованную грушу —записывается в формате:
\к< <имя группьО >
Пример регулярного выражения, аналогичною приведенному ранее, но исполь-
зующего именованные группы:
гех = /< (?<tagname>\w+) >(.*?) <\/\k<tagname»/
13,2.7 . Обычные символы
Все прочие символы в шаблонах регулярных выражений обозначают сами себя.
Если требуется вставить в шаблон символ, совпадающий с каким-либо специаль-
ным символом, его следует предварить знаком обратного слеша (\):
// Точка в шаблоне регулярного выражения — это специальный символ,
// обозначающий любой символ
const гех2 = /bhv.ru/;
// Результат: |bhv: [bhv. ru| [ohv ru|
// Чтобы вставить в шаблон символ точки, его нужно предварить обратным слешем
const гехЗ = /bhv\.ru/;
// Результат: bhv:ru |bhv.ru| bhv ru
// Чтобы вставить в регулярное выражение прямой слеш, обратный слеш
// или звездочку, обозначающие различные специальные символы,
//их также предваряют обратным слешем: \/, \\, \*
13.3. Поиск и замена
с помощью регулярных выражений
13.3.1. Обычный режим поиска
В обычном режиме, используемом по умолчанию, поиск останавливается после
нахождения первою фрагмента, совпадающего с заданным регулярным выра-
жением.
Метод ехес (<строка>) класса RegExp ищет в строке фрагмент, совпадающий с теку-
щим регулярным выражением, и возвращает результат поиска в виде массива. Пер-
вый (с индексом 0) элемент этого массива хранит найденный фрагмент. Помимо
этого, в объекте массива есть добавленные свойства:
♦ index — номер первого символа совпавшего фрагмента в исходной строке:
♦ input — сама исходная строка.
Если поиск завершился неудачей, метод возвращает null.
Пример:
rex = /\d+px/;
result = rex.exec('4pt 5px lOpx'l;
let si = result[0]; // ’5px'
let ind = result.index; //4
let s = result.input; // ’4pt 5px 10px‘
Если в шаблоне текущего регулярного выражения присутствуют группы, фрагмен-
ты, совпадающие с ними, будут храниться в последующих (с индексами больше 0)
элементах массива, возвращенною методом.
Если же в шаблоне текущего регулярного выражения присутствуют именованные
группы, в массиве, возвращенном методом, будет существовать добавленное свой-
ство groups. Его значением будет простой объект с набором свойств, соответст-
вующих отдельным именованным группам. Имя такого свойства совпадете именем
соответствующей группы, а значением свойства станет фрагмент, совпавший с этой
группой, или undefined, если группа ни с чем не совпала.
Пример:
rex = /<(\w+)>(?<ccntent>.*?)<\/\1>/;
result = rex.exec('HTML <strong>CSS</strong> JavaScript');
si = result[С]; // '<strong>CSS</strong>'
let s2 = result[1]; // 'strong'
let s3 = result[2]; // 'CSS'
let groups = result.groups; // (content: 'CSS'}
let content = groups.content; // 'CSS'
Если в регулярном выражении указать модификатор d. массив, возвращенный ме-
тодом. получит добавленное свойство indices. Оно будет хранить массив, каждый
элемент которого соответствует одному из совпавших фрагментов и представляет
собой вложенный массив с двумя элементами: номерами первого и последнего
символа очередного совпавшею фрагмента. Если же в текущем регулярном выра-
жении присутствуют именованные г руппы, то массив из свойства indices также
будет иметь добавленное свойство groups, хранящее простой объект со свойствами,
одноименными группам и хранящими массивы с номерами первого и последнего
символов соответствующего совпавшего фрагмента. Пример:
гех = /<(\w+)>(?<content>.*?)<\/\l>/d;
result. = rex.exec('HTML <strong>CSS</strong> JavaScript');
let indices = result.indices; // [ [5, 25], [6, 12], [13, 16] ]
let indicesGroups = indices.groups; // { content: [13, 16] }
Метод march (регулярное выражений) класса string ищет в текущей строке фраг-
мент, совпадающий с регулярным выражением, и возвращает результат поиска в виде
массива, аналогичного выдаваемому методом ехес() класса RegExp (см. ранее):
rex = /\d+px/;
result = '4pt 5рх Юрх'.match (rex);
si = result(OJ;
ind = result.index;
s = result.input;
// ’5px'
// 4
// Mpt 5px 10px'
Если поиск завершился неудачей, метод также возвращает null.
13.3.2, Глобальный поиск
При глобальном поиске выполняется поиск всех фрагментов заданной строки,
совпадающих с регулярным выражением.
Глобальный поиск устанавливается указанием в регулярном выражении модифика-
тора д.
В случае глобального поиска:
♦ метод ехес() класса RegExp— находит первый совпавший фрагмент, завершает
работу' с возвратом результата и заноси! в свойство lastindex регулярного вы-
ражения номер символа, располагающегося сразу за найденным фрагментом.
При следующем вызове этого метода будет выполнен поиск, начиная с этого
места, в результате чего будет найден следующий фра! мент, и т. д. Пример;
rex = /\d*px/g;
result = rex.exec('4pt 5px Юрх');
si = result(0]; // '5px'
ind = result.index; // 4
let li = rex.lastindex; // 7
result = rex.exec('4pt 5px 10px');
si - result(0]; // '10px'
ind = result.index; // 8
li = rex.lastIndex; // 12
result = rex.exec('5px 4pt 10px');
// Здесь метод exec() Бернет значение null — поиск закончен
Свойство lastindex, кстати, доступно также и для записи. Так что мы можем
присвоить номер символа, с которого следует начать новый поиск, например:
// Повторно ищем второй совпавший фрагмент
rex.lastIndex = 7;
result = rex.exec('5рх 4pt Юрх');
si = result[0]; // '10px'
ind = result.index; //8
♦ метод matchO класса String— возвращает обычный массив co всеми найденны-
ми совпадениями (фра! менты, совпадающие с i руппами, в него не включаются):
result = '4pt 5рх Юрх'.match(гех);
si = result[0]; // '5рх'
s2 = result(l]; // 'Юрх'
Метод matchAll(регулярное вьражение>} класса String возвращает итератор, кото-
рый раз за разом выдает сведения об очередном фрагменте текущей строки,
совпавшем с заданным регулярным выражением. Эти сведения возвращаются в виде
массива, аналогичного выдаваемому методом ехесО класса RegExp. Регулярное
выражение должно иметь модификатор д. Указание регулярного выражения без этою
модификатора приведет к возникновению ошибки типа ТуреЕггог. Пример:
for (let el of '4pt 5px Юрх'.matchAll (rex))
console.log('Фрагмент ’${el[0]}', начало: ${el.index)*)
// Результат:
// Фрагмент '5px', начало: 4
// Фрагмент 'Юрх', Начало: 8
13.3.3. Многострочный поиск
При многострочном поиске квантификаторы л и $ обозначают не только начало
и конец текста, но и начало и конец каждой отдельной строки в тексте.
Отдельные строки в строковом значении разделяются специальными символами \п
(реже — последовательностями символов \г\п):
let s = 'Первая строкаХпВторая строка\пТретья строка';
Многострочный поиск задается указанием в регулярном выражении модифика-
тора т.
Пример:
гех = /Л|0-9]+../;
тех = ZA[0-9] + .. Zm;
гех = /[0-9]+..$/;
тех = /[0-9]+..$/т;
// Результат:
// Результат:
ZZ Результат:
// Результат:
20pt| 30px\n200mm 5cm
2Opt 30рх\п|200тгг| 5cm
2 Opt 30px\n200mm 5crrj
2Opt 30px|\n200mm 5crr
13.3.4. Замена совпавших фрагментов
Метод replace о класса string, знакомый нам по разд. 6.3. может выполнять замену
фрагментов, совпавших с указанным регулярным выражением.
replace (<регулярное выражением < заменяющая подстрокам
Пример:
rex = ZCSS\dZ;
result = 'HTML CSS5 JavaScript’.replace(rex, 'CSS'); // 'HTML CSS JavaScript'
Задав регулярное въражение с модификатором g, можно заменить все совпавшие
фрагменты, а не только первый найденный:
result = '5рх 4рс 10рх'.replace(ZpxZg, 'mm'); // '5mm 4pt 10mm'
В заменяющей подстроке можно использовать следующие специальные символы:
♦ $& — обозначает весь совпавший фрагмент,
♦ $<номер>— фра! мент из группы с заданным номером (обратная ссылка);
♦ $<нмя> — фрагмент из группы с заданным именем (обратная ссылка);
♦ $' — часть строки до совпавшего фрагмента;
♦ $' — часть строки после совпавшего фрагмента;
♦ $$ — символ доллара ($).
Примеры:
result = 'HTML, CSS, JavaScript'.replace(Z\b\w+\bZg, '<em>$&<Zem>');
ZZ '<em>HTML<Zem>, <em>CSS<Zem>, <em>JavaScript<Zem>'
result = ' 5px 4pt 20?1.replace(Z(\d+)(px|pt)Zg, '$lmm'); ZZ '5mm 4mm 20 '
Метод replaceAil () класса string также поддерживает указание регулярного выра-
жения в первом параметре, однако только с установленным модификатором д. За-
дание регулярного выражения без этого модификатора приведет к ошибке типа
ТуреЕггог.
13.3.5. Прочие полезные инструменты
Метод split о класса string, описанный в разд. 6.3, может разбить строку, руко-
водствуясь указанным в качестве символа-разделителя регулярным вьраженнем:
split{^регулярное выражение>[, <предельныи размер масспва>])
Пример:
result = 'HTML,CSS;JavaScript'.split(/,I;/); // ['HTML', 'CSS', 'JavaScript']
Класс RegExp поддерживает еще несколько свойств (помимо lastindex), которые
могут оказаться полезными и которые доступны только для чтения:
♦ source — шаблон текущего регулярного выражения без символов слеша и мо-
дификаторов. представленный в виде строки:
♦ flags — набор модификаторов текущего регулярного выражения в виде строки;
♦ global — true, если установлен модификатор g, false — в противном случае;
♦ ignoreCase — true, если установлен модификатор i, false — в противном случае;
♦ multiline — true, если установлен модификатор m, false — в противном случае:
♦ has Indices — true, если установлен модификатор d, false — в противном случае;
♦ unicode — true, если установлен модификатор u, false — противном случае.
13.4. Упражнение. Использование
регулярных выражений для обработки строк
Когда мы программно формировали страницу с меню суши-бара «Йокогама» на
основе содержимого массива (см. упражнение 6.5), то так и не реализовали едино-
образный вывод цен. Они у нас выводятся с разными обозначениями рублей («₽»,
«руб» и «р», с точкой в конце и без точки) и с одновременным выводом старой
и новой цены.
Теперь настала пора исправить этот недочет. Сделаем так, чтобы цены выводились
с обозначением «Р», а старая цена располагалась после новой, заключалась в круг-
лые скобки и отображалась зачеркнутой.
I. Найдем в папке 6\ех6.5 сопровождающего книгу файлового архива (см. прило-
жение 5) файлы 6.5 html (страница), 6.5 css (внешняя таблица стилей). 6 5 1 .js
(сценарий с исходным массивом), 6 5 2js (сценарий, выводящий меню) и пап-
ку 6 5 (графические файлы с иллюстрациями блюд). Скопируем их куда-либо на
локальный диск.
Исходный массив содержит простые объекты, каждый из которых хранит све-
дения об одном из блюд. Цена блюда, представленная в виде обычной строки,
содержится в свойстве price такого объекта.
Чтобы реализовать единообразный вывод цен:
• если значение свойства price включает одно число— извлечь его и вывести
в составе меню в качестве единственной цены;
• если значение свойства price включает два числа, причем второе заключено
в круглые скобки, — извлечь оба. вывести первое число в качестве новой
цены, а потом, также в круглых скобках и зачеркнутым,— второе число в ка-
честве старой цены.
Для извлечения фрагментов строк, удовлетворяющих заданным шаблонам, как
мы уже знаем, нет ничего лучше регулярных выражений.
2. Откроем копию файла 6.5 js в текстовом редакторе и внесем в нее следующие
правки:
const rexOnePrice = /(\d+) (₽|руб\.’|р\.?)/i; // .......................... 1
const rexTwoPrices = /(\d+) (₽|руб\.?|р\.'?) \((\d+) (₽|руб\.?|p\.°)\)/i;
let s = '', t;
for (let el of data) {
let isNew = el.title.startsWith(newTitie);
let result, price; // ................................................. 2
if (result = rexTwoPrices.exec(el.price)) // .......................... 3
price = ${result[l]} ₽ (<del>$(result[3]} </del>) ;
else {
result = rexOnePrice.exec(el.price); // .......................... 4
price = ${result[l]) ₽';
)
s += 4<article${isNew ? 1 class="new"' : +
¥ctq-'-div$ (ol.pt ico}—+
getTag div${price) + // ............................................... 5
'</article>';
}
Сначала создаем два регулярных выражения (поз. 1 в приведенном коде). Пер-
вое совпадет с единичным числом, за которым следует обозначение денежной
единицы «₽», «руб» и «р», с точкой в конце и без точки. Оно содержит две груп-
пы: первая служит для извлечения найденного числа, вторая — для удобства
указания обозначений денежной единицы. Второе будет совпадать с таким же
числом (новой ценой), за которым через пробел в круглых скобках будет следо-
вать еще одно аналогичное число (старая цена). Это регулярное выражение со-
держит две пары из двух описанных ранее групп.
В теле цикла, перебирающего массив с данными, объявляем две переменные под
хранение соответственно результатов поиска с применением регулярных выра-
жений и строки с H I ML-кодом. формирующим цену (поз. 2).
Если строка, извлеченная из свойства price очередного элемента массива, сов-
падает со вторым регулярным выражением, извлекаем из первой и третьей групп
новую и старую цены и создаем на их основе строку с HTML-кодом, выводящим
новую и старую цены, причем старая цена будет выведена в круглых скобках и
зачеркнутой (поз. 3). В противном случае производим поиск с применением
первого регулярного выражения, извлекаем из первой группы число и создаем
на ее основе строку с HTML-кодом, который выведет единственную цену
(поз. 4).
Осталось лишь включить созданные таким образом строки в HTML-код, выво-
дящий очередную позицию меню (поз. 5).
Откроем страницу 6 5 html в веб-обозревателе и проверим, правильно ли выводятся
цены (рис. 13.2).
Суши-бар «Йокогама»
Новинка’
Диетические суши,
приготовляемые из
одноименного салата,
риса, кунжута и
водорослей мори.
М а гур о
55 е
Классические суши
Готовилл и> тунца и
риса.
Копченый угорь,
свежий салат, кунжут
и одноименный соус.
Готовится из
желтохвоста, лакедры,
зеленого лука, риса и
дайкона.
800Р(90О₽)
350 ₽
Рис. 13.2. Результат выполнения упражнения 73 4
13.5. Самостоятельное упражнение
В веб-сценарии, выводящем меню суши-бара «Йокогама», обеспечьте возможность
обработки строк с ценами, в которых между числом и обозначением рублей отсут-
ствует пробел, а между старой и новой ценой находится произвольное количество
пробелов.
Урок 14
Модули
У веб-сценариев, написанных нами ранее, есть три существенных недостатка:
♦ необходимость их явной привязки к странице с помощью тегов <script>.
При разработке сложных решений, код которых хранится в множестве файлов,
очень легко забыть привязать какой-либо из этих файлов к странице, и решение
не заработает;
♦ возможность возникновения конфликтов имен
Это критично, опять же, при разработке сложных решений в составе больших
рабочих трупп. Два программиста, входящие в состав рабочей группы, могут
объявить две разные функции с одинаковыми именами, и решение работать не
будет;
♦ трудности в защите критического кода и данных от несанкционированного дос-
тупа.
Если один программист напишет, скажем, функцию, которая должна вызываться
строго в одном месте кода, то ничего не помешает другому программисту, по
ошибке или злонамеренно, вызвать эту функцию в другом месте, нарушив тем
самым работу решения.
Этих недостатков можно избежать, оформив сценарии в вггде модулей.
14.1. Введение в модули
Модуль— веб-сценарий, изолирующий все объявленные в нем сущности (пе-
ременные. функции и классы) от других .модулей и не требующий обязательно-
го встраивания в веб-страницу.
Поскольку модуль изолирует все объявленные в нем переменные, функции и клас-
сы, другие модули не смогут получить к ним доступ. Таким образом, критический
код и данные, находящиеся в модуле, автоматически оказываются защищенными
от несанкционированного доступа извне.
Если же другим модулям необходим доступ к сущности, объявленной в текущем
модуле, текущий модуль должен явно выполнить экспорт этой сущности, а другие
модули — явно выполнить ее импорт.
Экспорт — обозначение какой-либо сущности (переменной, функции или
класса), объявленной в модуле, как доступной для использования в других мо-
дулях.
Импорт — извлечение текущим модулем сущности, объявленной в другом мо-
дуле и явно экспортированной из него, для использования.
Внимание!
Импортировать можно лишь те сущности, что были явно экспортированы . обьяв-
ляющих их модулях Все прочие сущности недоступны для импорта и соответственно
для использования
Операции экспорта и импорта могут выполняться исключительно н модулях. Попытка
выполнить их в обычных сценариях приведет к ошибке типа SyntaxError.
Код любого модуля всегда выполняется в строгом режиме, даже если таковой не
был указан явно.
В остальном модули ничем не отличаются от обычных веб-сценариев.
Ранее говорилось, что встраивать все модули в страницу необязательно. Практи-
чески всегда в веб-страницу встраивается лишь один модуль — стартовый.
Стартовый модуль— модуль, инициализирующий и запускающий всю необ-
ходимую функциональность.
Для встраивания модуля в страницу применяется тот же тег <script>, в который
обязательно следует вставить атрибут type со значением module. Если этого не сде-
лать. исполняющая среда не посчитает встроенный в страницу код модулем
и, в частности, не даст выполнить в нем операции экспорта и импорта.
Аналогично обычным сценариям встраиваемый в страницу модуль может быть:
♦ внутренним — хранящимся непосредственно в HT ML-коде страницы:
<script type="module">
</scr j pt:>
♦ внешним — хранящимся в отдельном файле:
<script type="module" src="module. js"x/script>
Внимание!
Модули, как внешние, так и внутренние, могут функционировать, только будучи загру-
женными с веб- сервера Загрузка модулей с локальной файловой системы заблокиро-
вана в целях безопасности
В дальнейшем для отладки решений, код которых реализован в виде модулей, мы
будем использовать веб-сервер http-server, написанный на JavaScript и работающий
под управлением исполняющей среды Node.js. Поэтому прямо сейчас следует уста-
новить сначала Node.js (см. приложение 2), а потом — http-server (см. приложе-
ние 3).
14.2. Экспорт
JavaScript поддерживает гри разновидности экспорта сущностей.
14.2.1. Экспорт по умолчанию
Экспорт по умолчанию— экспорт какой-либо одной сущности из объявленных
в модуле с целью импортировать ее, применив упрощенный синтаксис (импорт
по умолчанию, описываемый далее).
Экспорт по умолчанию применяется в случае, если из модуля необходимо экспор-
тировать лишь одну сущность.
Синтаксис экспорта по умолчанию:
export default <сущность>
Примеры:
♦ экспорт по умолчанию значения переменной:
let obj = { propl: 0, prop2: 'abc' };
export default obj;
или. в сокращенном написании:
export default { propl: 0, prop2: 'abc' };
♦ экспорт по умолчанию функции:
function func() (...)
export default func;
или, короче:
export default function func() (...)
♦ экспорт по умолчанию класса:
class Cis { . . . }
export default Cis;
или. в сокращенном написании:
export default class Cis (...)
Внимание!
Выполнить экспорт по умолчанию можно только у одной сущности из объявленных
в модуле При попытке экспортировать таким образом несколько сущностей возникнет
ошибка типа SyntaxError.
14.2.2. Множественный экспорт
Множественный экспорт— экспорт произвольного количества сущностей.
Импорт сущности, экспортированной таким образом, осуществляется по ее
имени или указанному псевдониму.
Множественный экспорт применяется, если из модуля требуется экспортировать
несколько сущностей примерно одинаковой значимости.
Поддерживаются два синтаксиса множественного экспорта.
14.2.2.1. Синтаксис № 1
Синтаксис № I множественного экспорта очень похож на синтаксис экспорта по
умолчанию — отсутствует лишь ключевое слово default:
export [{] <сущность> [} ]
Экспортируемые переменные следует взять в фигурные скобки.
Пример:
let obj = { propi: 0, prop2: 'abc' };
export { obj };
export function func() { . . . }
export class Cis { ... }
Синтаксис № l удобно применять в модулях небольшого объема, код которых пол-
ностью помещается в окне текстового редактора и не требует прокрутки для его
просмотра. В этом случае можно быстро выяснить, какие сущности экспортируют-
ся из модуля, просто взглянув на его код.
В модулях большого объема для экспорта удобнее применять синтаксис № 2.
14.2.2.2. Синтаксис № 2
Синтаксис №2 позволяет в одном выражении экспортировать произвольное коли-
чество сущностей:
export {<сущность 1> [as <псевдоним сущности 1>],
<сущность 2> [as <псевдоним сущности 2>],
• . . /
<сущность N> [as <псевдоним сущности W>]}
Пример:
let obj = { propl: 0, prop2: 'abc' };
function func() { . . . }
class Cis { . . . }
exporc { obj, func, Cis };
Выражений, выполняющих экспорт, может быть несколько:
export { var, func };
export { Cls };
По умолчанию указанные сущности экспортируются под их изначальными именами.
Однако при необходимости им можно дать псевдонимы. задаваемые как есть, без
кавычек. Пример экспорта функции func() под псевдонимом fnl
export { obj, func as fn, Cis };
Обычно выражения, выполняющие экспорт согласно синтаксису № 2, помещаются
в самом конце кода модуля. Это делается для удобства работы: чтобы узнать, какие
сущности экспортируются из модуля, достаточно прокрутить его код в конец и
найти там выражения, производящие экспорт. В особенности это касается модулей
большого объема, в которых обычно и применяется синтаксис № 2.
14.2.3. Гибридный экспорт
Гибридный экспорт — совмещает экспорт по умолчанию какой-либо одной
сущности и множественный экспорт всех остальных сущностей.
Гибридный экспорт применяется при экспорте какой-либо одной значительной
сущности и нескольких вспомогательных.
Есть два способа выполнить гибридный экспорт:
♦ использовать разные выражения — для разных разновидностей экспорта:
let obj = { propl: С, prop2: ’abc' };
function func(){...}
class Cis { . . . }
export default Cis; // Экспорт по умолчанию
export { obj, func }; // Множественный экспорт
♦ использовать синтаксис №2 множественного экспорта — указав у сущности,
экспортируемой по умолчанию, псевдоним default:
export { obj, func, Cis as default };
14.3. Импорт
Поддерживаются три разновидности импорта.
Как правило, выражения, выполняющие импорт, ставятся в начале кода модуля,
чтобы импорт сущностей был выполнен гарантированно перед их использованием.
14.3.1. Импорт по умолчанию
Импорт по умолчанию — импорт сущности, подвергшейся экспорту по у мол-
чанию.
Синтаксис импорта по умолчанию сущности с заданным именем из модуля с ука-
занным интернет-адресом (написание интернет-адресов импортируемых модулей
рассмотрено в разд. 14.3.4):
import <ммя> from <. интернет-адрес модуля>
Имя указывается без кавычек, интернет-адрес модуля— в виде строки.
Импортированная сущность станет доступна в импортирующем модуле под задан-
ным именем.
Пример:
import obj from 'modulel. js’;
bj.prop2 = 'def';
Заданное в выражении импорта имя может отличаться от имени, под которым сущ-
ность была экспортирована:
import importantObject from 'modulel.js';
importantObject.prop2 = ’def;
14.3.2. Множественный импорт
Множественный импорт — импорт сущностей, подвергшихся множественно-
му экспорту.
Поддерживаются два синтаксиса множественного импорта.
14.3.2.1. Синтаксис № 1
Синтаксис № I применяется, если требуется импортировать отдельные сущности,
экспортированные из модуля с указанным интернет-адресом:
import {ссущность 1> [as <псевдоним сущности 1>],
< сущность 2> [as <псевдоним сущности 2>],
• • • /
<сущность N> [as <псевдоним сущности №>]}
from <интернет-адрес мсдуля>
Интернет-адрес модуля задается в виде строки.
Пример импорта переменной var и функции func о:
import { obj, func } from 'module2.js';
const pr = obj.prop2;
func();
Если при экспорте сущности у нее был указан псевдоним (см. разд. 14.2.2.2),
в выражении импорта следует указать этот псевдоним:
import { fn } from 'module2m. js' ;
f n ();
При импорте сущностям можно дагь псевдонимы, записываемые как есть, без кавы-
чек. Тогда в текущем модуле эти сущности станут доступными под заданными при
импорте псевдонимами.
Пример импорта функции funcО с указанием у нее псевдонима function2:
import { obj, func as function2 } from 'module2.js';
function2();
Это может пригодиться при импорте одноименных сущностей из разных модулей,
или если какая-либо из импортируемых сущностей имеет слишком длинное имя.
которое трудно набирать.
14.3.2.2. Синтаксис № 2
Синтаксис Яр 2 применяется в случае, если требуется импортировать все сущности
(или, по крайней мере, большинство их), экспортированные из какого-либо модуля:
import * as <.г1мя> from <интернет-адрес модуля>
Имя указывается как есть, без кавычек, интернет-адрес модуля— в виде строки. Имя
может быть произвольным, однако должно удовлетворять соглашениям об имено-
вании переменных.
В результате в импортирующем модуле будет создана переменная с заданным име-
нем, и ей присвоен простой объект, свойства которого будут представлять все им-
портированные сущности
Пример:
import * as module? from 'module?.js';
const pr = module?.obj.prop?;
module?.func();
const objCls = new module?.Cis();
14.3.3. Гибридный импорт
Гибридный импорт — совмещает импорт по умолчанию и множественный им-
порт.
Гибридный импорт можно выполнить двумя способами:
♦ использовав разные выражения —для разных разновидностей импорта:
import Cis from 'module?.js’;
import { obj, func } from'module?.js1;
♦ использовав синтаксис вида:
import <имя сущности, экспортированной по умолчанию>,
{<. сущность 1> [as <псевдоним сущности 2>],
<сущность 2> (as <псевдоним сущности 2>],
" - - г
< сущность N> (as <псевдоним сущности N>]}
from <интернет-адрес модуля>
Пример:
import Cis, { obj, func } from 'module?.js';
14.3А Написание интернет-адресов
импортируемых модулей
Интернет-адреса импортируемых модулей, записываемые в выражениях импорта,
могут быть:
♦ полными:
import SomeClass from 'http://somesice.ru/modules/someclass.js';
♦ сокращенными абсолютными (отсчитываемыми от корневой папки сайта) —
должны начинаться с символа / (слеш);
import Cis from '/modules/classes/cls.js';
import func from ’/modules/functions/func.js';
♦ сокращенными относительными (отсчитываемыми от папки, в которой находит-
ся импортирующий модуль):
• путь к модулю того же уровня вложенности — предваряется символами ./:
// Модуль /modules/classes/cls2.js
import Cis from './cis.js';
Если импортируемый модуль находится во вложенной папке, следует указать
путь к этому модулю относительно папки, в которой находится импорти-
рующий модуль:
// Модуль /modules/special.js
import Cis from './classes/cls.js';
• путь к модулю предыдущего уровня вложенности — предваряется символа-
ми .. /:
// Модуль /modules/classes/utility/constants.js
import Cis from '../cis.j s';
Символы ../, вставленные в путь к модулю, вызывают «поднятие» в папку
более высокого уровня вложенности-
// Модуль /modules/classes/uti1ity/constants.js
import specials from '../../special.js';
import func from '../../functions/func.js';
14.3.5- Как выполняется импорт?
В процессе импорта в импортирующем модуле на каждую импортируемую сущ-
ность неявно создается константа JavaScript— переменная, объявленная посредст-
вом оператора const. Этой константе присваивается импортированная сущность:
значение переменной, ссылка на объявление функции или класса.
Поскольку для хранения каждой импортированной сущности применяется констан-
та, мы не сможем заменить эту сущность каким-либо другим значением, присвоив
его константе. Это сделано в целях предотвращения ошибок. Пример:
II Для хранения импортированного объекта будет создана константа obj
import ob j f rom 1modulel,j s’;
// Без проблем изменяем значение свойства этого объекта
obj.prop2 = 'def';
// Однако попытка заменить сам этот объект любым другим значением
// вызовет ошибку
// obj = 123;
14.4. Прогон модулей
Прогон— выполнение кода модуля без импорта из него каких бы то ни было
сущностей.
Например, если в каком-либо модуле выполняется создание элементов страницы,
нужных для работы остального кода, но ничего импортировать из этого модуля не
нужно, следует выполнить прогон модуля.
Синтаксис прогона модуля с заданным интернет-адресом.
import <лштернет-адрес модуля»
Интернет-адрес модуля указывается в виде строки.
Пример:
import ’/others/create-elements.j s';
14.5. Упражнение.
Реорганизация кода с применением модулей
Реорганизуем код, формирующий меню суши-бара «Йокогама», с применением
модулей.
1. Найдем в папке 13\ех13 4 сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 6 5.html (страница), 6 5.css (внешняя таблица стилей). 6.5 1 js
(сценарий с исходным массивом), 6 5 2js (сценарий, выводящий меню) и пап-
ку 6.5 (графические файлы с иллюстрациями блюд). Скопируем их куда-либо на
локальный диск.
Сначала превратим оба сценария, привязанные к странице, в модули.
2. Откроем копию файла 6 5 html в текстовом редакторе и пометим оба сценария
как модули:
^script src~"6.5.1.тп">ч/scripts
<script type=”module" src="6.5 1.js“X/script>
<script src-"6 5.2.js"x/&cript>
<script type="module" src="6.5.2 , js"X/script>
В модуле 6.5.1 .js экспортируем массив, на основе которою формируется меню.
Поскольку это единственная сущность, объявленная в модуле, подвергнем ее
экспорт} по умолчанию.
3. Откроем копию файла 65 1 js в текстовом редакторе и внесем в код необходи-
мые правки:
эоне* dsta
export default [
];
В модуле 6.5.2 js импортируем этот массив под именем data.
4. Откроем копию файла 6 5 2.js в текстовом редакторе и внесем следующие правки:
import data from './6.5.1 js1;
function getTag(strings, content) {
return <${strings[0]}>${contenr}</$(strings[0]}>';
}
Поскольку модуль 6.5 1.JS теперь импортируется из модуля 6 5 2js, мы можем
убрать из кода страницы привязку к модулю 6 5 1 .js.
5. Удалим из файла 6 5.1 .html соответствующий тег:
<script typc^"module"—sre» "-6.5.1. jn-"»<-/se-r-ipfe>
<script type="module" src="6.5.2.js"x/script>
Запустим веб-сервер http-server, указав ему в качестве корневой папку, в которой
находятся файлы примера. Запустим веб-обозреватель и выполним переход по ин-
тернет-адресу http://localhost:8080/6.5.hinil. Страница должна выглядеть гак же,
как и на рис. 13.2.
14.6. Самостоятельное упражнение
Реорганизуйте код спойлера Spoiler2 (см упражнение 11.7) с применением мо-
дулей.
Урок 15
Обработка программных ошибок.
Исключения
К сожалению, при выполнении программы иногда возникают программные ошиб-
ки Причиной их могут быть не только невнимательность разработчика, написав-
шего неправильный код, но и некорректные данные, предоставленные пользова-
телем.
При возникновении ошибки происходит аварийный останов выполнения веб-сце-
нария. Избежать этого можно, обрабатывая исключения.
15.1. Исключения
Исключение — сущность, создаваемая исполняющей средой при возникновении
программной ошибки и хранящая сведения о ней.
Возбуждение (или генерирование) — создание исполняющей средой исключе-
ния в ответ на возникновение ошибки.
Исключение является объектом одного из особых классов. Каждый класс представ-
ляет ошибку определенного типа. Вот эти классы:
♦ Error — базовый класс для всех остальных классов исключений.
Обычно используется для принудительного возбуждения исключений в простых
веб-сценариях и создания собственных классов исключений (см. разд. 15.3).
Следующие классы являются производными от класса Error и оповещают о воз-
никновении более специфических ошибок;
♦ SyntaxError — ошибка в синтаксисе какого-либо выражения;
♦ ТуреЕггог— заданное значение относится к неподходящему типу, выполнена
попытка изменить значение константы, обратиться к свойству, доступному
только для чтения, или вызвать метод у пустого значения (undefined или null);
♦ ReferenceError— обращение к несуществующей переменной или свойству, вы-
зов несуществующей функции или метода;
♦ RangeError — заданное значение не укладывается в допустимый диапазон.
Примеры: создание массива с помощью оператора new с указанием отрицатель-
ного размера (см. разд. 9 11.1). вызов у числа метода toPrecisionO с указанием
отрицательного количества цифр (см.разд. 5.1.3);
♦ URiError— ошибка обработки интернет-адреса, GET- или POST-параметра
(см. урок 28).
Все классы исключений поддерживают два свойства:
♦ name— имя класса исключения в виде строки:
♦ message — текстовое описание возникшей ошибки в виде строки.
ПОЛЕЗНО ЗНАТЬ
Можно сказать, что программная ошибка и исключение — синонимы. Поэтому про-
граммисты часто говорят не «возникает ошибка», а «возникает исключение».
15.2. Обработка исключений
Обработки исключения выполнение указанного обработчика в ответ на воз-
никновение исключения.
Обработчик исключения— фрагмент кода, выполняющийся при возникнове-
нии исключения.
Выражение для обработки исключения, которое может возникать в заданном фраг-
менте кода, записывается в следующем формате:
try {
<фрагмент кода, в котором может возникнуть исключение'?
}
[catch [(<имя переменной?)] {
{.обработчик исключения?
}]
[finally {
< ф1 анализа тор?
}]
Как только в заданном фрагменте кода возникает исключение, выполнение фрагмен-
та кода прерывается, и выполняется .бработчик исключения. После исполнения по-
следнего начнет выполняться следующий код.
Если же исключение не возникнет, фрагмент кода будет выполнен до конца, а обра-
ботчик выполнен не будет.
Финализатор будет выполнен после исполнения обработчика исключения (в случае
возникновения исключения) или фрагмента кода (если исключение не возникнет).
Если выполнение фрагмента кода или обработчика исключения завершается досрочно
операторами возврата результата (return), прерывания (break) или прерывания ите-
рации (continue), финализатор все равно будет выполнен.
Обработчик исключения или финализатор может отсутствовать — но не оба сразу.
Если в круглых скобках после ключевого слова catch указано имя переменной,
в обработчике исключения будет создана переменная уровня блока с указанным
именем, хранящая объект исключения.
Пример № I:
// Если элемент страницы с заданным якорем не обнаружен,
// метод getElementByld () возвращает значение null
const start = document.getElementByld('start');
try {
// Попытка вызнать любой метод у значения null приведет
// к возникновению исключения типа ТуреЕггог
start.addEventListener('click', startclick);
}
catch. {
console.log('Кнопка start отсутствует');
}
finally {
console.log('Выполнена привязка обработчиков к событиям’);
}
Пример № 2:
const start = document.getElementByld('start');
try {
start.addEventListener('click', startclick);
}
catch (exc) {
if (exc instanceof TypeErrcr)
// Если возникшее исключение относится к типу ТуреЕггог,
// выводим одно сообщение об ошибке
console.log('Кнопка start отсутствует');
else
// В противном случае выводим другое сообщение
console.log('${exc.name}: $(esc.message} );
Выражения обработки исключений можно вкладывать друг в друга:
try {
// Фрагмент кода 1
try (
// Фрагмент кода 2
} eaten {
// Обработчик исключения 2
} catch {
// Обработчик исключения 2
}
Исключение, возникшее во фрагменте кода 1, будет обработано обработчиком
исключения 1. Исключение, возникшее во фрагменте кода 2, будет обработано обра-
ботчиком исключения 2, а обработчик исключения 1 выполнен не будет.
Необработанное исключение — исключение, возникшее в коде, который не
заключен в выражение обработки.
Обработчик исключения по умолчанию — реализован в самой исполняющей
среде и обрабатывает необработанные исключения: выводит сообщение о воз-
никшей ошибке в консоли веб-обозревателя и аварийно завершает выполнение
сценария.
15.3. Принудительное возбуждение
исключений
Можно принудительно возбудить исключение заданного тина Обычно это произ-
водится в теле функции или метода, чтобы уведомить вызывающий код о возник-
новении какой-либо ошибки или нештатной ситуации.
Для возбуждения заданного исключения применяется выражение формата:
throw -^исключением
Исключение должно представлять собой объект одного из классов, описанных
в разд. 15.1.
Для создания объекта исключения, относящегося к заданному классу, применяется
выражение формата'
new <класс исключениям{<текстовое описание возникшей ошибки>)
Пример:
const start = document.getElementByld('start');
if (start)
start.addEventListener('click', startclick);
else
throw new ReferenceError('Кнопка start отсутствует');
Иногда в коде обработчика исключения повторно возбуждают возникшее исклю-
чение, чтобы передать его другому обработчику— «внешнему» по отношению
к текущему или обработчику по умолчанию:
const start = document.getElementByld('start');
try {
try {
start.addEventListener('click', startclick);
}
catch (exc) {
if (exc instanceof ТуреЕггог)
console.log(’Кнопка start отсутствует’);
else
throw exc;
}
}
catch (exc) {
console.log('Проблема: ${exc. message}');
15.3.1. Объявление своих классов исключений
Для оповещения кода о возникновении специфической ошибки или нештатной
ситуации разработчики часто объявляют собственные классы исключений. Как
правило, новые классы исключений делаются производными от класса Error. Опять
же. как правило, новой функциональности в такие классы не добавляют, т. е. дела-
ют их пустыми.
Конструкторы всех классов исключений принимают единственный параметр —
текстовое описание возникшей ошибки или нештатной ситуации.
Пример объявления и использования своего класса исключения ElementNotFoundError:
class ElementNotFoundError extends Error {)
const start = document.getElementByld('start');
if (start)
start.addEventListener('click', startclick);
else
throw new ElementNotFoundError('Кнопка start отсутствует');
15.4. Упражнение. Спойлер, часть 4
Спойлер, написанный нами при выполнении упражнения 11 2, хорош всем. За од-
ним исключением: при попытке создания спойлера на основе несуществующего
элемента страницы возникнет ошибка типа ReferenceError, мало о чем говорящая
другим разработчикам, которые будут использовать наш спойлер.
Сделаем так. чтобы при попытке создания спойлера из элемента страницы, который
не существует, возбуждалось исключение нашего собственного класса с текстовым
описанием, более подходящим случаю.
Назовем класс нового исключения ElementIsAbsenrError и сделаем его производ-
ным от класса Error. Единственный параметр конструктора нового класса пометим
как необязательный и дадим ему в качестве значения по умолчанию строку
'Заданный элемент отсутствует на странице'.
Код, при необходимости возбуждающий исключение, добавим в класс spoiler.
I. Найдем в папке 11\ех11 2 сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 8 4,html (страница со спойлерами), 8 4js (код спойлера) и
8 4css (таблица стилей с оформлением). Скопируем их куда-либо на локальный
диск.
2. Откроем копию файла 8.4.js в текстовом редакторе и внесем в его код следую-
щие правки:
class ElementlsAbsentError extends Error {
constructor(message^1 Заданный элемент отсутствует на странице1) {
super(message);
}
}
class Spoiler{
constructor(element, { state=false, decoration='' }={}) {
if (!element)
throw new ElementlsAbsentError;
this.#element = element;
}
}
Код, проверяющий существование элемента страницы, переданного конструкто-
ру класса Spoiler первым параметром, мы поместили в самом начале тела кон-
структора - - чтобы в случае отсутствия заданного элемента сразу же прервать
процесс создания объекта спойлера.
Проверим работу исправленного класса, попытавшись создать спойлер на осно-
ве заведомо несуществующего элемента страницы с якорем, например, spoiler3.
3. Откроем копию файла 84 html в текстовом редакторе и добавим к существую-
щему сценарию следующий фрагмент кода:
const spoiler3 = document.getElementByld('spoiler3');
try {
new Spoiler(spoiler3);
} catch (exc) {
ccnsole.log(exc.message);
}
Откроем страницу 8 4 html в веб-обозревателе и убедимся, что два имеющихся
спойлера работают. После чего посмотрим на консоль веб-обозревателя — в ней
должна быть выведена строка Заданный элемент отсутствует на ст ранние.
15.5. Самостоятельное упражнение
Исправьте статический метод create () класса spoiler2 (см. упражнение /7.7) таким
образом, чтобы при указании якоря несуществующего элемента страницы возбуж-
далось исключение класса ElementlsAbsentError с описанием формата Элемент
<якорь> отсутствует на странице.
Урок 16
Средства отладки
После написания все веб-сценарии (кроме разве что самых простых) требуют от-
ладки.
Отладка — пробный много кратный запуск программы с целью выявления
имеющихся в ней ошибок.
Для выполнения отладки все современные веб-обозреватели предоставляют весьма
удобные инструменты, выводящиеся па отдельных вкладках особой панели. Эта
панель нам уже знакома (см. рис. I. I).
16.1. Консоль веб-обозревателя
Консоль веб-обозревателя (или. просто, консоль) — интерактивная исполняющая
среда, которая входит в состав отладочных инструментов, встроенных в веб-
обозреватель.
16,1.1. Сообщения об ошибках, выводимые в консоли
В консоли выводятся сообщения обо всех ошибках, возникающих при выполнении
сценария и не обработанных явно (обработка исключений описана в разд. 15.2).
Сообщения выводятся в одном из двух следующих форматов:
♦ об ошибках, выявляемых уже на этапе компиляции кода (ошибках времени ком-
пиляции)'.
Uncaught Стил одпхбкл>: <.описание> <имя файла>',<№ строки кода>
Слово Uncaught, присутствующее в начале сообщения, говорит о том, что
исключение, представляющее возникшую ошибку, не было обработано в коде
сценария и поступило обработчику но умолчанию (который и вывел это сооб-
щение).
Тип ошибки— ЭТО ИМЯ класса исключения, представляющего ошибку. Описание
ошибки выводится по-английски. Нумерация строк в коде сценария производится
сверху вниз.
1 Тример сообщения об ошибке:
Uncaught SyntaxError: Unexpected сокеп '{' 8.4.html:58
Ошибка в синтаксисе выражения: наличие не на своем месте открывающей
фигурной скобки — в строке № 58 кода из файла 8 4 html;
♦ об ошибках, выявляемых только во время выполнения кода (ошибках времени
выполнения}'.
Uncaught <тип ошибки>:
at [<функция (метод)
at [<функция (метод)
at [<функция (метод)
<описание> <И1
W>] <имя файла>:<№
2>] <имя файла>‘.<№
1>] <имя файла>:<№
я файла>:<3? строки кода>
строки кода>:<№ символа>
строки кода>:<№ символа>
строки кода>:<№ символа>
Первая строка сообщения аналогична приведенной ранее.
В следующих строках показывается стек вызовов— список последовательных
вызовов функций и методов, выведенных в порядке, обратном порядку, в кото-
ром они вызывали друг друга. Так, функция (метод) 1, вызванная первой, выво-
дится последней, функция (метод) 2, вызванная функцией (методом) 2,— пред-
последней и т. д. На первой позиции присутствует функция (метод) N, на которой
закончилась цепочка вызовов, и именно в ее теле, скорее всего, возникла ошибка.
Если ошибка возникла в обычном коде, не являющемся телом функции или ме-
тода, стек вызовов будет содержать лишь одну строку без указания на какую-
либо функцию или метод.
Пример № 1:
Uncaught ReferenceError: val is not defined 8.4.js:33
at set state [as state] (8.4.js:33:9)
at #onlieaderClick (8.4.js:15:20)
Ошибка типа ReferenceError, вызванная обращением к несуществующей пере-
менной val. Возникла в теле сеттера динамического свойства state в позиции,
находящейся на символе № 9 строки № 33 кода из файла 8 4js Этот сеттер был
вызван закрытым методом onHeaderClick () в позиции, находящейся на сим-
воле № 20 строки № 15 кода из того же файла.
I Тример № 2:
Uncaught ReferenceError: ex is not defined
at 8.4.html:bl:29
8.4.html:61
Ошибка типа ReferenceError, вызванная обращением к несуществующей пере-
менной ех. Возникла в обычном коде, на символе №29 строки №61 кода из
файла 8 4 html.
Следует помнить, что ошибка может возникнуть не обязательно строго в позиции,
указанной в консоли веб-обозревагеля. Эта позиция показывает лишь, где испол-
няющая среда столкнулась с невозможность дальнейшего выполнения кода и была
вынуждена прервать его. Код. который действительно привел к возникновению
ошибки, может располагаться перед указанной позицией.
16.1,2. Вывод произвольных сообщений в консоли
В консоли веб-обозревателя можно выводить произвольные сообщения.
Глобальный объект окна веб-обозревателя поддерживает свойство console, которое
хранит объект класса Console, представляющий консоль. Для вывода сообщений,
относящихся к разным видам, этот класс предоставляет ряд методов, имеющих
одинаковый формат вызова:
<метод> (<значение 1>, <значение 2>, . . . <значение N>)
Все заданные значения будут выведены в одной строке через пробел.
А вот сами эти методы,
♦ logo — вывод сообщения нейтрального характера;
♦ debug () — вывод отладочного сообщения;
♦ info о — вывод информационного сообщения. В Mozilla Firefox в левой части
сообщения присутствует черный кружок с буквой «i»;
♦ warn () — вывод предупреждающего сообщения. В левой части сообщения при-
сутствует желтый треугольник с буквой «i»;
♦ error () — вывод сообщения об ошибке. В левой части сообщения находится
красный кружок с крестиком.
Есть возможность отобразить в консоли лишь сообщения, относящиеся к опреде-
ленным видам. Для выбора видов отображаемых сообщений предназначено меню,
располагающееся в панели инструментов, которая находится в верхней части
консоли (рис. 16.1). Это меню содержит ряд пунктов, функциональность которых
аналогична обычным флажкам: при выборе такой пункт-флажок помечается как
установленный, и слева от него появляется галочка, а при следующем выборе —
сбрасывается, и галочка пропадает. Вог эти пункты:
♦ Ошибки — отображать только сообщения, выведенные методом error ();
♦ Предупреждения — только методом warn ();
♦ Информация — только методами logo и info о;
♦ Детали шния — только методом debug ();
Уровни по умолчанию ▼
По умолчанию
Детализация
Информация
✓ Предупреждения
✓ Ошибки
Рис. 161. Меню видов сообщений, выводимых в консоли веб-обозревателя
♦ По умолчанию — устанавливает пункты Ошибки. Предупреждения и Ин-
формации.
Имейте в виду, что сообщения, выведенные методом debugO, по умолчанию нс
отображаются в консоли. Чтобы они отображались, установите в меню пункт-
флажок Детализация.
Метод clear () класса Console очищает консоль от ранее выведенных сообщений.
16.2. Отладчик
Отладчик— подсистема веб-обозревателя, предназначенная для более деталь-
ной отладки сценариев, в частности их приостановки, пошагового выполнения
и просмотра текущих значений переменных.
Чтобы открыть отладчик (рис. 16.2), следует вывести панель отладочных инстру-
ментов веб-обозревателя (см. упражнение 1 1) и переключиться на вкладку Источ-
ники.
Панель отладки
К £0 Элементы
Консоль Источники Сеть Производительность Память
Страница »
[0 6S.html 6.S.2.JS X 6.5.1 .js
Защита »
’О Юр
file//
’ЕЛ O/Oata, Документы/Рабс
▼ЕЗ 6.5
Pl sashimi_hamach.jpg
П sashim._unagi.jpg
Г*1 sushi_cnuldta.jpg
П sushL.maguro.jpg
0 sushi_tobiko.jpg
Q 6.5ntml
Q 6.51.JS
D 65.Z|S
0 6.5 css
1 function getragfstrings, content) {
2 return <5(strings[o]}>5fcontent}</5[strings(0B>'
» )
4
5 const newTitle ‘новинка:’;
6
7 const eenu « document.getfleeentByld(’eenu');
8
9 let s - ”, t;
10
11 for Цет el of data) {
12 let isHe» • el.title.startswith(newTiile);
13 if (isNew;
14 t » el.title.replacetnewlitle, ‘').trieStart()
IS else
16 t el.title;
17 s -г- '<artlcle${isHeM ? ’ class»"new" : ”}> t
18 getTag h2Vt} +
19 cleg src-"${el.i«age;">' s
20 getTag p$tel.desc}' +
21 getTag divS{el.price) +
22 '«/articles';
is }
24
► Выражения контрольною тначени
► Точки останова
▼ Область действия
Не при^титвлеь^
▼ Стек вызовов
Не приостановлено
* Точки останова для XHR/tetch
» Точки останова (X >М
► Глобальные прослушиватели
► Точки останова прослушиваге'тя ст
► Точки останова при нарушении CS
Область навигации
Область программного кода
Служебная область
Интерфейс отладчика разделен на три области:
♦ область навигации — показывает иерархический список всех файлов, хранящих
содержимое открытой страницы (HTML-файл. внешние веб-сценарии и таблицы
стилей, изображения и др.);
♦ область программного кода — отображает содержимое открытых файлов в от-
дельных вкладках;
♦ служебною область— содержит инструменты для управления отладчиком,
в частности панель отладки (см. разд. 16.2.3).
Границы между смежными областями можно перетаскивать мышью, тем самым
меняя их относительные размеры
16.2.1. Открытие файлов с программным кодом
Чтобы открыт ь файл с кодом, который требуется отладить, сначала следует сделать
так, чтобы в области навигации была выбрана вкладка Страница (как показано на
рис. 16.2). Далее достаточно просто щелкнуть на соответствующем пункте иерар-
хического списка, который находится на этой вкладке. — содержимое открытого
файла будет выведено в области программного кода, в отдельной вкладке. Количе-
ство открываемых файлов не ограничено (так, на рис. 16.2 открыты три файла:
6.5 html. 6 5.2.js и 6.5.1 js).
Чтобы переключиться на нужный файл, уже открытый в области программного
кода, надо щелкнуть на ярлыке соответствующей вкладки, сделав ее активной (на-
пример, на рис. 16.2 активной является вкладка с содержимым файла 6 5 2,js).
Закрыть какой-либо файл можно, наведя курсор мыши на ярлык соответствующей
вкладки и щелкнув на появившейся в правой части ярлыка кнопке закрытия,
имеющей вид крестика. На ярлыке активной вкладки эта кнопка присутствует по-
стоянно (см. рис. 16.2).
16.2.2. Создание и удаление точек останова
Точка останова— позиция в программном коде, на которой выполнение сце-
нария будет приостановлено.
После приостановки можно ггроизвести пошаговое выполнение кода (см. разд. 16.2.3)
с просмотром значений переменных (см. разд. 16.2.4) или продолжить его выпол-
нение в обычном режиме.
Создание и удаление точек останова производится в области программного кода, на
вкладке, выводящей содержимое нужного файла.
Создать точку останова на требуемом выражении можно, щелкнув мышью на рас-
положенной слева от этого выражения широкой белой полосе с порядковыми
номерами строк. Созданная точка останова отображается в виде довольно боль-
шой синей стрелки, находящейся на этой белой полосе и направленной вправо
(рис. 16.3).
const menu = >document.DgetEleinentById( ‘menu*);
Рис, 16.3. Точка останова
Удалить созданную ранее точку останова можно, еще раз щелкнув на ней.
Все созданные точки останова приводятся в списке, находящемся в служебной об-
ласти. в спойлере (раскрывающейся панели) Точки останова (рис. 16.4). Отдель-
ная позиция этого списка содержит флажок для деактивации точки останова, имя
файла, выражение, на котором поставлена точка останова, и порядковый номер
с троки кода, в котором она присутствует.
▼ Точки останова
□ Приостанавливать на необнаруженных исключениях
О Приостанавливать на обнаруженных исключениях
▼ Е 6 51.js
□ const data [ <7 X 1
▼ 6 5.2.js
Q const menu = do<ument.getblementBy!d('menu'); 7
Рис 16 4. Список точек останова в спойлере Точки останова служебной области
Список из спойлера Точки останова позволяет производить следующие действия
над точками останова:
♦ деактивация — на деактивированной точке останова приостановка выполнения
производиться не будет.
Для деактивации точки останова следует сбросить флажок, присутствующий
в соответствующем пункте списка (см. рис. 16.4);
♦ повторная активация — выполняется установкой упомянутого флажка;
♦ удаление:
• одной точки останова — в соответствующем пункте списка — наведением
курсора мыши на выражение, содержащее точку останова, и щелчком на
кнопке удаления, которая появится правее этого выражения и будет иметь
вид крестика (па рис. I6.4 эта кнопка показана в первом пункте списка).
Также можно щелкнуть на выражении в пункте списка правой кнопкой мыши
и выбрать в появившемся контекстном меню пункт Удалить точку оста-
нова:
• всех точек останова, кроме выбранной. — щелкнув на выражении в соответ-
ствующем пункте списка правой кнопкой мыши и выбрав в появившемся
контекстном меню пункт Удалить другие точки останова;
• всех точек останова, созданных в определенном файле, — в соответствую-
щем пункте списка — наведением курсора мыши на имя файла и щелчком на
кнопке удаления, которая появится правее имени файла.
Также можно щелкнуть в пункте списка на имени файла правой кнопкой
мыши и выбрать в появившемся контекстном меню пункт Удалить все точ-
ки останова в файле;
• всех точек останова, кроме созданных в выбранном файле, — щелкнув на
имени файла в соответствующем пункте списка правой кнопкой мыши и
выбрав в появившемся контекстном меню пункт Удалить другие точки
останова;
• всех точек останова — щелкнув в любом пункте списка на имени файла или
выражении правой кнопкой мыши и выбрав в появившемся контекстном
меню пункт Удалить все точки останова.
Созданные точки останова сохраняются, так что при последующей отладке тех же
файлов их не придется создавать заново.
16.2.3. Пошаговое выполнение кода
Как только в процессе выполнения кода сценария при открытом отладчике будет
достигнута какая-либо точка останова, выполнение кода приостановится После
этого можно произвести пошаговое выполнение кода, чтобы лучше понять, пра-
вильно ли он работает.
Пошаговое выполнение, или трассировка — выполнение кода по шагам, опера-
ция за операцией.
Внимание!
Если точка останова создана в коде, выполняющемся при загрузке страницы, следует
перезагрузить открытую в веб-обозревателе страницу, чтобы этот код выполнился по-
вторно — с последующей приостановкой
Выражение, на котором приостановилось выполнение, в области программного
кода бущет помечено синей рамкой с голубым фоном, а его фрагмент, описываю-
щий отдельную операцию, — выделен синим фоном (рис. 16.5).
__
const newlitle = Новинка:';
Рис 16.5. Строка кода, на которой было приостансалено выполнение
Как говорилось ранее, за один шаг отладчик выполняет лишь одну операцию.
Выражение, описывающее лишь одну операцию, будет выполнено за один шаг (на-
подобие показанного на рис. 16.5. где производится лишь присваивание значения
константе).
Выражение, описывающее несколько операций, потребует для выполнения соот-
ветствующего количества шагов. В качестве примера можно привести выражение,
показанное на рис. 16.6, которое содержит две операции — объявление двух пере-
менных — и потребует для выполнения двух шагов.
После выполнения очередного шага синее выделение переместится на следующую
операцию, и отладчик будет ждать указания сделать следующий шаг.
let s = *’, t; let s = *', t;
Шаг с обходом 1 Шаг с обходом 2
Рис. 16.6. Выполнение выражения, лисывающего две операции, за два шага
Как только все операции, записанные в помеченном выражении, будр выполнены,
при исполнении следующего шага синяя рамка сместится на следующее выраже-
ние. и выполнится первая из записанных в нем операций.
Для пошагового выполнения кода следует пользоваться кнопками, находящимися
в панели отладки, которая выводится в верхней части служебной области
(см. рис. 16.2). Можно выполнять следующие действия:
шаг с обходом — выполнение одной операции. Если выполняемая операция
представляет собой вызов функции или метода, тело вызванной функции (мето-
да) будет выполнено в обычном режиме (не пошагово).
Производится нажатием кнопки 2* (Пропустить следующий вызов функции),
нажатием клавиши <F10> или комбинации клавиш <Ctrl>+<’>;
♦ шаг с заходом — то же самое, что и шаг с обходом, только тело вызванной
функции (метода) также будет выполняться пошагово.
Производится нажатием кнопки £ (Перейти к следующему вызову функ-
ции ). нажатием клавиши <F11> или комбинации клавиш <Ctrl>+<;>:
♦ выход из функции — выполнение оставшейся части тела функции или метода
в обычном режиме и приостановка выполнения на операции, следующей за вы-
зовом этой функции (метода).
Производится нажатием кнопки
комбинации клавиш <Shift>+<F 1
J (Выйти из текущей функции), нажатием
> или <Ctrl>+<Shift><;>;
♦ возобновление — выполнение оставшегося кода в обычном режиме.
Производится нажатием кнопки !>, (Продолжить выполнение скрипта), на-
жатием клавиши <F8> или комбинации клавиш <Ctrl>+<\>.
После возобновления код будет выполнен либо до конца, либо до следующей
точки останова.
Обычно программисты пошагово выполняют определенный участок кода, вызы-
вающий устойчивые ошибки, работающий неправильно или потенциально про-
блемный. Для этого они применяют шаги с заходом (когда хотят проверить, как
работают используемые функции или методы) или с обходом. Если вместо шага
с обходом по ошибке был выполнен шаг с заходом, они завершают выполнение
функции или метода, произведя выход из функции. А после выполнения отлажи-
ваемого участка кода запускают возобновление, чтобы выполнить оставшийся код
побыстрее.
1 Тексты подсказок у этой и всех остальных кнопок из панели отладки, на взгляд автора, подобраны
неудачно.
16.2.4. Просмотр значений переменных
При пошаговом выполнении кода можно просмотреть текущее значение любой
переменной или свойства объекта. Для этого достаточно навести на имя этой пере-
менной (свойства) курсор мыши — рядом появится небольшая всплывающая под-
сказка, содержащая значение этой переменной или свойства (рис. 16.7).
Если значением переменной (свойства) является массив или объект, подсказка
покажет все элементы этого массива (рис. 16.8) и свойства этого объекта (рис. 16.9).
let "Marypo" ?i.ti
if (.sNew)
|t] = el .title.
Рис 16 7. -сплывающая подсказка co значением переменной на которую был наведен курсор мыши
for (let el of |data[) {
let isNew if (ishew t = e'. el se t -_ei s += <arj getT< ' < imj getT. getn "</ar } Array(5) ▼ B: desc: "Диетические суши, приготовляемые image: "6.5/sushi_chukka.jpg" price. ”55 ₽" title: "Чукка" ► [[Prototype]]: Object ► 1: {title: ‘Новинка: Marypo1, image: '6.5 ► 2: {title: 'тэбико', image: '6.5/sushi_to ► 3; {title: 'Унаги', image: '6.5/sashimi_u ► 4: {title: 'Хачачи', image: '6.5/sashimi_
menu.innerHTMl length: 5 ► [[Prot Type]]: Array(0)
Рис. 16.8. Подсказка с содержимым массива (первый элемент развернут.
for (let |ёТ of data) {
lex ... х.х,- .x._x....xu,----.-х.-х,
if । Object
desc: "Классические суши. Готовятся из ту
els< image: "6.5/sushi_maguro.jpg"
price: "65 ₽"
I title: "Новинка: Marypo"
► [[Prototype]]: Object
}
menu.inr
< ►
Вложенные массивы и объекты можно разворачивать. чтобы просмотреть их
содержимое. Так, на рис. 16.8 первый элемент массива, представляющий собой
объект, показан развернутым.
16.3. Инспектор DOM
Многие веб-сценарии меняют содержимое страницы, добавляя, изменяя или удаляя
ее элементы. Посмотреть на текущее содержимое страницы можно с помощью ин-
спектора DOM.
Инспектор DOM (Document Object Model, объектная модель документа) — по-
казывает текущее содержимое страницы в виде иерархии составляющих ее
HTML-тегов.
Открыть инспектор DOM (рис. I6.I0) можно, отобразив панель отладочных инст-
рументов веб-обозревателя (см. упражнение 11) и переключившись на вкладку
Элементы.
к Го Элементы Консоль Источники Сеть Производительность
«IDOCTYPE html»
«html lang="ru">
► «head» - </head>
▼ <|>ody>
«ЫХуши-бар «йокогама»</Ы>
▼ <main id="menu"> flex
▼ «article» flex
<Ь2»Чукка«/1т2>
<img src="6.5/sushi_chukka.jpg">
• • ▼ «p» == So
"Диетические суши, приготовляемые из одноименного салата,
риса, кунжута и водорослей нори."
</р>
<div>55 ₽</div>
«/article?
k«article class="new"> — «/article» flex
* «article» — «/article» flex
► «article» — «/article» flex
► «article» — «/article» flex
«/main»
«script src="6,5.i.js"»</script» ▼
html oody main#menu article p
Рис. 16 1С Инспектор DOM
HTML-код страницы показывается в виде иерархического «дерева». Мы можем
разворачивать отдельные его «ветви», чтобы посмотреть содержимое соответст-
вующих тегов.
При наведении курсора мыши на какой-либо тег в иерархическом списке создан-
ный им элемент выделяется на странице голубым фоном, а в небольшой всплы-
Ь2 306x42
Чукка
Рис. 16.11 Элемент страницы и всплывающая подсказка с его тегом и размерами
вающей подсказке рядом выводится имя создавшего его тега и геометрические
размеры в пикселах (рис. J6.l I).
16.4. Самостоятельное упражнение
Произведите пошаговое выполнение веб-сцснарисв, выводящих меню суши-бара
«Йокогама» (см. упражнение 13.4).
ЧАСТЬ III
Управление веб-страницей
и веб-обозревателем
С Урок 17 События и их обработка
Э Урок 18. Управление элементами веб-страниц
Э Урок 19 Графика и мультимедиа
Э Урок 20 Веб-формы и элементы управления
О Урок 21. Взаимодействие с веб-обозревателем
Урок 17
События и их обработка
Событие — это сигнал от веб-обозревателя о том, что в элементе страницы или
ином объекте что-то произошло. Существует много событий, часть которых возни-
кает в отвез на различные действия пользователя (наподобие щелчка мышью),
а часть — в процессе работы веб-обозревателя (скажем, по завершении загрузки
страницы).
Каждое событие, поддерживаемое веб-обозревателем, имеет уникальное имя Так.
событие, возникающее при щелчке на элементе страницы, носит название click,
а событие завершения загрузки страницы — load.
Обработчик события — это функция или метод, который выполняется в ответ на
возникновение заданного события в указанном элементе страницы или ином объ-
екте. Обработчик может, например, по щелчку на кнопке запускать вычисление
факториала введенного числа.
17.1. Обработчики событий
17.1.1. Привязка обработчиков к событиям
Привязка заданного обработчика к событию с указанным именем, возникающему
в определенном элементе страницы, выполняется вызовом у этого элемента метода
addEventListener о. Метод поддерживает два формата вызова:
addEventListener(<имя события?, <обработчик? i,
^обрабатывать на фазе погружения??])
addEventListener (<имя события?, <обработчик? [,
дополнительные параметры?])
Имя события должно быть задано в виде строки, обработчик— в виде ссылки на тре-
буемую функцию или метод.
С параметром обрабатывать на фазе погружения мы познакомимся в разд. 17.5.
Дополнительные параметры задаются в виде простого объекта со следующими свой-
ствами:
♦ once — если true, привязываемый обработчик будет выполнен лишь один раз,
а после — удален. Если false, обработчик будет выполняться многократно, пока
не будет удален явно (см. разд. П.1 3). По умолчанию: false;
♦ capture — если true, обработчик будет вызываться на фазе погружения события,
если false — на фазе источника или всплытия (фаты события описаны
в разд. 17.5). По умолчанию: false;
♦ passive — если true, в теле привязываемого обработчика будет невозможно от-
менить обработку события но умолчанию, если false— обработка события по
умолчанию может быть отменена (см. разд. 17.7). По умолчанию: true (в обра-
ботчиках события wheel в веб-обозревателях, отличных от Apple Safari) или
false (во всех остальных случаях);
♦ signal — ссылка на объект прерывателя, удаляющего привязанный обработчик
(подробности — в разд. 17.1.3).
Пример простой привязки обработчика onstartciick () к событию click, возни-
кающему в кнопке start:
start.addEventListener('click', onStartClick);
Пример привязки однократно выполняющегося обработчика:
image.addEventListener(’mouseover', onlmageMouseOver, { once: true });
К одному и тому же событию, возникающему в одном и том же элементе, можно
привязать произвольное количество обработчиков. Все эти обработчики будут вы-
полнены в том порядке, в котором были привязаны. Пример:
start.addEvencListener('click', onSrartClicKl);
start.addEventListener('click', onStartClick2);
start.addEventListener('click', onStartClick2);
Один и тот же обработчик может быть привязан к разным событиям, возникающим
в разных элементах управления:
imagel.addEventListener('mouseover', onlmageMouseOver);
image2.addEventListener('mouseover', onlmageMouseOver);
button.addEventListener(’click’, onlmageMouseOver);
17.1.2. Реализация обработчиков событий
Обработчик события может быть реализован в виде функции (обычной или стре-
лочной) или метода.
В каком виде реализовывать обработчики событий, зависит от конкретного случая.
Так, если при программировании используются классы, логичнее реализовать
обработчики в виде методов, в противном случае — функций.
17.1.2.1. Реализация обработчиков событий в виде функций
Если обработчик события реализован в виде функции.
♦ обычной, которая может быть именованной или анонимной, — в теле функции
при вызове будет создана локальная переменная this, хранящая ссылку на
элемент страницы, к которому привязан текущий обработчик. Через эту пере-
менную можно получить доступ к элементу страницы. Пример (код взят из ре-
зультата выполнения упражнения 2.3):
function onlnteractiveMouseOver() {
this.className = 'hovered';
}
interactive.addEventListener('mouseover', onlnteractiveMouseOver);
♦ стрелочной функции в теле функции бу дез доступна глобальная переменная
this, которая хранит ссылку на глобальный объект окна. Обратиться через нее
к элементу страницы, к которому привязан обработчик, не получится.
В этом случае для доступа к элементу страницы следует использовать свойство
currentTarget объекта события, который передается обработчику с единствен-
ным параметром (подробности — в разд. 17.3):
interactive.addEventListener('meuseover', evt => {
evt.currentTarqet.className = 'hovered';
});
Полезно знать
По устоявшимся правилам обработчикам события дают имена, записанные в стиле
camelCase и формате:
[on] <якорь или наименование элемента страницах имя событиям
Примеры:
onlnteractiveMouseOver{). onCalculateClick(), buttonclick()
17.1.2.2. Реализация обработчиков событий в виде методов
Если обработчик события представляет собой:
♦ обычный метод или метод, реализованный в виде свойства, которому присвоена
обычная функция,— в теле метода при вызове будет создана локальная пере-
менная this, хранящая ссылку на элемент страницы, к которому привязан теку-
щий обработчик (как и при реализации обработчика в виде обычной функции).
Обратиться к текущему объекту через эту переменную не получится.
Если же необходимо из тела обработчика получить доступ к текущему объекту,
можно пойти одним из двух путей:
• создать в элементе, к которому привязывается обработчик, добавленное
свойство, сохранить в нем ссылку на текущий объект и использовать для
обращения к текущему объекту это свойство:
class Spoiler{
constructor(element, { state=false, decoration='' }={}) {
this.header = element.queryselector('h6'' ;
II Создаем у объекта заголовка спойлера добавленное
// свойство obj и присваиваем ему ссылку на текущий объект
this.header.obj = this;
this.header.addEventListener('click',
this.^onHeaderClick);
}
^onHeaderClick() {
II E обработчике события используем ранее созданное
// свойство obj для обращения к текущему объекту
this.obj.state = !this.obi.state;
}
}
• принудительно указать у метода-обработчика события в качестве контекста
выполнения текущий объект, используя метод bind() класса Function
(см./л/зс). 8.8.2)'.
class Spoiler{
#onHeaderClick = (function () {
this.state = !this.state;
}).bind(this)
constructor(element, { state=false, decoration='’ }=(}) {
this.header.addEventListener('click',
this.#onHeaderClick);
}
}
Для доступа к элементу страницы, к которому привязан обработчик, в этом
случае следует использовать свойство currentTarget объекта события
(см. разд. 17.3);
♦ метод, реализованный в виде свойства, которому присвоена стрелочная функ-
ция. — в теле метода будет доступна переменная this, существующая в объяв-
лении класса и хранящая ссылку на текущий объект. Мы использовали эту
переменную для обращения к текущему объекту в теле метода при выполнении
упражнения 11.2.
Для доступа к элементу страницы, к которому привязан обработчик, в этом слу-
чае также следует использовать свойство currentTarget объекта события
(см. разд. 17.3).
17.1.3, Удаление привязки обработчика к событию
Если при привязке обработчика к событию в объекте дополнительных параметров
было указано свойство once со значением true (см.разд. 17.1 7), привязка обработ-
чика будет удалена после обработки первого же события.
В остальных случаях удалять привязку обработчика к событию придется самостоя-
тельно.
17.1.3.1. Удаление вызовом метода removeEventListener()
Для удаления привязки заданного обработчика события с указанным именем у эле-
мента страницы следует вызвать у этого элемента метод removeEventListener!):
removeEventListener(<имя события>, <обработчик>[,
«обрабатывать на фазе погружения?>])
removeEventListener(спмя события>, <обработчик> (,
<дополнительные параметры>])
Задаваемые параметры аналогичным таковым у метода addEventListener!)
(см. разд. 17. 1.7). И з дополнительных параметров поддерживается лишь capture.
Пример:
start.removeEventListener('click', onStartClick);
Полезно знать
Методы addEventListener () И removeEventListener!) наследуются классами элементов
страниц от общего базового класса EventTarget.
17.1.3.2. Удаление с помощью прерывателя
Прерыватель —сущность, позволяющая при необходимости прервать выпол-
нение какой-либо операции.
На текущий момент прерыватель позволяет лишь удалить привязку обработчика
к событию и прервать загрузку данных с сервера (см. урок 28).
Прерыватель представляется объектом класса Abort Controller. Объект создается
выражением формата:
new AbortController()
После создания прерывателя из его свойства signal можно извлечь прерывающий
сигнал.
Прерывающий сигнал — сущность, генерируемая прерывателем при создании
и служащая для передачи сообщения на прерывание операции
Прерывающий сигнал представляется объектом класса Abortsignal.
Прерывающий сигнал указывается в свойстве signal объекта дополнительных пара-
метров, передаваемого методу addEventListener!) (см. разд. 17.1.1).
Для подачи сообщения на прерывание, которое приведет к удалению привязки
обработчика к событию, у прерывателя следует вызвать метод abort!).
Пример:
// Создаем прерыватель
const shorter = new AbortController();
// Указываем прерывающий сигнал, созданный прерывателем, при привязке
// обработчика к событию
start.addEventListener('click', onStartClick,
{ signal: aborter.signal });
// Подаем сообщение на прерывание
aborter.abort();
// После этого привязка обработчика к событию будет удалена
17.1.4, Альтернативные способы привязки
обработчиков к событиям
17.1.4.1. Привязка в атрибутах тегов
С помощью этого способа обработчик можно привязать:
♦ в HTML-коде— использованием атрибута тега с именем формата оп<имя собы-
тия^ в качестве значения которого указывается непосредственно код обработ-
чика (например, вызов функции):
cinput type="button" onclick="onStartClick();" value="nycK">
Однако в этом случае функция-обработчик нс получит в качестве параметра
объект события (описан в разд. 17.3) и соответственно не сможет узнать сведе-
ния о событии;
♦ в коде сценария — с применением свойства, которое имеет такое же имя, как и
упомянутый ранее атрибут тега, и которому присваивается ссылка на функцию-
обработчик".
start.onclick = onStartClick;
Такой способ привязки обработчиков к событиям считается устаревшим, не реко-
мендуется к применению, однако часто встречается в старом коде.
17.1.4.2. Обработка щелчков на гиперссылках
Если заданный код следует выполнить при щелчке на гиперссылке, его можно
записат ь непосредственно в HTML-коде, в атрибуте href тега <а> в формате:
j avase г ipt:<выполняемый код>
Пример.
<а href="javascript:onStartClick();">Пуск</а>
Однако, если указанный код возвращает какое-либо значение, отличное от
undefined, веб-обозреватель удалит все содержимое страницы и выведет на ней это
значение.
Избежать этого можно, применив оператор поглощения void:
void(<выполняемыЛ код>}
Этот оператор поглотает результат, возвращенный заданным кодом, и возвращает
вместо него «безопасное» значение undefined.
Пример:
<а href="javascripc:vcid(onStartClick());">Пуск</а^
Полезно знать
Оператор поглощения поддерживает альтернативный форма! записи:
void * выполняемый код>
Правда, в атрибуте nref тега <а> записывать его в таком формате не допускается.
17.2. События, поддерживаемые элементами
веб-страниц
Элементы страницы поддерживают весьма много событий. В табл. 17.1 приведены
только основные.
Таблица 17.1. Основные события, поддерживаемые элементами страниц
Имя события Когда возникает?
События мыши
click Щелчок мышью
dblclick Двойной щелчок мышью
contextmenu Щелчок правой кнопкой мыши перед выводом контекстного меню
mousedown Нажатие кнопки мыши
mouseup Отпускание кнопки мыши
mcuseenter Наведение курсора мыши на элемент страницы'
mouseover
mousemove Перемещение курсора мыши на элементе страницы
mouse]eave Увод курсора мыши с элемента страницы*
mouseout
wheel Прокрутка колесика мыши
События клавиатуры Возникают только в элементах, способных принимать клавиатурный ввод (в гиперссыпках и элементах управления) а также в секции тела страницы (теге <body>)
keydowr. Нажатие любой клавиши
keyup Отпускание нажатой ранее клавиши
Таблица 17.1 (окончание)
Имя события Когда возникает?
Событие буфера обмена
сору Копирование выделенного текста
События CSS-анимации с двумя состояниями (создаваемой атрибутами стиля семейства transition)
transitionrun Запуск анимации (перед началом задержки, заданной атрибутом стиля transition-delay)
transitionstart Непосредственное начало анимации (после задержки, заданной атрибутом стиля trar.siticn-delay)
transitionend Конец анимации
t r ans itioncancel Прерывание анимации
События CSS-анимации с несколькими состояниями (создаваемой атрибутами стиля семейства animation)
animationstart Начало анимации (после задержки, заданной атрибутом стиля animation-delay)
animation!t eration Завершение очередного повтора анимации (если она воспроизво- дится только один раз, не возникает)
animationend Конец анимации
animationcancel Прерывание анимации
Прочие события
scroll Прокрутка содержимого элемента страницы (если это элемент с прокруткой)
load После загрузки содержимого элемента Возникает s секции тела страницы (теге <body>) и графическом изображении (теге <img>)
* Эти два события возникают при одинаковых условиях, но отличаются по количеству фаз их «прохож-
дения» (подробно о фазах событий рассказано е разд 17 5)
Чаще всего обрабатываются события мыши и клавиатуры. Остальные события об-
рабатываются много реже и в специальных случаях.
17.3. Объект события.
Классы событий и их свойства
Каждому обработчику события при вызове автоматически передается единствен-
ный параметр — объект, содержащий различные сведения о возникшем событии:
ссылка на элемент, в котором возникло событие, координаты курсора мыши на
момент возникновения события и др. Этот объект можно использовать в теле обра-
ботчика, например:
function onMouseOver(evt) {
// Свойства х и у объекта сооытия хранят горизонтальную
//и вертикальную координаты курсора мыши
const х = evt.y;
const у = evt.y;
}
Объекты, представляющие события различных типов, создаются на основе разных
классов. В табл. 17.2 приведены основные классы событий и их свойства.
Примечание
Все свойства классов событий доступны только для чтения Попытка присвоить такому
свойству новое значение вызовет ошибку типа ТуреЕггог.
Таблица 17.2. Основные классы событий и их свойства
Свойство Хранимое значение
Класс Event Представляет все события, кроме приведенных далее Также является базовым для всех остальных классов событий
currentTarget Элемент страницы, к которому привязан текущий обработчик
target Элемент страницы в котором изначально возникло событие (источник события)
type Имя аозникшего события в виде строки (например, 'click' или 'mouseover'). Полезно, если один и тот же обработчик применяется для обработки разных событий
Класс MouseEvent. Производный от класса Event Представляет все события мыши, кроме wheel
pageX Г оризонтальная координата курсора мыши относительно левого верхнего угла самой страницы в пикселах
pageY Вертикальная координата курсора мыши относительно левого верхнего угла самой страницы в пикселах
offsetX Горизонтальная координата курсора мыши относительно левого верхнего угла элемента-источника в пикселах
cffsetY Вертикальная координата курсора мыши относительно левого верхнего угла элемента-источника в пикселах
clientX X Горизонтальная координата курсора мыши относительно левого верхнего угла внутренней области окна в пикселах
clientY У Вертикальная координата курсора мыши относительно левого верхнего угла внутренней области окна в пикселах
screenX Горизонтальная координата курсора мыши относительно левого верхнего угла экрана в пикселах
screenY Вертикальная координата курсора мыши относительно левого 1ерхнегс угла экрана в пикселах
Таблица 17.2 (продолжение)
Свойство Хранимое значение
button Числовое обозначение нажатой кнопки мыши 0 — левая. 1 — средняя или колесико, 2 — правая
CtrlKey true, если в текущий момент нажата клавиша <Ctrl>, и false — в против- ном случае
shiftKey true, если . текущий момент нажата клавиша <Shift>, и false — в противном случае
altKey true, если в текущий момент нажата клавиша <Alt>, и false — в против- ном случае
relatedTarget В случае события mouseover или mouseenter — элемент страницы, с которого был уведен курсор мыши В случае события mouseout или mouseleave — элемент, на который был наведен курсор
Класс Wheel Event Производный от класса Event. Представляет событие wheel
deltaY Расстояние прокрутки пс вертикали положительное, если выполнялась прокрутка вниз, и отрицательное случае прокрутки верх
deltaX Расстояние прокрутки по горизонтали положительное если выполнялась прокрутка вправо и отрицательное в случае прокрутки влево Если мышь не поддерживает прокрутку по горизонтали, всегда хранит 0
deltaMode Обозначение единицы измерения для расстояния прокрутки: 0 — пикселы, 1 — строки, 2 — страницы
Класс KeyboardEvent Пооизг-эдный от класса Event. Представляет события клавиатуры
code Код нажатой клавиши в виде строки
key Значение нажатой клавиши е зиде строки (например 'а1, 'В'. '1', 'Enter')
ctrlKey true, если в текущий момент нажата клавиша <Ctrl>, и false — в против- ном случае
shiftKey true, если в текущий момент нажата клавиша <Shift> и false — в против- ном случае
altKey true, если в текущий момент нажата клавиша <Alt>, и false — в против- ном случае
repeat true, если возникновение события вызвано удержанием нажатой клавиши, false, если событие возникло при первоначальном нажатии клавиши
Класс ClipboardEvent Производный от класса Event. Представляет события буфера обмена
Класс TransitionEvent Производный от класса Event. Представляет события анимации с двумя состояниями
propertyName Имя анимируемого атрибута стиля в виде строки
elapsedTime Продолжительность анимации в -зиде числа в секундах
Таблица 17.2 (окончание)
Свойство Хранимое значение
KnaCC Animatlortvent Производный от класса Event Представляет события анимации с несколькими состояниями
animationName Имя набора состоянии анимации в виде строки
elapsedTime Продолжительность анимации в ?иде числа в секундах
* Свойства currentTarget и target хранят одинаковые значения только на фазе источника (подробно
о фазах событий рассказано в разд. 17.5).
** Перечень к^дсв клавиш находится по интернет-адресу http://tinyurl.com/2t73Kv5c
*** Перечень значении клавиш приведен по интернет-адресу http;//tinyurl.com/mrxhffk6
Если же в теле обработчика события не требуется получать сведения о возникшем
событии, то при объявлении обработчика параметр указывать необязательно. Мы.
кстати, при выполнении упражнений еще ни разу его не указывали.
17.4. Упражнение. Слайдер, часть 1
Клиент, которому мы когда-то делали спойлер, теперь заказал у нас слайдер
Слайдер— панель, выводящая разные графические изображения (или иные
фрагменты содержимого) по щелчкам на кнопках.
Клиент также попросил, чтобы с одного изображения на другое можно было пере-
ключаться также нажатиями клавиш ссгрелка влево>, <стрелка вправо> и клавиш
с цифрами (в последнем случае будет выведено изображение с соответствующим
порядковым номером).
Чтобы облегчить нам работу, клиент предоставил веб-страницу с «заготовкой» для
создания слайдера и таблицу стилей, задающую необходимое оформление.
Слайдер реализуем в виде класса slider. Его конструктор примет единственный
параметр — ссылку на элемент страницы, на основе которого будет создан слайдер
Код класса сохраним в файле 17 4js.
1, Найдем в папке 17Vsources сопровождающего книгу файлового архива (см при-
ложение 5) файлы 17.4 html (страница с «заготовкой» для слайдера), 17 4.css (таб-
лица стилей с необходимым оформлением) и панку 17 4 (изображения, которые
будут выводиться слайдером). Скопируем их куда-либо на локальный диск.
2. Откроем копию файла 17 4.html в текстовом редакторе и посмотрим на HTML-
код «заготовки» для слайдера:
<section id="sliderl">
<main>
<img src="17.4/1.jpg" class="active">
<img src="17.4/2.jpg">
<img src="17.4/3.jpg">
</main>
<nav>
<div class="active">l</div>
<div>2</div>
<div>3</div>
</nav>
</section>
Слайдер будет формироваться семантической секцией (пегом <section>) с яко-
рем slideri, к которой следует привязать стилевой класс slider. Внутри секции
находятся два элемента:
• семантическое основное содержимое (тег <main>) — сформирует панель,
в которой будут выводиться изображения
Внутри основного содержимого находятся графические изображения (теги
<img>), которые и будут поочередно выводиться слайдером. Изображение,
выводящееся в настоящий момент, помечается стилевым классом active;
• семантическая панель навигации (тег <nav>) — сформирует панель, содержа-
щую кнопки для вывода изображений.
Сами кнопки сделаны из обычных блоков (тегов <div>), в которых поставле-
ны их порядковые номера. Кнопка, соответствующая выводимому изображе-
нию, помечается стилевым классом active.
Что ж. можем приступить к написанию класса слайдера.
3. Создадим в папке, где хранятся файлы примера, файл 17 4js и сохраним в нем
код из листинга 17.1.
Листинг 17.1. Класс Slider (файл 17.4.js)
class Slider {
#images // ..................................................... 1
#buttons
#activelmagelndex = 0
ftgetButtonlndex(button) { // ...................................... 7
const btns = [. . . this. ttbuttons]; // ...................... 8
return btns.inaexOf(button); // ............................... 9
)
#switch() { // .................................................... 10
for (let i = 0; i < this.#images.length; i++)
if (i — this.#activelmagelndex) {
this.#images[i].classList.add('active');
this.#buttons[i].classList.add('active');
} else {
this.#images[i].classList.remove('active');
this.#buttons[i].classList.remove('active');
}
}
flbuttonClick = evt => { // ..................................... 11
this.flactivelmagelndex = this.flgetButtonIndex(evt.target);
this.flswitch();
)
flbodyKeyDown = evt => { // ..................................... 12
const num = +evt.key; // .................................... 13
if (num && num > 0 && num <= this.flbuttons.length) // ...... 14
this. flactiveImage Index = num - 1; // ................ 15
else if (evt.code = 'ArrowLeft' &&
this, flactive Imageindex > 0) // ................. 16
this. flactivelmagelndex—; // .......................... 17
else if (evt.code == 'ArrowRight' && // ..................... 18
this.flactivelmagelndex < this.ffbuttons.length - 1)
this.#activelmagelndex++; // .......................... 19
else
return; // ............................................. 21
this.flswitch() ; // ........................................ 20
1
constructor(element) {
element.classList.add('slider'); // .......................... 2
this.flimages = element.querySelectorAll('main img'); // .... 3
this.flbuttons = element.querySelectcrAll('nav > *'); // .... 4
for (let el of this. Abut tone) // ........................... 5
el.addEventListener('click', this.flouttonClick);
document.body.addEventListener('keydown',
this.flbodyKeyDown); // ...................................... 6
}
}
Мы объявили в классе 1ри закрытых свойства (поз. 1 в листинге 17.1): images
(сохранит набор выводимых изображений), buttons (набор кнопок) и
activeimageinoex (индекс выводимого изображения, изначально 0, т. е. первое
изображение).
В конструкторе класса сразу же привязываем к элементу страницы, формирую-
щему слайдер, ст и.тевой класс slider (поз. 2).
Все классы элементов страниц поддерживают метод querySelectorAll (), кото-
рый возвращает последовательность из всех потомков текущего элемента, соот-
ветствующих заданному CSS-селектору. Мы используем этот метод, чтобы по-
лучить и сохранить в ранее объявленных свойствах последовательности изобра-
жений (поз. 3) и кнопок (поз. 4).
Перебираем элементы последовательности кнопок и привязываем к каждой
кнопке закрытый метод buttonclick () (будет рассмотрен позже) в качестве об-
работчика события click (поз. 5).
Напоследок привязываем к секции тела страницы (представляющий ее объект
доступен через свойство body объекта document) в качестве обработчика события
keydown закрытый метод bodyKeyDown(), также рассмотренный позже (поз. 6).
В обработчике события click кнопок нам понадобится выяснить индекс кнопки,
на которой был выполнен щелчок мышью. Это будет выполнять закрытый метод
getButtonindex(<кнопка>) (поз. 7). К сожалению, последовательность кнопок,
возвращенная методом querySeiectorAll () и хранящаяся в свойстве buttons, не
поддерживает метод indexOfO, с помощью которого мы могли бы выяснить
индекс заданной кнопки. Поэтому придется сначала преобразовать последова-
тельность в массив путем деструктурирования (поз. 8) и уже у этого массива
вызвать метод indexOf () (поз. 9).
Еще нам понадобится вывести на экран изображение с индексом, хранящимся
в свойстве activeimageindex, скрыть остальные изображения, пометить кнопку
с тем же самым индексом и снять пометки с остальных кнопок. Для этого мы
напишем закрытый метод switch () (поз. 10). Его код достаточно прост и не тре-
бует пояснений.
Так же прост код закрытого метода buttonclick() — обработчика события click
кнопок (поз. I 1). В нем мы получаем индекс кнопки, па которой был выполнен
щелчок, вызвав объявленный ранее метод getButtonlndex (), присваиваем полу-
ченный индекс свойству activeimageindex и выводим изображение с этим
индексом, вызвав метод switchO.
Код метода bodyKeyDown () — обработчика события keydown секции тела страни-
цы — заметно сложнее (поз. 12). Рассмотрим его по частям.
Если требуется выяснить значение нажатой алфавитно-цифровой клавиши,
следует обратиться к свойству key объекта события. Так, если была нажата кла-
виша <А>, это свойство будет хранить строку ’а’, а если нажата клавиша <1> —
строку '1'.
Сначала выясняем, нажал ли пользователь клавишу с цифрой. Для этого извле-
каем значение нажатой клавиши из свойства key и пытаемся преобразовать его
в числовой тип оператором унарного плюса (поз. 13). Если была нажата клавиша
е цифрой, в результате мы получим число, в противном случае — значение NaN.
Проверяем, получили ли мы число, больше ли оно нуля и не превышает ли оно
размер последовательности кнопок (поз. 14). Если это так, вычитаем из полу-
ченного числа 1 и присваиваем свойству activeimageindex (ПОЗ. 15).
Если же необходимо узнать, какая функциональная клавиша была нажата,
удобнее воспользоваться свойством code объекта события, хранящим код нажа-
той клавиши в виде строки.
Так, далее нужно выяснить, нажал ли пользователь клавишу <стрелка влево>
(поз. 16). Выясняем это, проверяя, хранит ли свойство code объекта события
строку 'ArrowLeft' (код этой клавиши) и больше ли индекс выводимого изобра-
жения, чем 0 (т. е. не выводится ли первое по счету изображение). Если это так.
декрементируем этот индекс, тем самым предписывая слайдеру вывести преды-
дущее изображение (поз. 17).
Осталось проверить, не была ли нажата клавиша <стрелка вправо>, имеющая
код 'ArrowRight'. Дополнительно проверяем, не является ли выводимое изобра-
жение последним из имеющихся (поз. 18). Если все это так, инкрементируем
индекс выводимого изображения, чтобы отобразить следующее изображение
(поз. 19).
Если была нажата какая-либо из «правильных» клавиш, в конце вызывается
метод switch(), который выведет заданное изображение (поз. 20). В противном
случае выполнится оператор возврата (поз. 21) и до вызова метода дело не дой-
дет.
Осталось привязать сценарий 17 4js к странице и написать код, инициализи-
рующий слайдер.
4. Добавим в файл 17 4 html необходимый HI MI.-код:
«.section id="sliderl">
</section>
<scnpt src="17.4. js"X/script>
<script>
const sliderl = document.getElementByld('sliderl');
new Slider(sliderl);
</script>
Откроем страницу 17 4 html в веб-обозревателе, пощелкаем по кнопкам слайдера,
понажимаем клавиши на клавиатуре и понаблюдаем, как меняются изображения
(рис. 17.1).
Рис 17 1. Результат выполнения упражнения 17 4
различные изображения, выведенные слайдером
17.5 . Распространение события. Фазы события
Каждое событие после возникновения не сразу оказывается в элементе-источнике,
в котором оно возникло, а совершает довольно долгое «путешествие» по всем эле-
ментам, в которые источник последовательно вложен.
Распространение события (event propagation) — процесс перемещения воз-
никшего события по иерархии элементов страницы, в которые вложен элемент-
источник, сначала в одном, а потом — в др) том направлении.
Фаза события — один из этанон распространения события.
Процесс распространения события состоит из грех таких фаз:
♦ погружения— возникшее событие сначала появляется в самом внешнем эле-
менте страницы (теге <html>), далее переходит в секцию тела страницы (<body>),
потом— в наиболее «внешний» родитель элемента-источника и т. д., пока нс
окажется в непосредственном родителе источника. Можно сказать, что событие
«погружается» «вглубь» иерархии элементов;
♦ источника — событие появляется в самом источнике;
♦ всплытия — событие появляется сначала в непосредственном родителе источ-
ника. далее — в родителе родителя и т. д., пока не окажется в теге <html>. В этом
случае событие «всплывает» к «вершине» иерархии элементов, совершая путь,
обратный погружению.
К примеру, если щелкнуть на одной из кнопок слайдера, созданного при выпол-
нении упражнения 17.4, путь распространения события click будет таким
(табл. 17.3).
Таблица 17.3. Путь распространения события click
№ Элемент страницы Фаза
1 Погружения
2 --:body>
3 3 <section>
<nav>
4 <di v> Источника
5 <nav> всплытия
3 <sectJ.orv>
6 <body>
7 <html>
Фазы погружения и источника проходят все события.
Фазу всплытия проходят лишь всплывающие события:
♦ мыши, за исключением mouseenter и mouseleave;
♦ клавиатуры;
♦ буфера обмена;
♦ анимации.
Остальные события — невсплывающие— не проходят фазу всплытия.
Зная о распространении событий, можно обрабатывать все события, возникающие
в каких-либо элементах страницы, в одном обработчике — привязанном к роди те-
лю этих элементов. Это может существенно упростить код. Так, в нашем спойлере
все события щелчка мышью, возникающие в кнопках, можно обрабатывать в обра-
ботчике. привязанном к семантической панели навигации.
nav.addEventListener('click', onButtonClick);
События, возникающие в элементах-потомках, можно обрабатывать на фазе как
всплытия (как поступают чаще всего), так и погружения.
Для обработки событий на фазе всплытия в вызове метода addEventListener ()
(см. разд. 17.1.1) следует:
♦ передать третьим параметром значение false или вообще не указывать третий
параметр (поскольку false — его значение но умолчанию);
♦ в объекте дополнительных настроек, передаваемом третьим параметром, присво-
ить свойству capture значение false или вообще не создавать такое свойство
(поскольку false— его значение по умолчанию).
Для обработки событий на фазе погружения в вызове метода addEventListener()
необходимо:
♦ передать третьим параметром значение true;
nav.addEventListener(1 click', onButtonClick, true);
♦ в объекте дополнительных настроек, передаваемом третьим параметром, присво-
ить свойству capture значение true:
nav.addEventListener('click', onButtonClick, { capture: true });
Класс Event поддерживает два свойства, позволяющие выяснить детали распро-
странения события:
♦ eventPhase — хранит числовое обозначение текущей фазы события: 1 — погру-
жения. 2 — источника. 3 — всплытия;
♦ bubbles — хранит true, если событие всплывающее, и false — если оно не-
всплывающее.
Метод stoppropagation () класса Event прерывает дальнейшее распространение
события:
function onButtonClick(evt) {
evt.stopPropagation();
}
Если к событию привязано одновременно несколько обработчиков, и в одном из
них производится вызов метода stoppropagation(), все последующие обработчики
события все же будут выполнены. Чтобы отменить их выполнение, следует исполь-
зовать метод stopimmediatePropagation () класса Event.
17.6 . Упражнение. Слайдер, часть 2
Клиент, заказавший нам слайдер, попросил, чтобы при наведении курсора мыши на
кнопку рядом с ней появлялась миниатюре! соответствующего изображения.
I Найдем в папке 17\ех17 4 сопровождающего книгу' файлового архива (см. при-
ложение 5) файлы 17 4.html (страница со слайдером), 17 4 css (таблица стилей),
17.4 js (код слайдера) и папку 17 4 (изображения, выводимые слайдером). Скопи-
руем их куда-либо на локальный диск.
2. Откроем копию файла 17.4 html в текстовом редакторе и посмотрим на HTML-
код, создающий элемент, в котором будет выводиться миниатюра изображения
(в приведенном коде подчеркнут):
<div id="preview"></div>
«.section id="sliderl">
</section>
Это обычный блок (тег <div>) с якорем preview. В таблице стилей 17 4.css уже
имеются стили, задающие у него абсолютное позиционирование и необходимое
оформление. Нам останется лишь:
• для вывода этого блока на экран — поместить в него выводимое изображение
в качестве фона, указать у него координаты и привязать к нему стилевой
класс active;
• для скрытия блока — убрать у него привязку стилевого класса active.
Выводить блок preview на экран будем в обработчике события mouseover, а
скрывать — в обработчике события mouseout. Оба события станем обрабатывать
на фазе погружения, а оба обработчика привяжем к семантической панели нави-
гации — родителю всех кнопок.
3. Откроем копию файла 17 4.js в текстовом редакторе и внесем следующие правки:
class Slider {
flpreview // ........................................................... 1
flbuttonMouseOver = evt => { // ................................... 2
if (evt. target != evt. currentTarget) { // ................. 3
const buttonNumber = evt.target.textContent; // .......... 4
const imagelndex = +buttonNumber - la-
this, flpreview, style, left = evt pageX + 'px1; // ...... 5
this, flpreview, style, top = evt pageY + 'px'; // ....... 6
this.flpreview.style.backgroundlinage =
url($(this,flimages[imageindex] sre}) ; // 7
this.#preview.classList.add('active*); // .............. 8
)
}
#buttonMouseOut = evt => { // .................................. 9
if (evt.target ' = evt.currentTarget)
this.#preview.classList.remove('active’);
)
cons t rue to r(e1 ement) {
this.#preview = document.getElementByld('preview*); // .... 10
const nav = element.queryselector (’nav') ; // ........... 11
nav addEventListener(* mouseover *, this.ttbuttonMouseOver.
{ capture: true }); // .. 12
nav.addEventListener('mouseout', this.#buttonMouseOut,
{ capture: true }); // .. 13
}
}
Сначала объявляем в классе еще одно закрытое свойство preview— для хране-
ния ссылки на блок preview, в котором будет выводиться миниатюра изображе-
ния (поз. I в приведенном коде).
Далее объявляем закрытый метод buttonMouseOver (), который станет обработчи-
ком события mouseover сематической панели навигации (поз. 2). В нем, прежде
всего, проверяем (поз. 3), отличается ли элемент-источник события от элемента,
к которому привязан обработчик (т. е. возникло ли событие в одной из кнопок, а
не в самой панели навигации). Если это так, получаем (поз. 4) индекс изображе-
ния, миниатюру которого следует вывести, тем же способом, что применили
в обработчике события click кнопок (см. упражнение П.4).
Любой класс элемента страницы поддерживает свойство style. Оно хранит объ-
ект. представляющий встроенный стиль, который привязан к текущему элемен-
ту. Объект поддерживает ряд свойств, соответствующих отдельным атрибутам
стиля. Мы пользуемся им. чтобы указать у элемента preview горизонтальную
(поз. 5) и вертикальную координаты (поз. 6), которые извлекаем из свойств
радеХ и pageY объекта события
Изображение в блоке preview мы условились выводить в виде фона. Имея ин-
декс выводимого изображения, можем извлечь его из последовательности изо-
бражений посредством оператора []. Интернет-адрес файла с выводимым изо-
бражением извлекаем (поз. 7) из свойства sre класса изображения (тега <img>).
После чего привязываем к блоку preview стилевой класс active и тем самым вы-
водим его на экран (поз. 8).
В закрытом методе buttcnMouseOut () — обработчике события mouseout панели
навигации — проверяем, возникло ли событие в одной из кнопок, и. если это
гак, убираем у блока preview привязку стилевого класса active, чтобы скрыть
блок (поз. 9).
В начале тела конструктора ищем блок с якорем preview и заносим ссылку на
него в одноименное закрытое свойство (поз. Ю). В конце тела конструктора
ищем внутри слайдера панель навигации (поз. 11). привязываем к ней объявлен-
ные ранее обработчики событий buttonMouseOverО (поз. 12) и buttonMouseOut ()
(поз. 13).
Откроем страницу 17.4.html в веб-обозревагеле и переключимся на какое-либо изо-
бражение, наведем курсор мыши на одну из кнопок и проверим, выводится ли
миниатюра (рис. 17.2).
Рис. 17.2. Результат выполнения упражнения 17 6 слайдер
выводящий миниатюры изображений
17.7. Обработка событий по умолчанию
и ее отмена
Ряд событий, происходящих в некоторых элементах страницы, обрабатывается са-
мим веб-обозревателем.
Обработчик события но умолчанию — реализован в самом веб-обозревателе,
выполняется после всех обработчиков, написанных программистом, и выпол-
няет какие-либо типовые действия.
Обработчик событий по умолчанию у разных событ ий выполняет разные действия:
♦ click— зависит от элемента страницы (у гиперссылок — переход на целевую
страницу, у кнопки отправки данных — отправку данных);
♦ contextmenu — вывод контекстного меню;
♦ события клавиатуры — обработку нажатой клавиши;
♦ сору— копирование текста в буфер обмена.
У остальных событий обработчики по умолчанию отсутствуют.
В ряде случаев бывает необходимо сделать так, чтобы обработчик события по
умолчанию не выполнялся. Например, если при щелчке на гиперссылке должен
происходить не переход на другую страницу, а что-либо еще, в код обработчика
события click гиперссылки следует вставить выражение, отменяющее выполнение
обработчика по умолчанию.
Отменить обработчик по умолчанию можно вызовом метода pre vent De fault О объ-
екта события:
function onHrefCLick(evt) {
evt.preventDefault();
Класс Event поддерживает два свойства, позволяющие получить сведения об обра-
ботке события по умолчанию:
♦ cancellable — хранит true, если обработку события по умолчанию можно отме-
нить, и false в противном случае;
♦ defaultPreventea — хранит true, если обработчик по умолчанию этого события
был отменен ранее, и false в противном случае.
17.8. Упражнение. Слайдер, часть 3
Клиент, заказавший слайдер, опять недоволен. Говорит, что переключать изобра-
жения можно лишь щелчками мыши на кнопках. Л желательно, чтобы на эти кноп-
ки можно было нажимать и с клавиатуры.
Мы можем сделать это, реализовав кнопки в виде гиперссылок— элементов стра-
ницы. которые могут принимать клавиатурный ввод.
I Найдем в папке 17\ех17 6 сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 17 4 html (страница со слайдером), 17.4 css (таблица стилей),
17.4 js (код слайдера) и папку 17 4 (изображения, выводимые слайдером). Скопи-
руем их куда-либо на локальный диск.
2. Откроем копию файла 17 4 html в текстовом редакторе и исправим HTML-код,
создающий кнопки слайдера, заменив блоки на гиперссылки:
<nav>
<div olas£-i"ectJ vo">l< /aiv>
<a href="#" class="act±ve">l</a>
<div>2</div>
<a href="#">2</a>
<di-v»3<-/di-v»
<a href="#">3</a>
</nav>
При щелчке на гиперссылке выполнится обработчик события по умолчанию, ко-
торый произведет переход по записанному в гиперссылке интернет-адресу или.
если в гиперссылке вместо интернет-адреса указан символ решетки (как в нашем
случае), прокрутит открытую страницу в самый верх. Поэтому обработчик по
умолчанию необходимо отменить.
3. Откроем копию файла 17 4js в текстовом редакторе и внесем небольшую правку:
class Slider {
ttbuttonClick = evt => {
evt . preven.tDefault О ;
}
}
Откроем страницу 17 4 html в веб-обозревателе и проверим слайдер в работе.
17.9. Самостоятельное упражнение
В слайдере, написанном при выполнении упражнения 17.8, сделайте так. чтобы:
♦ при щелчке правой кнопкой мыши на выводимом изображении не отображалось
контекстное меню (чтобы предотвратить сохранение изображений на локальном
диске).
Подсказка: привяжите к событию contextmenu семантического основного содер-
жимого (тега <main>), в котором выводятся изображения, обработчик и в его теле
отменяйте обработку события по умолчанию;
♦ при наведении курсора мыши на активную кнопку миниатюра не выводилась
(все равно она не нужна).
Подсказка: привяжите к событию mouseover панели навигации еще один обра-
ботчик, выполняющийся на фазе погружения, перед уже существующим обра-
ботчиком, который выводит миниатюру, и в его теле прерывайте дальнейшее
распространение события вызовом метода stopimmediatePropagationf);
♦ можно было переходить на первое изображение нажатием клавиши <Ноте>,
а на последнее — клавишей <End>.
Подсказка: код клавиши <Ноте> — 'ноте', а клавиши <End>— 'Ena'.
Урок 18
Управление
элементами веб-страниц
18.1. Получение доступа к элементам страницы
Прежде чем выполнять какие-либо действия над элементом страницы, следует по-
лучить представляющий его объект. Мы хорошо усвоили это за предыдущие уроки.
Объекты, представляющие различные элементы отображаемой страницы, создают-
ся веб-обозревателем при ее загрузке и в совокупности составляют DOM.
DOM (Document Object Model, объектная модель документа) — иерархическая
структура взаимосвязанных объектов, представляющих элементы страницы.
Получить доступ к объектам элементов страницы можно только через объект,
представляющий саму страницу.
При исполнении сценария веб-обозреватель создает переменную window, в которой
сохраняет обвеют класса window, представляющий текущее окно. Свойство document
объекта окна хранит объект класса HTMLDocument, представляющий текущую веб-
страницу,— он-то нам и нужен.
Объект текущего окна является глобальным. Поэтому для обращения к его свойст-
вам и методам переменную window можно не указывать.
18.1.1. Доступ к элементам определенного типа
Класс HTMLDocument подзерживает ряд свойств, посредством которых можно полу-
чить доступ к некоторым (не всем!) элементам страницы:
♦ body — объект класса HTMLBodyElement, представляющий секцию тела страницы
(тег <body>):
document.body.addEventListener('keydown’, bodyKeyDown);
♦ head — объект класса HTMLHeadElement, представляющий секцию заголовка стра-
ницы (тег <head;>);
♦ images — коллекция всех графических изображений (тегов <img>). что есть на
ст ранице.
Коллекция — объект, имеющий функциональность пронумерованной последо-
вательности. Все коллекции поддерживают оператор доступа к элементу после-
довательности [] и свойство length.
Пример:
let imageCount = document.images.length;
♦ links— коллекция всех гиперссылок (тегов <а>) и областей в картах-изобра-
жениях (тегов <агеа>);
♦ forms — коллекция всех веб-форм (тегов <form>);
♦ scripts— коллекция всех веб-сценариев, привязанных к странице;
♦ activeElement — элемент страницы, имеющий фокус клавиатурного ввода (кла-
виатурный ввод могут принимать лишь гиперссылки, элементы управления
и секция тела страницы —тег <body>).
18.1.2. Доступ к любому элементу страницы
Следующие методы класса HTMLDocument позволят получить доступ к любому эле-
менту страницы:
♦ getElementByld(<якорь>) — возвращает элемент с указанным якорем (задается
в атрибуте тега id) или null, если такого элемента нет:
<img id= "output ’’>
const output = document.getElementByld('output' ;
♦ getElementsByName(<наименование>) — возвращает коллекцию элементов с ука-
занным наименованием (задастся в атрибуте тега name). Если таковых элементов
нет, возвращает пустую коллекцию. Пример:
<input type="text" name="namel">
const namel = document.getElementsByName('namel’)[OJ;
Возвращаемый методом объект коллекции относится к классу NodeList. Этот
класс поддерживает метод forEach () (см. разд. 9.7.7.7);
♦ getElementsByTagName (<имя тега?} — возвращает коллекцию элементов, создан-
ных тегом с указанным именем (ставится без символов < и >). Если таковых эле-
ментов нет, возвращает пустую коллекцию. Пример:
<ul>
<li>HTML;</li>
<li>CSS;</li>
<li>JavaScript.</li>
</ul>
// Получаем второй по счету пункт списка
const li2 = document.getElementsByTagName(’li')[1];
Коллекция, возвращаемая этим методом, относится к классу HTMLCollection,
который не поддерживает метод forEach();
♦ getElementsByClassName (<имя стилевого класса>) — возвращает коллекцию эле-
ментов (в виде объекта класса htmlcoI lection), к которым был привязан стиле-
вой класс с указанным именем (ставится без начальной точки). Если таковых
элементов нет. возвращает пустую коллекцию. Пример:
<div class="button">l</div>
<div class="button">2</div>
<div class="button">3</div>
const buttons = document.getElementsByClassName('button');
Можно указать несколько имен стилевых классов, разделив их пробелами. —
тогда в полученной коллекции окажутся элементы, к которым были привязаны
все эти стилевые классы:
<div class="button active">l</div>
<div class="button">2</div>
<div class="button">3</div>
let activeButton = document.getElementsByClassName('button active')[0];
♦ queryselector (<С55-селектор>) — возвращает первый имеющийся на странице
элемент, соответствующий заданному CSS-селектору, или null, если подходяще-
го элемента нет:
<nav>
<div class="active">l</div>
<div>2</div>
^div>3</div>
</nav>
// Получаем последние "кнопку" слайдера
const lastButton = document.queryselector(’nav div:last-child');
♦ querySelectorAll (<С35-селектор>) — возвращает коллекцию элементов (объект
класса NodeList), соответствующих указанному CSS-селектору. Если таковых
элементов нет, возвращает пустую коллекцию. Пример:
const buttons = document.querySelectorAll('nav div');
Можно указать несколько CSS-селекторов. разделив их запятыми. — тогда
в коллекции окажутся элементы, соответствующие, по крайней мере, одному из
заданных селекторов:
// Получаем коллекцию из панели навигации и вложеншлх в нее "кнопок"
const navAndButtons = document.querySelectorAll('nav, nav div');
Объект элемента страницы относится к классу, который представляет HTML-тег,
создающий этот элемент. Так, абзацы (теги <р>) представляются объектами класса
HTMLParagraphElement, заголовки (теги <hl> . . . <h6>) — объектами класса
HTMLHeadingElement, а блоки (ТСГИ <div>) — объектами класса HTMLDivElement. Все
классы-теги являются производными от класса HTMLElement.
Класс HTMLElement поддерживает все упомянутые ранее методы, за исключением
getElementByld() и getElemencsByName(). Так чго мы можем искать нужные элемен-
ты внутри какого-либо элемента:
<nav>
<div class="active">l</div>
<div>2</div>
<div>3</div>
</nav>
// Получаем панель навигации
const nav = document.getElementsByTagName(’nav')[0];
// И ищем в ней активную "кнопку"
activeButton = nav.queryselector('div.active');
18.1.3. Доступ к родителю, соседям и потомкам
Класс HTMLElement поддерживает свойства, позволяющие добраться до родителя,
соседей и потомков текущего элемента:
♦ parentElement— родитель текущего элемента или null, если текущий элемент
не имеет родителя:
// Получаем родителя панели навигации
const body = nav.parentElement; // Ter <body>
♦ children— коллекция потомков текущего элемента в виде объекта класса
HTMLCollection:
// Получаем коллекцию "кнопок" слайдера
const buttons = nav.children;
♦ chiIdElementCount — количество потомков текущего элемента;
♦ firstElementChiid— первый потомок текущего элемента или null, если теку-
щий элемент не имеет потомков:
// Получаем первую "кнопку" слайдера
const firstButton = nav.firstElementChiid;
♦ lastElementChild — последний потомок текущего элемента или null, если теку-
щий элемент не имеет потомков;
♦ nextElementSibling — следующий сосед текущего элемента или null, если тако-
вого нет;
♦ previousEiementsibiing— предыдущий сосед текущего элемента или null, если
гаковог о нет.
Также поддерживается метод contains (<элемент>), который возвращает true, если
указанный элемент является потомком текущего элемента, и false— в противном
случае:
// Проверяем, является ли активная "кнопка" потомком панели навигации
let result = nav.contains(activeButton); // true
18.2. Управление элементами веб-страницы
Для работы с элементами страниц класс HTMLElement предоставляет ряд свойств,
которые можно разделить на три группы: соответствующие атрибутам тега, храня-
щие местоположение и размеры элемента и прочие.
18.2.1. Работа с атрибутами тегов
Для представления атрибутов тега служат следующие свойства (табл. 18.1).
Таблица 18.1. Свойства класса HTMLElement, представляющие атрибуты тега
Атрибут тега Соответствующее свойство
class className
rowspan rowSpan
colspan colSpan
Остальные Имя свойства совпадает с именем атрибута тега
Все эти свойства непосредственно хранят значение соответствующего атрибута
тега. Примеры:
// Привязываем к панели навигации стилевой класс navigation-panel,
// присвоив строку с его именем свойству className,
// которое соответствует атрибуту тега class
nav.className = 'navigation-panel';
// Создаем там же встроенный стиль, используя свойство style,
// что соответствует одноименному атрибуту стиля.
nav.style = 'background-color: yellow;';
// Извлекаем из свойства id якорь и формируем на его основе ссылку
// на графический файл
const url = 'images/' + activeButton.id + ’.jpg';
// Присваиваем полученную ссылку свойству зге элемента output,
// чтобы вывести изображение на экран
output.sre = url;
Исключение составляют свойства, представляющие атрибуты тегов без значений.
Такое свойство хранит значение true, если соответствующий атрибут присутствует
в теге, и false — в противном случае. Пример:
<input type="text" id="txtDesc">
const txtDesc = document.getElementByld^'txtDesc');
// Делаем поле ввода недоступным, присвоив значение true свойству disabled,
II которое соответствует одноименному атрибуту тега без значения
txtDesc.disabled = true;
18.2.1.1. data-атрибуты и работа с ними
HTML позволяет записать в теге произвольное значение, создав для него data-
атрибут.
data-атрибут — атрибут тега, предназначенный для записи в теге произволь-
ного значения.
Имя data-атрибута должно начинаться с префикса data-. Если имя data-атрибута
состоит из нескольких слов, отдельные слова разделяются дефисами (-). Пример:
<img id-="somelmage" data-file-src="pic.jpg">
Для работы с data-атрибутами класс HTMLEiement предоставляет свойство dataset.
Оно хранит объект класса DOMStringMap, свойства которого соответствуют отдель-
ным data-атрибутам. Имя каждого из этих свойств совпадает с именем соответст-
вующего data-атрибута без префикса data-, записанным в стиле camelCase. Пример:
const somelmage = document.getElementByld('somelmage');
comst src = somelmage.dataset.fileSrc;
Полезно знать
Стиль, в котором записываются имена data-атрибутов, носит название kebab-case.
18.2.2. Получение местоположения и размеров
элементов веб-страниц
Приведенные далее свойства местоположения и размеров хранят величины в пик-
селах, и все — за двумя исключениями — доступны только для чтения
♦ clientwidth — видимая ширина текущего элемента, включая величины внутрен-
них просветов слева и справа, но без учета рамки, ширины вертикальной полосы
прокрутки и внешних просветов;
♦ clientHeight — видимая высота текущего элемента, включая величины внут-
ренних просветов сверху и снизу, но без учета рамки, высоты горизонтальной
полосы прокрутки и внешних просветов;
♦ offsetLeft — горизонтальная координата левого верхнего угла текущего эле-
мента относительно левого верхнего угла элемента из свойства offsetparent;
♦ offsetTop — вертикальная координата левого верхнего угла текущего элемента
относительно левого верхнего угла элемента из свойства offsetparent;
♦ offsetparent — элемент, относительно которого вычисляются значения свойств
offsetLeft и offsetTop, — ближайший позиционируемый родитель или. если все
родители непозинионируемые. секция тела страницы;
♦ offsetwidth — то же самое, что и clientwidth, но с учетом толщины левой и
правой сторон рамки и ширины вертикальной полосы прокрутки:
♦ offsetHeighc — то же самое, что и clientHeight, но с учетом толщины верхней
и нижней сторон рамки и высоты горизонтальной полосы прокрутки:
♦ scrolJwidth — ширина всего содержимого текущего элемента, включая величи-
ны внутренних просветов слева и справа, но без учета рамки, ширины верти-
кальной полосы прокрутки и внешних просветов;
♦ scrollHeight — высота всего содержимого текущего элемента, включая величи-
ны внутренних просветов сверху и снизу, но без учета рамки, ширины горизон-
тальной полосы прокрутки и внешних просветов;
♦ scroiiLeft — величина, на которую содержимое элемента прокручено по гори-
зонтали. Доступно для чтения и записи;
♦ scroiiTop— величина, на которую содержимое элемента прокручено по верти-
кали. Доступно для чтения и записи.
Понять назначение свойств «семейств» offset и scroll поможет рис. 18.1, изобра-
жающий элемент с прокруткой.
Рис. 1F 1. Назначение свойств «семейств» offset и scroll
18.2.3. Прочие инструменты
для управления элементами веб-страниц
11рочие свойства:
♦ textcontent — текстовое содержимое теку шего элемента:
// Задаем у активной "кнопки" новый текст
activeButton.textContent = 'Картинка №1';
♦ innerText — то же самое, что и textcontent;
♦ tagName— строка с именем тега, с помощью которого создан текущий элемент,
приведенным к верхнему регистру. Доступно только для чтения.
Класс HTMLElement поддерживает также следующие методы:
♦ getBoundingClientRect {) — возвращает объект класса DOMRect с координатами и
размерами текущего элемента, представленными в пикселах. Класс DOMRect под-
держивает свойства:
• left— горизонтальная координата левого верхнего угла элемента относи-
тельно левого верхнего угла видимой части его родителя:
• top — вертикальная координата левого верхнего угла элемента относительно
левого верхнего угла видимой части его родителя;
• right — горизонтальная координата правого нижнего угла элемента относи-
тельно левого верхнего утла видимой части его родителя;
• bottom — вертикальная координага правого нижнего угла элемента относи-
тельно левого верхнего угла видимой части его родителя;
• х — то же самое, что и left;
• у — то же самое, что и top;
• width— ширина элемента с учетом внутренних просветов и рамки;
• height — высота элемента с учетом внутренних просветов и рамки.
Этот метод ранее использовался в элементах с прокруткой для выяснения, ви-
дим ли какой-либо из потомков такого элемента на экране. Сейчас для этого
удобнее применять детектор видимости (см. разд. 21.5);
♦ scrollintoview о — прокручивает содержимое родителя текущего элемента та-
ким образом, чтобы текущий элемент стал видимым на экране. Поддерживает
два формата вызова:
scrolllntoView ([<вьр<звнивать по Bepxy?>=true])
scrollTntoView([^настройки прокрутки>])
В первом формате значение true параметра вьравнивание по верху предписывает
выровнять текущий элемент по верхней границе родителя, значение false— по
нижней.
Во втором формате настройки прокрутки задаются в виде простого объекта со
свойст вами:
• block — обозначение вертикального выравнивания текущего элемента в виде
строки: 'start' (по верхней границе родителя), 'center' (по центру), 'end'
(по нижней границе) или 'nearest' (по ближайшей). По умолчанию: 'start';
• inline — обозначение горизонтального выравнивания текущего элемента
в виде строки: 'start' (по левой границе родителя), ’center' (по центру),
'end' (по правой границе) или 'nearest' (по ближайшей). По умолчанию:
'nearest';
• behavior — обозначение режима прокрутки в виде строки: ’smooth’ (плав-
ная), ’instant’ (рывком) или ’auto' (на усмотрение веб-обозревателя). По
умолчанию: 'auto';
♦ scroll () — прокручивает содержимое текущего элемента, чтобы точка с ука-
занными координатами находилась в верхнем левом углу элемента. Поддержива-
ет два формата вызова:
scroll«горизонтальная координата?, <вертикальная координата?)
scroll «параметры прокрутки?)
Обе координаты указываются в виде чисел в пикселах. Начало координат нахо-
дится в левом верхнем углу элемента, горизонтальная координата отсчитывается
в направлении влево, вертикальная— вниз.
Во втором формате вызова параметры прокрутки указываются в виде простого
объекга со свойствами:
• left — горизонтальная координата:
• top— вертикальная координата;
• behavior — обозначение режима прокрутки в виде строки (см. описание ме-
тода scroll IntoView (j);
♦ scrollTo () — то же самое, что и scroll ();
♦ scrollByf) — прокручивает содержимое текущего элемента на заданные смеще-
ния. Два формата вызова:
scrollBy «смещение по горизонтали?, ^смещение по вертикали?)
scrollBy «параметры прокрутки?)
Оба смещения указываются в виде чисел в пикселах. Положительные величины
смещений вызывают прокрутку вправо и вниз, отрицательные — влево и вверх.
Во втором формате параметры прокрутки задаются в виде простого объекта со
свойствами:
• left — смещение по горизонтали;
• top — смещение по верт икали:
• behavior— обозначение режима прокрутки в виде строки (см. описание ме-
тода scroll IntoView ());
♦ isEqualNode «сравниваемый элемент?) — возвращает true, если текущий и срав-
ниваемый элементы полностью одинаковы (т. е. созданы одним и тем же тегом,
имеют одинаковый набор атрибутов и одинаковое содержимое), и false —
в противном случае;
♦ isSameNode «сравниваемая ссылка?) —возвращает true, если сравниваемая ссылка
указывает на текущий элемент, и false— в противном случае:
<nav id="buttons">
<div class="active">l</div>
<div>2</div>
<div>3</div>
</nav>
const lasrBcnl = document.queryselector('nav div:last-child’);
const lastBtn2 = document.getElementByld(’buttons’).lastElementChild;
let result = lastBtnl.isSameNcde(lastBtn2); // true
18.3. Упражнение. Стильная полоса прокрутки
Нам поступил очередной заказ — стильная полоса прокрутки для меню суши-бара
«Иокогама». И очень кстати: нам нужно на чем-то закрепить пройденный материал.
I. Найдем в папке 18\!sources сопровождающего книгу файловою архива (см при-
ложение 5) файлы 18 3htm[ (страница с меню и «заготовкой» для полосы про-
крутки) и 18 3 css (внешняя таблица стилей с оформлением) и папку 18 3 (графи-
ческие файлы с иллюстрациями блюд). Скопируем всё это куда-либо на локаль-
ный диск.
2. Откроем копию файла 18 3 html в текстовом редакторе и найдем код, создающий
прокручиваемое меню и «заготовку» для полосы прокрутки;
<main id="menu">
<article>
<Ь2>Чукка</Ь2>
</article>
</main>
<div id=’’scrollbar ">
<divx/div>
</div>
Элемент с прокруткой, формирующий само меню, создается тегом семантиче-
ского основного содержимого (<main>) с якорем menu. Полоса прокрутки создает-
ся блоком (тегом <div>) с якорем scrollbar, а захват полосы прокрутки— пус-
тым блОКОМ, НаХОДЯЩИМСЯ Внутри блока scrollbar.
Код, реализующий полосу прокрутки, запишем в файле 18 3,js.
3. Создадим в папке с остальными файлами примера файл 18.3js и откроем его
в текстовом редакторе.
Прежде всего следует получить доступ к элементу меню, полосе прокрутки и за-
хвату.
4. Напишем код, получающий доступ к упомянутым элементам страницы:
const menu = document.getElementByld(’menu’);
const scrollbar = document.getElementByld(’scrollbar’);
const handle = scrollbar.firstElementChiid;
Далее нужно сохранить вертикальную координату точки на захвате, на которой
пользователь нажал кнопку мыши. Эта величина далее потребуется для коррек-
тировки местоположения захвата на полосе прокрутки и позиции прокрутки
меню.
5. Запишем код, объявляющий переменную innerTop, в которой будет храниться
вертикальная координата точки нажатия на захвате:
let innerTop;
Когда пользователь нажмет клавишу мыши на захвате, начиная прокрутку
(и в захвате возникнет событие mousedown), нам следует сделать две вещи.
Во-первых, сохранить вертикальную координату точки нажатия на захвате.
Во-вторых, начать обрабатывать событие перемещения мыши (mousemove) над
секцией тела страницы и соответственно менять местоположение захвата и
позиции прокрутки у меню.
Отметим еще раз, что перемещение мыши будет обрабатываться у секции тела
страницы, а не у захвата или полосы прокрутки. Это повысит плавность про-
крутки и устранит рывки при слишком быстром перемещении мыши.
Обработчик события mousedown захвата реализуем в виде анонимной функции, а
обработчик события mousemove секции тела— в виде функции bodyMouseMove (),
чтобы при окончании прокрутки убрать ее привязку в качестве обработчика со-
бытия.
6. Добавим код обработчика события mousedown захвата, который инициирует на-
чало прокрутки:
handle.addEventListener(’mousedown', function (evt) {
innerTop = evt.offsetY;
document.body.add£vent Listener('mousemove', bodyMouseMove);
});
7. Запишем код, объявляющий функцию bodyMouseMove (), — обработчик события
mousemove секции тела:
function bodyMouseMove(evt) {
const у = evt.clientY - scrollbar.offsetTop - innerTop; // ............ 1
if (у > 0 && у < scrollbar.clientHeight - handle.offsetHeight) { // .. 2
handle.style.top = ${y}px ; // .................................... 3
const cy = (menu.scrollHeight - menu.clientHeight) *
у / menu.clientHeight; // ............................ 4
menu, scroll Top = cy; // ......................................... 5
}
}
Сначала вычисляем текущее положение захвата на полосе прокрутки, равное
вертикальной координате курсора мыши относительно внутренней области окна
за вычетом вертикальной координаты верхнего края полосы прокрутки и сохра-
ненной рапсе вертикальной координаты точки нажатия на захвате (поз. 1 в при-
веденном коде). Далее проверяем, не выходит ли вычисленное положение за
пределы полосы прокрутки (поз. 2). устанавливаем на соответствующую пози-
цию захват (поз. 3), вычисляем позицию, на которую следует прокрутить меню
(поз. 4), и прокручиваем его (поз. 5).
Закончив прокрутку, пользователь отпустит кнопку мыши на захвате (и в нем
возникнет собы 1 ие mouseup). В этот момент следует прекратить прокрутку, убрав
привязку обработчика события mousemove у секции тела.
8. Допишем код обработчика события mouseup захвата, который завершит про-
крутку :
handle.addEventListener('mouseup', function (evt) {
document.body.remcveEventListener('mousemove', bodyMouseMove);
});
9. Привяжем веб-сценарий 18 3.js к странице 18 3.html самостоятельно.
Откроем страницу 18.3 html в веб-обозревателе и попробуем прокрутить меню, вос-
пользовавшись стильной полосой прокрутки (рис. I8.2).
Суши-бар «Йокогама»
Суши-бар «Йокогама»
Лцм летучей pvtec
Магуро
Gt»
сущи,
КО
еджвт* «ywyn
и моо^ослеА нор*
CAAt’a. Р*С4. иужжуТД
* КДОрЭСЛ**
Унаги
«0*—НЫЙ VTOP4
емчтй салят, аумжут
Готовятся я» и
риса
ю»
Хамами
ГЬтояяКя и»
ж»А<иаа*1Д
л—tapir,»ле—л о
риса и дай—ма
54»
I
Рис 18.2. Результат рыполнения упражнения 18 3
стильная полоса прокрутки в разных позициях
18.4. Управление стилями CSS
18.4.1. Управление стилевыми классами
Для указания стилевых классов у элемента можно использовать свойство className,
соответствующее атрибуту тега class. Однако, если к элементу уже привязаны ка-
кие-то стилевые классы, и нужно привязать еще один или. наоборот, убрать при-
вязку какого-либо стилевого класса, пользоваться этим свойством неудобно.
Класс HTMLElement поддерживает доступное только для чтения свойство classList.
Оно хранит объект класса DOMTokenList — коллекцию привязанных к элементу сти-
левых классов.
Класс DOMTokenList поддерживает методы:
♦ add о — привязывает к текущему элементу стилевые классы с указанными име-
нами:
add(<имя стилевого класса 1>, <имя стилевого класса 2>, . . .
<имя стилевого класса N>)
Если какой-либо из стилевых классов уже привязан, его привязка не выполня-
ется.
Пример:
nav.classList.add('navigation’, 'panel', 'vertical');
♦ remove () — убирает привязку к текущему элементу стилевых классов с задан-
ными именами'.
remove (<ммя стилевого класса 1>, <имя стилевого класса 2>, . . .
<имя стилевого класса N>)
11ример:
nav.classList.remove('panel');
♦ toggle(<имя стилевого класса>) — если стилевой класс С указанным именем не
привязан к текущему элементу, выполняет его привязку, в противном случае —
удаляет его;
♦ replace () — заменяет один стилевой класс на другой:
replace(<имя заменяемого стилевого класса>,
<имя заменяющего стилевого класса>)
Возвращает true, если стилевой класс бы успешно заменен, и false — в против-
ном случае (например, если заменяемый стилевой класс & элементе отсутствует);
♦ contains (<имя стилевого класса>) — возвращает true, если стилевой класс
с указанным именем привязан к текущему элементу, и false— в противном случае:
if (nav.classList.contains('vertical'))
18 4.2. Управление встроенными стилями
Класс HTMLElement поддерживает свойство style, соответствующее одноименному
атрибуту стиля. Этому свойству можно присвоить CSS-код, создающий встроен-
ный стиль. Пример:
el.style = 'color: black; background-color: white;';
По style— необычное свойство. Если извлечь его значение, будет получена не
строка с CSS-кодом встроенного стиля, а объект класса cssstyleDeclaration, пред-
ставляющий этот стиль.
Класс cssstyleDeclaration поддерживает ряд свойств, представляющих различные
атрибуты стиля. Их имена формируются по следующим правилам:
♦ если имя атрибута стиля состоит из одного слова — свойство имеет то же имя;
♦ если имя атрибута стиля состоит из нескольких слов, разделенных дефисами. —
имя записывается в стиле camelCase.
Примеры именования свойств класса приведены в табл. 18.2.
Таблица 18.2 Примеры именования свойств класса CSSScyleDeclaration
Имя атрибута стиля Имя снойства
color color
background-color backgroundcolor
border-bottom-style borderBottomStyle
Примеры их использования
el.style.color = ’black';
el.style.backgroundcolor = ’white';
18.5. Изменение содержимого элементов
Для изменения содержимого элементов страниц класс HTMLElement предлагает три
свойства, доступные для чтения и записи:
♦ innerHTML — хранит НТМ1 -код содержимого текущего элемента:
<р id="parl">JavaScript</p>
const pari = document.getElementByld(’pari');
pari.innerHTML = 'Язык программирования <em>' + pari.innerHTML + '</em>';
/*
Результат:
Язык программирования JavaScript
*/
♦ textcontent — хранит текстовое содержимое элемента:
<р id="par2">JavaScript</p>
const par2 = document.getElementByld(’раг2’);
par2.textcontent = ’Язык программирования ' + par2.innerText;
/*
Результат:
Язык программирования JavaScript
*/
Если присвоить этому свойству HTML-код, все содержащиеся в нем теги будут
выведены как есть:
par2.textcontent = 'Язык программирования <em>' + par2.innerText + ’</em>';
/*
Результат:
Язык программирования <em>JavaScript</em>
*/
♦ innerText — ТО же самое. ЧТО И textcontent.
Класс HTMLElement поддерживает два полезных метода:
♦ insertAdjacentHTML (<^естопаг7ожен?;е>, <НТМЬ-код>} —вставляет указанный HTML-код
по заданному относительно текущего элемента местоположению. В качестве
местоположения указывается одна из строк:
• 'afterbegin' — после открывающего тега текущего элемента (в начало его
содержимого);
• 'beforeend' — перед закрывающим тегом текущего элемента (в конец содер-
жимого);
• 'beforebegin' — перед открывающим тегом текущего элемента;
• 'afterend' — после закрывающего тега текущего элемента.
11ример:
<р>язык программированич<р>
const pl = document.getElementsByTagName('p')[0];
const si = '<strong>JavaSuript</stronq> - ';
pl.insertAdj acentHTML('afterbegin', si) ;
const s2 = '<р>Применяется в веб-разработке</р>';
pl.insertAdjacentHTML('afterend', s2);
/*
Результат:
JavaScript - язык программирования
Применяется в веб-разработке
*/
♦ insertAdjacentText (<местоположение>, <текст>) — TO же самое, ЧТО И
insertAdjacentHTML(), только вставляет обычный текст.
18.5.1. Методы writef) и writelnf)
Добавить новое содержимое в элементы страницы и даже создать новые элементы
можно с помощью следующих методов класса HTMLDocument:
♦ write (<значение>) — вставляет указанное значение в то место страницы, в кото-
ром находится его вызов. Если значение представляет собой HTML-код, он
будет обработан. Пример:
// Создаем в этом месте новый абзац
document.write('<p>HTML и CSS</p>');
♦ writein(<значение>) —то же самое, что и write(), но после значения вставляет
разрыв строк (символ \п). Разрыв строк отображается веб-обозревателем в виде
обычного пробела.
Применение методов write () и writein() — самый простой (но, к сожалению, са-
мый медленный) способ вывести данные на экран. Он часто используется в про-
стых сценариях и при обучении программированию.
Внимание!
Если методы write () и writein () используются в обработчике события, ныведенные
ими данные полностью заменят исходное содержание страницы Поэтому в обработ-
чиках событий применять их нельзя
18.6. Создание элементов веб-страниц
путем конструирования
Существует другой способ создавать новые элементы страниц— более быстрый,
нежели применение методов write () и writein(). Это конструирование, при кото-
ром новые элементы создаются не формированием HTML-кода с последующей
передачей его веб-обозревателю для обработки, а непосредственным созданием
объектов, представляющих новые элементы.
Для создания объектов элементов страниц применяются два метода класса
HTMLDocument:
♦ createElement (<нмя тега>) — создает объект, представляющий тег с указанным
именем, и возвращает его в качестве результата. тега записывается в виде
строки без символов < и >;
♦ createTextNode (<текст>) — создает объект класса Text, представляющий тексто-
вое содержимое элемента, на основе указанного текста и возвращает его в каче-
стве результата.
Вывод сконструированного элемента на странице выполняется добавлением ею
в какой-либо другой элемент в качестве потомка. Это выполняют следующие мето-
ды класса HTMLElement:
♦ append о — добавляет заданные элементы— теги или текстовые фрагменты —
в конец текущего, после его последнего потомка:
append(<элемент 1>, <элемент 2>, . . . * элемент N>)
В качестве элементов можно указать обычные строки — они будут автоматиче-
ски преобразованы в объекты класса Text;
♦ prepend() — добавляет заданные элементы в начало текущего, перед его первым
потомком. Формат вызова тот же, что и у метода appendO;
♦ before () — вставляет заданные элементы перед текущим элементом на том же
уровне вложенности. Формат вызова тот же, что и у метода append();
♦ aftег() — вставляет заданные элементы после текущего элемента на том же
уровне вложенности. Форма! вызова тот же, что и у метода append О .
Пример:
<p><strong>JavaScript</strongx/p>
const р = document.getElementsByTagName('р')[0];
const strong = p.queryselector('strong');
p.prepend t'Изучайте ');
const t = document.createTextNode(' и ') ;
const em = document.createElement ('em’);
em. textContent = 'Python';
p.appendft, era, '!');
const hl = document.createElement(' hl');
hl.textContent = ’Совет на будущее';
p.before(hl);
const p2 = document.createElement('p');
p2.textContent = 'Они вам пригодятся.';
p.after(p2);
/*
Результат:
Совет на будущее
Изучайте JavaScript и Python!
Они ~ам пригодятся.
*/
♦ appendChild(<эпемент>) — добавляет указанный элемент — тег или текстовое
содержимое — в конец текущего, после чего возвращает добавленный элемент
в качестве результата:
♦ insertBefore() — вставляет один элемент перед другим.
appendCh i Id (Оставляемый элементу,
Олемент, перед которым его следует поместить>)
Если вторым параметром передать null, добавляемый элемент будет помещен
в конце текущего элемента (как при вызове метода appendChild ()). Вставленный
элемент возвращается в качестве результата. Пример:
<р>Первый абзац</р>
<р>Второй абзац</р>
const ps = document.getElementsByTagName('p');
const p3 = document.createElement('p');
p3.textContent = 'Третий абзац';
document.body.insertBefore(p3, ps(l]);
/*
Результат:
Первый абзац
Третий абзац
Второй абзац
*/
♦ insertAdjacentElement(< местоположением вставляемый элемент>) — вставляет
указанный элемент по указанному относительно текущего элемента местоположе-
нию. В качестве местоположения задается одна из ст рок:
• 'afterbegin' — после открывающего тега текущего элемента (в результате
вставляемый элемент станет его первым потомком):
• 'beforeend' — перед закрывающим тегом текущего элемента (вставляемый
элемент станет его последним потомком);
• 'beforebegin' — перед открывающим тегом текущего элемента (вставляемый
элемент станет его предыдущим соседом);
• 'afterend' — после закрывающего тега текущего элемента (вставляемый эле-
мент станет его следующим соседом).
Пример:
<р> - язык программирования<р>
const р4 = document.getElementsByTagName('р')[0];
const strong2 = document.createElement('strong');
strong?.textcontent = 'JavaScript';
p4.insertAdjacentElement('afterbegin', strong?);
const p5 = document.createElement('p');
p5.textcontent = ’Применяется в веб-разработке';
p4.insertAdjacentElement('afterend', p5);
/*
Результат:
JavaScript - язык программирования
Применяется в веб-разработке
*/
Следующие методы пригодятся, если нужно заменить один элемент другим или
вообще удалить его:
♦ replacewitho — заменяет теку щнн элемент указанными элементами:
replaceWith (^заменяющий элемент 1>, <заменяющмй элемент 2>, . . .
<заменяющий элемент N>}
В качестве элементов можно указать обычные строки — они будут автомати-
чески преобразованы в объекты класса Text.
1 Тример:
<p><em>HTML и CSS</em> - языки программирсвания</р>
const em2 = document.querySelector('р em');
const em3 = document.createElement('em');
етЗ. textcontent = ' JavaScript' ;
const em4 = document.createElement('em');
em4 .textConcent = ’Python’;
em2.replaceWith(em3, ' и em4) ;
/*
Результат:
JavaScript и Python - языки поограммироьания
*/
♦ replacechildren () — заменяет содержимое текущего элемента указанными эле-
ментами. В остальном аналогичен методу replaceWithO. Пример:
<p><em>HTML и CSS</env> - языки программирования</р>
const em5 = document.querySelector('р em');
const t2 = document.createTextNode('JavaScript1);
const t3 = document.createTextNode('Python');
em5.replaceChildren(t2, ' и ', t3);
/*
Результат:
JavaScript и Python - языки программрования
*/
♦ replaceChild (<заменяющ-1Й элементу, <заменяемый элемент>) — заменяет ОДИН
элемент другим и возвращает в качестве результата заменяемый элемент. Вызы-
вается у родил едя заменяемого элемента. Пример:
<p>HTML</p>
<р>Язык программирования</р>
const рб = document.getElementsByTagName('р')[и];
cons t h2 = document.createElement('hl');
h2.textContent = 'Python';
document.body.replaceChild(h2, pfo);
/*
Результат:
Python
Язык программирования
*/
♦ remove {) — удаляет текущий элемент:
<ul>
<li>JavaScript</li>
<li>PHP</li>
<li>Delphi</li>
</ul
const ul = document.getE.1 ementsByTagName ('ul') [01;
ul.lastElementChild.remove();
/*
Результат:
JavaScript
PHP
*/
♦ removeChiId[<удаляемым злемент>) — удаляет указанный элемент и возвращает
его в качестве результата. Вызывается у родителя удаляемого элемента. Пример:
<ul>
<li>JavaScript</li>
<li>₽HP</li>
<li>Delphi</li>
</ul
const ul2 = document.getElementsByTagName(’ul')[0] ;
ul2.removechild(ul2.lastElementChild);
/*
Результат:
JavaScript
PHP
*/
Создание новых элементов страницы путем конструирования выполняется быстрее,
чем способами, описанными в разд. 18.5. Однако код, конструирующий элементы,
очень громоздок и не отличается наглядностью. Поэтому конструирование приме-
няется в случаях, если требуется лишь добавить к содержимому страницы несколь-
ко новых элементов.
18.7. Упражнение. Фотогалерея с лайтбоксом
Следующий сделанный нам заказ —лайтбокс для фотогалереи.
Фотогалерея— набор графических изображений, организованный каким-либо
образом (в виде перечня, «плитки» и др.).
Лайтбокс— панель, выводящая полное !рафическое изображение при щелчке
на его миниатюре. Располагается поверх остального содержимого страницы
и обычно занимает все окно целиком.
Лайтбокс с полным изображением должен открываться при щелчке на миниатюре и
закрываться при нажатии кнопки закрытия, располагающейся в его верхнем правом
углу, или клавиши <Esc>.
Пам предоставили готовую фотогалерею с необходимым оформлением, у которой и
требуется создать лайтбокс И попросили вынести программный код в отдельный
файл.
I. Найдем в папке 18\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 18 7 html (страница с фотогалереей) и 18 7 css (таблица стилей
с оформлением) и папку 18 7 (изображения для фотогалереи). Скопируем всё это
куда-либо на локальный диск.
2. Откроем копию файла 18 7 html в текстовом редакторе и найдем код, создающий
фотогалерею:
<main class="photogallery">
<а href=#ximg src="18.7/l. jpg"x/a>
<a href=#ximg src="18.7/2. jpg"x/a>
</main>
Это сематическое основное содержимое (тег <main>) со стилевым классом
photogal lery. В ней находятся гиперссылки, содержащие сами изображения.
Отметим, что элементы, создающие лайтбокс, в HTML-коде отсутствуют. Их мы
создадим программно.
Наш лайтбокс мы организуем как блок (тег <div>) со стилевым классом
lightbox. Он заполнит собой всю видимую область окна веб-обозревателя.
В этом блоке будет находиться графический тег <img>, в котором станут выво-
диться полное изображение, и гиперссылка, содержащая значок крестика
(Unicode-код 0x2715) и формирующая кнопку закрытия.
Изначально лайтбокс скрыт. Чтобы вывести его на экран, достаточно дополни-
тельно привязать к нему стилевой класс active.
Все необходимые стили уже записаны в таблице стилей 18 7 css
3. Создадим в папке с файлами примера файл 18 7.js, в котором запишем код лайт-
бокса.
Сначала следует создать блок, формирующий лайтбокс и его содержимое. По-
местим лайтбокс непосредственно в секции тела страницы, в ее начале.
4. Запишем код. создающий лайтбокс и его содержимое:
ccnst lightbox = document.createElement(’div');
lightbox.className = 'lightbox';
const fulllmg = document.createElement('img');
lightbox.append(fulllmg);
const closeButton = document.createElement('a');
closeButton.href = ’#';
closeButton.textContent = '\u2715';
lightbox. aoper.d (closeButton) ;
document.body.prepend(lightbox);
Далее получим доступ к элементу, создающему саму фотогалерею, — по привя-
занному к нему стилевому классу photogallery.
5. Добавим код. получающий объект фотогалереи:
const phutogallery = document.queryselector('.photogallery');
Лайтбокс будет открываться при щелчках на изображениях, что выводятся в фо-
тогалерее. События щелчка мышью будем обрабатывать в элементе фотогалереи
(куда они всплывут из изображений).
6. Запишем обработчик события click фотогалереи:
photogallery.addEventListener('click', evt => {
evt.preventDefault();
if (evt.target.tagName = 'IMG') {
fulling.src = evt.target.src;
lightbox.classList.add('active');
}
}) ;
Поскольку события click, возникающие в изображениях, будут распространять-
ся через гиперссылки, в которые вложены эти изображения, нам следует отме-
нить обработчик этого события по умолчанию, который выполняет переход
по гиперссылке. Далее проверяем, является ли элемент-источник графическим
тегом <ing>. Если это так, заносим интернет-адрес выводимого изображения из
свойства src элемента-источника в свойство src тега <img>, находящегося в со-
ставе лайтбокса, чтобы это изображение появилось на экране. После чего оста-
нется лишь отобразить сам лайтбокс, привязав к нему стилевой класс active.
7. Добавим код обработчика события clicky кнопки закрытия лайтбокса:
closeButton.addEventListener('click', evt => {
evt. prevent. De fault () ;
lightbox.classList.remove('active');
});
11оскольку кнопка закрытия также представляет собой гиперссылку, не забыва-
ем отменить обработчик события по умолчанию.
8. Завершим код обработчиком события keydown секции тела страницы:
document.body.addEventListener('keydown', evt => {
if (evt.code == 'Escape')
lightbox.classList.remove('active');
)):
Код клавиши <Esc>— 'Escape'.
9. Привяжем веб-сценарий 18 7 js к странице 18 7 html самостоятельно.
Рис. 18.3 Результат выполнения упражнения 18 7 фотогалерея с лайтбоксом
в скрытом (слева) и открытом (справа) состоянии
Откроем страниц) 18 7 html в веб-обозревателе, щелкнем на каком-либо изображе-
нии, проверим, откроется ли лайтбокс (рис. 18.3), и закроем его.
18.8. Получение сведений о веб-странице
Класе HTMLDocument, представляющий страницу, поддерживает ряд свойств, храня-
щих сведения о самой странице. Все эти свойства, кроме одного, доступны только
для чтения:
♦ title — название текущей страницы, заданное в теге ctitlex Это единст венное
из упомянутых здесь свойств, доступное для записи (все остальные свойства
доступны только для чтения). Пример-
// Указываем у страницы новое название
document.title = ’Практикум JavaScript';
♦ url— полный интернет-адрес страницы в виде строки:
♦ referrer — интернет-адрес страницы, с которой был выполнен переход на
текущую страницу, в виде строки. Если текущая страница была открыта не в ре-
зультате перехода (а, например, набором интернет-адреса вручную или через
закладки веб-обозревателя), хранит пустую строку;
♦ characterset — название текстовой кодировки, в которой записан HTML-код
страницы, в виде строки;
♦ lastModified — временная отметка последнего изменения страницы в виде
строки формата:
<номер месяца>/<чмсло>/<год> <час^:<минугь1>:<секунды>
Нумерация месяцев начинается с 1;
♦ hidden — false, если в настоящий момент страница отображается на экране,
и true— в противном случае (например, пользователь переключился на другую
вкладку окна веб-обозревателя или свернул окно);
♦ visibilitystate — обозначение состояния отображения страницы в виде одной
из строк: 'visible’ (страница видима) или 'hidden' (скрыта).
При изменении состояния отображения страницы в ней возникает событие
visibilitychange.
Пример обработки этого события с целью приостановки воспроизведения видео,
когда страница скрывается, и возобновления, когда она вновь становится видимой:
cvideo id="film" src="/media/film.mp4"x/video>
const film = document.getElementByld('film’);
document.addEventListener('visibilitychange', () => {
if (document.hidden)
film, pause();
else
film, play О ;
});
18.9. Самостоятельные упражнения
I, Напишите веб-приложение, которое выводит на экран значения нажимаемых
клавиш и их коды.
Подсказка: обрабатывайте событие Keydown в секции тела страницы.
2. Переделайте слайдер, написанный при выполнении упражнения 17.8, таким
образом, чтобы элемент, в котором выводятся всплывающие миниатюры изо-
бражений, создавался программно.
Урок 19
Графика и мультимедиа
19.1. Получение сведений
о графических изображениях
Мы можем получить некоторые сведения о графическом изображении, выведенном
в теге <img>, — в частности, его изначальные размеры (разрешение). Для этого
применяются специфические свойства класса HTMLimageElement, производного от
класса HTMLElement и представляющего этот тег. Все свойства доступны только для
чтения:
♦ naturalwidth — изначальная ширина изображения в виде числа в пикселах;
♦ naturalHeight — изначальная высота изображения в виде числа в пикселах;
♦ complete — true, если загрузка файла с изображением завершена, и false —
в противном случае;
♦ currentsrc — интернет-адрес выводимого (рафического файла. Может отли-
чаться от заданного в атрибуте тега src или в свойстве src, если веб-сервер,
с которого запрашивается файл, выполнил перенаправление.
Также поддерживаются два дополнительных события, возникающие:
♦ load— по окончании загрузки файла с изображением:
<img src="image.png" id="jjngl">
const imgl = document.getElementByld(’imgl');
imgl.addEventListener('load', () => {
// Получаем изначальные размеры изображения
let w = imgl.naturalwidth;
Let h = imgl.naturalHeight;
});
♦ error— при невозможности за!рузи1ь файл с изображением (например, файл по
указанному в теге <img> интернет-адрес} не существует).
Оба события невсплывающие (см./х/зд. 17.5), не имеют обработчика по умолчанию
{см. разд. 17.7) и представляются объектом класса Event.
19.2. Управление
мультимедийными элементами
Мы можем получать сведения об аудио- и видеороликах, выведенных в тегах
<audio> и <video>. — в частности, продолжительность и разрешение (у видеороли-
ков). Мы также можем управлять их воспроизведением: запускать, приостанавли-
вать, изменять текущую позицию, настраивать уровень 1ромкости, приглушать
и вновь восстанавливать звук и др.
Для этого применяются специфические свойства, методы и события классов
HTMLAudioElement И HTMLVideoElemenr, производных ОТ класса HTMLElement И пред-
ставляющих теги <audio> и <video>.
19.2.1. Свойства
Классы HTMLAudioElement и HTMLVideoElement поддерживают следующие свойства:
♦ currentTime — текущая позиция воспроизведения ролика в виде вещественного
числа в секундах:
// Устанавливаем позицию воспроизведения видеоролика на 10 секунд
video1.currentTime = 10;
Использование этого свойства позволяет точно установить текущую позицию
воспроизведения ролика, однако изменение позиции может происходить с за-
держкой:
♦ volume— громкость звукового сопровождения в виде числа с плавающей точкой
от 0. ) (звук вообще не слышен) до 1.0 (максимальная громкость);
♦ muted — true, если звуковое сопровождение при текущем воспроизведении
ролика приглушено, и false— в противном случае;
♦ playbackRate — относительная скорость воспроизведения ролика. Указывается
в виде вещественного числа: число меньше 1.и задает замедленное воспроизве-
дение, больше 1.0 — ускоренное, отрицательное число — воспроизведение
в обратном направлении. Но умолчанию: 1. 0 (воспроизведение с обычной ско-
ростью). Пример:
// Замедленное вдвое воспроизведение
videol.playbackRate = 0.5;
// Ускоренное втрое воспроизведение
videcl.playbackRate = 3.0;
// Воспроизведение в обратном направлении с нормальной скоростью
videol.playbackRate = -1.0;
♦ preservespitch — если true, при ускоренном воспроизведении тембр звука
будет корректироваться, если false— не будет (по умолчанию: true);
♦ duration — продолжительность ролика в виде числа в секундах (только для чте-
ния);
♦ videowidth — ширина видеоролика в пикселах (только для чтения и только у ви-
деороликов);
♦ videoHeight — высота видеоролика в пикселах (только для чтения и только
у видеороликов):
♦ paused — true, если воспроизведение ролика приостановлено, false — в про-
тивном случае (только для чтения);
♦ seeking — true, если пользователь в настоящее время перемещает регулятор
текущей позиции ролика, false — в противном случае (только для чтения);
♦ ended — true, если воспроизведение ролика закончилось, false — в противном
случае (только для чтения);
♦ defaultMuted — то же. что и muted, но задает состояние звука по умолчанию,
которое будет использовано при последующих воспроизведениях ролика;
♦ defaultPlaybackRate — то же. что и playbackRate, но задает скорост ь воспроиз-
ведения по умолчанию, которая будет использована при последующих воспро-
изведениях ролика;
♦ currentSrc — интернет-адрес воспроизводимого в теку ший момент файла. Мо-
жет отличаться от заданного в атрибуте тега src или в свойстве src, если веб-
сервер. с которого запрашивается файл, выполнил перенаправление. Доступно
только для чтения;
♦ networkstate — статус загрузки мультимедийного файла в виде числа:
• о — файл еще не затру жался;
• 1 — файл заг ружей;
• 2 — файл заг ру жается;
• 3 — файл не найден.
Доступно только для чтения;
♦ readyState — статус самого ролика в виде числа:
• о — сведения о ролике недоступны;
• 1 — загружены только метаданные ролика (в частности, его разрешение и
продолжительность);
• 2 — загружен текущий кадр ролика;
• з — загружены текущий и следующий кадры ролика;
• 4 — загружено достаточно данных для бесперебойного воспроизведения ро-
лика.
Доступно только для чтения.
Помимо этого, поддерживаются свойства, соответствующие атрибутам тегов. Так
свойство src, соответствующее атрибуту тега src, хранит интернет -адрес воспроиз-
водимого файла.
19.2.2. Методы
Классы HTMLAudioElement И HTMLVideoElemenc поддерживают Следующие методы:
♦ load() — перезагружает текущий ролик. Обычно применяется, чтобы остано-
вить ролик и перемотать его в начало;
♦ pause () — приостанавливает текущий ролик;
♦ play () — запускает или возобновляет воспроизведение текущего ролика;
♦ fastseek(<позиция воспроизведення>) — устанавливает у текущего ролика за-
данную позицию воспроизведения. Позиция указывается в виде вещественного
числа в секундах.
Применение этого метода позволяет изменить позицию воспроизведения макси-
мально быстро, однако не настолько точно, как использование свойства
currentTime (см. разд. 19.2.1).
19.2.3. События
Все события, поддерживаемые мультимедийными элементами, невсплывающие, не
имеюг обработчика по умолчанию и представляются классом Event:
♦ loadscart — начата заг рузка файла с роликом;
♦ durationchange — загружена часть метаданных, в которой записана продолжи-
тельность ролика;
♦ loadedmetadata — засужена остальная часть метаданных, в которой записано,
в частности, разрешение ролика (если это видео);
♦ loadeddata — загружено достаточно данных, чтобы, по крайней мере, вывести
первый кадр ролика, однако его воспроизведение будет вестись с паузами на
подгрузку;
♦ progress — периодически возникает при загрузке файла;
♦ canplay — загружено достаточно данных, чтобы, но крайней мере, начать вос-
произведение ролика, однако в дальнейшем возможны паузы на подгрузку;
♦ canplaythrough — загружено достаточно данных, чтобы начать воспроизведение
ролика без пауз на подгрузку;
♦ play— воспроизведение ролика запущено или возобновлено нажатием кнопки
пуска или вызовом метода play о;
♦ playing — после события play, когда ролик реально начинает воспроизводиться.
Между этими событиями возможна задержка, пока веб-обозреватель подгружает
данные из файла;
♦ timeupdate — позиция воспроизведения ролика изменилась;
♦ volumechange — либо изменился уровень громкости, либо звук был приглушен,
либо, наоборот, восстановлен;
♦ waiting — в воспроизведении ролика возникла пауза, необходимая для подгрузки
данных;
♦ pause— воспроизведение ролика приостановлено;
♦ seeking— пользователь начал перемещать регулятор позиции воспроизведения;
♦ seeked— пользователь закончил перемешать регллятор позиции воспроизведе-
ния;
♦ racechange— скорость воспроизведения ролика изменилась;
♦ ended— воспроизведение ролика закончено;
♦ suspend — загрузка мультимедийного файла либо полностью завершилась, либо
приостановлена по какой-то причине (например, сервер перегружен);
♦ stalled — файл по какой-то причине не загружается (например, отсутствует на
сервере);
♦ abort — пользователь прервал загрузку файла (например, перешел на другую
страницу, не дождавшись окончания воспроизведения);
♦ error — ошибка при за> р\зке файла.
19.3. Упражнение. Стильный
видеопроигрыватель
Создадим средствами HTML. CSS и JavaScript стильный видеопроигрыватель,
в котором все элементы управления воспроизведением изначально скрыты и появля-
ются поверх видео при наведении на него курсора мыши (рис. !9.1).
Рис 19.1. Стильный видеопроигрыватель
слева — все элементы управления воспроизведением скрыты;
справа — элементы управления появились поверх видео после наведения на него курсора мыши
I. Найдем в папке 19\'sources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 19 3.html (страница с «заготовкой» для видеопроигрывателя)
и папк} 19 3 (вложенные папки images и media с видеофайлом и изображениями
для кнопок). Скопируем их куда-либо на локальный диск.
2. Откроем файл 19.3 html в текстовом редакторе и найдем в ней HTML-код, соз-
дающий проигрыватель:
<div id="player">
<video src="19.3/media/video.mp4"x/video>
<div class="interface">
<div class="play"x/div>
<div class="stop"></div>
<div class-"position"xdivx/divx/div>
<div class="mute"x/div>
<div class-"volume"xdivx/divx/div>
</div>
</div>
Он создается блоком (тегом <div>) с якорем player, в который вложены тег
<video> и блок со стилевым классом interface, содержащий все элементы
управления воспроизведением. Последние также создаются блоками, поскольку
их проще оформить. В число элементов входят кнопка пуска и паузы (стилевой
класс play), кнопка остановки (stop), регулятор позиции воспроизведения
(position), кнопка приглушения звука (mute) и регулятор громкости (volume).
Блок со стилевым классом interface изначально скрыт и выводится при наведе-
нии на блок player курсора мыши. Его можно вывести принудительно, привязав
к блоку player стилевой класс active.
В регуляторах указатели текущего положения создаются вложенными в них
пустыми блоками с голубым фоном. Задавая у такого блока ширину или высоту,
мы можем указать текущее положение регулятора.
Изображения в кнопках выводятся в виде графических фонов — так их проще
позиционировать.
3. Добавим в конце секции тела страницы тег <script>, в котором будем писать
сценарий, самостоятельно.
4. Запишем в созданном теге <script> код. получающий доступ к нужным элемен-
там страницы:
const player = document.getElementById(’player');
const video = player.firstElementChild;
const play = player.queryselectort'.play’);
const stop = player.queryselectorstop');
const position = player.queryselector('.position');
const posH = position.firstElementChild;
video.volume = 0.5;
Поскольку регулятор громкости изначально установлен на половину, мы сразу
же задаем у тега <video> половинную громкость.
После загрузки объема данных, достаточного для вывода первого кадра, регуля-
тор position следует установить в начало, а в кнопке play вывести изображение
«пуск» из файла 19 3\images\play png. Также нужно вывести на экран блок с эле-
ментами управления, чтобы пользователь сразу смог запустить видео.
5. Напишем обработчик событ ия canplay тега <video>, который выполнит все это:
video.addEventListener('canplay', () => {
posll. style .width = '0%';
play.style.backgroundlinage = 'url(19.3/images/play.png)';
player.classList.add('active'};
}) ;
Если ролик в текущий момент воспроизводится, в кнопке play следует вывести
изображение «пауза» (файл 19.3\images\pause png), а если поставлен на паузу —
изображение «пуск». При этом нужно также соответственно скрывать и выво-
дить блок с элементами управления.
6. Напишем обработчики событий playing и pause тега <video>, выполняющие эти
действия;
video.addEventListenerf'playing', () => (
play.style.backgroundimage = 'url(19.3/images/pause.png)';
player.classList.iemove('active');
});
videc.addEventListener('pause', () => {
play.style.backgroundimage = 'url(19.3/images/play.png)';
player.classList.add('active');
});
В процессе воспроизведения ролика нужно обновлять текущую позицию в регу-
ляторе position.
7. Объявим обработчик события timeupdate тега <video> — он и сделает все, что
нужно:
videc.addEventListener('t imeupdate’, () => {
posH.style.width = video.currentTime I video.duration * 100 +
}) ;
Вычислить ширину указателя (пустого блока, находящегося в блоке-регуляторе)
просто: мы делим текущую позицию воспроизведения на продолжительность
ролика, умножаем полученное частное на 100— и получаем ширину в процен-
тах.
Но окончании ролика следует привести проигрыватель в исходное положение.
Проще всего сделать это, выполнив перезагрузку ролика.
8. Объявим обработчик события ended проигрывателя, перезагружающий ролик:
video.addEventListener('ended', () => {
video.load();
});
9. Напишем обработчик события click кнопки play, который запустит воспроизве-
дение ролика или, если он уже проигрывается, приостановит его:
play.addEventListener('click*, () => {
i f (video.paused)
video.play();
else
video.pause() ;
});
10. Добавим обработчик события click кнопки stop, который вернет проигрыва-
тель в исходное положение:
stop.aadEventLisrener(’click*, О => {
video.load();
});
Осталось сделать так, чтобы при щелчках на регуляторе position соответствен-
но изменялась текущая позиция воспроизведения ролика.
11. Добавим обработчик события click регулятора position, который сделает это:
position.addEventListener('click', evt => {
video.currentTime = evt.otfsetX /
position.clientwidth * video.duration;
});
Текущую позицию воспроизведения вычисляем, разделив горизонтальную
координату позиции в регуляторе, на которой был выполнен щелчок, на его
ширину и умножив полученное частное на продолжительность ролика.
Откроем страницу 19 3.html в веб-обозревателе, запустим ролик на воспроизведение,
поставим на паузу и запустим вновь. Попробуем переместить регулятор позиции
воспроизведения и посмотрим, что получится.
19.4. Самостоятельное упражнение
В видеопроигрывателе, написанном при выполнении упражнения 19.3. реализуйте:
♦ настройку уровня громкости регулятором volume;
♦ приглушение звука кнопкой mute. При этом, если звук приглушен, на кнопке
следует вывести изображение из файла I9.3\images\unmute png, а если не приглу-
шен — изображение из файла 19 3\images\mute png
Урок 20
Веб-формы
и элементы управления
20.1 . Работа с элементами управления
Мы можем получать значения, занесенные в элементы управления, временно де-
лать их недоступными, реагировать на изменение значений в них, установку или
сброс флажка и др.
20.1.1 . Свойства, методы
и события элементов управления
Специфические свойства, методы и события классов, представляющих элементы
управления (помимо полученных в «наследство» от базового класса HTMLEiement),
представлены в табл. 20.1-20.3 соответственно. Классы событий, возникающих
в элементах управления, и их свойства представлены в табл. 20.4.
Таблица 20.1. Свойства элементов управления
Свойство Описание Поддерживающие его элементы управления
form Объект веб-формы, в которой находится текущий элемент управления Все
value Занесенное значение е виде строки Поля ечода. пыбора. регулятор, область редактирования и скрытое поле
defaultvalue Изначальное значение, заданное в атрибуте тега value
selectionstart Номер первого символа выделенного фрагмен- та значения, если ничего не выделено — номер символа под текстовым курсором Поля ввода и область редактирования
selectionEnd Номер последнего символа выделенного фраг- мента значения, если ничего не чыделено — номер символа под текстовым курсором
valueAsNumbei Введенное число в числовом (нестроковом) виде Поле анода числа
Заданное время в виде количества миллисекунд, прошедших с полуночи, ° числовом виде Поле ввода времени
valueAsDate Заданная дата в виде объекта класса Date Поле ввода даты
Таблица 20.1 (окончание)
Свойство Описание Поддерживающие его элементы управления
checked Если true — флажок или переключатель установлен, если false — сброшен Флажок и переключа- тель
defaultChecked Соответствует атрибуту тега checked
files Объект-коллекция выбранных файлов (будет рассмотрен на уроке 25) Поле выбора файлов
options Объект-коллекция пунктов имеющихся в списке Список
length Количест зо пунктсе в списке
selectedlndex Номер выбранного пункта (нумерация начина- ется с 0). Если ни один пункт не гыбран, хранит значение -1. Если список позволяет выбирать несколько пунктов одновременно, хранит номер первого выбранного пункта
selectedOptions Объект-коллекция выбранных пунктов
type ' select-multiple' —если можно выбрать произвольное количество пунктов, ' select-one' — если только один пункт
selected Если true — текущий пункт выбран, если false — не выбран Пункт списка
defaultSelected Соответствует атрибуту тега selected
index Порядковый номер текущего пункта, начиная с 1
text Текст текущего пункта
Таблица 20.2. Методы элементов управления
Метод Описание Поддерживающие его элементы управления
fOCUS() Устанавливает фокус ввода на текущий элемент Все
blur () Удаляет фокус ввода с текущего элемента
select() Выделяет весь текст Поля ввода и область редактирования
stepUp( [<с£>]) Увеличивает занесенное число на величину <1 Если параметр не указан, выполняет инкре- мент Поле вкода числа и регулятор
stepDown([<d>]) Уменьшает занесенное число на величину <1 Если параметр не указан, выполняет декремент
Таблица 20.2 (окончание)
Метод Описание Поддерживающие его элементы управления
add(<it>[, <р>]) Добавляет новый пункт it в позицию р. Если позиция не указана добавляет элемент в конец списка Список
remove (<р^) Удаляет пункт под номером р
Таблица 20.3. События элементов управления
Имя события Когда возникает?
События, имеющие отношение к занесенному значению Всплывающие Обработчик по умолчанию отсутствует
change В полях выбора флажках переключателях и списках — при изменении состояния (установке сбросе выборе пункта списка) В полях ввода и областях редактирования — при потере фокуса если занесенное значение было изменено
input В полях ввода и областях редактирования — сразу при изменении занесенного значения
invalid Занесенное значение некорректно Возникает перед отправкой данных, выполняемой формой, . результате чего отправка отменяется
select В полях ввода и областях редактирования — при выделении фрагмента значения
События фокуса ввода Обработчик по умолчанию отсутствует
tocus Получение текущим элементом фокуса ввода (невсплывающее)
focusin Получение текущим элементом фокуса ввода (всплывающее)
blur Потеря текущим элементом фокуса ввода (невсплывающее)
focusout Потеря текущим элементом фокуса ввода (всплывающее)
События буфера обмена Всплывающие
cut Вырезание выделенного текста (обработчик по умолчанию выполняет вырезание текста)
paste Вставка текста из буфера обмена (обработчик по умолчанию выполняет вставку текста)
Таблица 20.4. Классы событий и их свойства
Свойство Хранимое значение
Класс FocusEvent Производный от класса Event. Представляет события фокуса ввода
relatedTarqet В случае события fccus или focusin — элемент управления потерявший фо- кус вода В случае события blur или focuscut — элемент, получивший фокус
События буфера обмена представляются классом clipboardEvent
Остальные события представляются классом Event
20.1.2 . Получение и обработка
введенного в элемент значения
Получение значения, введенного в элемент управления, осуществляется по-разному
в зависимости от разновидности элемента управления:
♦ обычное поле ввода, поле ввода пароля, интернет-адреса, адреса электронной
почты, искомого слова, область редактирования или скрытое поле— значение
берется из свойства value без каких бы то ни было преобразований:
<input id="val">
const val = document.getElementByld('val');
Jet value = val.value;
♦ поле ввода числа или регулятор — введенное число, уже преобразованное в чи-
словой тип, можно извлечь из свойства valueAsNumber:
cinput type="number" io="num">
const num = document.getElementByld('num');
let number = num.valueAsNumber;
Это же число хранится в свойстве value в виде строки, которую следует преоб-
разовать в числовой тип:
// Преобразуем значение в числовой тип — явно
number = tnum. value;
// Или неявно
let а = num.value / 100;
Этот способ получения занесенного числа применялся ранее, до появления
свойства valueAsNumber;
♦ поле ввода даты и времени — дата и время в свойстве value хранятся в виде
строки формата <год>-<номер месяца>-<члсло>Т!<часЫ>:<М1-1нуть£> (нумерация МС-
сяцев начинается с 1):
<input type="datetime-local" id="dattim">
const dattim = document.getElementByld('dattim');
// Создаем на основе введенных даты и времени временную отметку —
II объект класса Date
let dt = new Date(dattim.value);
♦ поле ввода даты — дата в виде объекта класса Date хранится в свойстве
valueAsDate:
cinput type="date" id="dat">
const dat = document.getElementByld('dat’);
// Создаем на основе введенной даты временную отметку
let d = dat.valueAsDate;
Кроме того, заданная дата хранится в свойстве value в виде строки формата
<год>-<номер месяца>-<число> (нумерация месяцев начинается с I):
d = new Date(dat.value);
Этот способ получения введенной даты применялся ранее, до появления свойст-
ва valueAsDate;
♦ поле ввода времени— время в виде количества секунд, прошедших с полуночи
текущих суток, хранится в свойстве valueAsNumber.
Кроме того, время можно получить из свойства value, где оно хранится в виде
строки формата <часы>:<мннуты> в 24-часовом представлении:
♦ поле выбора цвета— цвет в свойстве value хранится в виде строки формата
#<дхо<в>, где r, G и в представлены шестнадцатеричными числами из двух
цифр;
♦ флажок и переключатель — из свойства checked:
<input type="сheckbox" id="chk">
const chk = document.getElementByld('chk');
if (chk.checked)
// Флажок установлен
else
// Флажок сброшен
♦ список с возможностью выбора только одного пункта — номер выбранного
пункта МОЖНО получить ИЗ свойства selectedlndex:
<select id="sel">
<opt ion value="html">HTML</option>
«.option value="css">CSS</option>
<opt ion value="j s">JavaScript </opt ion>
<cpt ion value="php">PHPc/option>
<option value="python">Python</opt ion>
</select>
const sei = document.getElementByld('sei’);
let selected = sei.selectedlndex;
♦ список с возможностью выбора нескольких пунктов — коллекцию выбранных
пунктов можно получить из свойства selectedoptions:
Cselect size="5" id="msel" multiple>
<opt ion value="html">HTMLc/option>
Coption value-"css">CSSc/option>
ception value="js">JavaScriptc/option>
coption value="php">PHPc/option>
coption value="python">Pythonc/opt ion>
c/select>
const msel = document.getElementByld('msel');
selected = [];
// Помещаем значения выбранных пунктов (задаются в атрибуте value
// тега <option>) в массив selected
for (let option of selectedOptions)
selected.push(option.value);
Также можно перебрать все пункты списка, коллекция которых доступна из
свойства options, и выяснить, какие из них выбраны, проверкой свойства
selected:
for (let: option of msel.options)
if (option.selected)
selected.push(option.value);
Этот способ применялся ранее, когда свойство selectedOptions еще не поддер-
живалось.
20.1.3 . Программное создание и удаление
пунктов списка
Для программного создания и удаления пунктов списка можно использовать мето-
ды add() и removed, поддерживаемые классом списка (см.разд. 20.1.1):
<select size="5" id="psel">
</select>
const psel = document.getElementByld(’psel’);
// Создаем пункты в списке psel
let languagevalues = Г'html', 'css', 'js', 'php', 'python'];
let languageTexts = ['HTML', 'CSS', 'JavaScript', 'PHP', 'Python'];
let i, opt;
for (i = 0; i < languagevalues.length; ++i) {
opt = document.createElement('option');
opt.value = languageVaiues[i];
opt.textContent = languageTexts[i];
psel.add(opt);
}
// Удаляем пункт JavaScript
psel.remove(2);
20.1.4 . Индикатор процесса
Класс HTMLProgressEiement, представляющий индикатор процесса, поддерживает
следующие свойства:
♦ max — максимально допустимое значение, выводимое индикатором процесса,
в виде вещественного числа (соответствует одноименному атрибуту тега
<progress>);
♦ value — текущее выводимое значение в виде вещественного числа. Если инди-
катор процесса находится в неопределенном состоянии, хранит значение 0. Со-
ответствует одноименному атрибуту тега <progress>;
♦ position — частное от деления текущего значения (свойство value) на макси-
мально допустимое (свойство max) в виде вещественного числа. Если индикатор
процесса находится в неопределенном состоянии, хранит значение -1. Доступно
только для чтения.
Пример:
^progress id="progress"X/progress>
const progress = document.getElementByld('progress');
progress.max = 100;
progress.value = 50;
let pos = progress.position;
20.2 . Свойства, методы и события веб-формы
Веб-форма представляется классом HTMLFormElement. Помимо свойств, соответст-
вующих атрибутам тегов, и свойств, методов и событий, унаследованных от базо-
вого класса HTMLElement, он поддерживает еще несколько свойств (табл. 20.5).
методов (табл. 20.6) и событий (табл. 20.7).
Таблица 20.5. Свойства веб-формы
Свойство Описание
elements Коллекция =сех элементов управления, имеющихся в веб-срорме
length Количество элементов управления в веб-форме
Таблица 20.6. Методы веб-формы
Метод Описание
submi t() Выполняет отправку данных, как если была нажата кнопка отправки данных
reset() Выполняет сброс веб-формы, как если бы была нажата кнопка сброса
Таблица 20.7. События веб-формы
Событие Когда возникает?
Всплывающие
submit Перед отправкой данных (обработчик по умолчанию производит отправку данных)
reset Перед сбросом веб-формы (обработчик по умолчанию сбрасывает веб-форму)
Таблица 20.7 (окончание)
Событие Когда 'юзникает?
Невсплывающие
formdata После создания объекта класса FormData, хранящего данные, которые были занесены в веб-форму (подробности — на уроке 28)
Событие submit представляется объектом класса SubmitEvent, не содержащего по-
лезных свойств или методов, событие reset— объектом класса Event. Событие
formdata представляется объектом класса FormDataEvent, поддерживающего свойст-
во formData, которое хранит ссылк} на созданный объект класса FormData.
20.3 . Упражнение. Клиентское веб-приложение,
формирующее CSS-код рамки, часть 1
Для практики напишем клиентское веб-приложение, генерирующее CSS-код для
создания у элемента страницы рамки на основе введенных пользователем данных.
I Найдем в папке 20Vsources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 20 3 html (страница с «заготовкой» для приложения) и скопиру-
ем его куда-либо на локальный диск.
2. Откроем копию файла 20 3 html в текстовом редакторе.
Страница, хранящаяся в файле, содержит набор элементов управления, в кото-
рые пользователь будет заносить необходимые сведения (рис. 20.1).
CSS-код для создания рамки
Толщина 1 рх
Стиль: Сплошная v
Цвет
Скругленные углы
Радиус скругления 5рх
Сгенерировать код
Рис 2С.1. «Заготовка» для создания веб-приложения
Вот перечень всех элементов страницы, которыми мы будем манипулировать
iipoi раммно, и их якорей:
• поле ввода Толщина — width;
• список Стиль — style;
• поле выбора цвета Цвет — color;
• флажок Скругленные углы — rounded;
• поле ввода Радиус скругления — radius;
• текст фиксированного форматирования (тег <рге>), в котором будет выво-
диться готовый CSS-код (он находится ниже кнопки, изначально пуст и по-
этому не виден на экране) — output.
Все элементы управления пометены в веб-форму с якорем form. В веб-форме
присутствует кнопка отправки данных Сгенерировать код.
3. Добавим в конце секции тела страницы тег <script>, в котором будем писать
сценарий, самостоятельно.
4. Запишем код, получающий доступ к упомянутым элементам страницы:
const form = document.getElementByld;'form');
const width = document.getElementByld;'width');
const style = document.getElementByld;'style');
const color = document.getElementByld;'color');
const rounded = document.getElementByld;'rounded');
const radius = document.getElementByld('radius 1};
const output = document.getElementByld('output');
Поле ввода Радиус скругления должно быть доступно для ввода только тогда,
когда установлен флажок Скругленные углы.
5. Напишем обработчик события change флажка Радиус скругления., который
будет делать поле ввода Скругленные углы доступным или недоступным, в за-
висимости от состояния флажка:
rounded.addEventListener('change', О => {
radius.disabled = Irounaed.checked;
));
Код. который при нажатии кнопки Сгенерировать код сгенерирует и выведет
на экран CSS-код для задания рамки, реализуем в виде обработчика события
submit веб-формы. Только не забудем отменить обработчик события по умолча-
нию, иначе страница будет перезагружена с потерей всех введенных данных.
Дополнительно сделаем так, чтобы сгенерированный стиль рамки привязывался
к элементу preview— так пользователь сразу увидит, какая рамка у него полу-
чилась.
6. Напишем обработчик событ ия change веб-формы, который сгенерирует CSS-код:
form.addEventListener('submit', evt => {
evt.preventDefault() ;
let s = 'border: ${width.value) ' +
'${style.options[style.selected)ndexj.value) ' +
'${color.value};‘;
i f (rounded.checked}
s += '\nborder-radius: ${radius.value};
output.style = s;
output.textContent = s;
});
В случае задания у рамки скругленных углов сгенерированный CSS-код будет
состоять из двух атрибутов стиля: border и border-radius — выведенных в две
строки. Мы добились этого, поместив в начале первой строки специальный сим-
вол \п, создающий разрыв строки. Текст фиксированного форматирования (тег
<рге>) выведет этот разрыв как есть.
Откроем страницу 20 3.html в веб-обозревателе и проверим веб-приложение в работе
(рис. 20.2).
CSS-код для создания рамки
Рис. 20 2. CSS-код для задания рамки сгенерирован
20.4 . Валидация
Валидация — проверка данных, занесенных в элементы управления, на кор-
ректность согласно заданным правилам.
Так, если в какое-либо поле должно быть занесено вещественное число, следует
проверить, является ли значение, занесенное пользователем, вещественным числом,
и, если это не так. вывести соответствующее сообщение.
Валидации подлежат лишь элементы управления, в которые можно заносить значе-
ния. Валидации не подлежат элементы управления, недоступные для пользователя,
доступные лишь для чтения и в текущий момент не выводящиеся на страницу.
Валидацию можно выполнить как средствами HTML, так и программно, в веб-
сценарии.
20.4.1 . Валидация средствами HTML
Различные элементы управления часть действий по валидации занесенного значе-
ния выполняют самостоятельно. Так. поле ввода числа самостоятельно проверяет,
является ли введенное значение целым числом.
Кроме того, язык HTML предоставляет ряд атрибутов тегов, которые позволяют
указать правила, согласно которым будет выполняться валидация введенных зна-
чений:
♦ required — без значения — указывает, что в элемент управления обязательно
должно быть занесено значение:
Толщина <input id="width" required>
♦ minlength — минимальная длина допустимой ко вводу строки в символах (соот-
ветствует свойству minLength);
♦ maxlength— максимальная длина допустимой ко вводу строки в символах (соот-
ветст вует свойству maxLength);
♦ pattern — в обычном поле ввода — регулярное выражение, которому должно
соответствовать заносимое значение. Указывается без ограничивающих слешей.
Пример:
<input name="name" pattern="[A-Za-z]+">
♦ min — в поле ввода числа или регуляторе — минимальное допустимое к вводу
число;
♦ max — в поле ввода числа или регуляторе— максимальное допустимое к вводу
число;
♦ step — в поле ввода числа — делитель, на который введенное число должно
делиться нацело'.
Если введенное значение не проходит Hl ML-валидацию, веб-обозреватель отобра-
зит возле соответствующею элемента управления всплывающее сообщение об
ошибке ввода (рис. 20.3).
Толщина | |
Q Заполните это поле
Рис. 2[j 3. Всплывающее сообщение об ошибке =вода
20.4.1.1. Вывод собственных сообщений об ошибках
Выводимое всплывающее сообщение об ошибке ввода содержит типовой текст, из
которого не всегда можно понять, что же требуется ввести. Однако есть возмож-
ность указать для всплывающего сообщения свой текст.
Или. менее формально, промежуток между двумя допустимыми к вводу числами.
Классы элементов управления поддерживает свойство validity, хранящее объект
класса Validitystate. Он содержит сведения о том, какого рода ошибка была до-
пущена при вводе значения.
Класс validitystate поддерживает следующие свойства, доступные только для
чтения:
♦ valid— true, если введенное значение полностью корректно, false— в пролив-
ном случае;
♦ valueMissing— true, если в обязательный для заполнения элемент управления
ничего не введено, false— в противном случае;
♦ badinput — true, если введено значение неподходящего типа (например, в ноле
ввода числа занесено что-либо, не являющееся числом), false — в противном
случае;
♦ typeMismatch — true, если введено значение подходящего типа, но некорректно-
го формата (например, поле ввода интернет-адреса занесена строка, не являю-
щаяся интернет-адресом), false— в противном случае;
♦ tooShort — true, если длина введенной строки меньше заданной в атрибуте тега
minlength, false— в противном случае;
♦ tooLong — true, если длина введенной строки превышает заданную в атрибуте
тега max length, false— в противном случае;
♦ patternMismatch — true, если введенное значение не совпадает с регулярным
выражением, заданным в атрибуте тега pattern, false — в противном случае;
♦ rangeunderflow — true, если число, занесенное в ноле ввода числа, меньше ука-
занного в атрибуте тега min, false— в противном случае;
♦ rangeoverflow — true, если число, занесенное в поле ввода числа, больше ука-
занного в атрибуте тега max, false — в противном случае;
♦ stepMismatch — true, если число, занесенное в поле ввода числа, не попадает
в интервал между значениями, заданный атрибутом тега step, false— в против-
ном случае;
♦ customError — true, если метод setCustomValidity О был вызван ранее с переда-
чей ему непустой строки, false — с передачей пустой строки.
Проверку значений этого объекта выполняют в обработчике события change эле-
мента управления.
Для указания своего текста сообщения об ошибке ввода применяется метод
setcustomvalidity о. поддерживаемый всеми элементами управления:
setCustomValidity(<текст сообщения об ошибке ввода>)
Если в вызове этого метода указан непустой текст сообщения, элемент управления
считается содержащим некорректное значение, и при попытке выполнить отправку
данных из веб-формы рядом с ним будет выведено всплывающее сообщение с за-
данным текстом.
Если же в вызове метода указать пустую строку, элемент управления будет счи-
таться содержащим корректное значение.
Если метод setCustomValidity () был вызван с передачей ему непустой строки,
свойству customError объекта с результатами валидации (который хранится в свой-
стве validity элемента управления) будет присвоено значение true. Вследствие
чего элемент управления будет счит аться содержащим некорректное значение.
Когда пользователь впоследствии исправит некорректное значение, и оно успешно
пройдет Н1 ML-валидацию, значение false в свойство customError объекта validity
автоматически занесено не будет. Вследствие чего элемент управления все так же
будет считаться некорректным, и при попытке отправить данные веб-обозреватель
будет выводить всплывающее сообщение об ошибке ввода. Чтобы убрать это со-
общение, предварительно необходимо проверить, хранит ли свойство customError
объекта validity значение true, и в этом случае вызвать метод setCustomValidity ()
с передачей пустой строки.
Пример:
Толщина cinput id="width" required>
const width = document.getElementByld('width');
width.addEventListener('change', () => {
// Сначала убираем сообщение об ошибке ввода,
// заданное при предыдущей валидации...
if (width.validity.customError)
width.setCustomValidity(' ');
// ...а уже потом проверяем значения свойств объекта с результатами валидации
if (width.validity.valueMissing)
width.setCustomValidity('Введите толщину’);
});
20.4,2 . Программная валидация
Если требуется выполнить валидацию введенного значения по более сложным пра-
вилам, инструментов, предлагаемых HTML, будет недостаточно. В этом случае
валидацию следует реализовать программно, полностью средствами JavaScript.
Программную валидацию надо производить также в обработчике события change
элемента управления. Если значение не удовлетворяет предъявляемым требо-
ваниям, необходимо задать сообщение об ошибке ввода вызовом метода
setCustomValidity () (см. разд. 20. 4.1. /).
Перед программной валидацией требуется проверить, хранит ли свойство
customError объекта результатов валидации значение true, и, если это гак, удалить
заданное ранее сообщение об ошибке ввода, вызвав метод setCustomValidity О
с передачей ему пустой строки. Если этого не сделать, пользователь, даже исправив
введенное ошибочное значение, получит то же самое сообщение об ошибке.
Пример проверки, является ли занесенная величина радиуса скругления положи-
тельной:
Радиус скругления <input. id="radius">
const radius = document.getElementByld(’radius');
radius.addEventListener('change', () => {
if (wjdth.validity.customError)
width.setCustomValidity(' ');
const w = parseFloat(radius.value);
if (w < 0)
radius.setCustomValidity('Введите положительный радиус скругления');
}) i
Событие change возникает после выполнения валидации, заданной средствами
HTML. Следовательно, в обработчике этого события можно получить доступ
к объекту результатов валидации, хранящемуся в свойстве validity.
Вообще, программную валидацию рекомендуется проводить, если введенное
значение удовлетворяет правилам HTML-валидации. Это можно сделать, предвари-
тельно выяснив, хранит ли свойство valid объекта с результатами валидации зна-
чение true.
Пример программной валидации, выполняемой лишь в случае, если значение про-
ходит Н FML-валидацию:
Радиус скругления cinput id= "radius" required>
const radius = document.getElementByld('radius');
radius.addEventListener('change', () => {
if (radius.validity.cust omError)
radius.setCustomValidity('');
if (radius.validity.valid) {
const r = parseFloat(radius.value);
if (r < 0)
radius.setCustomValidity('Введите положительный ' +
'радиус скругления');
}
});
20.4,3 . Дополнительные инструменты валидации
Классы элементов управления поддерживают два полезных метода:
♦ checkvalidity() — выполняет HTML-валидацию, создает и заносит в свойство
validity объект с результатами валидации (см. разд. 20.4.1.1). Возвращает true,
если введенное значение проходит HTML-валидацию, или false— в противном
случае. Никаких всплывающих сообщений об ошибках ввода не выводится:
♦ reportvalidity () — то же самое, что и checkvalidity (), только, если значение не
проходит HTML-валидацию, дополнительно выводит всплывающее сообщение
об ошибке ввода.
Еще могут пригодиться два свойства, поддерживаемые элементами управления:
♦ validationMessage— сообщение об ошибке ввода в виде строки. Если введенное
значение корректно, или текущий элемент управления не подлежит валидации,
хранит пустую строку;
♦ willValidate— true, если текущий элемент управления подлежит валидации,
false, если не подлежит (например, временно недоступен для пользователя).
20.5 . Упражнение. Клиентское веб-приложение,
формирующее CSS-код рамки, часть 2
Реализуем в веб-приложении, создающем CSS-код для рамки, валидацию. Пусть
в случае, если не введена толщины рамки, выводится сообщение Введите толщину
рамки, а если не указана единица измерения толщины — Задайте единицу изме-
рения после значения толщины.
I Найдем в папке 20\ех20.3 сопровождающего книгу ({тайлового архива (см. при-
ложение 5) файл 20.3 html и скопируем его куда-либо на локальный диск.
2. Откроем копию файла 20 3 html в текстовом редакторе.
Проверку, введена ли толщина рамки, будем выполнять средствами HTML.
3. Добавим в тег <input>, создающий поле для ввода толщины рамки, атрибут
requi red:
'•-input, id~~ "width " s-i-ze~"fe" ntaxlengt-ft=~1Ib1-'- vaiue-"-lpx">
Cinput id="width" size="6" maxlength="6" value="lpx" required>
Л вот выяснять, поставил ли пользователь после толщины рамки обозначение
единицы измерения CSS. будем программно.
Для проверки, присутствует ли обозначение единицы измерения после значения
толщины, мы напишем функцию isUnitPresents (<значениё>). Она будет воз-
вращать true, если в конце заданного значения присутствует одна из поддержи-
ваемых CSS единиц измерения, и false— в противном случае. Обозначения
единиц измерения будем хранить в отдельном массиве.
4. Добавим в сценарий код, создающий массив с обозначениями единиц измерения
CSS, и объявление функции isUnitPresents О:
const units = I'px', 'pt', 'пи', 'an', '%', 'in', ’pc'J;
function isUnitPresents (val.) {
for (let u of units) {
i f (val.endsWi th(u))
return true;
)
return false;
}
Мы перебираем массив обозначений единиц измерения и проверяем, заканчива-
ется ли переданное с параметром значение очередным обозначением. Если это
гак, прерываем выполнение функции и возвращаем true. В противном случае
после завершения цикла возвращаем false.
Программная валидация значения толщины будет выполняться в обработчке
события change поля для ввода этого значения.
5. Занесем код обработчика события change поля для ввода толщины рамки:
width.addEventListener('change', () => {
if (width.validity.customError)
width.setCustomValidity('');
if (width.valid!ty.valid) {
if (iisUnitPresents(width.value))
width.setCustomValidity('Задайте единицу измерения после ' +
'значения толщины');
) else if (width.validity.valueMissing)
width.setCustomValidity('Введите толщину');
});
Откроем страницу 20 3.html в веб-обозревателе, удалим значение толщины, изна-
чально присутствующее в поле ввода Толщина, нажмем кнопку Сгенерировать
код и посмотрим, выводится ли всплывающее сообщение с просьбой ввести тол-
щину. Далее запишем значение толщины без указания единицы измерения, снова
попробуем сгенерировать CSS-код и проверим, будет ли выведено всплывающее
сообщение. Напоследок введем корректное значение толщины и сгенерируем код.
20.6. Диалоги
Диалог— имитация отдельного диалогового окна средствами HTML. Диалог
выводится поверх остального содержимого страницы и может как блокировать
его {модальный диалог), так и не блокировать {немодальный).
Диалог создается парным тегом <dialog>, в который помещается все его содер-
жимое. Изначально диалог закрыт. Открыть его можно только программно, а за-
крыть как программно, так и нажатием клавиши <Esc>.
Диалог представляется классом HTMLDiaiogEiement. Этот класс поддерживает ряд
полезных свойств, методов и событий.
Методы класса HTMLDialogElement:
♦ show() — выводит текущий диалог на экран в качестве немодального;
♦ showModal () — выводит текущий диалог на экран в качестве модального;
♦ close () — закрывает открытый ранее диалог.
Свойства класса диалога, доступные как для чтения, так и для записи:
♦ эреп— true, если диалог открыт, false, если закрыт;
♦ returnvalue — значение, возвращаемое из диалога, в виде строки. Должно зано-
ситься явно в коде, закрывающем диалог.
События диалога:
♦ close — возникает после закрытия диалога:
♦ cancel — возникает после закрытия диалога нажатием клавиши <Esc>, наряду
с событием close.
Пример диалога:
cinput type="button" icb"show" уа1ие="Вывести диалог">
«dialog id="dialog">
<select id="sel">
<option value-"html">HTML</option>
Coption value=-"css">CSS</option>
Coption va1ue="j s">JavaSc r ipt</opt ion>
copti on va1ue="php"> PH P</option>
coption value="python">Python</option>
</select>
cinput type="button" id="ok" value="OK">
cinput type="button" id="cancel" уа1ие="0тмена">
</aialog>
const show = document.getElementByld('show’);
const dialog = document.getElementByld(’dialog1);
const sei = document.getElementByld(’sei');
const ok = document.getElementByld(’ok’);
const cancel = document.getElementByld(’cancel1);
// При нажатии кнопки Вывести диалог открываем диалог
show.addEventListener(’click1, () => {
di alog.showModal();
});
// При нажатии кнопки OK закрываем диалог и указываем выбранный в списке пункт
//в качестве возвращаемого значения
ok.addEventListener(’click’, () => {
dialog.returnvalue = sei.options[sei.selectedlndex].value;
dialog.close();
});
// При нажатии кнопки Отмена закрываем диалог и задаем пустую строку
// в качестве возвращаемого значения
cancel.addEventListener(’click’, () => (
dialog.returnvalue = ’’;
dialog.close();
});
// Обрабатываем закрытие диалога
dialog.addEventListener(’close', () => {
if (dialog.returnvalue) {
// Диалог был закрыт нажатием кнопки ОК. Получаем выбранный в списке
// пункт.
const selectedltem = dialog.returnvalue;
} else
// Диалог был закрыт нажатием кнопки Отмена
});
Есть другой способ закрыть диалог и обработать его закрытие. Для этого следует
поместить все элементы управления, присутствующие в диалоге, в веб-форму, ука-
зать в ее теге атрибут method со значением dialog и превратить кнопки, закрываю-
щие диалог, в кнопки отправки данных. После щелчка на любой из этих кнопок
диалог будет закрыт автоматически, а его свойству returnvalue присвоено значение
атрибута value тега кнопки, на которой был выполнен щелчок (т. е. выводимая на
кнопке надпись). Пример:
<div><input type="button" id="show" value="Вывести jjwanor">c/div>
<dialog id="dialog">
cform method="dialog">
Cselect id="sel">
coption value="html">HTML</opt ion>
<opt ion value="css">CSS</opt ion>
</select>
cinput type="submit" value="OK">
cinput type="submit" уа1ие="0тмена">
c/form>
c/dialog>
const show = document.getElementByld('show’);
const dialog = document.getElementByld('dialog');
const sei = document.getElementByld('sel');
show.addEventListener('click', () => {
dialog.showModal();
dialog.addEventListener('close', () => {
if (dialog.returnvalue = 'OK') {
// Диалог закрыт нажатием кнопки ОК. Получаем выбранный в списке пункт,
const selectedltem = sel.options[sel.selectedlndex].value;
} else
// Диалог закрыт нажатием кнопки Отмена
Как видим, программный код стал компактнее.
20.7. Самостоятельные упражнения
I. Доработайте веб-приложение, генерирующее CSS-код для рамки (см. упраж-
нение 20.5}, добавив валидацию значения поля ввода Радиус скругления, ана-
логичную валидации, реализованной в поле Толщина
2. Найдите в папке 20\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 20 7 html. На основе хранящейся в нем «заготовки» напишите
веб-приложение (рис. 20.4), генерирующее CSS-код для формирования просве-
тов на основе введенных пользователем данных (внутренние просветы создают-
ся атрибутом стиля padding, внешние — margin). Реализуйте в приложении ва-
лидацию — проверку на ввод всех четырех значений просветов и на указание
в них единицы измерения.
CSS-код для создания просветов
Сверху: 10рх
Слева. 15pt Справа" 20mm
Снизу: 20рх
Вид просветов:
S' Внутренние С Внешние
Сгенерировать код
padding 10рх 20mm 20рх 15pt,
Рис. 20.4 Веб-приложение генерирующее CSS-код для формирования просветин
Урок 21
Взаимодействие
с веб-обозревателем
Почти все инструменты для взаимодействия с веб-обозревателем доступны через
объект, представляющий текущее окно веб-обозревателя. Этот объект относится
к классу window и хранится в переменной window, создаваемой автоматически.
Исключение составляет лишь детектор видимости — он реализован отдельным
классом.
Объект текущего окна является глобальным. Поэтому для обращения к его свойст-
вам и методам переменную window можно не указывать.
Класс window, равно как и все прочие классы ВОМ, что мы здесь рассмотрим, не
является потомком класса HTMLElement.
BOM (Browser Object Model, объектная модель веб-обозревателя) — набор объ-
ектов, представляющих окно веб-обозревателя, текущий интернет-адрес и све-
дения о клиентском компьютере.
21.1. Работа с окном веб-обозревателя
Мы можем выяснить местоположение и размеры окна веб-обозревателя, величину,
на которую была прокручена открытая в нем страница, принудительно прокрутить
страницу, отреагировать на завершение загрузки страницы, изменение размеров
окна и т. п.
Все это выполняется посредством свойств (табл. 21.1), методов (табл. 21.2) и собы-
тий (табл. 21.3) класса window.
Таблица 21.1, Свойства класса Window
Свойство Описание
innerWidth Ширина внутренней области окна
innerHeight Высота внутренней области окна
outerwidth Ширина всего окна
outerHeight Высота всего окна
screenLeft Горизонтальная координата левого верхнего угла окна относительно левого верхнего угла экрана
screenTop Вертикальная координата левого верхнего угла окна относительно левого верхнего угла экрана
Таблица 21.1 (окончание)
Свойство Описание
screenX To же самое, что и screenLeft
screenY То же самое, что и screenTop
pageXOffset Величина, на которую открытая в окне страница была прокручена по горизонтали
pageYOffset Величина, на которую открытая в окне страница была прокручена по вертикали
scrollX То же самсе, что и pageXOffset
scrollY То же самое, что и pageYOffset
Свойства класса window, приведенные в табл. 21.1, хранят числовые величины, из-
меряемые в пикселах, и доступны только для чтения.
Таблица 21.2. Методы класса Window
Метод Описание
blur() Деактивирует текущее окно
focus() Активирует текущее окно
print() Открывает стандартное диалоговое окно печати
scroll() scrollBy() scrollTo() Аналоги одноименных методов класса HTMLElement (см разд. 18 2 3), толькс выполняющие прокрутку страницы г- окне // Прокручиваем страницу на 200 пикселов вправо window.scrollBy(200, 0); // Прокручиваем страницу на 300 пикселов вверх window.scrollByt0, -300); // Прокручиваем страницу в позицию [0, 500] window.scrollTo(0, 500);
Таблица 21.3. События класса window
Событие Когда возникает?
load После загрузки страницы и всех связанных с ней файлов (изображений мультимедийных ртликов, внешних таблиц стилей и веб-сценариев)
pageshow После вывода страницы и события load
resize После изменения размеров окна
focus После активизации окна
blur После деактивизации окна
beforeunload Перед уходом с текущей страницы
pagehide Перед скрытием текущей страницы, после события beforeunload
Таблица 21.3 (окончание)
Событие Когда возникает?
beforeprint Перед началом печати текущей страницы или ее выводом для предва- рительного просмотра
afterprint После начала печати текущей страницы или закрытия окна предварительного просмотра
hashchange После перехода на другой якорь
Помимо этого, в окне можно обрабатывать любые всплывающие события, возни-
кающие в элементах страницы. Вот пример обработки в окне события click:
window.addEventListener( ’click’, () => {
// Обрабатываем событие click
});
21.1.1. Еще один способ записи веб-сценариев,
манипулирующих элементами страницы
Чтобы веб-сценарий, манипулирующий элементами страницы, смог работать,
к моменту его исполнения в памяти уже должны присутствовать объекты, пред-
ставляющие эти элементы. Поэтому такие сценарии обычно помещают в конец
секции гела страницы, например:
<html>
<body>
<script>
// Здесь помещается код сценария
</script>
<body>
</html>
Однако подобный сценарий можно оформить и как обработчик события load окна.
Это событие возникает, когда вся страница загружена, следовательно, его обработ-
чик исполняется, когда в памяти уже сформированы все объекты, представляющие
элементы страницы. Такой сценарий можно поместить куда угодно — например,
в секцию заголовка страницы
<html>
<head>
<scr ipt>
window.addEventListener(’load1, () => {
// Здесь помещается код сценария
});
</script>
</heaa>
<body>
</body>
</html>
21.1.2. Предотвращение ухода с веб-страницы
Есть возможность при попытке перейти па другую страницу вывести окно-
предупреждение, спрашивающее, действительно ли пользователь хочет выполнить
переход. Переход будет выполнен, только если пользователь дает положительный
ответ.
Чтобы реализовать это, достаточно привязать к событию beforeunload окна обра-
ботчик и выполнить в нем одно из следующих действий:
♦ отменить обработчик события по умолчанию, вызвав метод prevent De f ault ()
окна:
window.addEventListener('beforeunioad', evt = {
evt.preventDefault();
}) ;
♦ вернуть произвольною непустую строку в качестве результата. Этот способ
работает лишь в том случае, если для привязки обработчика к событию исполь-
зовался альтернативный способ (см. разд. 17.1.4.Г}. 11ример:
window.onbeforeunload = () => 'Уйти с текущей страницы?';
Выводимое окно-предупреждение будет содержать стандартный текст, различный
у разных веб-обозревателей. К сожалению, указать у этого окна другой текст не-
возможно.
21.2. Работа с текущим интернет-адресом
Есть возможность получить интернет-адрес текущей страницы, равно как и его со-
ставные части (адрес хоста, путь и пр.), программно перейти по другому интернет-
адресу и перезагрузить текущую страницу. Это выполняет класс Location, который
представляет текущий интернет-адрес и объект которого хранится в свойстве
location объекта окна.
Свойства класса Location (все, за исключением одного) доступны для чтения и
записи
♦ href— полный интернет-адрес в виде строки:
let href = window.location.href;
// 'http://www.dm.ru:000/pages/page.html?par=12#anch'
// Выполняем программный переход по другому интернет-адресу
location.href = 'https://www.google.ru/';
♦ origin — ст рока формата обозначение протоколахадрес хоста>[:<номер ТСР-
порта>], причем номер TCP-порта присутствует только в том случае, если он ука-
зан в интернет-адресе:
let org = location.origin; // 'http://www.dm.ru:8000'
Это свойство доступно только для чтения;
♦ host— строка форма] а <адрес хоста>[:<номер ТСР-порта>], причем номер ТСР-
порта присутствует только в том случае, если он указан в интернет-адресе:
let host = location.host; // ’www.dm.ru:8000'
♦ protocol — обозначение протокола с завершающим символом двоеточия, но без
двойного слеша:
let protocol = location.protocol; // ’http:'
♦ hostname — адрес хоста:
let hn = location.hostname; Il 'www.om.ru'
♦ port — номер TCP-порта в виде строки. Если порт в интернет-адресе не указан,
хранит пустую строку. Пример:
let port = location.port; // '8000'
♦ pathname — путь с начальным символом слеша. Если путь не указан, хранит
строку со слешем ('/'). Пример’
Jet path = location.pathname; // '/pages/page.html'
♦ search — строка c GET-параметрами, включающая начальный вопросительный
знак. Если таковые в интернет-адресе отсутствуют, хранит пустую строку. При-
мер:
let search = location.search; // '?par=12'
♦ hash — якорь с начальным символом решетки. Если якоря нет, хранит пустую
строку. Пример:
let hash = location.hash; 11 '#anch'
Если присвоить этому свойству другой якорь (с начальным символом решетки
или без такового), будет выполнен переход по этому якорю:
location.hash = 'anch2';
Также поддерживаются следующие три метода:
♦ assign{<интернет-адрес>) — выполняет переход по указанному интернет-адресу.
Теку щий интернет-адрес сохраняется в истории веб-обозревателя. Пример:
location.assign('https://www.google.ru/');
♦ replace (<интернет-адрес>) — TO же самое, ЧТО И assign (), ТОЛЬКО текущий ИН-
тернет-адрес не сохраняется в истории веб-обозревателя;
♦ reload о — перезагружает текущую страницу.
21.3. Получение сведений о клиентском
компьютере и веб-обозревателе
Получить сведения об экранной подсистеме клиентского компьютера позволяет
объект класса Screen, хранящийся в свойстве screen объекта окна. Его свойства
приведены в табл. 21.4— все они хранят числовые величины и доступны только
для чтения.
Таблица 21.4 Свойства класса Screen
Свойство Описание
availwidth Ширина свободной области экрана (без учета панели задач) в пикселах
availHeight Высота свободной области экрана (без учета панели задач) в пикселах
width Ширина всего экрана в пикселах
height Высота всего экрана з пикселах
colorDepth Глубина ивета в битах на пиксел
Пример:
<img id^"logo">
const logo = document.getElementByld('logo');
if (screen.availwidth > 1920)
logo.src = '/images/logc_hi_res.png';
else
logo.src = '/images/logo.png';
Класс Navigator представляет программу веб-обозревателя. Объект этого класса
доступен из свойства navigator объекта окна.
Единственное полезное свойство объекта веб-обозревателя — language. Оно хранит
обозначение текущей локали в виде строки. Это обозначение можно использовать
при форматированном выводе чисел и временных отметок (см. разд. 5.1.3.1 и 7.2.1).
Пример.
> let а = 1234567.3210123
> a.toLocaleString(navigator.language)
< '1 234 567,321'
> let d = new Date()
> d.toLocaleString(navigator.language, { timeZone: 'Asia/Sakhalin' })
< '17.11.2023, 01:31:00'
21.4. Вывод стандартных диалоговых окон
Веб-обозреватели могут отображать стандартные диалоговые окна: сообщения,
предупреждения и запроса на ввод значения. Для их вывода используются три сле-
дующих метода класса window:
♦ alert([<текст сообщения:*]) — выводит окно-сообщение с указанным текстом
и кнопкой ОК (рис. 21.1). Если текст не указан, будет выведено окно с типовым
текстом, различающимся у разных веб-обозревателей. Пример:
window.alert;1 Введите неотрицательное число');
Подтвердите действие на chrome.//new-tab-page
Введите неотрицательное число
Рис. 21.1. Окно-сообщение
♦ conf i rm (<текст сообщениям —выводит окно-предупреждение с указанным текстом
и кнопками ОК и Отмена (рис. 21.2). Возвращает true, если пользователь нажал
кнопку OK, false — если нажал кнопку Отмена Пример:
window.confirm('Выполнить операцию?');
Подтвердите действие на chrome://new-tab-page
Выполнить операцию7
Рис. 21.2 Окно предупреждение
♦ prompt (<текст сообщения>[, <изначалъное значение>]) — ВЫВОДИТ ОКНО С запро-
сом на ввод значения, содержащее указанный текст, поле ввода для занесения
требуемого значения, кнопки ОК и Отмена (рис. 21.3). Если было задано изна-
чальное значение, оно будет подставлено в ноле ввода. Возвращает введенное
значение в виде строки, если пользователь нажал кнопку ОК, и null — если бы-
ла нажата кнопка Отмена. Пример.
window.prompt('Введите свое имя', 'Вася Пупкин');
Рис 21 3. Окно с запросом на внод значения
Эти три метода в настоящее время задействуются только при обучении программи-
рованию, в экспериментах и разработке прототипов. Использование их в рабочих
веб-решениях считается плохим стилем программирования.
21.5. Детектор видимости
21.5.1. Область видимости и детектор видимости
Часто требуется отслеживать, присутствует ли какой-либо элемент страницы в об-
ласти видимости.
Область видимости — область в окне веб-обозревателя (или элементе страни-
цы с прокруткой), в которой видна часть содержимого страницы (или элемента
с прокруткой).
Элементы страницы, частично или полностью попадающие в область видимости,
видны посетителю также частично или полностью. Элементы, выходящие за преде-
лы области видимости, посетитель не видит (рис. 21.4).
1 Содержимое веб-страницы
2 Элемент, находящийся
за границами области
видимости
3 Элемент, частично
находящийся в области
видимости
4 Область видимости
5 Элемент, полностью
находящийся в области
видимости
Рис. 21.4. Схематичное изображение области видимости
При прокрутке страницы (или содержимого элемента с прокруткой) область види-
мости перемещается по странице (содержимому элемента). При этом одни элемен-
ты постепенно входят в область видимости и становятся видимыми, а другие ухо-
дят за ее границы и соответственно скрываются.
Отслеживание попадания элементов страницы в область видимости удобно реали-
зовать с применением детектора видимости
Детектор видимости— программный инструмент, отслеживающий появление
заданных элементов страницы в области видимости и выход из нее.
ПОЛЕЗНО ЗНАТЬ
♦ Отслеживание попадания элементов в область видимости может пригодиться, на-
пример, при реализации отложенной загрузки изображений.
Отложенная загрузка — загрузка только тех элементов страницы (обычно изо-
бражений). которые находятся в области видимости.
Отложенная загрузка изображений позволяет ускорить первоначальный вывод стра-
ниц, поскольку изначально загружаются и выводятся только те изображения, что
непосредственно видны посетителю. Остальные изображения загружаются позже,
как только посетитель прокрутит страницу до тех ее участков, в которых находятся
эти изображения.
♦ Ранее выяснять, какие элементы страницы попадают в область видимости, а ка-
кие— нет. приходилось самостоятельно, реализуя довольно сложную проверку
в обработчике события scroll окна веб-обозревателя. Сейчас это лучше делать по-
средством детектора видимости. Кстати, событие scroll окном больше не поддер-
живается.
21.5.2. Создание детектора видимости
Детектор видимости представляется объектом класса intersectionobserver. Для
создания этого объекта применяется выражение формата:
new Intersectionobserver (<.функция-обработч1'1к> [, <параметры>J)
Функция обработчик вызывается, когда часть какого-либо из отслеживаемых элемен-
тов. определяемая указанным пределом (о нем — чуть позже), в процессе прокрут-
ки окажется в области видимости. Мы рассмотрим эту функцию вразд. 21.5.4.
Внимание!
Функция-обработчик н процессе прокрутки вызывается дважды когда отслеживаемый
элемент входит в область видимости и когда он выходит из нее
Необязательные параметры задаются в виде простого объекта со следующими свой-
ствами:
♦ root — может содержать:
• null — детектор будет отслеживать попадание элементов в область видимо-
сти окна веб-обозревателя в результате прокрутки самой страницы;
• ссылку на объект элемента с прокруткой— будет отслеживаться попадание
элементов, вложенных в указанный элемент, в его область видимости.
Значение по умолчанию: null;
♦ rootMargin— просветы между границами окна или элемента и границами облас-
ти видимости. Задаются в формате, поддерживаемом атрибутами стилей CSS
margin и padding. Указание просветов позволяет уменьшить размеры области ви-
димости для достижения каких-либо специальных эффектов. По умолчанию:
'Орх' (просветы отсутствуют);
♦ threshold— предел или массив пределов.
Предел— числовое значение, указывающее часть отслеживаемого элемента,
которая должна присутствовать в области видимости.
Предел указывается в виде вещественного числа от 0.2 (хотя бы один пиксел от-
слеживаемою элемента должен попасть в область видимост и) до 1.0 (отслежи-
ваемый элемент должен полностью находиться в области видимости). Можно
указать:
• один предел — тогда функция-обработчик будет вызвана один раз. как только
обозначенная заданным пределом часть отслеживаемого элемента окажется
в области видимости.
Например, если указать предел 0.5, то при прокрутке страницы, как только
половина отслеживаемого элемента войдет в область видимости, будет вы-
звана функция-обработчик. При дальнейшей прокрутке, когда элемент станет
уходить из области видимости и будет видим наполовину, функция будет вы-
звана во второй раз;
• массив пределов— тогда функция-обработчик будет вызываться каждый раз,
когда часть отслеживаемого элемента, обозначенная очередным пределом из
заданного массива, окажется в области видимости.
Например, если указать массив [0.1, 0.9], то когда элемент будет входить
в область видимости, функция-обработчик вызовется дважды: как только
видимыми станут 10 и 90% элемента. А когда элемент будет уходить из об-
ласти видимости, функция также вызовется дважды: когда видимыми останут-
ся 90 и I 0% отслеживаемою элемента.
Значение свойства по умолчанию: 0.0.
Примеры.
// Отслеживаем прокрутку самой страницы в веб-обозревателе.
// Детектор видимости просигнализирует, когда половина очередного
// отслеживаемого элемента окажется в области видимости.
const options! = { threshold: 0.5 };
const vdl = new Intersectionobserver(vdHandler, options!);
// Отслеживаем прокрутку содержимого элемента с прокруткой scroller.
const elScroller = document.getElementByld('scroller');
const options2 = { root: elScroller, threshold: 0.5 };
const vd2 = new Intersectionobserver(vdHandler, options2);
// Детектор видимости просигнализирует, когда в области видимости
// окажется 1/ц, /2, /4 отслеживаемого элемента и, наконец, элемент
// полностью.
const options3 = { threshold: [0.25, 1.5, 0.75, 1.0] };
const vd3 = new Intersectionobserver(vdHandler, options3);
// Отслеживаемый элемент должен появиться в области видимости полностью,
// причем на расстоянии в 10 пунктов от верхнего или нижнего ее края
// (в зависимости от направления прокрутки)
const options4 = { threshold: 1.0, rootMargin: '10pt Opt' };
const vd4 = new Intersectionobserver(vdHandler, options4);
Класс intersect ionobserver поддерживает доступные только для чтения свойства
root, rootMargin и threshold, соответствующие одноименным свойствам передан-
ною конструктору объекта с параметрами.
21.5.3 . Указание отслеживаемых элементов
После создания объекта детектора следует указать, какие элементы он должен от-
слеживать.
Для ЭТОГО применяется метод observe(<алемент>) класса Intersectionobserver. Эле-
мент должен задаваться в виде ссылки на представляющий его объект. Пример:
const ellmgl = document.getElementByld('imgl');
vdl.observe(ellmgl);
Метод unobserve (<алемент>) того же класса убирает заданный элемент из числа от-
слеживаемых:
const ellmg3 = document.getElementByld('img3');
vdl.unobserve(elImg3);
Метод disconnect () убирает из числа отслеживаемых все ранее заданные элементы.
21.5.4 . Отслеживание попадания элементов
в область видимости
Когда часть каког о-либо из отслеживаемых элементов, обозначенная одним из за-
данных в параметрах пределов, оказывается в области видимости (причем неважно,
входит ли этот элемент в область или выходит из нее), детектор видимости вызыва-
ет заданную при его создании функцию-сбработчик (подробности — в разд. 21.5.2).
Эта функция принимает два параметра:
♦ массив объектов класса Intersect!onObserverEntry, каждый из которых хранит
сведения об одном из отслеживаемых элементов, находящихся в области види-
мости:
♦ ссылку на объект текущего детектора видимости.
Класс intersectionObserverEntry поддерживает следующие свойства, доступные
только для чтения:
♦ target — ссылка на сам отслеживаемый элемент;
♦ intersectionRatio— видимая часть элемента в виде вещественного числа от 0.0
(элемент полностью находится вне области видимости и, таким образом, неви-
дим) до 1.0 (элемент полностью находится в области видимости и. следователь-
но. видим целиком);
♦ intersectionRect — объект класса DOMRect с координатами и размерами видимой
области текущего элемента, представленными в пикселах. Этот объект аналоги-
чен тому, что возвращается методом getBoundingClientRect () элемента страни-
цы (см. разд. 18.2.3);
♦ isintersecting — если true, элемент находится в области видимости, если
false — за ее пределами;
♦ boundingClientRect — объект класса DOMRect с координатами и размерами теку-
щего элемента, представленными в пикселах;
♦ rootBounds — объект класса DOMRect с координатами и размерами области види-
мости. представленными в пикселах;
♦ time — текущее время в виде вещественного числа. Представляет собой коли-
чество миллисекунд, прошедшее с момента открытия страницы. Может быть
использовано для вычисления времени, в течение которого элемент присутствует
в области видимости.
Пример отслеживания попадания элементов в область видимости:
function vdHandler(elements, detector) {
for (let el of elements) {
if (el.isintersecting) {
// Элемент видим
} else {
// Элемент невидим
}
}
Массив объектов класса intersectionObserverEntry, описывающих видимость от-
слеживаемых элементов, также можно получить в любой момент, вызвав метод
takeRecoras() у объекта детектора видимости.
const elements = vdl.takeRecords();
for (let el of elements) (
21.6. Упражнение.
Отложенная загрузка изображений
Владелец суши-бара «Йокогама», услышав о детекторе видимости и отложенной
загрузке, захотел, чтобы изображения блюд в меню его заведения загружались от-
ложенно. Более того, он пожелал также, чтобы изображения изначально были не-
видимыми, по мере вхождения в область видимости постепенно проявлялись, а по
мере выхода из нее — скрывались.
1. Найдем в папке 21\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 21 6 html (страница с меню) и 21 6 css (таблица стилей
с оформлением) и папку 21 6 (файлы с изображениями блюд). Скопируем все это
куда-либо на локальный диск.
2. Откроем копию файла 21 6 html в текстовом редакторе.
Страница, хранящаяся в этом файле, содержит пять семантических статей (тегов
<article>), каждая из которых включает графический тег <img> с data-атрибутом
data-src (см. разд. 18.2.1.1), хранящим интернет-адрес графического файла.
Чтобы вывести изображение в таком теге, следует присвоить интернет-адрес из
его data-ai рибута data-src свойству src.
3. Добавим в конце секции тела страницы тег <script>, в котором будем писать
сценарий, самостоятельно.
4. Запишем код, создающий конфигурационный объект с параметрами для детек-
тора видимости:
const options = {
rootMargins: 'LOpt Opt',
threshold: [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
};
Свойству threshold мы присвоили массив с шестью пределами, отличающимися
друг от друга на 0,2. В результате функция-обработчик, которую мы укажем при
создании детектора видимости, будет вызываться, как только очередная /5 часть
элемента окажется в области видимости. Благодаря этому мы сможем относи-
тельно плавно менять степень непрозрачности элемента при его вхождении
в область видимости или выходе из нее.
5. Добавим объявление функции-обработчика попадания элемента в область види-
мости:
function vdHandler(elements) {
for (let el of elements) { // ......................................... 1
if (el.islntersecting && el.target.dataset.src) { // ............. 2
el.target.src = el.target.dataset.src; // .................... 3
el.target.dataset.src = '// .................................. 4
)
el.target.style.opacity = el.intersectionRatio; // ............... 5
}
}
Перебираем полученный с параметром массив обьектов класса
intersect ionobserverEntry в цикле (поз. 1 приведенного кода). Через свойство
target очередного элемента массива обращаемся к представляемому им графи-
ческому тегу (поз. 2) и проверяем, хранит ли его data-атрибут data-src непустую
строку и видим ли этот тег в настоящий момент (т. е. не было ли еще загружено
изображение, выводимое в теге, и нужно ли его загружать). Если это так. при-
сваиваем (поз. 3) интернет-адрес из этого data-атрибута свойству src мафиче-
ского тега (чтобы вывести изображение), а в сам data-атрибут (поз. 4) заносим
пустую строку (тем самым указывая, что изображение уже загружено и выведе-
но). Наконец, устанавливаем у графического тега степень непрозрачности, рав-
ную части этого тега, присутствующей в области видимости (поз. 5).
6. Добавим код, создающий объект детектора видимости:
const vd = new Intersectionobserver(vdHandler, options);
7. Допишем код, который получает досту п ко всем тегам <img>, вложенным в теги
<article>, и добавляет их в число отслеживаемых:
const clings = document.querySelectorAll(’article img');
for (let el of clmgs)
vd.observe(el);
Откроем страницу 21 6 html в веб-обозревателе, прокрутим ее вниз и понаблюдаем,
как изображения в процессе прокрутки то постепенно проявляются, то исчезают.
21.7. Детектор изменения размеров
Детектор изменения размеров— программный механизм, отслеживающий из-
менение размеров заданных элементов страницы.
Элемент страницы будет изменяться в размерах, например, если его размеры зада-
ны в процентах от размеров родителя, и пользователь в текущий момент изменяет
размеры окна веб-обозревагеля. Также размеры элемента могут изменяться про-
граммно при изменении его содержимого, указании у него других размеров, вели-
чин внутренних отступов и (или) толщины рамки.
21.7.1. Создание детектора изменения размеров
Детектор изменения размеров представляется объектом класса ResizeObserver. Для
создания этого объекта применяется выражение формата:
new ResizeObserver (<функцмя-обработчик>)
Функция -обработчик вызывается, когда размеры какого-либо из отслеживаемых эле-
ментов изменяются. Мы рассмотрим эту функцию в разд. 21.7.3.
Пример:
const rd = new ResizeObserver(rdHandler);
21.7.2. Указание отслеживаемых элементов
Для указания элементов, отслеживаемых детектором, служит метод observe () клас-
са ResizeObserver:
obse rve (<элемент> [, < параметра ])
Элемент должен задаваться в виде ссылки на представляющий его объект:
const elDivl = document.getElementByld('divl');
rd.ooserve(elDivl);
Параметры указываются в виде простого объекта. На текущий момент поддержи-
вается лишь параметр box, задающий обозначение размеров, изменение которых
будет отслеживаться детектором, в виде одной из строк:
♦ 'content-box' — размеры содержимого элемента без учета внутренних отступов
и рамки.
В этом случае, если, например, программно изменить у элемента значения внут-
ренних отступов, детектор посчитает, что размеры элемента не изменились.
Размеры измеряются в виртуальных пикселах, не зависящих от конкретной
платформы;
♦ 'border-box' — размеры элемента с учетом внутренних отступов и рамки.
Теперь при программном изменении внутренних отступов у элемента детектор
уведомит об изменении размеров элемента.
Единица измерения размеров — те же виртуальные платформно-независимые
пикселы;
♦ 'device-pixel-concent-box' — ТО же самое, что и 'content-box', только размеры
измеряются в физических пикселах, геометрические размеры которых зависят от
конкретной платформы.
Этот режим применяется в случаях, если при изменении размеров элемента тре-
буется изменять размеры связанного с ним холе га HTML (см. урок 27).
Значение параметра но умолчанию: 'content-box’.
Пример:
const elDiv2 = document.getElementByld('div2');
rd.observe(elDiv2, { box: 'device-pixel-content-box' });
Метод unobserve (<элемент>) класса ResizeObserver убирает заданный элемент из
числа отслеживаемых:
rd.unobserve(elDiv2);
Метод disconnect () убирает из числа отслеживаемых все ранее заданные элементы.
21.7.3. Отслеживание изменения размеров элементов
Если размеры какого-либо из отслеживаемых элементов изменились, детектор вы-
зывает заданную при его создании Функцию-обработчик (подробности — в разд. 21.7.1).
Эта функция принимает два параметра:
♦ массив объектов класса ResizeObserverEntry, каждый из которых хранит сведе-
ния об одном из отслеживаемых элементов, чьи размеры изменились;
♦ ссылку на объект текущего детектора изменения размеров.
Класс ResizeObserverEntry поддерживает следующие свойства, доступные только
для чтения:
♦ target — ссылка на сам отслеживаемый элемент;
♦ contentBoxSize — массив из простых объектов, каждый из которых хранит
новые размеры одной из частей содержимого элемента, без учета внутренних
отступов и рамки. Каждый такой объект содержит следующие свойства:
• inlineSize — ширина, если текст в элементе выводится по горизонтали, или
высота, если текст выводится по вертикали;
• blocksize— высота, если текст выводится по горизонтали, или ширина, если
текст выводится по вертикали.
Все размеры измеряются в виртуальных пикселах.
Выражение величин новых размеров в виде массива предусмотрено для случаев,
если элемент страницы состоит из нескольких частей (что может случиться, на-
пример. в случае многоколоночной верстки);
♦ borderBoxSize — массив из простых объектов, каждый из которых содержит но-
вые размеры одной из частей элемента, с учетом внутренних отступов и рамки.
Объекты аналогичны содержащимся в массиве из свойства contentBoxSize;
♦ devicePixelCcntentSize — TO же самое, ЧТО И contentBoxSize, ТОЛЬКО размеры
измеряются в физических пикселах;
♦ contentRect — объект класса uOMRect с новыми координатами и размерами со-
держимого элемента, представленными в виртуальных пикселах. Этот объект
аналогичен тому, что возвращается методом getBcundingciientRect о элемента
страницы (см.разд. 18.2.3}.
И ^начально класс ResizeObserverEntry поддерживал свойство contentRect, а
свойства contentBoxSize, borderBoxSize и deviceFixelContentSize В нем отсутст-
вовали. В настоящее время после появления поддержки трех этих свойств свой-
ство contentRect поддерживается лишь для совместимости.
Пример использования детектора изменения размеров для настройки кегля шрифта
у отслеживаемого элемента:
<dov id="main">4eTeKTOp измерения pa3MepoB</div>
const main = document.getElementByld(’main’);
function rdilanaler (elements) {
for (const el cf elements) {
// Следует проверить, не запущен ли код в старом веб-обозревателе,
// поддерживающем лишь свойство contentRect
if 'че1. contentBoxSize) {
el.target.style.fontsize =
Math.max(1.5, el.contentBoxSize[0].inlineSize / 100) + "rem";
} else {
el.target.style.fontsize =
Math.max(l.5, el.contentRect.width / 100) + "rem";
)
}
const rd = new ResizeObserver(rdHandler);
rd.ooserve(main);
21.8. Самостоятельное упражнение
Реализуйте упрощенный вариант отложенной загрузки изображений, когда изобра-
жения просто загружаются в процессе прокрутки без каких-либо дополнительных
визуальных эффектов.
ЧАСТЬ IV
Асинхронное и параллельное
программирование
С Урок 22. Таймеры и фоновые потоки
Э Урок 23 Промисы и асинхронные функции
Э Урок 24 Асинхронные итераторы и генераторы
Урок 22
Таймеры и фоновые потоки
22.1. Таймеры, периодические и однократные
Таймер — программный механизм, позволяющий вызвать заданную функцию
по истечении указанного промежутка времени.
Поддерживаются таймеры двух видов.
♦ Периодический (или многократный)— срабатывает многократно, каждый раз
вызывая заданную функцию.
Периодический таймер создается вызовом метода setinterval () класса window:
set Interval (<функция> [, < промежуток временм>=01,
<параметр 1>, <параметр 2>, . . . <параметр Л/>]1)
Промежуток времени задается в виде числа в миллисекундах. Минимальное до-
пустимое значение — 4 мс, если указано меньшее значение или если оно вообще
не указано, промежуток времени принимается равным 4 мс.
Параметры, заданные третьим и последующими параметрами, будут переданы
в указанную функцию при вызове.
В качестве функции можно указать метод. Однако следует помнить, что перемен-
ная this в теле указанного метода (как и функции) ссылается на глобальный
объект, и получить через нее доступ к текущему объекту не получится.
Метод setinterval () возвращает целочисленный идентификатор созданного
таймера. Его можно использовать впоследствии, чтобы удалить таймер.
Удалить ранее созданный периодический таймер с заданным идентификатором
МОЖНО ВЫЗОВОМ метода clearinterval () класса Window:
clearinterval(<идентификатор таймера>}
♦ Однократный — срабатывает лишь один раз, после чего уничтожается.
Однократный таймер создается с помощью метода setTimeouto класса Window,
аналогичного методу setinterval ().
Для удаления ранее созданного, но еще не сработавшего однократного таймера
следует применять метод clearTimeoutО класса window, аналогичный методу
clearinterval().
Полезно знать
Синхронный программный код— код. фрагменты которого выполняются стро-
го в том порядке, в котором они записаны.
Синхронным является код, выполняющийся при загрузке страницы, например:
const interactive = document.getElementByld('interactive');
interactive.addEvenrListener('mouseout', () => {
interactive.className = ' ';
));
interactive.addEventListener('mouseover', () => {
nteractive.className = 'hovered';
});
Сначала выполнится первое выражение, получающее доступ к элементу' страницы
с якорем interactive. После него выполнится второе выражение, привязывающее обра-
ботчик к событию mouseover этого элемента, а за ним третье, которое привяжет обра-
ботчик к событию mouseout.
Асинхронный программный код — код. фрагменты которого могут быть выпол-
нены в произвольном порядке, в произвольные и даже не всегда предсказуемые
моменты времени.
Асинхронным кодом, в частности, являются тела обработчиков событий. Так. обработ-
чики событий mouseover и mouseout элемента interactive (см. приведенный пример) мо-
гут выполниться в порядке, отличном от порядка их записи, и в моменты времени,
предсказать наступление которых невозможно.
К асинхронному коду также относятся таймеры, обработчики промисов и асинхронные
функции (см. урок 23).
22.2. Упражнение.
Отзывчивое веб-приложение, вариант 1
Один из наших клиентов написал веб-приложение для вычисления простых чисел
в заданном диапазоне (рис. 22.1). И теперь жалуется, что во время вычисления веб-
обозреватель «подвисает», не позволяя ни прокрутить список, в котором выводятся
вычисленные простые числа, ни переместить, ни свернуть свое окно.
Вычисление простых чисел
От: |1
до: 100000
23201
23203
23209
23227
23251
23269
23279
23291
23293
23/97
Рис 221. Веб-приложение для е<э)числения простых чисел
Дело в том, что. когда веб-обозреватель занят выполнением программного кода,
ему не остается времени ни на что другое, даже на то. чтобы отреаг ировать на дей-
ствия пользователя. Программисты говорят, то веб-обозрсватель — однопоточная
программа.
Выходом может стать разбиение процесса вычислений на отдельные короткие эта-
пы, выполняемые через одинаковые промежутки времени. Тогда веб-обозреватель
сможет отреагировать на действия пользователя в промежутке между выполнением
отдельных этапов. И приложение станет более отзывчивым.
Реализовать такой подход проще всего посредством периодического таймера.
1. Найдем в папке 22\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 22 2.html (само приложение). Скопируем его куда-либо на ло-
кальный диск.
2. Откроем копию файла 22 2 html в текстовом редакторе и посмотрим на HTML-
код, создающий интерфейс приложения
<form>
<р>От: <input type="number" min="l" value="l">
до: <input typr>="number" min="l" value="100"x/p>
<pxinput type="submit" value="BbFMCJiHTb"x/p>
<pxselect size="10"x/selectx/p>
</form>
В поля ввода чисел От: и до: заносятся начальное и конечное числа диапазона,
в котором нужно найти простые числа, кнопка отправки данных Вычислить за-
пускает вычисления, а нижний список, изначально пустой, служит для вывода
найденных простых чисел.
3. Найдем программный код. выполняющий вычисления:
const form = document. forms [О];
form.addEventListener(’submit', evt => {
evt.preventbefault();
const min = form.elements[0].valueAsN umber;
const max = form.elements[1].valueAsNumber;
form.elements[3].irmerHTML = '';
ferm.elements[21.disabled = true;
for (let i = min; i <= max; i++) {
if (i % 2 != 0) {
let flaq = true;
for (let j = 3; j <= Math.floor(Math.sqrt(i)); j += 2)
if (i % j = 0) {
flag = false;
break;
)
if (flag) (
const el = document.createElement('option');
el.textConcent = i;
form.elementsL3].add(el);
}
}
}
form.elements[2|.disabled = false;
}) ;
Все вычисления выполняются в обработчике события submit веб-формы. Из по-
лей ввода От: и до: извлекаются граничные числа диапазона, и кнопка Вычис-
ли ль делается недоступной (чтобы пользователь во время вычисления случайно
не нажал ее снова). Далее производятся сами вычисления: в цикле перебираются
числа из заданного диапазона, для представления каждого найденного прослою
числа создается и добавляется в список отдельный пункт. По окончании вычис-
лений кнопка Вычислить вновь делается доступной для пользователя.
Алгоритм вычисления простых чисел несложен: простым считается число, не
делящееся нацело на 2, и все нечетные числа оз трех до значения квадратного
корня из этого же числа, округленного до ближайшего меньшего целого.
Для доступа к веб-форме разработчик приложения использовал коллекцию из
свойства forms страницы (см.18.1.1). А для доступа к элементам управле-
ния. что находятся в этой веб-форме, он применил коллекцию из свойства
elements веб-формы (см. разд. 20.2).
Как условились ранее, разобьем весь диапазон чисел, заданный пользователем,
на части, по 1000 чисел r каждой, и будем обрабатывать эти части по очереди.
Обработку вынесем в функцию, запускаемую периодическим таймером каждые
0,1 с.
Приступим к переделке приложения. Перепишем его код заново — так проще.
4. Удалим выражение, привязывающее обработчик к событию submit веб-формы:
const form = document.forms[0];
rorm.addEvonrLiar.onor ('submit' >—evt -»—{—т—?—. }) /
5. Добавим первое выражение нового кода:
let min, max, i, timer;
Мы объявили переменные min и max для хранения начального и конечного чисел
диапазона, i — для текущего обрабатываемого числа, timer — под идет ифика-
тор таймера.
6. Добавим объявление функции computesimplevalues (), которая будет обрабаты-
вать очередные 1000 чисел из заданного диапазона:
function computeSimpleValues() {
for (let k = 0; i <= max && k <= 1000; i++, k-t + ) {
if (i % 2 ! = 0) {
let flag = true;
for (let j = 3; j <= Math.floor(Math.sqrt(i)); j += 2)
if (i % j == 0) {
flag = false;
break;
}
if (flag) {
const el = document.createElement('option');
el.textContent = i;
form.elements[3].aad(el);
}
}
}
if (i >= max) {
window.clearinterval(timer);
form.elements[2].disabled = false;
}
}
Код этой функции похож на код обработчика события submit из предыдущего
варианта сценария, но с двумя отличиями. Во-первых, цикл, в котором произво-
дится поиск простых чисел, будет выполняться, пока текущее число из перемен-
ной i не выходит за верхнюю границу (max) и количество обработанных чисел не
превышает 1000. Во-вгорых. по окончании обработки всех чисел из диапазона
(если значение переменной i, которая в тот момент будет хранить последнее из
обработанных чисел, выходит за верхнюю границ} диапазона шах) мы удаляем
таймер вызовом метода clearinterval () и делаем доступной кнопку Вычис-
ли 1ь. тем самым приведя приложение в изначальное состояние.
7. Напишем обработчик события submit веб-формы, запускающий вычисления:
form.addEventListener(’submit’, evt => {
evt.prevent Def ault();
min = form.elements[0].valueAsNumber;
max = form.elements[1].valueAsNumber;
i = min;
form.elements[3].innerHTML =
fj rm.elements 12].disabled = true;
timer = window.setinterval(computesimpleValues, 100);
});
He забываем перед запуском вычислений занести начальную границу диапазона
в переменную i.
Откроем страницу 22 2 html в веб-обозревателе, зададим диапазон, в котором следу-
ет найти целые числа, и запустим процесс вычислений (см. рис. 22.1). Пока идут
вычисления, попробуем переместить или свернуть окно веб-обозревателя — это
должно у нас получиться.
22.3. Фоновые потоки
Есть и другой способ повысить отзывчивость веб-приложения — вынести код, вы-
полняющий длительные вычисления, в фоновый поток.
Поток — своего рода виртуальный процессор, создаваемый операционной сис-
темой для выполнения программного кода. Может выполнять лишь один фраг-
мент кода.
Основной ноток — поток, в котором выполняется сам веб-обозреватель и код
обычных веб-сценариев.
Фоновый поток — поток, работающий параллельно основному и выполняю-
щий заданный код.
Отдельные потоки работают независимо друг от друга, не мешают друг другу, но и
не имеют доступа к данным друг друга.
22.3.1. Создание фонового потока
Перед созданием фонового потока следует вынести весь код, который должен вы-
полняться в этом потоке, в отдельный файл.
Внимание!
Фонозые потоки можно создавать лишь на страницах, загруженных с веб-сервера По-
пытка создать фоновый поток на странице, загруженной с файлозой системы, приве-
дет к ошибке,
Фоновый поток представляется объектом класса Worker. Объект фонового потока
создается выражением формата:
new Worker(<интернет-адрес файла с кедом фонового потока>[,
дополнительные параметры^])
Интернет-адрес файла задается в виде строки.
Дополнительные параметры указываются в виде простого объекта со свойствами,
представляющими эти параметрами. Наибольший интерес вызывает параметр type,
задающий обозначение разновидности потока в виде одной из строк: ’classic' (код
потока исполняется как обычный веб-сценарий) или 'module' (код потока исполня-
ется как модуль — см.урок 14). Значение параметра по умолчанию: 'classic'
22.3.2. Передача данных между потоками
Ранее говорилось, что различные потоки полностью изолированы друг от друга и
не имеют доступа к данным друг друга. Поэтому, если требуется передать данные
из одного потока другому, придется пользоваться инструментами, описываемыми
далее.
Для передачи заданного значения другому потоку следует использовать метод
postMessage (<значение>) класса Worker. Передаваемое значение может принадлежать
любому типу.
Для передачи значения:
♦ из основного потока фоновому — метод следует вызвать у объекта фонового
потока, созданного ранее (см. разе). 22.3.1);
♦ из фонового потока основному — метод следует вызвать у объекта, представ-
ляющего основной поток.
Этот объект доступен из переменной self, автоматически создаваемой в фоно-
вом потоке.
При получении значения, переданного из другого потока, в объекте, который пред-
ставляет поток, передавший значение, возникает событие message. Объект этого
события создается на основе класса MessageEvent. Последний поддерживает свойст-
во data, хранящее само переданное значение.
Для получения значения, переданного:
♦ основному потоку из фонового — событие следует обрабатывать в объекте
фонового потока;
♦ фоновому потоку из основного — событие следует обрабатывать в объекте,
представляющем основной поток и доступном из переменной self.
22.3,3. Завершение фонового потока
Для завершения фонового потока следует:
♦ из основного потока — вызвать у объекта завершаемого фонового потока метод
terminate();
♦ из того же фонового потока — вызвать у объекта, представляющего основной
поток и доступного из переменной self, метод close(). В результате фоновый
поток будет завершен основным потоком.
ПОЛЕЗНО ЗНАТЬ
♦ Переменная self — это, собственно, не переменная, а свойство объекта класса
WorkerGlobalScope, являющегося глобальным для кода фонового потока.
♦ Фоновый поток — пример параллельного кода.
Параллельный программный код — код, предназначенный для выполнения
в фоновом потоке.
22.4. Упражнение.
Отзывчивое веб-приложение, вариант 2
Есть и другой способ сделать приложение для поиска простых чисел более отзыв-
чивым — вынести код, выполняющий вычисления, в фоновый поток. Так и сде-
лаем.
I. Найдем в папке 22\ех22 2 сопровождающего книгу файлового архива (см. при-
ложение 5) файл 22.2.html (само приложение). Скопируем его куда-либо на ло-
кальный диск.
2. Откроем копию файла 22-2.html в текстовом редакторе и удалим все выражения
сценария, за исключением первого:
const form = document.forms(0 ] ;
Jot min, max,—Jr?—tim^r;
Сразу после создания фонового потока основной поток перешлет ему простой
объект со свойствами min и max (соответственно начальное и конечное значения
диапазона, в котором следует искать простые числа). Получив этот объект,
фоновый поток начнет вычисления.
Найдя очередное простое число, фоновый поток перешлет его основному, чтобы
последний вывел его в списке. Когда поиск простых чисел закончится, фоновый
поток отправит основному строку 'stop', и основной поток в ответ на это за-
вершит работу фонового потока.
Начнем работу с написания кода фонового потока. Сохраним его в файле 22.4.JS
3. Создадим в папке с файлом 22 2 html файл 22 4js и сохраним в нем код из листин-
га 22.1.
Листинг 22.1. Фоновый поток (файл 22.4.JS)
self.addEventListener(’message', evt => {
for (let i = evt.data .min; i <= evt. .data .max; ++i) {
if (i % 2 != 0) {
let flag = true;
for (let j = 3; j <= Math.floor(Math.sqrt(i)); j += 2)
if (i % j -= 0) {
flag = false;
break;
}
if (flag)
self.postMessage (i);
}
}
self.postMessage('stop');
});
Этот код представляет собой обработчик события message, который извлекает из
полученного простого объекта начальное и конечное значения диапазона чисел,
в котором требуется найти простые числа, и производит их поиск. При нахож-
дении очередного простого числа код отправляет его основному потоку. По за-
вершении поиска основному потоку отправляется строка 'stop'— сообщение
об окончании вычислений и предписание завершить фоновый поток.
4. Переключимся на файл 22 2 html и добавим выражение, объявляющее перемен-
ную, в которой будет храниться объект фонового потока:
let worker;
5. Добавим объявление обработчика события message фоновою потока, который
будет принимать данные от фонового потока:
function processResult(evt) {
if (evt.data = 'stop') {
form.elements[2].disabled = false;
worker.terminate();
} else {
const el = document.createElement(’option’);
el.textcontent = evt.data;
form.elements[3].add(el);
}
}
Если была получена строка 'stop' (т. е. вычисления закончены), следует сделать
кнопку Вычислить вновь доступной и завершить работу фонового потока.
В противном случае (если было получено простое число) следует добавить по-
лученное значение в список найденных простых чисел.
6. Допишем объявление обработчика события submit веб-формы, который запустит
вычисления:
form.addEventListener('submit', evt => {
evt.preventDefault();
form.elements[3].innerHTML = '';
form.elements[2J.disabled = true;
worker = new Worker(’22.4.js'j;
worker.addEventListener('message', processResult);
const data = {
min: form.elements[0].valueAsNumber,
max: form, elements[1].valueAsNumber
};
worker.postMessage(data;;
));
После создания фонового потока не забываем отправить ему простой объект
с граничными значениями диапазона, чтобы запустить вычисления.
Запустим веб-сервер htlp-server, указав ему в качестве корневой папку, в которой
находятся файлы примера. Запустим веб-обозреватель и выполним переход по ин-
тернет-адресу http://Iocalhost:8080/22.2.hlml. Проверим приложение в работе.
22.5. Самостоятельные упражнения
I. Реализуйте в первом варианте веб-приложения для поиска простых чисел
(см. упражнение 22.2) возможность прерывания вычислений по нажатию кнопки
Стоп. Сделайте так. чтобы до начала вычислений эти кнопка была недоступна
2. Реализуйте то же самое во втором варианте этого веб-приложения (см.упраж-
нение 22.4).
Урок 23
Промисы
и асинхронные функции
Часто асинхронный код, присутствующий в составе веб-сценариев, вычисляет ка-
кие-либо значения, подлежащие дальнейшей обработке. Получение этих значений
может оказаться весьма нетривиальной задачей. Так, значения, которые вычисля-
ются в функции, указанной в таймере (см. разд. 22.1), придется либо получать через
глобальные переменные, либо обрабатывать в той же функции (как поступили мы).
Для реализации обоих подходов понадобится писать довольно сложный и зачастую
запутанный код.
Однако можно применить третий подход— использование промисов и асинхрон-
ных функций.
23.1. Промисы
Промис — сущность, представляющая произвольное значение, которое будет
получено позже, спустя неопределенный промежуток времени.
Нагрузка — значение, представляемое промисом. Может быть произвольного
типа.
Прочие может находиться в одном из трех состояний:
♦ ожидания — нагрузка еще не получена. В этом состоянии промис пребывает
сразу после создания;
♦ подтвержденном — нагрузка успешно получена;
♦ отклоненном— при получении нагрузки возникла ошибка.
23.1.1. Создание промисов
Прочие представляется объектом класса Promise. Он создается выражением фор-
мата:
new Promise(<функция, выполняющая получение нагрузки^
Функция, выполняющая получение нагрузки- задаваемая в выражении, обязана прини-
мать два параметра:
♦ ссылку на функцию, которая должна быть вызвана в теле заданной функции при
успешном получении нагрузки. В качестве единственного параметра этой функ-
ции передается сама полученная нагрузка;
♦ ссылку на функцию, которая должна быть вызвана в теле заданной функции
в случае возникновения ошибки или пион нештатной ситуации.
Единственным параметром этой функции, как правило, передается объект
исключения, описывающий возникшую ошибку. Хотя, в принципе, ей можно
передать практически любое значение.
Первому параметру функции традиционно дается имя resolve или res. второму —
reject или rej.
Пример функции, которая получает число, возвращает промис, находящийся в со-
стоянии ожидания, запускает однократный таймер, в его геле проверяет, является
ли переданное число четным, помещает полученный результат в промис и под-
тверждает его. Если же функция получила что-либо, отличное от числа, она откло-
няет возвращенный промис и помещает в него исключение типа ТуреЕггог:
function isNumberEven(val) {
let timer;
return new Promise((resolve, reject) => {
timer = window.setTimeout(() => {
if (typeof val == ’number’)
resolve(val % 2 == 0) ;
else
reject(new ТуреЕггог('Требуется число1));
I, 100);
});
}
23.1.2. Обработка промисов
Для обработки промисов применяются следующие три метода, поддерживаемые
классом Promise:
♦ then {<функция then>[, <функция catcA>]) — задает у текущего промиса функции
then и catch.
Функция then выполняется при подтверждении текущего промиса. Должна при-
нимать в единственном параметре его нагрузку. В теле этой функции произво-
дится обработка полученной нагрузки.
функция catch выполняется при отклонении текущего промиса. Должна прини-
мать в единственном параметре объект переданного через промис исключения.
В теле этой функции производится обработка возникшего исключения. Если
функция catch не задана, переданное исключение будет передано обработчику по
умолчанию, встроенному в веб-обозреватель.
1 Тример:
let р = isNumberEven(1000С);
р.then(payload => {
if (payload)
console.log(’Число четное’)
else
console.log('Число нечетное’)
}, exc => {
console.log(exc.message);
)
); // Число четное
p = isNumberEven(10001) ;
... // Число нечетное
p = isNumberEven('abode');
... // Требуется число
Функция then может возвращать результат одного из следующих типов:
• другой промис — который будет возвращен методом then О без изменений;
• любое значение, не являющееся промисом, — метод then() заключит его во
вновь созданный промис. сразу же пометит его как подтвержденный и вернет
в качестве результата:
• если функция then не вернет результат — метод then() вернет вновь создан-
ный промис. хранящий значение undefined и уже помеченный как подтвер-
жденный;
• если в теле какой-либо из функций возникнет ошибка — метод then о вернет
промис, хранящий объект исключения и уже помеченный как отклоненный.
У промиса, возвращенного методом then о, также можно вызвать метод then о
для обработки. Пример:
р = isNumberEven(100СЮ);
р.then(payload => payload ? 'Число четное' : 'Число нечетное')
,then(m => { console.log(m); }); // Число четное
♦ catch (<функция catcn>} —задает у текущего промиса функцию catch:
р = isNumberEven(’abcde');
р.then (...)
.catch(exc »> { console.log(exc.message); }); // Число четное
Как правило, метод then о применяется лишь для указания функции then, а для
задания функции catch используется метод catch о промиса — так код получа-
ется более наглядным;
♦ finally (<функц! 1я finally>) —задает у те куще] о промиса функцию finally.
Функция finally выполняется после функции then или функции catch. Не должна
принимать параметров и возвращать результат. В ее теле обычно производятся
какие-либо завершающие действия.
Пример:
p.then( . . . )
.catch (...)
.finally(() => { console. log ('Обраб отка числа завершена'); });
23.1.3. Массовая обработка промисов
Если требуется работать с несколькими промисами, могут оказаться полезными
следующие статические методы класса Promise:
♦ all ^последовательность промисов>) — возвращает промис, который:
• подтверждается — когда все промисы из заданной последовательности будут
подтверждены. В качестве нагрузки возвращенный промис получит массив
нагрузок промисов из последовательности',
• отклоняется - - если хотя бы один промис будет отклонен. Он будет хранить
исключение из первого отклоненного промиса.
Пример:
let pl = isNumberEven(1000);
let p2 = isNumberEven(1001) ;
let p3 = isNumberEven(4321) ;
Promise.all(|pl, p2, p3]).then(vals => { console.log(vals); });
// (3) [true, false, false]
pl = isNumberEven(100l);
p2 = isNumberEven(’1001');
p3 = isNumberEven(4321);
Promise.all(Ipl, p2, p3]).catch(exc => { console.log(exc.message); });
// Требуется число
♦ allSettleci (^последовательность промисов>) — возвращает промис, который под-
тверждается. когда каждый промис из указанной последовательности будет либо
подтвержден, либо отклонен. В качестве нагрузки возвращаемый промис полу-
чит массив простых объектов со свойствами:
• status — если 'fulfilled', соответствующий промис подтвержден, если
'rejected' —отклонен;
• value — присутствует, только если соответствующий промис подтвержден,
и хранит его нагрузку;
• reason — присутствует, только если промис отклонен, и хранит исключение,
вызвавшее отклонение промиса.
I Тример:
pl = isNumberEven(1000);
р2 = isNumberEven('1001');
рЗ = IsNumberEven(4321);
Promise.allSettled([pl, p2, рЗ]).then(result => {
for (let r of result)
if (r.status == 'fullfilled*)
console.log(r.value);
else
console.log(e.reason.message);
});
/*
Результат:
true
Требуется число
false
*/
♦ any ^последовательность промисов>) — возвращает промис. который:
• подтверждается — как только какой-либо промис из заданной последователь-
ности будет подтвержден. Возвращенный промис получит нагрузку подтвер-
жденного промиса.
Все промисы, ставшие подтвержденными позже, игнорируются;
• отклоняется — если все промисы из последовательности отклонятся. Возвра-
щенный промис будет хранить исключение класса AggregateError, производ-
ного от класса Error, которое возбуждается самой исполняющей средой.
Объект этого исключения имеет свойство errors, хранящее массив с исклю-
чениями из всех отклоненных промисов.
Пример:
pl = isNumberEven('ЮпС') ;
р2 = isNumberEven([]);
рЗ = isNumberEven(new DateO);
Promise.any([pl, p2, p3]).catch(result => {
for (let r of result)
console.log(r.message);
});
/*
Результат:
Требуется число
Требуется число
Требуется число
*/
♦ race {^последовательность промисов>) —возвращает промис. который:
• подтверждается — как только какой-либо промис из заданной последователь-
ности булст подтвержден. Возвращенный промис получит нагрузку подтвер-
жденного промиса;
• отклоняется — если какой-либо промис из заданной последовательности от-
клонится. Возвращенный промис будет хранить исключение, извлеченное из
отклоненного промиса.
23,1.4. Дополнительные инструменты
для работы с промисами
При работе с промисами могут пригодиться два следующих статических метода,
поддерживаемые классом Promise:
♦ resolve(<нагрузка>) — возвращает уже подтвержденный промис, хранящий за-
данную нагрузку. Если в качестве нагрузки указал ь промис, он будет возвращен
без изменений;
♦ reject(<исключение>) — возвращает уже отклоненный промис, содержащий за-
данное исключение.
Класс window поддерживает два события, имеющие отношение к промисам:
♦ unhandledrejection — возникает при отклонении промиса, если у него не была
указана функция catch. Имеет обработчик по умолчанию, выводящий сообще-
ние об отклоняющем исключении в консоли веб-обозревателя, Если отменить
обработчик но умолчанию, сообщение выводиться не будет:
window.addEventListener(’unhandledrejection’, evt => {
evt.preventDefault() ;
});
Сведения об этом событии представляются объектом класса PromiseRejectionEvent,
производного от класса Event. Этот класс поддерживает два свойства (помимо
унаследованных):
• reason — исключение, которое привело к отклонению промиса:
• promise — сам отклоненный промис:
♦ rejectionhandled — возникает, если функция catch была указана у промиса
лишь после того, как он был отклонен и возникло событие unhandledrejection.
11евсплывающее и не имеет обработчика по умолчанию. В остальном аналогич-
но событию unhandledr eject ion.
23.2. Асинхронные функции
|| Асинхронная функция— функция, возвращающая результат в виде промиса.
Асинхронная функция сразу после вызова возвращает в качестве результата авто-
магически созданный промис. Как только в теле функции встретится оператор
return, заданное в нем значение помещается в возвращенный ранее промис в каче-
стве нагрузки, и промис подтверждается. Возникновение любого исключения в те-
ле функции вызывает отклонение промиса с занесением в него этого исключения
Если асинхронная функция в качестве результата возвращает промис, он возвраща-
ется как ест ь, без изменений.
Формаг объявления обычной асинхронной функции — именованной или ано-
нимной:
async function [<имя функцгк!>] ([<перечень параметров, ф
принимаемых функцией, через запятую>]) {
<тело функцип>
Пример:
async function isNumberEven(val) {
if (typeof val = 'number')
return val % 2 = 0;
else
throw new ТуреЕггог('Требуется число');
}
Также можно объявлять асинхронные стрелочные функции— поставив ключевое
слово async перед перечнем параметров:
const isNumberEven = async val •> {
if (typeof val = 'number')
return val % 2 =» 0;
else
throw new ТуреЕггог('Требуется число');
);
Промис, возвращенный асинхронной функцией, может быть обработан посредст-
вом инструментов, описанных в разд. 23.1.2'.
let р = isNumberEven(10000);
р.then(payload => {
console.log(payload ? 'Число четное' : 'Число нечетное')
)); // Число четное
В виде асинхронных функций реализуется код, вызывающий другие асинхронные
функции или обычные функции, возвращающие промисы, и использующий для
обработки возвращенных промисов оператор ожидания await (описан в разд. 23.3).
23.2.1. Асинхронные методы в классах
В классах JavaScript можно объявлять асинхронные методы — как объектные, так и
статические, в том числе геттеры и сеттеры — в форматах:
async <ммя метода?([<имена параметров, принимаемых методом, 'Ъ
через запятую?]) {
<тело метода?
async get <имя динамического свойства?() {
<тело геттера?
}
async set <имя динамического свойства? (<имя параметра?) {
<тело сеттера?
}
static async <имя метода?([<имена параметров, принимаемых методом,
через запятую>]) {
<тело метода?
)
static async get <имя динамического свойства>() {
<тело геттера>
}
static async set <имя динамического свойства>(<имя параметра^ {
<тедо сеттера>
Пример:
class SomeClass {
// Открытый асинхронный метод
async methodl(){...}
Il Открытый статический асинхронный метод
static async staticMethodl(){...}
II Закрытый асинхронный метод
async ftmethodz(){...}
// Закрытый статический асинхронный метод
static async #staticMethod2() { . . . }
Асинхронные методы вызываются так же, как и обычные:
const sc = new SomeClass();
sc.methodl()-then( . . . ).catch( . . . );
23.3. Оператор ожидания
Применение средств для обработки промисов. описанных в разд. 23.1.2, приводит
к появлению громоздкого и малопонятного кода. Гораздо удобнее использовать для
этого оператор ожидания await:
await <промис>
Этот оператор приостанавливает выполнение кода и ждет, когда заданный промис
будет подтвержден или отклонен. Если промис будет подтвержден, оператор вернет
его нагрузку в качестве результата. Если промис будет отклонен, оператор повторно
возбудит исключение, извлеченное из промиса.
Внимание!
Оператор ожидания await допускается применять лишь в телах асинхронных функций
(см разд 23 2)
Пример (оператор ожидания помещен в тело асинхронной функции однократного
вызова — см. разд. 8.5.3):
(async function () {
try {
const res = await isNumberEven(10000);
console.log(res);
} catch (exc) {
console.log(exc.message);
}) 0;
23.4. Упражнение.
Отзывчивое веб-приложение, вариант 3
Напишем еще один вариант приложения для вычисления простых чисел. В нем
функция, запускаемая таймером, будет выполнять лишь собственно вычисления,
а их вывод станет производиться в обработчике события submit веб-формы. Так
мы отделим процесс вычисления данных от их вывода на экран, как того требует
хороший тон программирования.
Передавать данные от кода, вычисляющего данные, коду, который выводит их,
будем через промис.
1 Найдем в папке 22\ех22 2 сопровождающего книгу файлового архива (см. прило-
жение 5) файл 22 2 html (само приложение). Скопируем его куда-либо на локаль-
ный диск
2. Откроем копию файла 22 2 html в текстовом редакторе и удалим все выражения
сценария, за исключением первого:
const form = document.forms[0];
lot-nun, max,—i-,—timer;
Как и ранее, весь диапазон чисел, среди которых следует найти простые, разде-
лим на части по 1000 чисел в каждой и будем обрабатывать эти части по очере-
ди, посредством однократною таймера (так проще реализовать передачу данных
через промис).
Код, выполняющий поиск простых чисел в очередной части заданного диапа-
зона, реализуем в исправленной функции computeSimpleValues (). Она примет
двумя параметрами начальное и конечное числа очередной части диапазона.
В своем теле функция вернет промис, задав в нем функцию получения нагрузки,
запускающую однократный таймер с функцией, которая и займется поиском
простых чисел в заданной части. Все найденные простые числа функция из
таймера занесет во вновь созданный массив, по окончании поиска поместит
этот массив в промис. созданный ранее и возвращенный из функции
computesimpleValues (), в качестве нагрузки и подт вердит этот промис.
3. Запишем объявление функции computeSimpleValues ():
function computeSimpleValues(first, last) {
return new Promise((resolve, reject) => {
const timer = window.setTimeout(() => (
const result = [];
for (let i = first; i <= last; i++) {
if (i % 2 ! = 0) {
let flag = true;
for (let j = 3; j <= Math.floor(Math.sqrt(i));
j += 2)
if (i % j = 0) (
flag = false;
break;
}
if (flag)
result.push(i);
}
}
resolve(result);
});
D;
}
Промис, возвращаемый функцией cornputeSimpleValues(), ради простоты станем
обрабатывать оператором ожидания (см. разд. 23.3).
4. Добавим код, привязывающий обработчик к событию submit веб-формы:
form.addEventListener(’submit’, async evt => { // .................. 1
evt.preventDefault();
const min = form.elements[0].valueAsNumber;
const max = form.elements[l].valueAsNumber;
form.elements 13].innerHTML = ’’;
form.elements[2].disabled = true;
for (let i = min; i <= max; i += 1000) { // .................... 2
const last = Math .min (max, i + 1000); Il ............... 3
const numoers = await cornputeSimpleValues(i, last); // .... 4
for (let n of numbers) { // ................................ 5
el = document.createElement(’option');
el.textContent = n;
form.elements[3].add(el);
}
}
form.elements[2].disabled = false;
});
В обязательном порядке превращаем обработчик события submi t в асинхронную
функцию — иначе не заработает оператор ожидания await (поз. 1 в приведенном
коде).
Разбиение заданного диапазона чисел на части, запуск поиска простых чисел
в очередной части и вывод результатов выполним в цикле со счетчиком. На-
чальным и конечным значениями счетчика станут начальное и конечное зна-
чения диапазона, а приращение станет увеличивать текущее значение счетчика
на 1000 — размер части (поз. 2).
Начальное значение каждой части хранится в счетчике— переменной 1. Конеч-
ное же значение будет вычисляться как наименьшее из конечного значения диа-
пазона и суммы текущего значения и 1000 (поз. 3).
Далее запускается поиск простых чисел в очередной части диапазона— вызо-
вом функции computeSimpleValues () с передачей ей начального и конечного зна-
чений части. Перед вызовом функции ставим оператор ожидания await — чтобы
дождаться момента, когда возвращенный функцией промис будет подтвержден,
и извлечь из промиса нагрузку — массив найденных простых чисел (поз. 4).
Далее перебираем полученный массив простых чисел и выводим числа в списке
(поз. 5).
Откроем страницу 22 2 html в веб-обозревателе и проверим приложение в работе.
23.5. Самостоятельное упражнение
Реализуйте в веб-приложении для поиска простых чисел (см. упражнение 23.4)
возможность прерывания вычислений по нажатию кнопки Стоп. Сделайте так.
чтобы до начала вычислений эти кнопка была недоступна.
Подсказка. Объявите глобальную переменную с именем, например, stop и изна-
чальным значением false. В обработчике щелчка на кнопке Стоп измените значе-
ние этой переменной на true, чтобы вызвать останов вычислений. В теле функции
comnuteSimpleValuesО перед запуском таймера проверяйте значение переменной
stop и, если оно равно true, отклоняйте возвращенный промис, передав ему чис-
ло 0— оно послужит ему сигналом, что вычисления были прерваны пользовате-
лем. В обработчике события submit поместите вызов функции computeSimpleValues()
в обработчик исключений и, если в качестве исключения было получено число 0,
прерывайте цикл обработки чисел.
Урок 24
Асинхронные итераторы
и генераторы
Промисы и асинхронные функции позволяют получать значения, которые будут
выработаны в будущем, в неопределенный момент времени. Но что делать, если
требуется получить последовательность таких значений? Использовать асинхрон-
ные итераторы и генераторы, аналогичные обычным, рассмотренным на уроке 12.
24.1. Асинхронные итераторы
Асинхронный итератор— итератор (см. разд. 12 7), метод next() которого воз-
вращает’ промисы. в качестве нагрузок содержащие значения из заданного на-
бора.
Асинхронный итератор, как и обычный, может выдаваться функцией, методом или
объектом.
Итерируемая функция должна возвращать простой объект с методом [Symbol,
asynciteranor] (), который, в свою очередь, должен возвращать асинхронный ите-
ратор. Итерируемый класс должен поддерживать аналогичный метод [Symbol,
asynclterator]().
В качестве имени упомянутого метода используется значение статического свойст-
ва asynclterator класса Symbol.
Пример итерируемой функции, выдающей промисы с названиями различных язы-
ков, которые применяются в веб-разработке, посредством асинхронного итератора;
> function languages() {
const langl = 'HTML';
const lang2 = ’CSS’;
const lang3 = 'JavaScript';
let i = -1, timer;
return {
[Symbol.asynclterator]() {
return {
next: function() {
return new Promise((resolve, reject) => {
timer = window.setTimeout(() => {
if (i = 0)
resolve({ value: langl });
else if (i == 1)
resolve({ value: lang2 });
else if (i = 2)
resolve({ value: lang3 });
else
resolved done: true });
}, 100);
});
24.2. Цикл перебора последовательности
с ожиданием
Для обработки асинхронных итераторов следует применять никл перебора после-
довательности с ожиданием.
Цикл перебори последовательности с ожиданием — перебирает один за дру-
гим промисы из заданной последовательности, на каждой итерации приостанав-
ливая исполнение кода до тех пор. пока очередной промис не будет подтвер-
жден, и присваивая нагрузку подтвержденного промиса заданной переменной:
for await ([<оператор о&ьявления>] <переменная> of последовательности)
{
<отело цикла>
}
Если переменная ранее не была объявлена, следует поставить какой-либо оператор
объявления переменной: let или var. Переменная, объявленная оператором let,
будет доступна только в теле цикла.
Внимание!
Цикл перебора последовательности с ожиданием может быть использоьан только
в асинхронной функции
Пример:
> (async () => {
for await (let 1 of languages())
console.log(1);
HO
< HTML
< CSS
< JavaScript
Исключение, возбужденное в теле асинхронного итератора, прервет выполнение
цикла. Пример обработки такого исключения:
(async () => {
try (
for await (let 1 of languages 0)
console.log(1);
} catch (exc) {
// Обрабатываем исключение
}
}) О;
24.3. Асинхронные генераторы
|| Асинхронный генератор—генератор, выдающий промисы.
Асинхронный генератор, как и обычный, может быть реализован в виде функции
или метода.
Формат объявления асинхронного генератора-функции:
async function* <имя генератора-функцнн>А<ммена параметров,
принимаемых генератором-функцией, через запятую>]) {
< тело генера тора - функпт л £>
}
Для выдачи очередного вычисленного значения применяется оператор возврата
с приостановкой yield.
Пример:
> async function* languages() I
yield 'HTML';
yield 'CSS';
yield 'JavaScript';
}
> (async () => {
for await (let 1 of languages())
console.log(1);
}) 0
< HTML
< CSS
< JavaScript
Асинхронный генератор-метод объявляется так же. как и асинхронный метод,
только в начале его имени ставится символ звездочки (*). В объявлении закрытого
генератора-метода символ * должен стоять перед символом #. Пример:
class SomeClass {
// Открытый асинхронный генератор-метод
async *methodl(){...}
// Открытый асинхронный статический генератор-метод
static async *staticMethcdl() { . . . }
// Закрытый асинхронный генератср-метод
async *#method2() { . . . }
// Закрытый асинхронный статический генератор-метод
static async *#staticMethod2() { . . . }
}
24.4. Упражнение.
Отзывчивое веб-приложение, вариант 4
Напишем еще один вариант веб-приложения для поиска простых чисел, на этот раз
применив асинхронный итератор.
I Найдем в папке 23\ех23 4 сопровождающего книгу файлового архива (см. прило-
жение 5) файл 22 2 html (само приложение). Скопируем его куда-либо на локаль-
ный диск.
2. Откроем копию файла 22.2 html в текстовом редакторе и удалим все выражения
сценария, за исключением первого:
const form = document.forms[0];
lunction- -computoSimploValuestmin, max)—{——v—т—F
Объявим итерируемую функцию computeSimpleValues (). В качестве параметров
она получит начальное и конечное числа заданного диапазона. Асинхронный
итератор, возвращенный ею. на каждой итерации будет выдавать промис с мас-
сивом простых чисел, найденных при обработке очередной части диапазона
(равной, как и в предыдущих случаях, 1000 чисел).
Новая функция самостоятельно выполнит все действия по поиску простых чисел
в диапазоне, включая разбиение его на части.
3. Напишем объявление функции computeSimpleValues О :
function computeSimpleValues(min, max) {
let first = min;
return {
[Symbol.asynclterator]() {
return {
next: function!) (
return new Promise((resolve, reject) => {
const timer = window.setTimeout(() => {
if (first <= max) {
const last = Math.min(max, first + 1000);
const result = [];
for (let i = first; i <= last; i++) {
if (i % 2 != 0) {
let flag = true;
for (let j = 3;
j <= Math.floor(Math.sqrt(i));
j += 2)
if (1 % j = 0) {
flag = false;
break;
}
if (flag)
result.push(i);
}
}
first = last + 1;
resolve(( value: result });
} else
resolve(( done: true });
});
)) ;
}
};
}
};
}
Обработчик события subnit веб-формы теперь будет лишь запускать процесс
вычислений и выводить результаты.
4. Добавим обработчик submit веб-формы:
fcrm.addEventListener(’submit’, async evt => {
evt.preventDefault();
const min = form.elements|0].valueAsNumber;
ccnst max = form.elements[1].valueAsNumber;
form.elements[3].innerHTML = ’’;
form.elements[2].disabled = true;
for await (let numbers of computeSimpleValues(min, max)) {
for (let n of numbers) {
el = document.createElement(’option’);
el.textcontent = n;
form.elements[3].add(el);
}
}
ferm.elements[2].disabled = false;
});
Откроем страницу 22.2 html в веб-обозревателе и проверим приложение в действии.
Поскольку функция computeSimpleValues() теперь выполняет все вычисления само-
стоятельно и является независимой от «внешнего» кода, мы можем вынести ее
объявление в отдельный файл и использовать в других разработках.
ЧАСТЬ V
Web API
С Урок 25 Работа с локальными файлами и перетаскивание
О Урок 26 Долговременное хранение данных
Э Урок 27. Программная графика
О Урок 28. Загрузка данных с ^еб-сервера
Урок 25
Работа с локальными файлами
и перетаскивание
Полезно знать
Программные инструменты, описываемые в чисти V (включая средства для работы
с локальными файлами и реализации перетаскивания), входят в состав Web API.
Web API (Web Application Programming Interface, интерфейс программирования
веб-приложений) - набор программных инструментов, не относящихся к ПОМ
и ВОМ.
25.1. Работа с локальными файлами
Внимание!
Веб-обозреватель может работать только с локальными файлами, явно выбранными
пользователем Считывать произвольные файлы с локального диска он не может
Выбор файлов для обработки в веб-сценарии или отправки серверу выполняется
в поле выбора файлов. Оно формируется одинарным тегом <input>, в котором за-
писывается атрибут type со значением file. Пример:
cinput type="file" id="file" multiple>
Класс такого поля поддерживает свойство files. Оно хранит объект класса
FileList, который является коллекцией выбранных в ноле файлов. Если в теге
<input> отсутствует атрибут без значения multiple, коллекция будет содержать
лить один файл. Каждый из элементов коллекции — это объект класса File, пред-
ставляющий отдельный выбранный файл.
25.1.1. Получение сведении о файлах
В свойствах класса File (табл. 25.1), доступных только для чтения, хранятся раз-
личные сведения о текущем файле.
Таблица 25.1. Свойства класса File
Свойство Описание
name- Имя файла без пути к нему в виде строки
type MIME-тип файла в виде строки
Таблица 25.1 (окончание)
Свойство Описание
size Размер в байтах в виде числа
lastModifled Временная отметка последнего изменения файла в виде количества мил- лисекунд, прошедших с полуночи 1 января 1970 года
Вот пример считывания сведений о выбранных файлах:
<input type="file" id=”file" multiple>
<div id="output"x/div>
const file = document.getElementByld(’file’);
const output = document.getElementByld('output');
file.addEventListener('change', () => {
let s = '';
for (let f of file.files)
s += '<h2>${f.name}</h2>4 +
'<р>Размер: ${f.size} байтов</р>' +
'<р>Тип: ${f.type)</p>' +
'<р>Последнее изменение: ' +
'${(new Date(f.lastModified)).toLocaleString()}</p>';
output.innerHTML = s;
}) ;
25.1.2. Считывание текстовых файлов
Для чтения содержимого текстового файла, указанного пользователем в поле выбо-
ра файлов, следует использовать асинхронный метод text О класса File. Он воз-
вращает промис. в качестве нагрузки хранящий содержимое текущего файла в виде
с [роки.
Вот пример считывания выбранного пользователем текстового файла и вывода его
содержимого на экран:
<input type="file" id="file" accept=".txt">
<pre id="output"x/pre>
const file = document.getElementByld('file’);
const output = document.getElementByld('output');
file.addEventListener('change', async () => {
const [f] = file.files;
if (f)
output.textContent = await f.textO;
});
25.1.3. Считывание
графических, аудио- и видеофайлов
Чтобы вывести на экран содержимое графического файла, следует представить его
в виде объектного адреса.
Объектный адрес — аналог интернет-адреса, представляющий хранящийся
в памяти объект.
Объектный адрес, указывающий на заданный файл, создается вызовом стагического
метода createObjectURL {<файл>) встроенного класса URL. Возвращенный объектный
адрес имеет строковый тип.
Полученный объектный адрес следует присвоить свойству src графического тега
<img> — и соответствующий файл будет выведен на экран.
Пример:
cinput type="file" id=''file” accept="image/*">
ci mg id^"output">
const file = document.getElementByld('file’);
const output = document.getElementByld('output');
file.addEventListener('change', () => {
const [f] = file.files;
if (f)
output.src = URL.createObjectURL(f);
});
Таким же образом можно воспроизвести аудио- или видеоролик, выбранный поль-
зователем:
cinput type="file" id="file" accept="audio/*">
caudio id="output">c/audio>
const file = document.getElementByld('file'’;
const output = document.getElementByld('output');
file.addEventListener('change', () => {
const [f] = file.files;
if (f)
output.src = URL.createObjectURL(f);
});
Каждый объектный адрес, созданный вызовом статического метода createObjectURL(),
занимает оперативную память. Поэтому, как только нужда в объектном адресе от-
падет. его следует удалить. Для удаления указанного о&ьектного адреса достаточно
вызвать статический метод revokeObj ectURL (собъектный адрес>) класса url.
25.2. Упражнение. Предварительный просмотр
выбранного графического файла
Часто в веб-формах для выгрузки графического файла на сервер делают панель
предварительного просмотра. Давайте и мы сделаем нечто подобное.
I. Найдем в папке 25\!sources сопровождающего книгу файлового архива (см. при-
южение 5) файл 25.2 html (страница с полем выбора файла) и скопируем ее куда-
либо на локальный диск.
Эта страница содержит поле выбора файла file, расположенный под ней графи-
ческий тег preview, в котором будет выводиться выбранное изображение,
и оформляющую все это внутреннюю таблиц) стилей.
2. Откроем копию страницы 25.2 html в текстовом редакторе, вставим в конце сек-
ции тела страницы тег <script> и запишем в нем код сценария:
const file = document.getElementByld(’file’);
const preview = document.getElementByld('preview');
let objURL;
file.addEventListener('change', () => {
if (objURL)
URI.. revokeObjectURL (objURL) ;
const [f] = file.files;
preview.src = URL.createObjectURL(f);
});
Рис. 25.1. Результат выполнения упражнения 25.2
Для хранения объектного адреса мы объявили глобальную переменную objURL.
Вывод изображения производим в обработчике события change поля выбора
файла. В нем сначала проверяем, хранится ли в переменной objURL ранее соз-
данный объектный адрес (т. е. было ли ранее выведено какое-либо изображе-
ние), и. если это так, удаляем его. Л уже потом выводим изображение из файла,
выбранного пользователем.
Откроем страницу 25 2 html в веб-обозревателе, выберем в поле какой-либо графи-
ческий файл и посмотрим на его содержимое, выведенное на странице (рис. 25 1).
25.3. Перетаскивание
Перетаскивание (иначе drag'n'drop)— технология переноса данных из одного
элемента страницы (источника) в другой (приемник) простым перетаскиванием
мышью.
Реализация перетаскивания на странице включает несколько этапов, которые мы
рассмотрим но порядку.
25.3.1. Превращение элемента-источника
в перетаскиваемый
Можно перетаскивать элемент страницы мышью или нет, зависит от значения
атрибута draggable, поддерживаемого всеми тегами:
♦ "auto" — можно перетаскивать только выделенный текст, изображения (<img>)
и гиперссылки (<а>). Это поведение по умолчанию;
♦ "true" — перетаскивать можно;
♦ "false" — перетаскивать нельзя.
Пример:
<р draggable-"!rue">Тащите меня семеро!</р>
25.3.2. Задание перемещаемых данных в источнике
Целью перетаскивания является перенос данных из источника в приемник. Перено-
симые данные задаются в источнике в обработчике любого из следующих событий:
♦ dragstart — возникает, когда пользователь начинает операцию перетаскивания;
♦ drag — периодически возникает несколько раз в секунду в процессе операции
перетаскивания.
Эти события всплывающие, имеют обработчик по умолчанию, запускающий опе-
рацию перетаскивания (если обработчик отменить, она не начнется).
Оба события представляются классом ::ragEvent. Он поддерживает свойство
dataTransfer с автоматически созданным объектом класса DataTransfer, в который
следует занести переносимые данные. Один такой объект может содержать целый
набор переносимых значений, относящихся к разным типам (например, строку
обычного текста, фрагмент HTML-кода и интернет-адрес).
Для занесения заданного значения в набор переносимых данных применяется метод
setDataO класса DataTransfer:
set Data (< тип значения:», <само значение>)
Оба параметра указываются в виде строк. В качестве типа значения обычно исполь-
зуют MIME-тип. стандартный или придуманный разработчиком.
Метод clearDataO класса DataTransfer удаляет из набора ненужное значение, от-
носящееся к указанному типу.
clear Data ([ <77 in значения;»])
Если тип значения не указан, из набора будут удалены все значения.
Для представления перетаскиваемых данных на экране сам веб-обозреватель соз-
дает небольшое графическое изображение, перемещающееся вместе с курсором.
Однако мы можем указать для этой цели свое изображение, вызвав метод
setDraglmage() класса DataTransfer:
setDragimage(<изображение>, <X>, <Y>)
Изображение задается в виде ссылки на тег <img> или графический холст (см. урок 27).
хит— это горизонтальная и вертикальная координаты точки на заданном изобра-
жении, которая при выводе изображения будет располагаться точно под кончиком
курсора мыши. Координаты задаются в виде чисел в пикселах. Начало координат
находится в верхнем левом углу изображения, горизонтальная координата отсчи-
тывается в направлении вправо, вертикальная координата — вниз.
Пример:
<р id= "dragsource" draggable="true">TainnTe меня семеро I</р>
<img id="dragimage" src="di.png" style="display: none;">
const dragsource = document.getElementByld('dragsource’);
const dragimage = document.getElementByld(’dragimage');
dragsource.addEventListener('dragstart', evt => {
const dt = evt.dataTransfer;
dt.setData('text/plain', 'Важные данные. Тащите аккуратнее.');
dt.setData('text/html', 'Важные данные. <еш>Тащите аккуратнее.</em>’);
dt.setDraglmage(dragimage, 30, 25);
});
25.3.3. Указание допустимых операций
В процессе перетаскивания пользователь может выбрать одну из трех операций:
♦ перемещение данных— при простом перетаскивании;
♦ копирование данных — при удержании клавиши <Ctrl>;
♦ создание ссылки на данные — при удержании клавиши <Alt> (реализуется край-
не редко).
Разработчик может ограничить набор допустимых операций, присвоив свойству
effectAllowed класса DataTransfer, одно из следующих строковых обозначений:
♦ 'all' —допустимы все гри операции;
♦ 'uninitialized' — то же. что и 'all';
♦ 'move' —допускается только перенос данных;
♦ 'сору' —только копирование;
♦ ' link' — только создание ссылки;
♦ 'copyMove' — перенос и копирование:
♦ 'linkMove' — перенос и создание ссылки;
♦ 'copyLink' — копирование и создание ссылки;
♦ 'попе' — не допускается ни одна операция.
Значение свойства по умолчанию: 'uninitialized'.
Набор допустимых операций указывается там же. где выполняется задание перено-
симых данных, — в обработчике события dragstart источника:
dragsource.addEventListener('dragstart', evt => {
const dt = evt.dataTransfer;
// Разрешаем только копирование и перенос данных
dt.е ffectAllowed = 1copyMove';
});
25.3.4. Подготовка элемента-приемника
Чтобы превратить элемент страницы в приемник для перетаскивания, достаточно:
♦ привязать обработчик к событию dragover, периодически возникающем) в при-
емнике при перемещении над ним курсора мыши;
♦ в этом обработчике отменить обработчик события по умолчанию (который от-
меняет операцию перетаскивания) вызовом метода preventDefault() у объекта
события
Тогда при наведении на приемник курсор мыши изменит форму, обозначая вы-
бранную пользователем операцию: перемещение, копирование или создание ссылки.
Пример:
<div id="dragdest">BannTe всё auria!</div>
const dragdest = document.getE LementByld('dragdest');
dragdest.addEventListener('dragover', evt => {
eve.prevent Default();
Свойство dropEffect класса DataTransfer хранит строковое обозначение операции,
которую пользователь собирается выполнить в процессе перетаскивания:
♦ 'move' — перемещение данных;
♦ 'сору'—копирование;
♦ ' link' — создание ссылки:
♦ 'попе' —отмена операций перетаскивания
Свойство dropEffect доступно нс только для чтения, но и для записи, что дает воз-
можность принудительно указать другую операцию, отличную от выбранной поль-
зователем:
dragdest.addEventListener('dragover', evt => {
evt.prevent Default();
// Принудительно указываем выполнить перенос данных
evt.dataTransfer.dropEffect = 'move';
}) ;
В приемнике в процессе перетаскивания также возникают следующие события
♦ dragenter— при наведении курсора мыши на элемент;
♦ dragleave — при уводе курсора мыши с элемента.
Обработчики по умолчанию у этих событий отсутствуют.
В обработчиках этих событий можно, например, визуально выделять элемент-
приемник с целью показать, что в него можно «положить» переносимые данные.
Все три рассмотренных здесь события: dragover, dragenter и dragleave— всплы-
вающие.
25.3.5. Завершение перетаскивания
Когда пользователь отпускает кнопку мыши над приемником, в последнем возни-
кает событие drop. Код, завершающий операцию переноса данных, записывается
в обработчике этого события.
Набор переносимых значений находится в объекте класса DataTransfer, хранящем-
ся в свойстве dataTransfer объекта события. Извлечь их поможет метод getDataO
класса DataTransfer:
getData(<тип извлекаемого значения>)
Он возвращает хранящееся в текущем наборе значение, относящееся к указанному
типу, в виде строки. Если в наборе отсутствует значение такого типа, возвращается
пустая строка.
Событие drop всплывающее, имеет обработчик по умолчанию, который обрабаты-
вает операцию перетаскивания самостоятельно.
Внимание!
Практически всегда обработчик события drop по умолчанию принимает переносимые
данные за интернет-адрес и пытается выполнить переход по нему. Поэтому обработку
события drop по умолчанию следует отменять
Пример:
dragdest.addEventListener('drop', evt => {
evt.prevent Default();
let s = evt.dataTransfer.getData(’text/plain’);
if (evt.dataTransfer.dropEffeet = 'move')
// Выполняем перенос данных
else
// Копируем данные
});
По завершении перетаскивания в элементе-источнике возникает событие dragend.
Оно всплывающее, не имеет обработчика по умолчанию и может применяться для
выдачи какого-либо оповещения о завершении переноса данных.
25.4. Упражнение. Экзаменатор
Напишем веб-приложение «Экзаменатор», предлагающее перетащить мышью из
левого перечня в правый все языки, применяемые при разработке веб-страниц.
I. Найдем в папке 25\'sources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 25 4.html (страница с «заготовкой» для экзаменатора, рис. 25.2)
и скопируем его куда-либо на локальный диск.
Оба перечня, присутствующие на этой странице, созданы обычными блоками,
отдельные позиции этих перечней— также блоками. Левый перечень имеет
якорь all, правый — selected, кнопка I огово — done, а блок для вывода резуль-
татов — output.
Экзаменатор
Перенесите из левого перечня в правый все языки, применяемые для
разработки веб-ст рашш
Рис. 25.2. «Заготовка» для написания экзаменатора
2. Откроем копию страницы 25 4 html в текстовом редакторе, вставим в конце сек-
ции тела страницы тег <script> и запишем в него выражения, получающие дос-
туп к нужным элементам страницы:
const all = document.getElementByld('all');
const selected = document.getElementByld('selected');
Перенос данных инициируем в обработчике события dragstart, который привя-
жем к отдельным позициям перечня all В обработчике занесем в набор перено-
симых данных якорь перетаскиваемого элемента (который сформируем про-
граммно чуть позже) и разрешим только операцию перемещения.
3. Напишем обработчик события dragstart позиций перечня all:
function dragStart(evt) {
evt.dataTransfer.setData('text/id', this.id);
evt.dataTransfer.effectAllowed = 'move';
}
В вызове метода setDataо можно указать как стандартный MIME-тип данных,
так и свой собственный. Мы придумали свой тип text/id, обозначающий якорь.
Теперь следует привязать этот обработчик события к позициям перечня all,
сделать их перетаскиваемыми и задать у них якоря. Якоря эти будут формиро-
ваться согласно шаблону: al1<порядковып номер>.
4. Добавим код. выполняющий эти действия:
for (let i = 0; i < all.children.length; ++i) {
all.children(i].addEventListener('dragstart', dragstart);
al1.chiIdrenfi].draggable = 'true';
all.children[i].id = 'all${i} ;
}
He забудем соответственно подготовить приемник— перечень selected.
5. Добавим код обработчика события dragover перечня selected, в котором разре-
шим перемещение данных:
function dragOver(evt) {
evt.preventDefault();
}
selected.addEventListener('dragover', dragCver);
При сбросе перетаскиваемой позиции перечня all в перечне selected следует
получить якорь перенесенной позиции, найти ее по полученному якорю и пере-
местить в перечень sel ected.
6. Напишем обработчик события drop перечня selected, который выполнит все эти
действия:
function drop(evt) {
evt.preventDefault() ;
const s = evt.dataTransfer.getData('text/id');
if (s) {
const el = all.querySelectcr('#$(s)*);
if (el)
selected.appendChild(el);
}
}
selected.addEventListener('drop', drop);
He забываем отменить обработчик события drop по умолчанию, иначе веб-
обозреватель отправит нас неизвестно куда.
Откроем страницу 25 4 html в веб-обозревателе и перетащим несколько позиций из
левого перечня в правый (рис. 25.3).
Экзаменатор
Перенесите из левого перечня в правый все языки, применяемые для
разработки веи-странпм
Python HTML
Assembler CSS
TypeScnpt JavaScript
Java XAML
C++
Го юво’
Рис 25.3. Результат выполнения упражнения 25 4
несколько позиций перетащены из левого перечня в правый
25.5. Перетаскивание файлов
в поле выбора файлов
Чтобы реализовать перетаскивание файлов из проводника в поле выбора файлов,
следует:
♦ в обработчике события dragover поля выбора файлов — разрешить перетаскива-
ние, отменив обработчик события по умолчанию;
♦ в обработчике события drop поля выбора файлов — извлечь коллекцию полу-
ченных файлов из свойства files класса DataTransfer. Эта коллекция представ-
ляет собой объект класса FileList (см. разд. 25.1).
Пример:
cinput type="file" id="file" multiple>
const file = document.getElementByld('file’);
file.addEventListener(’dragover1, evt => (
evt.preventDefault() ;
});
file.addEventListener(’drop’, evt => {
evt.prevent Default();
for (let f of evt.dataTransfer.files) {
// Перебираем и обрабатываем полученные файлы
}
});
25.6. Самостоятельные упражнения
1. В приложении-экзаменаторе (см. упражнение 25.4) реализуйте:
• перетаскивание позиций в образном направлении — из правого списка в ле-
вый;
Предварительный просмотр
Выберите файлы
Выбрать файлы Число файлов 7
Рис 25.4. Просмотрщик выбранных пользователем файлов
• проверку правильности ответа по нажатию кнопки Готово!. В случае верного
ответа в элементе output должна выводиться зеленая надпись «Правильно!»,
в случае неверного ответа — красная надпись «Неправильно!».
Подсказка', для вывода надписи нужным цветом воспользуйтесь стилевыми
классами right и wrong, уже имеющимися во внутренней таблице стилей.
2. Напишите веб-приложение просмотрщика выбранных пользователем файлов.
Реализуйте в этом приложении поддержку перетаскивания файлов из провод-
ника.
В папке 25Vsources сопровождающего книгу файлового архива (см. приложе-
ние 5) вы найдете файл 25 6.2 html — страницу с «заготовкой» для него.
У вас должно получиться гак, как показано на рис. 25.4.
Урок 26
Долговременное хранение
данных
Все данные, выработанные сценарием, существуют, пока в веб-обозревателе от-
крыта текущая страница. Как только будет выполнен переход на другую страницу,
все эти данные пропадут. Более того, они пропадут, даже если просто перезагру-
зить создавшую их страницу.
Поэтому часто возникает необходимость в долговременном хранении значений —
на время, пока открыто текущее окно веб-обозревателя, на заданный срок или на
неопределенно долгое время.
26.1. Хранилища: локальное и сессионное
Хранилище— программный инструмент, реализующий долговременное хране-
ние произвольных значений под уникальными именами.
Доступны два хранилища:
♦ сессионное — хранит данные, пока открыто текущее окно веб-обозревателя. По-
сле закрытия окна полностью очищается.
Подходит для временного сохранения данных на случай внезапной перезагрузки
страницы и для передачи данных между страницами;
♦ постоянное (или локальное) — хранит данные сколь угодно долго, даже после
закрытия всех окон веб-обозревагеля.
Подходит в случаях, если данные требуется хранить в течение неопределенно
долгого времени.
Все значения, сохраняемые в обоих хранилищах, связываются с адресом хоста,
с которого была загружена текущая страница. Страницы с другого хоста получить
доступ к ним не смогут.
Предельный объем каждого из хранилищ — 5 Мбайт.
Доступ к хранилищам можно получить через следующие свойства класса Window:
♦ sessionstorage— к сессионному хранилищу;
♦ localstorage — к постоянному хранилищу.
Оба хранилища представляются объектами класса storage и соответственно имеют
одинаковую функциональность.
Работа с хранилищами выполняется вызовами четырех методов класса storage:
♦ set item (<имя>, <значение>) — записывает в хранилище значение под указанным
именем. И имя, и значение задаются в виде строк. Если значение с таким именем
уже имеется в хранилище, перезаписывает его;
♦ getItem(<nznH>) — возвращает сохраненное ранее значение с указанным именем
или null, если его в хранилище нет;
♦ removeitem(<имя>) — удаляет из хранилища значение с указанным именем;
♦ clear о — очищает хранилище от всех значений.
Пример:
// Считываем из хранилища значение с именем someValue
let someValue = window.localstorage.getltem(’someValue’);
if (someValue)
// Если оно существует, преобразуем его в числовой тип
someValue = +someValue;
else
// Если такого значения в хранилище нет, задаем ему величину
//по умолчанию
someValue = 10;
// Сохраняем значение someValue
window.localstorage.setItem('someValue', someValue);
Следует помнить, что запись данных в хранилище не является гарантией, что они
будут храниться там в течение долгого времени. Пользователь может очистить
хранилище, дав соответствующую команду веб-обозревателю, — и тогда все со-
храненные значения пропадут. Кроме того, веб-обозреватель сам удалит из храни-
лища давно не извлекавшиеся значения, если предельный объем хранилища будет
превышен.
26.2. Упражнение.
Передача данных между веб-страницами
Чтобы закрепить новые знания, напишем две страницы: на первой пользователь
будет вводить свое имя. а на второй увидит персонализированное приветствие. Для
передачи данных между страницами применим сессионное хранилище.
I. Найдем в папке 26\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 26 2 1 .html (страница с полем ввода имени) и 26.2.2.html (стра-
ница. выводящая приветствие). Скопируем их куда-либо на локальный диск.
Страниц*! 26.2 1 html содержит поле ввода Введите свое имя с якорем name и ги-
перссылку Вывести приветствие, ведущую на страницу 26 2 2.html. Страница
26 2 2 html содержит пустой заголовок первого уровня с якорем output, в котором
будет выводиться приветствие, и гиперссылку Назад, ведущую на страницу
26 2 1.html.
На странице 26 2 1 html будем сохранять приветствие, введенное в поле name,
в сессионном хранилище под именем name. Выполним это в обработчике собы-
тия beforeunload окна веб-обозревателя, возникающего перед уходом с текущей
страницы. — это наилучшее место для сохранения данных.
2. Откроем копию файла 26 2 1 html в текстовом редакторе, вставим в секцию тела
страницы тег <script> и запишем в него необходимый код:
window.addEventListener(’beforeunload’, () => {
const name = document.getElementByld('name');
window.sessionstorage.setltemf ’name’, name.value);
H;
На странице 26.2.2 html извлечение ранее сохраненного имени станем произво-
дить в обработчике события load окна.
3. Откроем копию файла 26.2.2 html в текстовом редакторе, вставим в секцию тела
страницы тег <script> и напишем следующий код:
window.addEventListener ('load', ()=•>{
const output = document.getElementByld!'output');
const n = window.sessionstorage.getltem('name';;
output.textcontent = 'Здравствуйте, ${n}!';
});
Откроем страницу 26 2 1 html в веб-обозревателе, занесем в поле ввода свое имя,
щелкнем на гиперссылке Вывести приветствие (рис. 26.1) и посмотрим на адресо-
ванное нам приветствие (рис. 26.2). Далее щелкнем на гиперссылке Назад и повто-
рим описанные действия еще раз.
Введите свое имя
Вывести привед ите
Рис 26.1. Результат выполнения
упражнения 26.2' веб-страница
для ввода имени
Здравствуйте, Владимир!
Назад
Рис. 26.2. Результат выполнения упражнения 26.2
веб страница с персонализированным приветствием
26.3. Cookie
Cookie— произвольное значение, хранящееся под уникальным именем в тече-
ние заданного срока.
Механизм cookie схож с хранилищем (см. разд. 26./)— он позволяет сохранять
в отдельных cookie произвольные строковые значения, а создаваемые cookie связы-
ваются с адресом хоста, с которого была загружена страница. Однако есть и раз-
личия:
♦ cookie может быть создан не только веб-сценарием, но и веб-сервером;
♦ все cookie, связанные с определенным адресом хоста, отсылаются веб-серверу,
работающему на этом хосте, в составе каждого клиентского запроса. Благодаря
этому веб-сервер всегда сможет получить данные, сохраненные в cookie;
♦ у каждого cookie при создании можно указать срок хранения: до определенной
даты, в течение определенного времени или до закрытия текущею окна веб-
обозревателя. Cookie с истекшим сроком хранения удаляются автоматически;
♦ предельный объем значения, сохраняемою в cookie, — 4 Кбайт.
Внимание!
Сохранение cookie поддерживается лишь на веб-страницах, загруженных с веб-сер-
вера
Набор из всех имеющихся cookie, связанных с текущим адресом хоста, хранится
в свойстве cookie объекга страницы (доступного из свойства document окна) в виде
строки.
Чтобы создать набор cookie, хранящих заданные значения под указанными имена-
ми, свойству cookie следует присвоить строку, содержащую подстроки формата
<пмя>=<значение>, разделенные символами точки с запятой. Между точкой с запя-
той и очередной подстрокой можно поставить пробел. Количество создаваемых
cookie не ограничено (нужно лишь следить, чтобы их объем не превысил приведен-
ный ранее лимит). Пример:
document.cookie = ’name1-Владимир; пате2=Дронов';
Если cookie с указанным именем не существует, он будет создан. Если же такой
cookie уже существует, его значение будет заменено на указанное в подстроке.
В состав строки, присваиваемой свойству cookie, можно добавить параметры соз-
даваемых cookie, записанные в формате <параметр>-<значенме> и разделенные так-
же точками с запятой. Поддерживаются следующие параметры:
♦ expires— временная отметка, до которой следует хранить создаваемые cookie,
в виде строки в американском формате (такая строка возвращается методом
toUTCString () класса Date — см. разд. 7.2):
const dateExpiration = new Date(2024, 11, 31);
document.cookie = 'theme=light; expires=' +
dateExpiration.toUTCString();
♦ max-age — срок хранения создаваемых cookie в виде целого числа в секундах:
const maxAge = 60 * 60 * 24 * 365 * 2; //2 года
document.cookie = 'theme=light; max-age=’ + maxAge;
Должен быть указан лишь один из параметров: expires или max-age.
Если ни один из этих параметров не указан, cookie будут храниться до закрытия
текущего окна веб-обозревагеля;
♦ domain — адрес хост а, с которым следует связать создаваемые cookie. Созданные
cookie также будут доступны страницам, загруженным с адресов хостов сле-
дующих уровней. Пример:
document.cookie = ' name 1«=Вл а димир; пате2=Дронов; domain=site.ru';
В результате cookie с именами namel и name2 будут доступны страницам, загру-
женным с адресов хостов sife.ru. web.site.ru, support.site.ru. api.site.ru и т. п.
Если параметр не указан, cookie будут доступны лишь текущему адресу хоста,
но не адресам хостов следующих уровней;
♦ path— путь, с которым следует связать создаваемые cookie, в виде строки.
Cookie будут доступны лишь страницам, располагающимся по этому пути и
вложенным в него путям. Пример:
document.cookie = 'name1=Владимир; пате2=Дронов; path=/articles';
В результате cookie с именами namel и пате2 будут доступны страницам, распо-
лагающимся по путям /articles, /articles/software, /articles/sofhvare/programming
и т. и.
Если параметр не указан, cookie будут связаны с текущим путем.
Получить все cookie, доступные текущей странице, можно из того же свойства
cookie объекта страницы. Они представляются строкой, состоящей из подстрок
формата <тля>=<значение>, разделенных последовательностями из символа точки
с запятой и пробела. Пример:
const cookies = document.cookie; Il 'namel-Владимир; пате2=Дронов'
Получить значение cookie с заданным именем можно, например, с помощью функ-
ции get Cookie (<имя>), объявление которой имеет следующий вид:
function getCookie(name) {
return document.cookie
.split('; ')
.find(ccokie => cookie.startsWith( ${name} = '))
?.split(’=')[1];
}
Пример использования этой функции:
const namel = getCookie('namel'); // 'Владимир'
Чтобы удалить ненужный больше cookie, следует присвоить ему произвольное зна-
чение и указать в параметре expires временную отметку, располагающуюся в про-
шлом:
document. cookie = 'name1=Владимир; пате2=Дронов; ' +
'expires=Thu, 01 Jan 1970 00:00:00 GMT';
Cookie активно использовались для долговременного хранения данных в прошлом,
до появления более удобного инструмента— хранилищ. В настоящее время они
применяются существенно реже, однако до сих пор можно встретить старый код.
использующий их.
26.4. Самостоятельное упражнение
Реализуйте хранение в локальном хранилище текста, занесенного в область редак-
тирования, до того момента, когда он будет отправлены веб-серверу.
В папке 26\’sources сопровождающего книгу файлового архива (см. приложение 5)
вы найдете файл 26.4 html — страницу с «заготовкой».
Подсказки. Создайте переменную для хранения логического признака, был ли текст
отправлен веб-серверу. Изначально этот признак должен быть равен false (текст не
отравлен). Сохраняйте текст в обработчике события beforeunload окна, но только
если этот признак равен false. Извлекайте текст из хранилища в обработчике собы-
тия load окна. В обработчике события submit веб-формы устанавливайте упомяну-
тый ранее признак равным true и удаляйте из хранилища записанный туда ранее
текст.
Урок 27
Программная графика
В состав Web API входят инструменты для программного рисования на страницах
произвольной графики
27.1. Холст
Холит (или канва} — элемент страницы, предназначенный для программного
вывода графики.
Холст создается парным тегом <canvas> без содержимого. Поддерживаются два не-
обязательных атрибута:
♦ width— ширина холста в пикселах (по умолчанию: 300);
♦ height — высота холста в пикселах (по умолчанию: 150).
Пример:
ccanvas id="draw" width="640" height="480M></canvas>
Холст представляется классом HTMLCanvasElement. Он поддерживает свойства width
и height, соответст вующие одноименным атрибутам тега <canvas>.
Графический контекст — объект, представляющий «поверхность» холста
и, собственно, рисующий графику.
Для получения графического контекста следует вызвать у объекта холста метод
getcontextо, передав ему в качестве параметра строку ’2d'. Метод вернет в каче-
стве результата объект класса CanvasRenderingContext2D, представляющий графиче-
ский контекст. I Тример:
const draw = document.getElementByld(’draw');
const ctx = draw.getContext('2d*);
Рисование выполняется вызовом методов класса CanvasRenderingContext2L. описан-
ных далее.
Внимание!
Координаты на холсте и размеры рисуемых фигур указываются i пикселах Начало
координат находится в леном верхнем углу холста, а их значения отсчитываются в на-
правлении вправо и .-низ
27.2. Рисование прямоугольников
Прямоугольники нарисовать проще всего— достаточно вызвать один из трех ме-
тодов:
♦ strokeRect(<х>, <у>, <ширмна>, <высота>) — рисует прямоугольник С левым
верхним углом в точке с координатами [х, у] и с указанными шриной и высотой.
Рисуемый прямоугольник представляет собой один контур без заливки;
♦ fillRect (<х>, <у>, <ширт!на>, <высота>) — то же самое, что и strokeRect(), но
выводимый прямоугольник содержит только заливку без контура.
Пример (рис. 27.1, а):
ctxl.strokeRect(10, 10, 17С, 120);
ctxl.fillRect(20, 20, 50, 50);
ctxl.fillRect(120, 70, 50, 50);
♦ clearRect(<x>, <y>, <ширина>1 <высота>) —очищает прямоугольную область
с левым верхним углом в точке с координатами [х, у] и с указанными шириной и
высотой. Пример (рис. 27.1, б):
ctx2.fillRect(10, 10, 170, 120);
ctx2.clearRect(20, 20, 110, 50);
a
Рис. 274. а — выводимые прямоугольники содержат только заливку без контура
б — прямоугольная область залита черным цветом методом fillRect (),
и в ней очищена прямоугольная область методом clearRectO
6
27.3. Указание основных параметров графики
Основные параметры графики, включающие цвета линий, заливок и настройки
тени, действуют на любую графику, рисуемую на холсте.
27.3.1, Цвета линий и заливок
Для указания цвета рисуемой графики применяются свойства:
♦ strokestyle — цвет линий контура;
♦ fillstyle — цвет заливок.
Значение цвета может быть указано в любом формате, поддерживаемым CSS. По
умолчанию и контуры, и заливки выводятся черным цветом.
Пример (рие. 27.2):
ctx3.fillRect(10, 10, 170, 120);
ctx3.strokeStyle = '#ffffff’;
ctx3.strokeRect(20, 20, 150, 100);
ctx3.strokeRect(40, 40, 110, 60);
ctx3.fill Style = 'liqhtgrey';
ctx3.fillRect(60, 60, 70, 20);
Рис. 27.2. Контуры и заливки выведены цветами
з соответствии с приведенным примером кода
27.3.2- Параметры тени
Параметры тени, которая автоматически создается у рисуемых фигур, указываются
посредством четырех свойств:
♦ shadowBlur — величина размытия в виде положительного вещественного числа
или 0 (по умолчанию: 0);
♦ shadowcolor — цвет тени в любом формате, похдерживаемом CSS (по умолча-
нию: полностью прозрачный черный);
♦ shadowOffsetx — горизонтальное смещение тени в виде вещественного числа.
Положительные значения вызывают смешение вправо, отрицательные — влево.
По умолчанию: о;
♦ shadowOffsetY — вертикальное смещение тени, задаваемое вещественным чис-
лом. Положительные значения вызывают смещение вниз, отрицательные —
вверх. Но умолчанию: 0.
Для вывода тени следует задать ненулевое значение, по крайней мере, у одного из
свойств: shadowBlur, shadowOffsetX или shadowOffsetY.
Пример (рис. 27.3):
ctx2.strokeRect(10, 20, 30, 100);
ctx2.shadowcolor = 'black';
ctx2.shadowBlur = 5;
ctx2.strokeRect(65, 20, 30, 100);
ctx2.shadowOffsetX = 15;
ctx2.shadowOffsetY = -10;
ctx2.strokeRect(120, 20, 30, 100);
в соответствии с приведенным примером кода
27.4. Вывод текста
Для вывода текста применяются методы:
♦ strokeText (<текст>, <х>, <у>[, <максммальная шмрмна>]) —ВЫВОДИТ указанный
текст на экран. Левый конец базовой линии текста располагается в точке с коор-
динатами [х, у]. Если указана максимальная ширина, а ширина выводимого текста
превышает ее, текст будет либо уменьшен в размерах (в случае незначительного
превышения), либо выведен меньшим кеглем. Выведенный текст представляет
собой один контур без заливки:
♦ fillText (<текст>, <х>, <у>(, <максимальная ширина>]) — то же самое, что и
strokeText (), но выведенный текст содержит одну заливку без контура;
♦ measureText(<текст>) — возвращает объект класса TextMetrics, содержащий эк-
ранные размеры указанного текста. Интерес представляет только свойство width
этого класса, хранящее ширину текста в пикселах.
Параметры выводимого текста задаются в следующих свойствах:
♦ font — основные параметры шрифта (включая его гарнитуру, кегль, насыщен-
ность и начертание) в формате, поддерживаемом атрибутом стиля CSS font (по
умолчанию: ' Юрх sans-serif, т. е. шрифт без засечек кеглем 10 пикселов);
♦ font stretch — степень растяжения или сжатия шрифта по горизонтали в виде
одной из строк (в порядке от наиболее растянутых к наиболее сжатым): 'ultra-
expanded', 'extra-expanded', 'expanded', ' semi-expanded', 'normal' (обычный
шрифт без растяжения или сжатия), 'semi-condensed', 'condensed', 'extra-
condensed' или 'ultra-condensed'. По умолчанию: 'normal'.
Конкретная степень растяжения или сжатия, соответствующая указанному обо-
значению, зависит от заданного шрифта. Кроме того, не все шрифты поддержи-
вают все приведенные степени растяжения (сжатия);
♦ fontvariantcaps — обозначение формы букв в виде одной из строк:
• 'normal ’ — обычная форма букв;
• 'small-caps' — первая альтернативная форма. Буквы нижнего регистра на-
поминают буквы верхнего регистра, уменьшенные в размерах. Буквы разных
регистров имеют разные размеры;
• 'all-small-caps' —то же, что и 'small-caps', только буквы разных регист-
ров имеют одинаковые размеры;
• 'petite-caps’ -- вторая альтернативная форма. Буквы разных регистров
имеют разные размеры;
• 'all-petite-caps' — то же, что и 'petite-caps', только буквы разных реги-
стров имеют одинаковые размеры;
• 'unicase' — буквы верхнего регистра выводятся первой альтернативной
формой, буквы нижнего per истра — обычной;
• 'titling-caps' — третья альтернативная форма, разработанная специально
для вывода строк, которые написаны полностью в верхнем регистре.
Значение по умолчанию: 'normal'.
Конкретная форма букв, соответствующая указанному обозначению, зависит от
используемого шрифта. Кроме того, не все шрифты поддерживают все приве-
денные формы букв;
♦ fontKerning — обозначение режима кернинга, используемого при выводе текста,
в виде одной из строк:
• 'auto' — на усмотрение веб-обозревателя. Обычно шрифт малого кегля вы-
водится без кернинга, шрифт большого кегля — с кернингом;
• 'normal’ — кернинг используется всегда;
* 'попе' — кернинг не используется никогда.
Значение по умолчанию: 'auto';
♦ letterspacing— расстояние между отдельными буквами текста в формате, под-
держиваемом атрибутом стиля CSS letter-spacing (по у молчанию: 'Орх');
♦ wordspacing — расстояние между отдельными словами текста в формате, под-
держиваемом атрибутом стиля CSS word-spacing (по умолчанию: 'Орх'):
♦ textAlign — горизонтальное выравнивание текста относительно точки, указан-
ной в вызове метода strokeText () или fillText О, в виде одной из строк:
• ' left' — по левому краю,
• 'center' — по центру;
• 'right' — по правому краю;
• 'start' — по левому краю для языков с направлением письма слева направо,
по правому краю для языков с обратным направлением письма;
• 'end' — по правому краю для языков с направлением письма слева направо,
по левому краю для языков с обратным направлением письма.
Значение по умолчанию: 'start';
♦ textBaseline — вертикальное выравнивание текста относительно базовой линии
в виде одной из строк (рис. 27.4):
• 'alphabetic' — текст располагается на базовой линии;
• 'top' — под базовой линией:
Ьаз'вая линия
Alphabetic > Middle Bottom
Рис 27 4. Варианты вертикального выравнивания текста относительно базовой линии
• 'middle’ — середина текста совпадает с базовой линией;
• ’bottom’ — над базовой линией.
Значение но умолчанию: 'alphabetic';
♦ textRendering — обозначение режима оптимизации, используемого при выводе
текста, в виде одной из строк:
• 'auto' — на усмотрение веб-обозревателя;
• 'optimizeSpeed' — максимально быстрый вывод текста (при этом может по-
страдать его разборчивость и точность позиционирования);
• 'optimizeLegibility’ — вывод максимально разборчивого текста (однако
страдают скорость вывода и точность позиционирования);
• 'geometricPrecision' — максимально точное позиционирование текста
(страдают скорость вывода и разборчивость текста).
Значение по умолчанию: 'auto'.
27.5. Упражнение. Столбчатая диаграмма
Нарисуем на странице столбчатую диаграмму, изображающую рейтинг популярно-
сти различных языков программирования.
1. Найдем в папке 27\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 27 5 html (страница с «заготовкой» диаграммы) и 27 5 1 js
(исходные данные для построения диа! раммы). Скопируем их куда-либо на ло-
кальный диск.
2. Откроем копию файла 27 5 1.js в текстовом редакторе и посмотрим на его содер-
жимое:
const languages = [
['JavaScript', 19.1],
['Java', 14],
['Python', 13.4],
['C#', 13.3],
['Typescript', 13.3],
['PHP', 7.1],
['Kotlin', 3.2]
1;
Исходные данные собраны в массив languages. Каждый его элемент содержит
сведения об одном языке и также является массивом из двух элементов: назва-
ния языка и его рейтинга в процентах.
3. От кроем копию файла 27.5 html в текстовом редакторе и найдем код. создающий
холст:
<canvas id="draw" width="500" height="2L0"x/canvas>
Впоследствии мы подгоним ширину холста гак. чтобы построенная диаграмма
в нем поместилась.
4. 1 Тривяжем к странице 27 5.html веб-сценарий 27 5 1 .js самостоятельно.
Сценарий, рисующий диаграмму, вынесем в отдельный файл 27 5 2.js.
5. Создадим в папке с файлами примера файл 27 5 2 js и запишем в него выражения,
получающие доступ к холсту и к его графическому контексту:
const draw = document.getElementByld(’draw');
const ctx = draw.getContext('2d');
Чтобы вычислить ширину диаграммы, ширину отдельного столбца следует
умножить на количество столбцов. Количество столбцов будет равно количест-
ву языков, а ширина столбца— ширине самого длинного названия языка про-
граммирования. Вычислим ее. условившись для вывода названий применить
шрифт sans-serif кеглем 20 пикселов.
Рейтинг самого популярною языка программирования (разумеется, это
JavaScript) составляет 19.1%. Поэтому разобьем воображаемую вертикальную
шкалу процентов нашей диаграммы не на 100 делений, а на 20, иначе столбцы
по высоте окажутся практически неотличимыми друг от друга.
6. Добавим в сценарий код, выполняющий необходимые вычисления:
ctx.font = '20рх sans-serif';
let iwidth = 0, maxRating = languages[0][1];
for (let 1 of Languages) {
a = ctx.measureText(1[0]).width;
if (iwidth < a)
iwidth = a;
}
После выполнения этого кода искомая ширина столбца окажется в переменной
iwidth.
Ширина диаграммы равна сумме совокупной ширины столбцов и совокупной
ширине просветов между ними. Положим каждый такой просвет равным 5 пик-
селов.
7. Добавим выражение, устанавливающее ширину холста равной ширине диа-
граммы:
draw.width = iwidth * languages.length + 5 * (languages.Length - 1);
После изменения размеров холста все нарисованные на нем фигуры будут уда-
лены. а все настройки графики — сброшены.
8. Добавим выражения, вновь задающие параметры шрифта:
ctx.font = '20рх sans-serif';
ctx.textAlign = 'center';
ctx. textBaseline = 'bottom';
9. Добавим выражение, вычисляющее высоту самого высокого столбца:
const iheight = draw.height - 25;
Она равна высоте холста за вычетом 25 пикселов — под вывод названия языка.
10. Напишем код. выводящий диаграмму:
for (let i = 'J; i < languages.length; ++i) (
const a = iheiqht * languages[i][1] I 20;
const xi = i * (iwidth + 5);
const yi = draw.height - a;
ctx.fillRect(xi, yi, iwidth, a);
ctx.fillText(languages[i][0।, xi + iwidth / 2, yi, iwidth);
}
Высоту рисуемого столбца вычисляем делением рейтинга соответствующего
языка программирования на 20 и умножением на предельную высоту столбца.
При выводе надписи горизонтальную координату точки, в которой она будет
выведена, устанавливаем в середину столбца — поскольку ранее указали для
выводимого текста выравнивание по центру.
11. Привяжем сценарий 27 5 2 js к странице 27 5 html самостоятельно.
Откроем страницу 27 5.html в веб-обозревателе и посмотрим на нарисованную диа-
। рамму (рис. 27.5).
27.6. Рисование сложных фигур
Для рисования более сложных, нежели прямоугольники, фигур графический кон-
текст холста предлагает ряд следующих инструментов.
27.6.1. Начало и завершение рисования
Код, рисующий сложную фигуру, должен быть заключен в вызов двух методов,
первый из которых запустит процесс рисования, а второй завершит его и, собст-
венно, выведет фигуру на экран. Воз эти методы:
♦ beginPathO — запуск рисования;
♦ strike () — завершение рисования с выводом только контуров нарисованных
фигур:
ctx.beginPath() ;
// Рисуем контуры без заливки
ctx.stroke();
♦ fill () — завершение рисования с выводом только заливок нарисованных фигур:
ctx. begiriPath ();
// Рисуем фигуры из одних залиьок без контуров
ctx.fill();
27.6.2. Перемещение пера
Перо— воображаемый инструмент, рисующий сложные фигуры. Рисование
фигуры начнется в точке, где в текущий момент находится перо, а по заверше-
нии перо окажется в конечной точке фигуры.
Перемещение пера в точку с координатами [х, у] выполняет метод moveTo ():
moveTo(<х>, <у>)
Изначально перо установлено в точку [0, 0] — в левый верхний угол холста.
27.6.3. Рисование прямых линий
Прямую линию из точки, где находится перо, в точку с координатами [х, у] прово-
дит метод lineTo():
lineTo(<х>, <у>)
После рисования перо остается в точке [х, у].
Пример (рис. 27.6. а):
ctxl.beginPath();
ctxl.strokestyle = 'blue*;
ctxl.moveTo(50, 0);
ctxl.lineTo(100, 100);
ctxl.lineTo(0, 100);
ctxl.lineTo(50, 0);
ctxl.stroke();
Пример (рис. 27.6, б):
ctx2.beginPath();
ctx2.fillstyle = 'blue'
ctx2 .moveTo (5 ), 0) ;
ctx2.lineTo(100, 100);
ctx2.lineTo(C, 300);
ctx2.lineTo(50, 0);
ctx2.fill();
Рис. 27.6 Рисование прямых линий а — с выводом только контура нарисованной фигуры;
б — с выводом только заливки нарисованной фигуры
27.6.4. Замыкание контура
Чтобы замкнуть рисуемый контур, проведя прямую линию от текущего местопо-
ложения пера до точки, в которой началось рисование, следует вызвать метод
closePathO. IТример (см. рис. 27.6, и):
Ctx3.beginPath();
ctx3.strokestyle = 'blue*;
ctx3.moveTo(5G, 0);
ctx3.li.neTo (100, 100);
ctx3.1ineTo(0, 100);
ctx3.closePath();
ctx3.stroke();
При рисовании фигур из заливок методом fill () контур будет замкнут автоматиче-
ски (см. рис. 27.6. б).
27.6.5, Указание параметров линии
Мы можем указать у линий сложных фигур различные параметры: толщину, форму
концов и пр. Эти параметры могут быть заданы в любом месте кода, расположен-
ного между вызовами методов beginPath () и stroke О/fill О . Они будут действо-
вать на все линии рисуемой фигуры.
Для указания параметров линий применяются следующие свойства:
♦ linewidth — толщина линий в пикселах ( по умолчанию: 1):
ctxl.beginPath();
ctxl.moveTo(G, 50);
ctxl.lineTo(80, 50);
ctxl.linewidth = 5;
ctxl.stroke();
♦ linecap— обозначение формы концов линий в виде строки:
• 'butt' — плоский;
• 'round' — скругленный;
• 'square' — квадратный.
Но умолчанию: 'butt'.
1 Тример:
ctx2.beginPath();
ctx2.moveTo(0, 50);
ctx2.lineTo(80, 50);
ctx2.linewidth = 30;
ctx2.lineCap = 'round1;
ctx2.stroke');
♦ lineJoin — обозначение формы соединений линий в виде строки:
’miter' —заостренная;
'round'— скругленная;
* 'bevel'—срезанная.
1 Jo умолчанию: 'miter';
♦ miterLimit— степень заострения соединения линий в виде вещественного чис-
ла. Чем больше степень заострения, тем более заостренным будет соединение
двух линий. По умолчанию: 10.
Помимо этого, можно указать стиль линий — будут ли они штриховыми или пунк-
тирными. Для этого применяется метод setLineDash ():
setLineDash (<лгассив с параметрам стиля линии>)
В передаваемом методу массиве все элементы должны представлять собой числа,
которые поочередно указывают длину то очередного штриха, то очередного сво-
бодного пространства между двумя штрихами.
Примеры:
ctx3.beginPath();
ctx3.setLineDash([6, 3]);
ctx3.moveTo(0, 50);
ctx3.1ineTo(100, 50);
ctx3.stroke();
ctx4.beginPath();
ctx4.setLineDash([I1?, 3, 5, 3]) ;
Ctx4 .moveTo (0, 50);
ctx4.1ineTo(100, 50);
ctx4.stroke();
Чтобы вновь проводить сплошные линии, следует вызвать метод setLineDash(),
передав ему пустой массив.
Свойство lineDashOffset задает смещение штрихов и свободных пространств, со-
ставляющих стиль линий, в виде числа:
ctx5.beginPath();
ctx5.setLineDash([10, 3, 5, 3]);
ctx5.moveTo(0, 50);
ctx5.1ineTo(10r, 50);
ctx5.lineDashOffset = 9;
ctx5.linewidth = 5;
ctx5.stroke();
Метод getLineDash() возвращает заданный ранее массив с параметрами стиля
линий.
27.6.6- Рисование дуг
Для рисования дуг (частей окружностей и эллипсов) используются гри метода:
♦ аге () — рисует круглую дугу с центром в точке с координатами [х, у], указан-
ными радиусом, начальным и конечным углами:
агс(<х>, <у>, <радиус>, ^начальный угол>, <конечный угол>[,
<против часовой стрелки?>])
Оба угла задаются в радианах и отсчитываются от горизонтальной оси в направ-
лении часовой стрелки. Если последним параметром передать значение true,
дуга будет рисоваться против часовой стрелки, если задать false или вообще не
указывать последний параметр — по часовой стрелке.
11римеры:
ctxl.beg inPath ();
ctxl.arc(50, 50, 45, 0, Mach.PI /2);
ccxl.scroke();
ctx2.beginPath();
ctx2.arc(50, 50, 45, 0, Math.PI / 2, true);
ctx2.stroke();
ctx3.beginPath();
ctx3.arc(50, 50, 45, 0, Math.Pl * 2);
ctx3.stroke();
♦ arcToO — рисует круглую дугу с указанным радиусом между двумя касательны-
ми. Первая касательная проводится между точкой, в которой находится перо,
и точкой с координатами [xi, у1]. вторая — между точками с координатами
[xl, yl] и [х2, у2] (рис. 27.7).
Формат:
агсТо(<х1>, <у1>, <х2>, <у2>, <радиус>}
Рис. 27.7. Рисование дуги с указанным радиусом между двумя касательными
Пример:
ctx4.beginPath();
ctx4.moveTo(5, 95);
ctx4.lineTo(5, 50);
ctx4.arcTo(5, 5, 50, 5, 45);
ctx4.arcTo(95, 5, 95, 50, 45);
ctx4.1ineTo(95, 95);
ctx4.stroke();
♦ ellipse () — рисует эллиптическую дугу с центром в точке с координатами [х, у],
указанными радиусами, углом поворота, начальным и конечным углами'.
ellipse(<х>, <у>, <радиус по горизонтали>, <радиус по вертикали>,
<угол поворота>, <начальный угол>, <конечный угол>[,
<против часовой стрелки?>])
Вее углы задаются в радианах и отсчитываются от горизонтальной оси в направ-
лении часовой стрелки. Если последним параметром передать значение true,
дуга будет рисоваться против часовой стрелки, если задать false или вообще не
указывать последний параметр по часовой стрелке.
11римеры:
ctx5.beginPath();
ctx5.ellipse(50, 50, 45, 15, 0, 0, Math.PI * 2);
ctx5.lineWidth = 5;
ctx5.stroke();
ctx6.beg inPath();
Ctx6.ellipse(50, 50, 45, 15, Math.Pl / 4, 0,
Math.Pl *2);
ctx6.lineWidth = 5;
ctx6.stroke();
ccx7.beginPath();
ctx7.ellipse(50, 50, 45, 15, 0, -Math.PI / 2,
Math.PI / 2);
ctx7.lineWidth = 5;
ctx7.stroke();
ctxB.beginPath();
ctxP.ellipse(50, 50, 45, 15, Math.PI / 4,
-Math.PI / 2, Math.PI / 2);
ctxS.lineWidth = 5;
ctx8.stroke();
27.6.7. Рисование кривых Безье
Кривая Безье — кривая, описываемая начальной, конечной и одной (квадрати-
ческая кривая Безье) или двумя ключевыми (кубическая кривая Безье) точками.
Для рисования кривых Безье используются два метода:
♦ quadraticCurveTo () — рисует квадратичную кривую Безье от точки, в которой
находится перо, до точки [х, у]. Ключевая точка находится по координатам [сх,
су] (рис. 27.8).
Рис 27 8. Рисование квадратичной кривой Безье
Формат:
quadraticCurveTo(<сх>, <су>, <х>, <у>)
Пример:
ctxl .beginPath () ;
ctxl.moveTo(50, 5)
ctxl.quadraticCurveTo(5, 5, 50, 95};
ctxl.quadraticCurveTo(95, 95, 50, 5);
ctxl.fill ();
♦ bezierCurveTo () — рисует кубическую кривую Безье от точки, в которой нахо-
дится перо, до точки [х, у]. Ключевые точки находятся по координатам [cxl, cyl]
и [сх2, су2] (рис. 27.9).
Рис. 27.9. Рисование кубической кривой Безье
Формат:
bezierCurveTo(<cxl>, <cyl>, <cx2>, <cy2>, <x>, <y>)
1Тример:
ctx2.beginPath();
Ctx2.moveTo(50, 30)
ctx2.bezierCurveTo(5, 5, 5, 80, 50, 80);
ctx2.moveTo(50, 30)
ctx2.bezierCurveTo(95, 5, 95, 80, 50, 80);
ctx2.fill();
27.6,8. Рисование прямоугольников
Чтобы нарисовать в составе сложной фигуры прямоугольник, нужно вызвать метод
rect():
rect(<x>, <у>, <ширина>, <высота>)
Левый верхний угол прямоугольника будет находиться в точке [х, у]. По окончании
рисования перо останется в той же точке.
Пример:
ctxl.beginPath();
ctxl.rect(30, 40, 60, 40);
ctxl.lineTo(3c, 5);
ctxl.stroke();
Метод roundRect () рисует прямоугольник co скругленными углами:
roundRect(<x>, <y>, <ширина>, <высота>, <радиусы скругления углов>)
Первые четыре параметра аналогичны гаковым у метода recto. Радиусы скругления
углов могут быть заданы в виде:
♦ числа — задает радиус скругления у всех углов в пикселах;
♦ массива из одного числового элемента — то же самое;
♦ массива из двух чисел — первое число задаст радиус скругления у верхнего ле-
вого и нижнего правого углов, второе — у верхнего правого и нижнего левою;
♦ массива из трех чисел — первое число задаст радиус скругления у верхнего ле-
вого угла, второе — у верхнего правого и нижнего левого, третье — у нижнего
правого;
♦ массива из четырех чисел — первое число задаст радиус скругления у верхнего
левого утла, второе — у верхнего правого, третье — у нижнего правого, четвер-
тое — у нижнего левого.
Любое значение радиуса также может быть указано в виде простого объекга со
свойствами х (радиус скругления по горизонтали) и у (по вертикали).
Примеры:
ctx2.beginPath();
ctx2.roundRect(20, 30, 60, 40, 10);
ctx2.stroke();
ctx3.beginPath();
ctx3.roundRect(20, 30, 60, 40, [10, 0, 10]);
ctx3.stroke();
const rs = { x: 20, y: 40 };
ctx4.beginPath();
ctx4.roundRect(20, 30, 60, 40, [rs, rs, 0, 0] ) ;
ctx4.stroke();
27.6.9. Альтернативный способ
рисования сложных фигур
Есть и другой способ нарисовать сложную фигуру: создать объект контура, сфор-
мировать с его помощью контур требуемой фигуры и отобразить его на канве.
Объект контура создается на основе класса Pach2D посредством выражения формата:
new Path2D ([<ксн1др>])
Если параметр не указывать, будет создан «пустой» контур, не содержащий ника-
кой графики. Если же задать контур, также представленный объектом класса Path2b,
будет создана копия этого контура.
Класс Path2D поддерживает методы moveTo(), lineTo (), closePathO, arc(), arcTo(),
ellipse(), quadraticCurveTo(), betierCurveTo(), rect() и roundRect(), с помощью
которых и выполняется создание необходимого контура (см. разд. 27.6.2-27.6.8).
Пример:
const р = new Pach2:?();
р.moveTo(50, 0);
р.lineTo(100, 100);
р.lineTo(0, 100);
р.lineTo(50, 0);
Также поддерживается метод addPath (<контур>), добавляющий заданный контур
к существующему.
Для вывода указанного контура на холсте у графического контекста следует вы-
звать один из следующих методов:
♦ stroke (<контур>) — выводит заданный контур без заливки:
ctxl.strokestyle = 'blue';
ctxl.stroke(p);
♦ fill (<контур>) — выводит заливку, созданную на основе заданного контура:
ctx2.fillstyle = 'blue';
ctx2.fill(p);
Графический контекст поддерживат два метода, которые могут пригодиться при
использовании объектов класса Path2D:
♦ isPointlnPath(<контур>, <х>, <у>) — возвращает true, если точка с координа-
тами [х, у] находится па заданном контуре или внутри его. и false— в против-
ном случае:
let fl = ctxl.isPointlnPath(р, 100, 100); // true
let f2 = ctxl.isPointlnPath(p, 50, 50); // true
let f3 = ctxl.isPointlnPath(p, 120, 100); // false
♦ isPoincinStroke (<контур>, <x>, <y>) — возвращает true, если точка с коорди-
натами [х, у] находится строго на заданном контуре (но не внутри его), и false —
в противном случае:
fl - ctxl.jsPointlnPathip, 100, 100); // true
f2 = ctxl.isPointlnPath(p, 50, 50); // false
f3 = ctxl.isPointlnPath(p, 120, 100); // false
27.7. Градиенты и графические закраски
Для рисования линий и заливок, помимо сплошных цветов, можно применять гра-
диенты и графические закраски.
27.7.1. Линейные градиенты
Градиент— вид закраски, в котором один цвет постепенно переходит в дру-
гой, потом в третий и т. д.
Линейный градиент— градиент, в котором переходящие друг в друга цвета
располагаются на прямой линии (рис. 27. Ю).
Ключевая точка — точка градиента, в которой присутствует «чистый» цвет.
Для создания линейного градиента применяется метод createLinearGradient() гра-
фического контекста:
createLinearGradient(<xl>, <у1>, <х2>, <у2>)
Начало градиента будет находиться в точке [xi, у1], конец— в точке [х2, у2]. Эти
координаты указываются относительно холста.
Метод возвращает объект класса CanvasGradient, представляющий созданный гра-
диент.
Для добавления к градиенту ключевой точки у полученного объекта градиента
нужно вызвать метод addColorStop():
addColorStop ^относительное расстояние:», <цвет>}
Относительное расстояние указывается в виде вещественного числа от 0.0 (начало
градиента) до 1.0 (его конец), цвет— в любом формате, поддерживаемом CSS.
Полученный градиент присваивается свойствам графического контекста:
♦ strokestyle — для закраски конту ров;
♦ fillstyle — для закраски заливок.
Примеры:
const grdl = ctxl.createLinearGradient(0, 0, 100, 0);
grdl.addColorStopt0, 1 white’);
grdl.addColorStop(0.5, 'black');
grdl.addColorStop(1, 'lightqrey');
ctxl.beginPath();
ctxl.rect(0, 0, 100, 100);
ctxl.fillstyle = grdl;
ctxl.fill();
const grd2 = ctx2.createLinearGradient(0, 0, 0, 100);
grd2.addColorSt op(0, 'white');
grd2.addColorStop(0.5, 'black');
grd2.addColorStop(l, 'lightgrey');
ctx2.beginPath();
ctx2.rect(0, 0, 100, 100);
ctx2.fillstyle = grd2;
ctx2.fill();
const grd3 = ctx3.createLinearGradient(0, 0, 100, 100);
grd3.addColorStop(0, 'white');
grd3.addColorStop(0.5, 'black');
grd3.addColorStop(l, 'lightgrey');
ctx3.begi nPath();
ctx3.rect(0, 0, 100, 100);
ctx3.fillStyle = grd3;
ctx3.fill ();
Сразу после своего создания градиент фиксируется на холсте, а отдельные части
градиента проявляются на рисуемых с его применением фигурах. Это также отно-
сится к радиальному, угловому градиентам и графической закраске, рассматривае-
мым далее.
Пример:
const grd4 = ctx4.createLinearGradient(0, 0, 100, 0) ;
grd4.addColorStop(0, 'white’);
grd4.addColorStop(0.5, 'black');
grd4. addColorStop (1, ' lightgrey'); I
ctx4 . beginPath () ; |
ctx4.rect(0, 0, 30, 100);
ctx4.rect(33, 0, 30, 100); I
ctx4.rect(67, 0, 30, 100); I^^Ib
ctx4.fillstyle = grd4;
ctx4.fill();
27.7.2. Радиальные градиенты
Радиальный градиент— градиент, в котором переходящие друг в друга цвета
располагаются на концентрических окружностях (рис. 27.11). Начало такого
I радиента находится на начальной окружности, конец — на конечной.
Рис, 27.11. Радиальный градиент
Для создания радиального градиента применяется метод createRadialGradient()
графического контекста:
createRadialGradient(<xl>, <yl>, <R1>, <х2>, <у2>, <R2>}
[xl, yl] — координаты центргт начальной окружности г радиента, ri — ее радиус.
[х2, у2] и R2— координаты центра и радиус его конечной окружности.
Возвращаемый результат — объект класса CanvasGradient, представляющий соз-
данный градиент. Добавить ключевые точки можно вызовами метода addColorStop().
Примеры:
const grd5 = ctx5.createRadialGradient(50, 50, IC, 50, 50,50);
grd5.addColorStop(0, 'white');
grd5.addColorStop(0.5, 'black');
grd5.addColorStop(1, 'lightgrey');
ctx5.beginPath();
ctx5.rect(0, 0, 100, 100);
ctx5.fillstyle = grd5;
ctx5.fill();
о
const grd6 = ctx6.createRadialGradient(0, 0, 20, 0, 0, 200);
grd6.addColorStop(0, 'white');
grd6.addColorStop(0.5, 'black');
grd6.addColorStop(1, 'lightgrey');
ctx6.beqinPath();
ctx6.rect(0, 0, 100, 100);
ctx6.fillstyle = grd6;
27.7.3. Угловые градиенты
Угловой (или конусный) градиент— градиент, котором переходящие друг
в друга цвета располагаются по кругу в направлении хода часовой стрелки
(рис. 27.12). Начало такого градиента находится на начальном углу, нм же и за-
канчивается градиент.
Рис 27.12 Угловой градиент
Чтобы создать угловой градиент, у графического контекста следует вызвать метод
createConicGradiend():
createConicGradient(сначальный угол>, <х>, <у>)
Начальный угол измеряется в радианах и отсчитывается от горизонтальной линии
про часовой стрелке, [х, у] — это координаты центра круга градиента.
Возвращаемый результат— объект класса CanvasGradient, представляющий соз-
данный градиент. Добавить ключевые точки можно вызовами метода addColorStop ().
Примеры:
const grd7 = ctx9.createConicGradient(-Math.PI / 4, 50, 50);
grd7.addColorStop(0, 'white');
grd7.addColorStop(1, 'black');
ctx7.beginPath();
ctx7.rect(0, 0, 100, 100);
ctx7.fillstyle = grd7;
ctx7.fill();
const grd8 = ctxlO.createConicGradient(Math.PI / 2, 50, 50);
grdS.addColorStop(0, 'white');
grd8.addColorStop(0.3, 'black');
grdft.addColorStop<0.5, 'lightgrey');
grd8.addColorSt op{0.7, 'black');
grd8.addColorSt op(1, 'whi te');
ctx8.beginPath();
ctx8.rect(0, C, 100, ICO);
ctx8.fillstyle = grdlO;
ctx&.fill();
277.4. Графическая закраска
Графическая закраска— заполняет линии контура или заливки заданным гра-
фическим изображением, повторяющимся или неповторяющимся.
Графическую закраску создает метод createPattern () графического контекста:
createPattern(<изображение>, <режим повторенмя>)
В качестве изображения могут быть указаны собственно изображение (тег <img>),
видеоролик (тег <video>) или другой холст. Режим повторения задается в виде одной
из строк:
♦ 'repeat' — повторять и по горизонтали, и по вертикали;
♦ 'гереас-х' — повторять только по горизонтали:
♦ 'repeat-y' — повторять только по вертикали;
♦ 'no-repeat' вообще не повторять.
Метод createPattern() вернет в качестве результата объект класса Canvaspattern,
представляющий созданную графическую закраску. Ее следует присвоить свойству
strokestyle или fillstyle.
Примеры:
<img src="img.png" id="img">
const img = document.getElementByld('img');
const ptn9 = ctx9.createPattern(img, 'repeat');
ctx9.beginPath();
ctx9.rect(0, C, 100, 100);
ctx9.fillstyle = ptn9;
ctx9.fill();
const ptnlO = ctxlO.createPattern(img, 'no-repeat’) ;
ctxlO.beginPath();
ctxlO.rect(0, 0, 100, 100);
ctxlO.fillstyle = ptnlO;
ctxlC.fill();
Создавать графическую закраску можно лишь после того, как закончится загрузка
файла с изображением, используемым в качестве закраски. Добиться этого можно,
поместив создающий закраску код в обработчик события load окна (или тега <img>
с нужным изображением):
<img src="img.png" id=”img">
const img = document.getElementByld('img’);
window.addEventListener(’load', () => {
const ptn9 = ctx7.createPattern(img, ’repeat’);
ctx9. becrinPath ();
ctx9.rect(0, 0, 100, 100);
ctx9.fillstyle = ptn9;
ctx9.fill();
});
27.8. Преобразования
Преобразование— манипуляция над системой координат холста: смещение ее
начала, изменение масштаба или поворот.
Преобразования задают следующие методы графического контекста:
♦ trans Late (<dx>, <dy>) — смещает начало системы координат на величину dx по
горизонтали и на величину dy по вертикали. Положительные величины вызыва-
ют смещение вправо и вниз, отрицательные — влево и вверх.
Пример:
ctxl.strokeRect(0, 0, 40, 40);
ctxl.translate(50, 30)
ctxl. fillRect ((', 0, 40, 40);
♦ scale (<sh>, <sv>) — изменяет масштаб системы координат до значения sh по
горизонтали и до значения svno вертикали. Величины масштаба задаются в ви-
де числа с плавающей точкой: значения меньше 1 уменьшают масштаб, а боль-
ше 1 — увеличивают.
Пример:
ctx2.strokeRect ГО, 0, 10(’, 50);
ctx2.scale(0.7, 1.5)
ctx2.fillRect(0, 0, 100, 50);
♦ rotate (<угол>) — поворачивает систему координат на укачанный угол. Угол за-
дается в радианах, положительные значения вызывают поворот по часовой
стрелке, отрицательные — против.
1 Тример:
ctx3.strokeRect(20, 0, 80, 20);
ctx3.rotate(Math.PT / 4)
ctx3.fillRect(20, 0, R0, 20);
27.9. Вывод сторонних изображений
Для вывода сторонних изображений применяется метод drawimage () графического
контекста. Он может быть вызван в одном трех форматов:
♦ drawimage (<изображение>, <х>, <у>) — выводит заданное изображение, распола-
гая его левый верхний угол в точке с координатами [х, у], без изменения его
размеров. В качестве изображения могут быть указаны собственно изображение
(тег <img>), видеоролик (тег <video>) или другой холст.
Пример:
<img src-"img.png" id="img">
const img = document. getE.lementById(' img’) ;
ctxl.drawlmage(img, 1G, 20);
♦ drawl mage (<изображение>, <x>, <y>, <.w>, <h>)—то же самое, только выведен-
ное изображение будет иметь ширину ^и высоту h.
I Тример:
ctx2.drawlmage(img, 10, 20, 90, 80);
♦ drawlmage (<изображе№ie>, <fx>, <fy>, <fw>, <fh>, <x>, <y>, <w>, <h>) — вы-
резает из изображения фрагмент с левым верхним углом в точке с координатами
ffx, fy], шириной fw и высотой fh, после чего выводит его в точке [х, у] холста,
задав ему ширину уи высоту п.
1 Тример:
ccx3.drawlmaqe(img, 0, 10, 30, 27, 10, 20, 60, 54);
При выводе сторонних изображений могут оказаться полезными два следующих
свойства графического контекста:
♦ imageSmoothingEnabled— если true, при выводе изображения в увеличенном
масштабе будет задействовано сглаживание, если false— не будет (по умолча-
нию: true).
Сглаживание повышает качество выводимого изображения, однако при его за-
действовании вывод (особенно больших изображений) будет выполняться мед-
леннее;
♦ imageSmoothingQuality— обозначение качества сглаживания выводимого изо-
бражения в виде строки: 'low' (низкое), ’medium' (среднее) или 'high' (высокое).
По умолчанию: 'low'.
Чем выше качество, тем медленнее выполняется вывод изображения.
27.10. Управление композицией
Для управления композицией применяются два следующих свойства графического
контекста:
♦ giobaiAipha — задает уровень прозрачности рисуемой графики числом с пла-
вающей точкой от о.о (полная прозрачность) до 1. ) (полная непрозрачность). По
умолчанию: 1.0.
Пример:
ctxl.fillstyle = ’black';
ctxl.fillRect(0, 0, 70, 70);
ctxl.fillstyle = ’lightgrey';
ctxl.giobaiAipha = 0.5;
ctxl.fillRect(30, 30, 70, ^O);
♦ globalcomposl teoperation — указывает, как рисуемые фигуры накладываются на
уже нарисованные. Значение указывается в виде одной из строк:
• 's.urce-over' — новая фигура рисуется поверх старой, выводятся
обе фигуры:
• 'source-atop' — выводятся лишь старая фигура и та часть новой.
что накладывается на старую;
• 'source-in' — выводится лишь та часть новой фигуры, что накла-
дывается на старую;
• 'source-out' — выводится лишь та часть новой фигуры, что не
накладывается на старую;
• 'destination-over' — новая фигура рисуется под старой, выво-
дятся обе фигуры;
• 'destinaticn-atcp' — выводятся лишь новая фигура и та часть
старой, что накладывается на новую;
• 'destination-in' — выводится лишь та часть старой фигуры, что
накладывается на новую;
• 'destination-out' — выводится лишь та часть старой фигуры, что
не накладывается на новую;
• 'lighter' — область наложения фигур закрашивается цветом, по-
лученным в результате сложения цветов обеих фигур:
L0
• 'сору' — выводится только новая фигура, старая удаляется;
• 'хог' — выводятся только части новой и старой фигур, не накла-
дывающиеся друг на друга:
• 'multiply' — область наложения фигур закрашивается цветом,
полученным в результате умножения цветов обеих фигур;
• 'screen' — область наложения фигур закрашивается цветами, по-
лученными умножением инвертированных цветов обеих фигур и
инвертированием получившегося произведения цветов:
• 'overlay' — комбинация 'multiply' и 'screen'. Темные участки
области наложения фигур станут темнее, а светлые — светлее. За
основу берутся цвета старой фигуры;
• 'darken' — для закрашивания области наложения фигур выбира-
ются наиболее темные пикселы накладывающихся частей фигур;
• 'lighten' — для закрашивания выбираются наиболее светлые
пикселы накладывающихся частей фигур;
• 'color-dodge' — цвета пикселов старой фигуры делятся на инвер-
тированные цвета пикселов новой фигуры;
• 'color-burn* — инвертированные цвета пикселов старой фигуры
делятся на цвета пикселов новой фигуры, и полученное частное
инвертируется;
• ’hard-light' — то же самое, что и 'overlay', только за основу бе-
рутся цвета новой фигуры;
• 'soft-light' — то же самое, что и 'hard-light', только с более
мя1 кими цветами;
• 'difference' — цвета пикселов старой фигуры вычитаются из
цветов пикселов новой фигуры;
• 'exclusion' — то же самое, что и 'difference', только с меньшей
контрастностью;
• 'hue' — цвета пикселов области наложения фигур создаются на
основе яркости и цветности цветов пикселов старой фигуры и от-
тенка цветов пикселов новой фигуры;
• 'saturation' — цвета пикселов области наложения фигур созда-
ются на основе яркости и оттенка цветов пикселов старой фигуры
и цветности цветов пикселов новой фигуры;
• 'color' — цвета пикселов области наложения фигур создаются на
основе яркости цветов пикселов старой фигуры и оттенка и цвет-
ности цветов пикселов новой фигуры;
• 'luminosity' — цвета пикселов области наложения фигур созда-
ются на основе оттенка и цветности цветов пикселов старой фигу-
ры и яркости цветов пикселов новой фигуры.
По умолчанию: 'source-over'.
11ример:
ctxl2.fillstyle = 'black';
ctxl2.fillRect(О, О, 7C, 70);
ctxl2.fillstyle = 'lightgrey';
ctxl2.globalCompositeOperaticn = 'xor';
ctxl2.fillRect(30, 30, 70, 70);
27.11. Создание масок
Миска— область, делающая видимой лишь часть холста. За пределами маски
любая рисуемая графика остается невидимой.
Для создания маски нужно нарисовать контур сложной фигуры (как это сделать,
описывалось в разд. 27.б), только в конце вместо метода stroke () вызвать метод
clip О.
Пример:
ctxl.beginPath();
ctxl.rect(20, 0, 60, 100);
ctxl.clip();
ctxl.beginPath();
ctxl.arc(50, 50, 50, 0, 2 * Math.PI);
ctxl.fill();
27.12. Сохранение и восстановление
состояния холста
Состояние холста включает в себя параметры рисуемой ( рафики (цвета линий
и заливок, толщину линий, форму их концов и др.), преобразования системы
координат и все созданные на холсте маски.
Метод save () сохраняет состояние холста, а метод restore () восстанавливает:
// Сохраняем текущее состояние холста
ctx.save()
// Задаем параметры для рисования графики
// Рисуем графику
// Восстанавливаем состояние холста
ctx.restore();
//И тем самым возвращаемся к изначальным параметрам
Состояние холста можно сохранить неоднократно— последовательными вызовами
метода save О. Тогда при первом вызове метода restore () будет восстановлено по-
следнее сохраненное состояние холста, при втором — предпоследнее и т. д.
27.13. Упражнение. Рисование цветка
Нарисуем, применяя полученные ранее навыки, вот такой цветок (рис. 27.13).
Рис. 27.13. Задание к упражнению 27 13
I Найдем в папке 27\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 27.13 html и скопируем его куда-либо на локальный диск.
В файле содержится «заготовка» для рисования цветка, включающая холст
с якорем draw и размерами 300x300 пикселов.
2. Откроем копию файла 27.13 html в текстовом редакторе, добавим в конец секции
тела с границы тег <script> и запишем в него первые выражения сценария:
const colors = ['red', 'green’, ’blue’, 'yellow'];
const draw = document.getElementByld('draw');
const ctx = draw.getcontext('2d');
Они объявят массив с наименованиями цветов, которыми будут закрашены
отдельные лепестки, получат доступ к холсту, а потом к его графическому кон-
текст)'.
3. Добавим выражение, смещающее центр системы координат в центр холста,—
так удобнее рисовать:
ctx.translate(150, 150);
4. Напишем выражение, сохраняющее состояние холста:
ctx.save();
Впоследствии мы сможем вернуться к этому состоянию, просто восстановив его.
5. Напишем код, рисующий лепестки:
for (let color of colors) {
ctx.beginPath();
ctx.fillstyle = color;
ctx.moveTo(0, 0);
ctx.quadraticCurveTo(-50, -75, 0, -150);
ctx.quadraticCurveTo(5C, -75, 0, 0);
ctx.fill ();
ctx.rotate(Math.PI / 2);
}
Каждый лепесток закрашивается очередным цветом из массива colors, после
чего система координат поворачивается на 90й.
Осталось нарисовать центральный кружок, состоящий из двух половинок: чер-
ной и белой. Но сначала восстановим ранее сохраненное состояние и укажем
композицию, при которой новые фигуры рисуются под старыми (поскольку
кружок находится под лепестками).
6. Напишем код, рисующий кружок:
ctx.restore();
ctx.globalCompositeOperation = 'destination-over';
ctx.beginPath();
ctx.arc(0, 0, 30, -Math.PI / 2, Math.PI / 2);
ctx.fill();
ctx.beginPath();
ctx.arc(0, 0, 29, Math.PI /2, 1.5 * Math.PI);
ctx.stroke();
Откроем страницу 27 13 html в веб-обозревателе и полюбуемся на нарисованный
цветок.
27.14. Самостоятельные упражнения
♦ Создайте у диаграммы из упражнения 27.5 вертикальную шкалу, проградуиро-
ванную в процентах, с делениями каждые 2%.
♦ Закрасьте столбцы той же диаграммы линейным градиентом, в котором цвета
изменяются от светло-серого до черного в направлении снизу вверх.
У вас должно получит ься гак (рис. 27.14).
Рис. 27 14. Столбчатая диаграмма из упражнения 27 5 дополненная вертикальной шкалой.
проградуиро^энной в процентах, и закрашенная линейным градиентом
Урок 28
Загрузка данных с веб-сервера
Web ЛР1 предоставляет средства для программной загрузки с веб-сервера любых
данных: фрагментов веб-страниц (для непосредственного вывода), данных в фор-
мате JSON (для вывода после обработки, подробности — в разд. 28.4) и др.
Внимание!
Загрузка данных с помощью описываемых далее инструментов поддерживается лишь
на веб-страницах, загруженных с веб-сервера, и только с веб-серверов
28.1. Отправка клиентского запроса
на загрузку данных
Клиентский запрос — блок данных, отправляемый веб-обозревателем веб-
серверу, содержащий интернет-адрес требуемых данных и другие сведения,
которые могут понадобиться веб-серверу для их отправки.
Для отправки клиентского запроса на загрузку данных, находящихся по указанному
интернет-адресу, применяется метод fetch() класса Window:
fetch(<интернет-адрес>[, <параметрь£> ])
Интернет-адрес задается В виде строки. Параметры мы изучим в разд. 28.1.1.
Метод fetch О в качестве результата возвращает промис, который:
♦ подтверждается — при получении любого ответа от веб-сервера, даже содер-
жащего сообщение об ошибке’,
♦ отклоняется — только если клиентский запрос не удалось отправить из-за про-
блем с сетью или некорректных параметров, заданных в вызове метода.
Пример:
const url = 'http://www.somesite.ru/others/htinl/fraqinentl.html';
const г = await window.fetch(url);
28.1.1. Параметры клиентского запроса
В вызове метода fetch о можно указать необязательные параметры, задаваемые
в виде простого объекта. Поддерживаются следующие параметры:
♦ method— HTTP-метод, используемый для получения данных, в виде строкового
обозначения ('get', 'post' и т. д.). По умолчанию: 'get';
♦ body — данные, передаваемые веб-серверу, в виде объекга класса URLSearchParams
(см. разд. 28.1.2.1) или FormData (см. разд. 28.1.2.2). Принимается во внимание,
только если для получения данных используется метод, отличный от GET и
HEAD. По умолчанию: null;
♦ headers — заголовки, добавляемые в отправляемый HTTP-запрос (наряду со
сформированными веб-обозревателем).
Заголовок клиентского запроса— какое-либо служебное значение, пересылае-
мое веб-серверу в составе клиентского запроса. Представляет собой часть опи-
сания запрашиваемых данных и передаваемых серверу значении (если таковые
передаются — см. разд. 28.1.2). Имеет строго определенное имя. регламентиро-
ван н ое и нтер нет-с тан дартам 11.
Указание заголовков запроса подробно будет рассмотрено в разд. 28.1,1.1.
Значение по умолчанию: null (отсутствие дополнительных заголовков);
♦ mode — обозначение режима обработки межхостовых запросов.
Межхостовый (или межсайтовый, или кросс-доменный) клиентский запрос —
клиентский запрос, отправленный веб-серверу, который работает на хосте, от-
личном от того, с которого была загружена текущая страница.
Обозначение режима задается в виде одной из строк:
• ’same-origin’ — любые межхостовые запросы запрещены. Можно отправ-
лять запросы лишь на хост, с которого была загружена текущая с граница.
Отправка запроса на «чужой» хост вызовет ошибку;
• ’no-cors’ — разрешены лишь простые межхостовые запросы (выполненные
с применением HTTP-методов GET, POST и HEAD). Расширенные межхос-
товые запросы (выполненные с применением других ПТТР-методов) запре-
щены. и попытка их выполнить вызовет ошибку;
• ’cors' —любые межхостовые запросы разрешены. Однако для выполнения
расширенных запросов потребуется получить от веб-сервера разрешение на
их отправку.
Внимание!
Чтобы веб-обозреватель смог прочитать данные, отправленные с веб-сервера, кото-
рый работает на другом хосте, в этом веб-сервере должна быть реализована под-
держка межхостовых запросов Как это сделать, будет рассмотрено в разд. 30 3 Там
же ^писано, каким образом веб-сервер может отправить веб-обозревателю разреше-
ние на выполнение расширенных межхостовых запросов
Значение параметра по умолчанию: ’cors’.
♦ cache — обозначение режима работы кеша веб-обозревателя.
Кеш — хранилище, в котором веб-обозреватель временно сохраняет все загру-
женные с сервера файлы для ускорения последующего доступа к ним.
Кешированный файл (или кешированная копия) — копия загруженного файла,
хранящаяся в кеше.
Вместе с запрошенным файлом веб-обозреватель получает от сервера указание,
в течение какого времени следует хранить кешированную копию. По истечении
этого времени кешированная копия считается неактуальной и подлежащей про-
верке на актуальность.
Обозначение режима указывается в виде одной из строк:
• 'default' — если нужный веб-обозревателю файл находится в кеше и не
устарел, он берется из кеша. Если кешированный файл устарел, веб-обо-
зреватель отправляет серверу клиентский запрос с просьбой сообщить, не из-
менился ли кешированный файл по сравнению с оригинальным, хранящимся
на сервере, и, если не изменился, берет его из кеша, в противном случае за-
гружает файл с сервера и заносит в кеш, перезаписывая имеющуюся кеширо-
ванную копию. Если нужного файла нет в кеше, он за! ружаегся с сервера и
сохраняется в кеше;
• 'no-store' — запрашиваемый файл всегда загружается с сервера (даже если
он есть в кеше и не устарел) и не записывается в кеш.
Этот режим применяется для загрузки каких-либо часто обновляемых данных
(чтобы не захламлять ими кеш) и секретных сведений (которые нежелательно
сохранять где-либо на локальном диске).
Остальные режимы применяются в крайне специфических случаях:
• 'reload' — запрашиваемый файл всегда загружается с сервера и записыва-
ется в кеш;
• 'no-cache' — то же самое, что и 'default', только проверочный запрос для
подтверждения актуальности кешированного файла отправляется серверу
всегда, даже если кешированный файл еще не устарел;
• 'force-cache' — то же самое, что и 'default', только, если запрашиваемый
файл есть в кеше (неважно, актуальный или уже устаревший), всегда исполь-
зуется именно он;
• 'only-if-cached' — всегда используются только кешированные файлы. Если
же требуемого файла в кеше нет. сам веб-обозреватель создает серверный от-
вет с кодом статуса 504 (превышено допустимое время выполнения запроса;
о серверных ответ ах и кодах статуса рассказано в разд. 28.2).
Значение по умолчанию: 'default';
♦ signal — прерывающий сигнал, отменяющий запрос (см. разд. 17.1.3.2).
Пример:
const aborter = new AbortController();
const r = await window.fetch(url, {
mode: 'cors',
cache: 'no-store',
signal: aborter.signa]
});
28.1.1.1. Задание заголовков клиентского запроса
Дополнительные заголовки, добавляемые в отправляемый клиентский запрос, ука-
зываются в свойстве headers простого объекта с параметрами, передаваемого мето-
ду fetcho. Заголовки можно указать в виде:
♦ простого объекта, в котором свойства задают имена заголовков, а значения
свойств — значения этих заголовков:
const г = fetch('/data', {
headers: {
// Указываем серверу, чтобы он выслал данные в формате JSON...
Accept: 'application/j son',
// ...и на русском языке
’Accept-Language’: 'ru-RU'
}
);
♦ объекта класса Headers, создаваемого выражением формата:
new Headers ([ <простой оСгьект с заголовками ])
Заголовки, заданные в указанном простом о&ьекте, будут сразу же добавлены
в создаваемый объект заголовков:
const headers = new Headers({
Accept: 'application/json',
'Accept-Langua ge': 'ru-RU'
});
const r2 = fetch(’/data', { headers });
Набор заголовков, представленный в виде объекта класса Headers, обрабатыва-
ется быстрее, чем записанный в виде простого объекта. Поэтому класс Headers
следует использовать в случае, если один и тот же набор заголовков должен
заноситься в разные клиентские запросы,— это немного повысит производи-
тельность.
Класс Headers поддерживает следующие полезные методы:
♦ append (<пмя>, <значенме>) —добавляет в текущий набор новый заголовок с за-
данными именем И значением:
const cHeaders2 = new Headers О;
oHeaoers2.append(’Accept-Language', 1ru-RU');
if (expectJSONType]
oHeaders2.append('Accept', 'application/json');
Если заголовок с заданным именем уже существует, указанное значение будет
добавлено к уже существующему и отделено от него запятой:
oHeaders2.append('Accept-Language', 'en-US');
// Б результате отправляемый запрос будет содержать заголовок:
// Accept -Language: ru-RU, en-US
♦ set (<имя>, <значение>) — аналогичен append (), только, если заголовок с указан-
ным именем уже существует, полностью заменяет имеющееся в нем значение
заданным:
□Headers2.set('Accept-Language', 1ru-RU’);
//В результате отправляемый запрос будет содержать заголовок:
// Accept-Language: ru-RU
♦ get (<имя>) — возвращает строковое значение заголовка с заданным именем или
null, если такого заголовка нет:
let s = oHeaders2.get('Accept-Language'); // ’ru-RU'
s = oHeaders2.get('Other-Header'); // null
♦ has (<имя>) — возвращает true, если заголовок с заданным именем существует,
и false — в противном случае:
s = oHeaders2.has('Accept-Language'); // true
s = oHeaders2.has('Other-Header'); // false
♦ delete (<имя>) —удаляет из текущею набора заголовок с заданным именем. Если
такого заголовка нет. ничего не делает:
♦ entries () — возвращает итератор (см. разд. 12.1), на каждой итерации выдаю-
щий массив из двух элементов: имени очередного заголовка и его значения:
for (let pair of oHeaders2.entries())
console.log(pair[0] + ': ' + pair[l]);
♦ keys() —то же самое, что и entries(), только на каждой итерации выдает имя
очередного заголовка:
♦ values () — то же самое, что и entries (), только на каждой итерации выдает
значение очередного заголовка;
♦ forEach(<фумкц1 v?> [, <контекст выполнен 1я>]) — перебирает заголовки, содер-
жащиеся в текущем наборе, и передает каждый заголовок заданной функции.
Функция должна принимать три параметра: значение очередного заголовка, его
имя и ссылку на текущий набор. Функция не должна возвращать результата.
Можно указать контекст выполнения, который будет доступен в теле функции
через локальную переменную this.
28,1.2. Передача данных веб-серверу
Часто веб-сервер, чтобы успешно сформировать ответ, нуждается в каких-либо
исходных данных, и веб-обозреватель должен их ему отправить в составе клиент-
ского запроса. В зависимости от реализации сервера, эти данные могут быть
отправлены HTTP-методом GET или POST, а в последнем случае—с использова-
нием одного из двух доступных методов кодирования.
28.1.2.1. Передача данных HTTP-методом GET
При отправке данных HTTP-методом GET данные отправляются веб-серверу в виде
набора GET-параметров.
GET-napauemp— произвольное значение, пересылаемое веб-серверу в составе
интернет-адреса под заданным уникальным именем.
Отдельный GET-параметр. хранящий одно значение, записывается в формате:
<имя СЕТ-параметра>=<значение GET-параметрам
GET-параметры в наборе разделяются символами амперсанда (&). Набор GET-
параметров записывается в конце интернет-адреса, после пути, через символ во-
просительно! о знака (?).
Формируемый набор GET-параметров удобно представить в виде объекга класса
URLSearchParams, создаваемого выражением формата:
new URLSearchParams ([<исходные данные>])
Если исходные данные не указаны, будет создан «пустой» набор GET-параметров.
Если же исходные данные заданы, в созданный объект будут помещены GET-пара-
метры, созданные на их основе. Исходные данные можно указать в виде:
♦ строки с набором GET-параметров, извлеченные из интернет-адреса, с началь-
ным символом вопросительного знака или без такового:
const str = '?search=javascript&page=K ;
const getParams = new URLSearchParams(str);
♦ последовательности из вложенных массивов, каждый из которых хранит два
элемента: имя и значение очередного GET-параметра:
const arr = [['search', 'javascript'], ['page', 10]];
const getParams2 = new URLSearchParams(arr);
♦ простого объекта, свойства которого превратятся в GET-параметры:
const obj = { search: 'javascript', page: 10 };
const getParams3 = new URLSearchParams(obj);
Класс URLSearchParams поддерживает следующие методы:
♦ append(<имя>, <значеиие>) — добавляет в текущий набор новый GET-параметр
С заданными именем И значением:
getParams.append(’search’, 'j ava’);
Если GET-параметр с заданным именем уже существует в текущем наборе, будет
создан еще один GET-параметр с тем же именем и указанным значением:
getParams.append('search’, ’python');
// В результате объект будет содержать следующий набор GET-параметров:
// search=java&search=python
♦ set(<nMH>, <значение>) — аналогичен appendo, только, если GET-параметр
с указанным именем уже существует, заменяет имеющееся в нем значение задан-
ным и удаляет все остальные GET-параметры, имеющие то же имя;
getParams.set('search', ’javascript');
//В результате объект будет содержать следующий набор GET-параметроь:
// search^javascript
♦ tostringо — возвращает строку, содержащую GET-параметры из текущего на-
бора и пригодную к помещению в состав интернет-адреса. Возвращаемая строка
не содержит начальный символ вопросительного знака. Если текущий набор
«пуст», возвращает пустую строку. Пример:
const г = await window.fetch('/data?$[getParams.toString()}');
♦ get {<имя>} — возвращает строковое значение GET-параметра с заданным именем
или null, если такого GET-параметра нет:
let s = getParams.get('search'); // 'javascript'
s = getParams.qet('sort'); // null
Если в текущем наборе присутствует несколько GET-параметров с заданным
именем, возвращается значение самого первого из них:
getParams.append('search', 'python');
s = getParams.get('search’); // 'javascript'
♦ getAll(<ммя>) — возвращает массив строковых значений всех GET-параметров
с заданным именем, имеющихся в текущем наборе. Если ни одного из подходя-
щих GET-параметров нет. возвращает пустой массив. Пример:
s = getParams.getAl1('search'); // ['javascript', 'python']
♦ has (<имя>[, <значение>]) — возвращает true, если GET-параметр с заданным
именем существует в текущем наборе, и false — в противном случае:
s = getParams.has('page'); // true
s = getParams.has('sort'); // false
Если указано еще и значение, возвращает true лишь в том случае, если в теку-
щем наборе существует GET-параметр с заданными именами значением.
s = getParams.has('раде', 10); // true
s = getParams.has('page', 9); // false
♦ delete (<имя>[, <значение>]) — удаляет из текущего набора все GET-параметры
с заданным именем. Если дополнительно задано значение, будет удален лишь
GET-параметр с заданными именем и значением. Если ни одного подходящего
GET-параметра нет, ничего не делает;
♦ entries () — возвращает итератор (см. разд. 12.1}, на каждой итерации выдаю-
щий массив из двух элементов: имени очередного GET-параметра и его значе-
ния;
♦ keys () —то же самое, что и entries О, только на каждой итерации выдает имя
очередного GE1 -параметра:
♦ values О — то же самое, что и entries О, только на каждой итерации выдает
значение очередного GET-параметра;
♦ forEach (<функция> [, <контекст выполнениям]) —перебирает GE I -параметры, со-
держащиеся в текущем наборе, и передает каждый GET-параметр заданной
функции. Функция должна принимать три параметра: значение очередного GET-
параметра, его имя и ссылку на текущий набор. Функция не должна возвращать
результата. Можно указать контекст выполнения, который будет доступен в теле
функции через локальную переменную this;
♦ sort о — сортирует GET-параметры, хранящиеся в текущем наборе, по их име-
нам.
28.1.2.2. Передача данных НТТР-метадом POST
При отправке данных HTTP-методом POST данные отправляются веб-серверу
в виде набора POST-параметров.
POST-napa.uemp — анатогнчен GET-параметру (см. разд. 28.1.2.1), только
пересылается не в составе интернет-адреса, а в самом клиентском запросе.
В каком виде представлять POST-параметры, зависит от метода кодирования дан-
ных, поддерживаемого веб-сервером.
Кодирование данных
методом application/x-www-form-urlencoded
Если данные, пересылаемые серверу HTTP-методом POST, должны быть закодиро-
ваны методом application/x-www-f-..rm-url encoded, эти данные можно представить
в виде объекта класса URLSearchParams (как и набор GET-параметров —
см. разд. 28.1.2.1). Объект с данными следует присвоить свойству body простого
объекта с параметрами, передаваемого методу fetch() (см. разд. 28.1.1). Пример:
const postParams = new URLSearchParams();
postParams.set('name', 'vasyapupkin');
postParams.set('password', '12345678');
const r = await window.fetch('/login', {
method: 'post', body: pcstParams
});
Перед отправкой данных, представленных объектом класса URLSearchParams, в за-
прос автоматически добавляется заголовок Content-Type со значением application/
x-www-form-urlencoded—названием использованного метода кодирования данных.
Кодирование данных методом multipart/form-data
Если же данные, пересылаемые серверу, следует закодировать методом
multipart/form-data, для их представления необходимо применить объект класса
FormData. Объект этого класса создается выражением формата:
new FormDuta((<веб-фррма>[, ^кнопка отправки данных>]])
Если не указыват ь веб-форму, будет создан «пустой» набор POST-параметров:
const postParana = new FormData();
Если же указать веб-форму (в виде ссылки на тег <form>). данные, хранящиеся в ней.
будут занесены в создаваемый набор в виде отдельных POST-параметров. При этом
наименование отдельного элемента управления (заданное в атрибуте тега name) ста-
нет именем соответствующего POST-параметра. Пример:
const form = document.getElementByld('fileform');
const postParams2 = new FormData(form);
Если дополнительно задать кнопку отправки данных. в создаваемом наборе будет
создан еще один POST-параметр с именем, совпадающим с наименованием кнопки,
и надписью на этой кнопке (заданной в атрибуте тега value) в качестве значения.
Класс FormData поддерживает следующие методы:
♦ append () — имеет два формата вызова:
• append(<имя>, < значение^ — добавляет в текущий набор новый POST-пара-
метр С заданными именем И значением:
postParams.append(’title', 'Фото');
Если POST-параметр с заданным именем уже существует в текущем наборе,
будет создан еще один POST-параметр с тем же именем и указанным значе-
нием,
• append (<имя>, <файл>(, <имя файла>]] —добавляет в текущий набор новый
POST-параметр с заданными именем, хранящий указанный файл под заданным
именем файла. Если имя файла не указано, будет использовано изначальное
имя файла без пути. Пример:
const photc = document.getElementByld('photo');
postParams.append(’photo', photo.files[0]);
♦ set() — аналогичен append(), только, если POST-параметр с указанным именем
существует, заменяет имеющееся в нем значение заданным и удаляет все
остальные POST-параметры, имеющие го же имя. Оба поддерживаемых форма-
та вызова аналогичны таковым у метода appendO;
♦ get (<имя>) — возвращает строковое значение POST-параметра с заданным
именем или null, если такого POST-параметра нет. Если в текущем наборе при-
сутствует несколько POST-параметров с заданным именем. возвращается значе-
ние самого первого из них;
♦ getAii(<имя>) — возвращает массив строковых значений всех POST-параметров
с заданным именем, имеющихся в текущем наборе. Если ни одного из подходя-
щих POST-параметров нет, возвращает пустой массив;
♦ Ьаз(<имя>) — возвращает true, если POST-параметр с заданным именем сущест-
вует в текущем наборе, и false — в противном случае;
♦ delete(<лмя>) — удаляет из текущего набора все POST-параметры с заданным
именем. Если ни одного подходящего POST-нараметра нет, ничего не делает:
♦ entries О — возвращает итератор (см. разд. 12.1), на кггждой ит ерации выдаю-
щий массив из двух элементов: имени очередного POST-параметра и его значе-
ния;
♦ keys () — то же самое, что и entries о, только на каждой итерации выдает имя
очередного POST-параметра;
♦ values () — то же самое, что и entries О, только на каждой итерации выдает
значение очередного POST-параметра.
Объект класса FormData. хранящий отправляемые данные, следует присвоить свой-
ству body простого объекта с параметрами, передаваемого методу fetch()
(см. разд. 28.1. Г)'.
const г = await window.fetch('/photoupload', {
method: 'post', body: postParams
});
Перед отправкой данных, представленных объектом класса FormData, в запрос
автоматически добавляется заголовок Content-Туре со значением multipart/form-
data — названием использованного метода кодирования данных.
28.1.3. Использование объектов запросов
Если в разных местах кода необходимо загружать данные с одного и того же ин-
тернет-адреса с одними и теми же параметрами, удобнее заранее создать готовый
объект запроса и подставлять в вызовы метода fetch о именно его. Объект запроса
обрабатывается веб-обозревателем немного быстрее, чем интернет-адрес и пара-
метры запроса, заданные по отдельности.
Объект запроса создается на основе класса Request с помощью выражения формата:
new Request(<интернет~здрес>[, <параметры>])
Интернет-адрес и параметры задаются в том же виде, что и у метода fetch () класса
window (см. разд. 28.1.1).
При использовании готового о&ьекта запроса метод fetch () класса Window следует
вызвать в формате:
fetch(<готовый объект запроса>)
Пример:
const form = document.getElementByld('fileform');
const postParams - new FormData(form);
const headers = new Headers({
Accept: ’application/json',
'Accept-Language': 'ru-RU'
});
const q = new Request('/photoupload', {
method: 'post',
body: postParams,
headers
});
const r = await window.fetch(q);
28.2. Обработка серверных ответов
Как говорилось в разд. 28.1.1, асинхронный метод fetch() возвращает промис,
в который при подтверждении заносится в качестве нагрузки объект полученного
серверного ответа.
Серверный ответ — блок данных, отправляемый веб-сервером веб-обозрева-
телю в ответ на клиентский запрос, обычно содержащий запрошенные данные
и различные сведения о них, включая код и описание статуса.
Код статуса — описывает результат обработки клиентского запроса веб-сер-
вером. Представляет собой грехзначное десятичное число.
Например, код статуса 200 сообщает об успешной отправке затребованных данных,
а код статуса 404 — об ошибке, вызванной отсутствием запрошенных данных на
сервере1.
Описание статуса — текстовое пояснение, прилагающееся к код} статуса.
Пишется по-английски.
Ответ может и не содержать запрошенных данных (пустой серверный ответ).
Такая ситуация может возникнуть, например, если веб-обозреватель запросил не-
существующие данные (возникла «ошибка 404»). Также сервер может отправить
пустой ответ, чтобы }ведомить веб-обозреватель о чем-либо (например, о том, что
кешированный файл еще актуален),— в этом случае вею нужную информацию не-
сет код статуса.
Объект серверного ответа создается веб-обозревателем на основе класса Response.
28.2.1. Получение сведений о серверном ответе
Класс Response поддерживает ряд доступных только для чтения свойств, позво-
ляющих получить необходимые сведения о серверном ответе:
♦ ок — если true, веб-сервер успешно отправил затребованные данные, и false —
если при их отправке произошла какая-либо ошибка.
Данные считаются успешно полученными, если полученный от сервера ответ
имеет код статуса от 200 до 299 включительно;
♦ status — код статуса ответа в виде числа;
♦ statusText — описание статуса ответа в строковом виде;
♦ headers — заголовки ответа в виде объекта класса Headers (был описан в
разд. 28.1.1.1).
Заголовок серверного ответа— аналог заголовка запроса (см. разд. 28.1.1),
пересылаемый веб-обозревателю в составе серверного ответа.
♦ url — интернет-адрес, с которого пришел ответ;
1 Печально знамени гая «ошибка 404».
♦ bodyused— true, если данные, пришедшие с ответом, уже считаны, и false —
если еще не были считаны.
При чтении заголовков серверного ответа может пригодиться метод getsetcookie()
класса Headers. Он возвращает значения всех заголовков Set-Cookie, в которых
задаются cookie, отправленные сервером веб-обозревателю для сохранения. Воз-
вращаемый методом результат представляет собой массив из строк формата <имя
соок!е>=<значение cookie>.
28.2,2. Получение данных,
отправленных веб-сервером
Чтобы извлечь сами полученные с ответом данные, следует вызвать один из приве-
денных далее асинхронных методов класса Response. Каждый из этих методов воз-
вращает промис, подтверждаемый после успешного извлечения данных в затребо-
ванном формате. Нагрузкой промиса являются сами извлеченные данные.
А теперь — сами методы:
♦ text () — извлекает текстовые данные и выдает их в строковом виде. Применя-
ется для получения фрагментов обычного текста и HTML-кода. Пример:
// Загружаем файл с фрагментом HTML-кода
const г = await fetch('/fragments/!r5.html');
// Если файл был успешно получен, извлекаем и выводим его содержимое
if (г.ok) {
const f = await r. text О;
output.innerHTML = f;
}
♦ json() — извлекает данные, записанные в формате JSON (см. разд. 28.4), и вы-
дает их в виде простого объекта, готового к обработке;
♦ ЫоЬ() — извлекает двоичные данные и выдает их в виде объекта класса Blob.
Применяется для получения данных, представленных в двоичном виде: графи-
ческих изображений, аудио, видео и др.
Объект класса Blob, выданный этим методом, подобен файлу (объекту класса
File). В частности, он может быть преобразован в объектный адрес вызовом
статического метода с reat eObject URL () класса URL (см. разд. 25.1.3).
Пример:
const г = await fetch('/images/img3.jpg');
if (r.ok) {
const b = await r.blobf);
const url = URL.createObjectURL(b);
outputImage.src = url;
}
28.3. Упражнение.
Сборка веб-страницы из отдельных частей
Неожиданно вышел на связь владелец суши-бара «Иокогама» и попросил сделать
так, чтобы каждая позиция в меню его заведения загружалась из отдельного
HTML-файла. Чтобы облегчить нам работу', он предоставил все необходимые
файлы.
1. Найдем в папке 28Vsources сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 28 3 html (пустая страница) и 28.3.css (внешняя таблица сти-
лей) и папку 28 3 (файлы с фрагментами HTML-кода, создающими отдельные
позиции меню, и графические файлы с иллюстрациями блюд). Скопируем всё
это куда-либо на локальный диск.
2. Откроем копию папки 28 3 и посмотрим, какие HTML-файлы в ней содержатся.
Там мы увидим файлы sushi_chukka.html, sushi jnaguro html, sushi_tobiko html.
sashimi_unagi.html и sashimi_hamachi.html.
Вывести их на страницу клиент просил именно в гаком порядке.
Копия страницы 28 3.html содержит тег семантического основного содержимого
(<main>) с якорем menu. В нем-то и будут выводиться отдельные позиции меню,
загруженные из приведенных ранее файлов
Сначала создадим массив из имен HTML-файлов, храпящих отдельные позиции
меню. И заодно получим тег menu, в котором будем выводить эти позиции.
3. Откроем копию файла 28 3.html в текстовом редакторе, вставим в конец секции
тела страницы тег <script> и запишем в нем первые выражения сценария:
const fragments = ['sushi chukka.html', 'sushi maguro.html',
'sushi tobiko.html’, 'sashimi unagi.html',
'sashimi _ aamachi.html'];
const menu = document.getElementByld('menu');
Пам требуется перебрать созданный ранее массив файлов в цикле, на каждой
итерации загружая очередной файл, получая хранящийся в нем HTML-код в ви-
де обычного текста и выводя этот код в теге menu. Для вывода фрагмента HTML-
кода удобнее всего применить метод insertAdjacentHTMLQ (см. разд. 18.5).
4. Добавим код. выполняющий все эти действия-
(async () => {
for (let f of fragments) {
const r = await window.fetch('/28.3/${f}');
if (r.ok)
menu.insertAdjacentHTML('beforeend', await r.textO);
}
}) () ;
Поскольку в коде применяется оператор ожидания await (см. разд. 23.3), мы
оформили этот код в виде асинхронной функции, в нашем случае — однократ-
ного вызова.
Запустим веб-сервер http-server, указав ему в качестве корневой папку, в которой
находятся файлы примера. Откроем веб-обозреватель и выполним переход по ин-
тернет-адресу http://localhost:8080/28.3.htmL Посмотрим, отобразилось ли меню.
28.4. Формат JSON
JSON (JavaScript Object Notation, объектная нотация JavaScript) — текстовый
формат, применяемый для записи данных произвольной структуры и основан-
ный на объектной нотации JavaScript.
Данные, записанные в формате JSON, очень похожи на JavaScript-код, создающий
простой объект. Эти данные могут описывать как отдельный документ, так и мас-
сив документов.
Документ — объект, записанный в формате JSON.
Атрибут — свойство документа JSON.
Отличия формата JSON от JavaScripl следующие:
♦ имена атрибутов берутся в двойные кавычки:
♦ атрибуты могут хранить только данные следующих типов:
• строки (заключаются только в двойные кавычки);
• числа;
• логические величины;
• массивы;
• поддокументы;
• null;
| Поддокумент —документ, хранящийся в атрибуте другого документа.
♦ комментарии не допускаются;
♦ после последнего атрибута ставить запяту ю нельзя;
♦ в массивах — после последнего элемента ставить запятую также нельзя.
Данные в формате JSON хранятся в файлах с расширением json в виде обычного
текста, записанного в кодировке UTF-8.
Пример документа JSON, содержащего два атрибута:
{
"паше": "JavaScript",
"version": 2017
Пример массива из двух документов:
[
{
"папе": "JavaScript",
"version": 2017
{
"name": "Python",
"name": "3.10"
)
]
Чтобы получить JSON-данные, загруженные с веб-сервера, в виде, пригодном для
обработки, у объекта серверного ответа следует вызвать асинхронный метод ajax()
(см. разд. 28.2.2). Если сервер прислал единичный J SON-доку мент, этот метод
выдаст представляющий его простой объект. Если же был получен массив JSON-
документов, метод выдаст массив, содержащий простые объекты. Пример:
c'nst г = await fetch(’/data/json/javascript.json');
if (r.ok) {
const json = await r.jsonO;
const languageName = json.name;
const languageversion = json.version;
J
Преобразовать заданный json-код, представленный в виде строки, в вид, пригод-
ный для обработки, можно вызовом статического метода parse(<Д5С«-код>) встро-
енною класса json:
const json = '{ "name": "JavaScript.", "version": 2017
const obj = JSON.parse(json);
// Результат:
// {
// name: 'JavaScript',
// version: 2017
// }
Для обратного преобразования — простого о&ьекта ИЛИ массива в JSON-КОД —
применяется метод stringifyO того же класса:
stringifу(<простой объект>I <массив>}
Пример:
const obj = {
name: 'Python’,
version: '3.10*
};
const json = JSON.stringify(obj);
// Результат:
// '{ "name": "Python", "version": "3.10" }'
28.5. Упражнение.
Программное создание веб-страницы
на основе загруженных JSON-данных
Не успели мы закончить чтение разд. 28.4, как с нами снова связался владелец
суши-бара «Иокогама». Теперь он хочет, чтобы меню формировалось программно
на основе данных, загруженных с сервера и представленных в формате JSON.
Что ж, уважим давнего клиента! Тем более что он предоставил в качестве «заготов-
ки» практически полностью готовый код, который нам нужно лишь немного пере-
делать.
I. Найдем в папке 28\'sources сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 28,5.html (пустая страница), 28 5.css (внешняя таблица стилей),
28 5 json (JSON-код исходного массива, на основе которого будет создаваться
меню) и 28 5.js (сценарий, создающий меню) и папку 28 5 (графические файлы
с иллюстрациями блюд). Скопируем всё это куда-либо на локальный диск.
Страница 28 5 html осталась такой же, какой мы ее помним по предыдущим
упражнениям. Переделывать ее не нужно.
Сценарий 28 5 .js содержит код, написанный нами при выполнении упражне-
ния 6.5. Его требуется переделать таким образом, чтобы он загружал исходный
массив из файла 28 5 json.
2. Откроем копию файла 28 5 json в текстовом редакторе и посмотрим на имею-
щийся в нем JSON-код:
[
{
"title": "Чукка",
"image": "28.5/sushi chukka.jpg",
"desc": "Диетические суши, приготовляемые из . . .
"price": "55 ₽"
},
]
Этот код описывает массив из документов, каждый из которых содержит атри-
буты: title (название блюда), image (интернет-адрес файла с иллюстрацией),
desc (описание) и price (цена). Имена атрибутов совпадают с именами свойств
простых объектов из исходного массива, с которым мы работали, выполняя
упражнение 6.5, так что значительных правок в код сценария вносить не при-
дется.
3. Откроем копию файла 28.5 js и найдем код, непосредственно формирующий
меню и также знакомый нам по упражнению 6.5'.
for (let el of data) {
menu, j mer HTML = s;
Это цикл, перебирающий элементы исходного массива, который хранится
в переменной data, и создающий на их основе HTML-код меню.
Перед выполнением этого кода нам следует загрузить файл 28 5 json. извлечь из
него исходный массив и присвоить его переменной data.
4. Добавим необходимый код:
(async () => {
const г = await window.fetch('26 5.json1);
if (r.ok) {
const data = await r.jsonO;
for (let el. of data) {
}
menu.innerHTML = s;
}
}) 0 ;
Запустим веб-сервер http-server, указав ему в качестве корневой папку, в которой
находятся файлы примера. Откроем веб-обозреватель и выполним переход по ин-
тернет-адресу htip://localhost:8080/28.5.1nml, Проверим, как выводится меню.
28.6. Работа с интернет-адресами
Для представления интернет-адресов служит класс url. Его удобно использовать,
если необходимо «разобрать» какой-либо интернет-адрес на отдельные состав-
ляющие: адрес хоста, путь, имя якоря, набор GET-параметров. — или изменить
в нем одну из составляющих.
Объект класса url создается выражением формата:
new URL(<riHTepHeT-awpec>[, <базовый интернет-адрес>])
Обрабатываемый интернет-адрес задается в виде строки:
const url = new URL('http://www.somesite.ru/pages/page1.html');
Если заданный интернет-адрес является относительным, необходимо дополнитель-
но указать базовый интернет-адрес, относительно которого он будет отсчитываться.
Если этого не сделать, возникнет ошибка. Базовый интернет-адрес также задается
в виде строки. Пример:
const url2 » new URL(1/pages/page1.html', ’http://www.somesite.ru');
Класс URL поддерживает следующие свойства, из которых все, кроме двух, доступ-
ны как для чтения, так и для записи:
♦ protocol — обозначение протокола с завершающим символом двоеточия, но без
двойного слеша;
♦ hostname — адрес хоста;
♦ port — номер TCP-порта в виде строки. Если порт в текущем интернет-адресе
не указан, хранит пустую строку:
♦ host — строка формата <адрес хоста>[:<номер тср-порта>], причем номер тср-
порта присутствует только в том случае, если он указан в текущем интернет-
адресе;
♦ origin — строка формата <обозначение гротоколахадрес хоста>[:<номер ТСР-
порта>], причем номер TCP-порта присутствует только в том случае, если он ука-
зан в текущем интернет-адресе. Свойство доступно только для чтения;
♦ pathname — путь с начальным символом слеша. Если путь отсутствует, хранит
строку со слешем ('/')•
♦ search — строка с GE1 -параметрами, включающая начальный вопросительный
знак. Если таковые в текущем интернет-адресе отсутствуют, хранит пустую
строку;
♦ searchParams — набор GET-параметров в виде объекта класса URLSearchParams
(см. разд. 28.1.2. /). Свойство доступно только для чтения;
♦ hash— имя якоря с начальным символом решетки. Если якоря нет. хранит пус-
тую строку;
♦ href — теку щий интернет-адрес в виде строки.
28.7. Самостоятельное упражнение
Переделайте страницу, выводящую диаграмму рейтинга различных языков про-
граммирования (см. упражнение 27.5), таким образом, чтобы исходный массив
загружался из JSON-файла.
ЧАСТЬ VI
Разработка
веб-приложений
С Урок 29 Разработка традиционных веб-приложений.
Платформа Node js
Э Урок 30 Двухзвенные веб-приложения Разработка бэкендов
Э Урок 31. Разработка фронтендов. часть 1 Компоненты
О Урок 32. Разработка фронтендов. часть 2. Маршрутизация
Урок 29
Разработка
традиционных веб-приложений.
Платформа Node.js
Обычный веб-сайт — это просто папка с файлами, храпящими содержимое сайта
(веб-страницы, внешние таблицы стилей, веб-сценарии, изображения, аудио-, ви-
деоролики, JSON-документы и др.), и типовая программа веб-сервера, наподобие
Apache НТ1Р Server или nginx. Веб-сервер принимает от веб-обозревателей клиент-
ские запросы на выдачу тех или иных файлов. загружает и выдает требуемые
файлы.
Традиционное (или однозвенноё) веб-приложениех — это по большому счету
веб-сервер, который не выдает готовые страницы, хранящиеся в файлах, а гене-
рирует их самостоятельно, в момент получения клиентских запросов на их
выдачу.
Скажем, традиционное веб-приложсние, выдающее меню суши-бара «Йокогама»,
при получении запроса считает файлы с описаниями блюд, сгенерирует на их осно-
ве полноценную страницу с меню и отправит ее веб-обозревателю. Последний,
получив страницу, даже не «догадается», что она была сгенерирована программно,
а не загружена из файла.
Такие веб-приложения можно разрабатывать на языке JavaScripi и выполнять под
управлением исполняющей среды Node.js.
Node.js (или просто Node) — независимая исполняющая среда JavaScript, по-
зволяющая разрабатывать и выполнять практически любые программы: веб-
приложения. консольные, графические приложения и др.
Поскольку Node.js является независимой и соответственно не встроена ни в какой
веб-обозреватель, она не включает в свой состав программные инструменты, харак-
терные для веб-обозревателей: DOM, ВОМ и Web API. Вместо этого она поддер-
живает Node.js API.
Node.js API (Node.js Application Programming Interface, интерфейс программи-
рования приложений для Node.js) — набор программных инструментов, содер-
жащий средства для работы с файлами, сетью, взаимодействия с операционной
системой и др.
1 Почему традиционное и однозвенное, мы узнаем на уроке 30.
Приложения, написанные на JavaScript и предназначенные для выполнения
в Node.js. носят название Node-приложений.
ПОЛЕЗНО ЗНАТЬ
Веб-приложения, помимо традиционных (или однозвенных), бывают также двухзвен-
ные (рассмотрены на уроке 30) и клиентские (реализованные в виде веб-страниц и
работающие в веб-обозревателе — см. упражнение 2.4).
А вообще, веб-приложение — это приложение, основанное на веб-технологиях (напри-
мер, написанное на HTML, CSS и JavaScript) или использующее для работы веб-
технологии (скажем, применяющее для пересылки данных протокол HTTP).
29 .1. Упражнение.
Проект и манифест Node-приложения
Начнем разработку традиционного веб-приложения, реализующего простую фото-
галерею. Напишем это приложение на JavaScript и запустим под управлением
Node.js.
Разработка любого Node-приложения начинается с создания нового проекта.
Проект — совокупность всех файлов с программным кодом разрабатываемого
приложения, помещенная в отдельную папку (папку проекта). Включает, по-
мимо всею прочего, манифест проекта.
Манифест проекта — ключевые сведения о проекте: его имя, описание, версия
и пр. Хранится в формате JSON в файле package.json в папке проекта.
Проект нового приложения поместим в папку photogallery. И создадим манифест.
I . Создадим где-либо на локальном диске папку photogallery.
2 . Создадим в этой папке файл packagejson и сохраним в нем код из листинга 29.1.
Листинг 29.1. Манифест проекта photogallery (packagejson)
"name": "photogallery",
"type": "module",
"version": "1.0.0",
"description": "Простая фотогалерея",
"main": "index.js",
"scripts": {
),
"keywords": [
"photogallery",
"photo",
"gallery"
],
"author": "Читател! ",
"license": "ISC"
Как говорилось ранее, манифест проекта — это JSON-документ, в атрибутах ко-
торого хранятся различные сведения о проекте. Манифест включает следующие
атрибуты:
• паше — имя проекта. Может быть произвольным. Однако должно содержать
только лишь буквы латиницы, цифры, символы дефиса и подчеркивания:
• type — тип модулей, используемых в проекте. Значение "module" предписы-
вает использовать стандартные модули JavaScript, описанные на уроке 14;
• version— помер версии проекта. Может быть выбран произвольно:
• description — произвольное описание проекта:
• main — имя стартового модуля (модуля, запускающего приложение). 11о при-
нятым в среде Node-разработчиков соглашениям должен иметь имя index.js
или app.js:
• scripts — поддокумент со сценариями проекта (описаны в упражнении 29.2);
* keyword— массив с ключевыми словами, описывающими проект. Ключевые
слова могут быть выбраны произвольно. Можно указать пустой массив;
• author— имя разработчика приложения или название создавшей его органи-
зации. Может быть произвольным;
• license — обозначение лицензии, согласно которой приложение будет рас-
пространяться. Если приложение исключительно учебное и не предназнача-
ется для широкого распространения, можно указать лицензию ISC.
Наш первый проект Node-приложения создан. Пока он совершенно пуст, но скоро
мы это исправим.
ПОЛЕЗНО ЗНАТЬ
Создать «пустой» манифест проекта можно, открыв утилиту Terminal или командную
строку, выполнив переход в папку проекта и подав команду:
npm init -у
нрт - это многофункциональная инструментальная утилита, поставляемая в составе
Node.js. С ее помощью, в частности, запускаются сценарии проекта и устанавливаются
дополнительные программные библиотеки.
29 .2. Упражнение. Простейшее веб-приложение,
выдающее приветствие. Сценарии проекта
Создав проект с манифестом, можно начать писать программный код приложения.
Поскольку мы пока еще новички в Node-программировании, сейчас реализуем
ISC (Internet Systems Consortium) — некоммерческая организация, занимающаяся поддержкой ин-
фраструктуры Интернета, в частности разрабатывающая для него эталонное программное обеспече-
ние.
в приложении лишь выдачу простой веб-страницы с приветствием. 11 заодно позна-
комимся со сценариями проекта.
По существующим соглашениям, учебные и находящиеся в разработке веб-при-
ложения должны работать через ГСР-порт 3000, 8000 или 8080. Выберем
порт 8000.
I Найдем в папке 29\ех29.1 панку photogallery (папка проекта) и скопируем ее куда-
либо на локальный диск.
Ранее мы указали в манифесте проекта, что стартовый модуль носит имя index js.
Создадим этот модуль и запишем в него весь код приложения, благо он невелик.
2. Создадим в папке photogailery файл index.js и запишем в него код из листинга 29.2.
Листинг 29 2. Стартовый модуль (index.js)
import { createServer } from 'node:http'; Il ............................ 1
const port = 8000; // ................................................... 2
const server = createServer(); // ....................................... 3
server, on ('request', (req, res) => { // ............................... 4
const s = ’<!doctype html>' + // .................................... 5
' <html lanq=’'ru">' +
' <head>’ +
' <meta charset="UTF-8">' +
' <meta name="viewpcrt" 1 +
'content="width=device-width, ' +
'initial-scale=1.0">' +
’ <title>npHBeTCTBwe</title>' +
' </head>' +
' <body>' +
’ <Ы>Привет, мир!</Ы>' +
' </body>' +
res.end(s); // ................................................... 6
});
server.listen(port); // .............................................. 7
Node.js API представляет собой набор модулей, встроенных в саму Node.js
(встроенных модулей). Любую сущность, реализованную во встроенном модуле,
можно импортировать, использовав языковые конструкции импорта, описанные
к разд. 14.3.
Имя встроенного модуля, записываемое в выражении импорта, следует пред-
варить префиксом node: (впрочем, как показывает практика, этого можно и не
делать).
Традиционное веб-приложение— это совокупность двух составляющих:
• веб-сервера — который принимает клиентские запросы и генерирует пустые
серверные ответы;
• генератора данных — который при получении очередного запроса генерирует
запрашиваемые данные и заносит их в сгенерированный ответ для отправки.
Node.js API содержит реализацию первой из этих составляющих — полнофунк-
ционального веб-сервера. Это фабрика объектов (функция, возвращающая объ-
екты) createServer(), находящаяся во встроенном модуле http, и ее нам следует
импортировать (поз. 1 в листинге 29.2).
Объявим константу для хранения номера TCP-порта (поз. 2), через который
будет работа ть наше приложение (порт 8000).
Получим объект веб-сервера, вызвав импорт ированную ранее фабрику объектов
createServer () (ПОЗ. 3).
Когда веб-сервер получает очередной клиентский запрос, в нем возникает собы-
тие request. Обработчик этого события должен принимать два параметра:
объект полученного клиентского запроса (традиционно этому параметру дают
имя req) и объект сгенерированного сервером пустого серверного ответа (res).
Для привязки обработчика к событию, возникающему в каком-либо объекте,
применяется метод on о, вызываемый у этого объекта и полностью аналогичный
методу addEventListener () (поз. 4).
В виде обработчика события request реализуется вторая составляющая веб-
приложения — генератор данных. У нас он формирует строку с HTML-кодом
страницы приветствия (поз. 5) и заносит ее в серверный ответ, вызвав у него
метод end( [«.заносимое содержимое>]) (поз. 6).
Осталось запустить веб-сервер, входящий в состав приложения, указав ему ра-
ботать через TCP-порт с заданным номером. Для этого достаточно вызвать у объ-
екта сервера метод listen «номер ТСР-порта>) (поз. 7).
Приложение готово. Можем запустить и проверить его.
3. Запустим утилиту Terminal или командную строку, выполним переход в папку
проекта и подадим команду на запуск стартового модуля в среде Node.js:
node index.js
4. Откроем веб-обозреватель и выполним переход по интернет-адресу: http://
localhost:8000/.
Появится страница с приветствием Привет, мир!
Чтобы завершить выполнение приложения, достаточно переключиться в экзем-
пляр утилиты Terminal или командной строки, в котором оно было запущено,
и нажать комбинацию клавиш <Ctrl>+<C>. Также можно просто закрыть этот
экземпляр утилиты Terminal или командной строки.
5. Остановим выполнение веб-приложения.
Что-то у нас нет желания каждый раз при запуске приложения набирать имя
стартового модуля... Нельзя ли использовать для этою более короткую команду?..
Сценарий проекта — короткая команда, запускающая на выполнение старто-
вый модуль, возможно, с указанием каких-либо параметров. Должен иметь
уникальное в пределах проекта имя.
Создадим сценарий проекта с именем serve, который запустит стартовый мо-
дуль.
6. Откроем манифест проекта package.json и добавим в него код, создающий сцена-
рий проекта serve:
{
"scripts": {
"serve": "node index.js"
},
}
Сценарии проекта записываются в атрибуте scripts манифеста в виде атрибутов
хранящегося там поддокумента. Имя атрибута задаст имя сценария, а значением
атрибута должен быть текст команды, запускающей стартовый модуль.
7. Запустим проект на выполнение, использовав только что созданный сценарий,
для чего наберем в утилите Terminal или командной строке команду:
npm run serve
Слово serve, присутствующее в конце команды, — это имя сценария проекта.
8. 11роверим приложение в работе еще раз.
9. Завершим выполнение приложения.
Утилита npm, посредством которой мы запустили сценарий проекта, спросит,
действительно ли мы хотим завершить работ} приложения. Введем букву «У»
и нажмем клавишу <Enter>, чтобы ответить положительно.
Внимание!
По окончании выполнения упражнения не забываем останавливать работу веб-при-
ложения Автор больше не будет напоминать об этом
29. 3. Упражнение.
Маршрутизатор и контроллеры
Превратим наше первое веб-приложение в полноценную веб-фотогалерею. Для
чего реализуем в нем маршрутизацию и напишем необходимые контроллеры.
29.3.1. Теория
Каким образом веб-приложение узнает, какую страницу запрашивает у него веб-
обозреватель? Анализируя путь, извлеченный из интернет-адреса, который при-
сутствует в составе полученного клиентского запроса. То есть выполняя маршрути-
зацию.
Маршрутизация — процесс определения, какой контроллер следует запустить,
в зависимости от пути, полученного в составе клиентского запроса.
Маршрутизатор — подсистема веб-приложения, выполняющая маршрутиза-
цию.
Контроллер — подсистема веб-приложения, запускающаяся в ответ на получе-
ние пути определенного формата и генерирующая серверный ответ с соответ-
ствующими данными.
Контроллер обычно реализуется в виде функции.
29.3.2. Практика
Для простоты реализуем в приложении поддержку только файлов формата JPEG.
Изображения и сведения о них, включающие название и необязательное описание,
будем хранить в папке data, находящейся в папке проекта.
Каждому изображению, содержащемуся в фотогалерее, будут соответствовать два
файла: JPEG-файл с самим изображением и JSON-файл со сведениями о нем. Оба
файла будут иметь одинаковые имена — дтя удобства обработки.
Кроме того, создадим в папке проекта папку static, в которой сохраним файл
с внешней таблицей стилей, задающей оформление для страниц приложения
Добавим в наше веб-приложение:
♦ маршрутизатор— реализованный в виде функции router () формата:
router («-объект клиентского запроса>, ^объект серверного ответа>)
Объявим эту функцию в модуле router.js, хранящемся в папке src, которую созда-
дим в папке проекта;
♦ контроллеры — выполняющиеся при получении путей форматов:
• / (слеш, «корень приложения) — index() — выдающий главную страницу
с перечнем изображений:
index(Собъект клиентского запроса:?, <объект серверного ответа>}
Каждая позиция перечня будет содержать название изображения и его
миниатюру;
• !<иденпшфикатор> — detail () — выдающий страницу с изображением, ко-
торое имеет заданный идентификатор'.
detail{<идентификатор>, <обьект клиентского запроса>,
< объект серверного ствета>)
В качестве идентификатора будем использовать имя файла, в котором хра-
нится изображение, без расширения — для простоты.
Страница также будет содержать название изображения и его описание (если
оно указано).
Если изображение с заданным идентификатором отсутствует, контроллер
выдаст страницу с сообщением об «ошибке 404»;
• /<//.ия файла> — staticFileO — ищущий файл с заданным именем сначала
в папке static, потом — в папке data и выдающий его:
staticFile (<.нмя файла>, <обгъект клиентского запроса>,
<объект серверного ответа>)
Если запрошенный файл не удастся найти ни в одной панке, контроллер вы-
даст пустой ответ с кодом статуса 404 — сообщение об «ошибке 404»;
• любого другого пути — error () — выдающий ответ с указанным кодом ста-
туса. содержащий страницу с заданным сообщением об ошибке:
error(<сообщение об ошибке>, <кол статуса>,
< объект клиентского запроса>, <объект серверного ответа>)
Все контроллеры объявим в отдельных модулях, имена которых совпадут
с именами контроллеров, и сохраним в панке src\controllers папки проекта.
I Найдем в нанке 29\ех29 2 сопровождающего книгу файлового архива (см. при-
ложение 5) папку photogallery (папка проекта) и скопируем куда-либо на локаль-
ный диск.
2. Найдем в папке 29\'sources сопровождающего книгу7 файлового архива (см. при-
ложение 5) папку photogallery, а в ней — панки data (JPEG-файлы с изображения-
ми и JSON-файлы со сведениями о них) и static (внешняя таблица стилей). Ско-
пируем обе папки в панку проекта.
JSON-файлы из папки data содержат документы с атрибутами title (название
изображения) и desc (дополнительное описание, может отсутствовать).
Имена графических и JSON-файлов представляют собой временные отметки,
выраженные в виде количества миллисекунд, прошедших с полуночи 1 января
1970 года. Это значение можно получить, вызвав метод getTimeO класса Date
(см. разд. 7.2).
Сначала переделаем стартовый модуль.
3. Откроем копию файла index js в текстовом редакторе и заменим имеющийся
в нем код приведенным в листинге 29.3.
Листинг 29.3. Стартоный модуль (index.js)
import router from ’./sre/router.js';
import { createServer } from 'node:http*;
const port = 8000;
const server = createServer();
server.on('request', (req, res) => {
router(req, res);
});
server.listen(port);
Теперь в обработчике события request веб-сервера вызывается функция
router о, которая реализует маршрутизатор и которую мы сейчас напишем.
4. С оздадим в папке проекта папку src. а в ней — файл router js. в который запишем
код из листинга 29.4.
Листинг 29.4. Маршрутизатор (srctrouter.js)
import { URL } from 'node:url’;
import index from './ccntrollers/index.js';
import detail from './controllers/detail.js’;
import staticFile from './controllers/staticfile.js';
import error from './controllers/error.js';
const reDetail = /л\/([0-9]+)$/; // ................................. 1
const reStatic = /А\/([-_0-9a-z]+\,[a-z]{2,3))$/; // ................ 2
export default function router(req, res) { // ....................... 3
const requestedPath = new URL (req. url, // ..................... 4
http://${req.headers.host)').pathname;
const rD = reDetail.exec(requestedPath); // ..................... 5
const rS = reStatic.exec(requestedPath);
if (rD) // ...................................................... 6
detail(rD[l], req, res) ;
else if (rS) // ................................................. 7
staticFile(rS[l], req, res);
else if (requestedPath = '/') // ................................ 8
index(req, res);
else
error('Страница не найдена', 404, req, res); // ................. 9
}
В процессе маршрутизации нам потребуется выяснять, ссылается полученный
путь на главную страницу, страницу с выбранным изображением или файл. Для
этого мы создадим два регулярных выражения.
Первое регулярное выражение определит шаблон для пути, ссылающегося на
страницу с выбранным изображением. Оно совпадет с любым путем, содержа-
щим слеш и последовательность из произвольных цифр (это идентификатор
изображения). Идентификатор изображения заключен в группу — для удобства
его извлечения (поз. 1 в листинге 29.4).
Второе регулярное выражение определит шаблон для пути, ссылающегося на
файл. Оно совпадет с любым путем, содержащим слеш, произвольный набор
букв латиницы, цифр, дефисов и знаков подчеркивания (это имя файла), точки и
еще двух или трех произвольных букв латиницы (расширение файла). Имя фай-
ла, точка и расширение заключены в группу — также для удобства извлечения
(см. поз. 2).
Функцию router(). реализующую маршрутизатор, мы подвергли экспорту по
умолчанию (см. разд. 14.2.1\ поскольку это единственная сущность в модуле,
подлежащая экспорту (поз. 3).
Объект запроса, который функция router о получаете первым параметром, под-
держивает свойство url, которое хранит строку, представляющую собой сово-
купность пути и набора GET-параметров. Чтобы выделить из этой строки имен-
но путь, придется создать на ее основе объект класса url, не забыв указать при
его создании базовый интернет-адрес (подробности— в разд. 28.6). Отметим,
что в Node.js класс url объявлен во встроенном модуле url, и его следует пред-
варительно импортировать. Базовый интернет-адрес можно составить из обозна-
чения протокола, двойного слеша и адреса текущего хоста. Объект запроса под-
держивает свойство headers, хранящее простой объект с заголовками запроса.
Адрес текущего хоста можно извлечь из свойства host этого объекта, соответст-
вующего заголовку Host (поз. 4).
Получив путь, сравниваем его с обоими регулярными выражениями (поз. 5).
Если путь совпал с первым регулярным выражением (т. е. пришел запрос на
страницу с выбранным изображением), вызываем контроллер detail О, передав
ему идентификатор изображения, извлеченный из пути (поз. 6).
Если путь совпал со вторым регулярным выражением (т. е. пришел запрос на
файл), вызываем контроллер staticFile(), передав ему извлеченное из пути имя
файла (поз. 7).
Если полученный путь представляет собой слеш (пришел запрос на главную
страницу), вызываем контроллер index () (поз. 8).
Если получен путь, не совпадающий ни с регулярными выражениями, ни со
слешем, вызываем контроллер error(), передав ему соответствующее сообще-
ние и код статуса 404 (поз. 9).
Займемся контроллером index о. При генерировании страницы саму фотогале-
рею поместим в тег семантического основного содержимого (<main>) со стиле-
вым классом photos. Отдельная позиция фотогалереи будет представлять собой
семан гическую статью (тег <article>), в который вложена гиперссылка, ссы-
лающаяся на страницу с соответствующим изображением. В гиперссылке будут
находиться заголовок второго уровня (<h2>) с названием изображения и тег
<img>c самим изображением.
5. Создадим в папке src папку controllers, а в ней— файл index.js. в котором сохра-
ним код из листинга 29.5.
Листинг 29.5. Контроллер, выдающий главную страницу (src\controllers\index.js)
import { opendir, readFile } from 'node:fs/promises';
import { parse } from 'node:path';
export default async function index(req, res) { // .................. 1
let s = ’cidoctype html>' +
'<html>' +
' <heaa>' +
' Cmeta charset="rJTF-8">' +
’ cmeta name="viewport" ’ +
' content="width=^device-width, ' +
’initial-scale=l.0">' +
' clink href="/styles.css" rel="stylesheet">' +
’ сг!11е>Главная :: Фстогалерея</С1Т1е>’ +
’ c/head>' +
’ <body>' +
’ chi >Фотогалереяс/Ы>' +
' <main class="photos">';
const dir = await opendir ('data'); // ........................... 2
for await (let file of dir) { // ................................. 3
const { name, ext } = parse(file.name); // ................... 4
if (ext = '.jpg’) { // ....................................... 5
const descFileName = 'data/${name}.json ; // ............ 6
const fileDescStr = await readFile(descFileName); // ... 7
const fileDesc = JSON.parse(fileDescStr); // ............ 8
s += ’ carticle>' +
ca href="/${name}">‘ +
ch2>${fileDesc.title}c/h2>' +
<img src="/${file.name}">' +
’ c/a>’ +
' c/article>';
}
}
s += ’ c/main>' +
' </body>' +
’c/html>';
res.end(s); // ................................................... 9
}
При генерировании главной страницы мы будем активно использовать асин-
хронные функции из состава Node.js ЛР1 и оператор ожидания await. Поэтому
превратим функцию index о. реализующую контроллер, в асинхронную (поз. I
в листинге 29.5).
В этой функции мы постепенно создаем строку с HTML-кодом, описывающим
главную страницу.
Для генерирования страницы нам потребуется перебрать все JPEG-файлы
в папке data. Для этого применяем асинхронную итерируемую функцию
opendir(<путь к папке>') из встроенного модуля fs/promises (поз. 2). Она воз-
вращает асинхронный итератор, на каждой итерации выдающий сведения об
очередном файле из папки с заданным путем. К сожалению, в этой функции
нельзя указать т ребования к перебираемым файлам (например, их расширение).
Для перебора файлов из папки применяем цикл перебора с ожиданием (поз. 3).
Сведения об очередном файле, выдаваемые на каждой итерации цикла, пред-
ставляются объектом класса Dirent из встроенного модуля fs.
Далее нам понадобятся имя и расширение очередного файла, выданного циклом.
Получить их поможет функция parse (<полное имя файла>} из встроенного моду-
ля path. Полное имя выданного файла можно получить из свойства name объекта
класса Dirent, представляющего файл. В качестве результата (поз. 4) функция
возвращает простой объект со свойствами паше (имя файла без расширения)
и ext (расширение файла с начальной точкой).
Далее, если полученный файл имеет расширение jpg (поз. 5), создаем имя соот-
ветствующего ему JSON-файла, который хранит сведения об изображении
(поз. 6). Считываем содержимое этого JSON-файла в виде строки, применив
асинхронную функцию readFile (<путь к фаОлу>) из встроенного модуля
fs/promises (поз. 7). Преобразуем считанный JSON-код в простой объект, при-
годный для обработки, применив статический метод parse() класса json (поз. 8).
И создаем на основе извлеченных сведений фрагмент HTML-кода, описываю-
щий очередную позицию перечня изображений.
Напоследок заносим готовую строку с HTML-кодом в серверный ответ, приме-
нив уже знакомый метод end().
Напишем контроллер staticFile(), который будет выдавать файлы из папок
static и data
6. Создадим файл src\controllers\staticfile.js и сохраним в нем код из листинга 29.6.
Листинг 29.6. Контроллер, выдающий файлы (src\controllers\staticfile js)
import { existsSync, createReadStream } from 'node:fs';
export default function staticFile(fileName, req, res) {
const filePath = static/?{fileName}'; // .............................. 1
if (existsSync(filePath)) { // ........................................ 2
const stream = creareReadStream(filePath) ; // .................... 3
stream.pipe(res); // .............................................. 4
} else (
const filePath = 'data/${fileName}'; // ........................... 5
if iexistsSync(filePath)) {
const scream = createReadStrearo(filePath);
st ream.pipe(res);
} else {
res.statusCode = 404; // .................................. 6
res. end (); // ......................................... 7
}
)
}
Сначала проверяем, хранится ли файл с заданным именем в папке static. Форми-
руем путь к этом} файлу (поз. I в листинге 29.6). Проверяем, существует ли этот
файл, использовав функцию existsSync (<путь к файлу?) из встроенного модуля
fs (поз. 2). Если файл существует, вызываем функцию createReadStream(<путь
к файлу?) из того же модуля (поз. 3), которая возвращает поток (программный
механизм, позволяющий обрабатывать файлы, не загружая их в память полностью,
а по частям). Наконец, вызываем у полученного потока метод pipe (<ответ?),
который перенесет содержимое файла, представляемого текущим потоком,
в заданный серверный ответ (поз. 4). В результате файл будет отправлен веб-
обозревателю.
Если же требуемый файл отсутствует в папке static, анало! ичным образом про-
веряем, существует ли он в папке data, и, если существует, отправляем его в со-
ставе ответа (поз. 5).
Если же запрашиваемого файла нет ни в папке static, ни в папке data, заносим
в ответ код статуса 404, присвоив его свойству statuscode (поз. 6). и вызываем
у ответа метод end() без параметров, тем самым побуждая Node.js отправить
пустой ответ. Получив пустой ответ с кодом статуса 404, веб-обозреватель
«поймет», что такого файла нет.
На очереди — контроллер detail о, который выдаст страницу с выбранным изо-
бражением.
7. Создадим файл src\controllers\detail js и занесем в него код из листинга 29.7.
Листинг 29.7. Контроллер, выдающий страницу с выбранным изображением
(src\controllers\detail.js)
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import error from './error.js';
export default async function detail(id, req, res) {
const jpgFilePath = 'data/${id}.jpg ; // ................................................................................................................................................................... 1
if (!existsSync(jpgFilePath)) { // ............................... 2
error('Изображение не найдено’, 404, req, res); // ........... 3
return;
}
const descFilePath = 'data/${id}.json';
const fiLeDescStr = await readFile(descFilePath);
const fD = JSON.parse(fileDescStr);
let s = ’Odoctype html>' +
'chtml>' +
’ <head>' +
’ vmeta charset-"UTF-8">' +
’ <meta name="viewport" ' +
'content="width-device-width, ' +
' initial-scaled. 0">' +
’ dink href="/styles.css" rel="sty]esheet">' +
<title>${fD.title} :: Фотогалерея*:/title>' +
’ </head>' +
' <body>' +
’ <Ь1>Фотогалерея</Ь1>' +
<h2>$(fD.title}</h2>' +
' <section class="photo">' +
<img src="/${id}.jpg">' +
’ </section>’;
if (to.desc)
s += ' <div>${fD.desc}</div>';
s += ’ </body>’ +
’</html>’;
res.end(s);
}
Здесь нам. в принципе, все знакомо по коду контроллеров index о (см. лис-
тинг 29.5) и staticFileO (см. листинг 29.6). Нужно лишь отметить несколько
специфических моментов.
Сначала мы формируем п}ть к JPEG-файлу, в котором хранится изображение
с требуемым идентификатором (поз. I в листинге 29.7), и проверяем его наличие
(поз. 2). Если файл не существует, вызываем контроллер error о, передав ему
соответствующее сообщение и код статуса 404 (поз. 3). В противном случае
формируем и отправляем строку с HTML-кодом страницы.
Осталось лишь написать контроллер error о, который будет выдавать страницы
с сообщениями об ошибках.
8. Создадим файл src\controilers\error.js, в которых запишем код из листинга 29.8.
Листинг 29.8. Контроллер, выдающий страницу с сообщением об ошибке
(src\controllers\error.js)
export default function error(message, statusCode, req, res) {
res.statusCode = statusCode;
const s = *<!doctype html>' +
’<html>’ +
’ <head>' +
’ cmeca charset="UTF-8">’ +
’ <meta name="viewport" ' +
'content="width=device-width, ' +
’initial-scale=l.0">' +
' clink href="/styles.css" rel="stylesheet">' +
' <title>0mw6Ka :: OoToranepes</title>' +
’ </head>' +
’ <body>' +
’ <Ы>Фотогалерея</Н1>' +
’ <Ь2>0^иока</Ь2>* +
<p>$(message}</h2>‘ +
’ </body>’ +
’</html>’;
res.end(s);
Фотогалерея
O iiiiiokov .trpciio Hiuia iii.iu k.im'hiiiku Tpeiyap linitimi i.ikhi
I Inti
Рис. 29.1 Результат .ылолнения упражнения 29.3: главная веб-страница
Этот код не требует комментариев. Единственное: не забываем указать у от-
правляемого ответа требуемый код статуса, присвоив его свойству statuscode.
Запустим приложение, воспользовавшись сценарием проекта serve (см. упражне-
ние 29.2). Откроем веб-обозреватель и перейдем по интернет-адресу http://
local host: 8000/. Посмотрим на главную страницу (рис. 29.1). Щелкнем на заголовке
какой-либо из миниатюр или на самой миниатюре и проверим, выводится ли стра-
ница со сведениями об изображении (рис. 29.2). Напоследок попробуем выбрать
заведомо несуществующее изображение (например, перейдя на страницу
с каким-либо изображением, изменив последнюю цифру в идентификаторе, при-
сутствующем в интернет-адресе, и выполнив переход) и посмотрим на страницу
с сообщением об ошибке.
Рис. 29.2. Результат выполнения упражнения 29 3. веб-страница с выбранным изображением
29.4. Упражнение. Обработка GET-параметров
Реализуем в нашей фотогалерее поиск изображений по указанной пользователем
подстроке. Для этого поместим в верхней части главной страницы веб-форму с по-
лем ввода и кнопкой отправки данных.
Если подстрока указана, будут выводиться лишь изображения, у которых эта под-
строка присутствует в названии или описании. При выполнении поиска регистр
символов учитываться не будет.
Подстроку для поиска будем передавать через GET-параметр search. Соответст-
венно поле ввода для занесения искомой подстроки получит такое же наимено-
вание.
1. Найдем в папке 29\ех29.3 сопровождающего книгу файлового архива (см. при-
ложение 5) папку photogallery (папка проекта) и скопируем куда-либо на локаль-
ный диск.
Если пользователь ранее указал подстроку для поиска, ее следует вывести в поле
ввода веб-формы. Условимся, что подстрока, извлеченная из GET-параметра
search, в дальнейшем будет сохранена в переменной rawsearch.
2. Откроем копию файла src\controllers\mdex.js в текстовом редакторе и внесем в код
необходимые правки:
export default async function index(req, res) {
let s = '<!docr.ype html>' +
' <Ь]>Фотогалерея</Н1>' +
' <form action="/" class="inline">' +
' <input type="search" name="search" ' +
value="${rawSearch}">’ +
' <input type="submit." valuer"Найти">’ +
' </form>' +
* <main class="phoTos">';
}
Теперь реализуем собственно поиск изображений по заданной подстроке.
3. Внесем в тот же файл src\controllers\index js следующие правки:
export default async function index(req, res) {
const url = new URL(req.url, http://${req.headers.host} );//.!
const rawSearch = url.searchParams.get('search') ?? ' '; // ...... 2
const search = rawSearch.toLowerCase(); // ....................... 3
let s = '<!doctype html>' +
<main class="photos">’;
const dir = await opendir(’data');
for await (let file of dir) {
const { name, ext } = parse(file.name);
if (ext = '.jpg’) {
const fileDesc = JSON.parse(fileDescStr);
const title = fileDesc.title.toLowerCase(); // ......... 4
const desc = (fileDesc.desc ?? ''). toLowerCase () ; // ... 5
if (!search || (title .includes(search) I I
desc.includes(search))) // ............. 6
s += ' <article>* +
I
</article;>';
Чтобы извлечь из текущего интернет-адреса нужный GET-параметр, сначала
преобразуем интернет-адрес из строки в объект класса url (поз. 1 в приведенном
коде). Из его свойства searchParams извлекаем объект класса URLSearchParams,
представляющий набор имеющихся GET-параметров. и обращаемся к его мето-
ду get (), чтобы получить значение GET-параметра search. Если такого GET-
параметра в наборе нет, метод get о вернет значение null, которое мы заменяем
пустой строкой, чтобы упростить дальнейшее программирование. Полученное
значение присваиваем константе rawsearch—оно впоследствии будет выведено
в поле ввода подстроки (поз. 2). Далее мы приводим это же значение к нижнему
регистру и присваиваем константе search— это значение используем непосред-
ственно при поиске (поз. 3).
В процессе формирования перечня изображений название очередного изображе-
ния приводим к нижнему регистру (поз. 4). Далее точно гак же поступаем с опи-
санием, предварительно заменив его пустой строкой, если оно не указано
(поз. 5). Приведя подстроку, название и описание изображения перед собственно
поиском к нижнему per истру. мы добьемся, чтобы поиск выполнялся без учета
регистра символов.
Очередную позицию в перечень изображений добавляем, только если подстрока
для поиска не задана ИЛИ входит в состав названия ИЛИ описания (поз. 6).
Запустим приложение, откроем веб-обозреватель и выполним переход по интернет-
адресу http://localhost:8000/. Зададим какую-либо подстроку в поле ввода, нахо-
дящемся в веб-форме поиска, и нажмем кнопку Найти. Убедимся, что будут выве-
дены лишь изображения, у которых названия или описания включают заданную
подстроку (рис. 29.3).
Рис. 29.3. Результат выполнения упражнения 29 4 результаты поиска
29.5. Упражнение.
Обработка POST-параметров
Осталось добавить фотогалерее возможности добавления и удаления фото.
Для добавления фото предусмотрим отдельную страницу с веб-формой, в которую
будут заноситься добавляемое изображение и сведения о нем. После успешного
добавления фото будет выведена главная страница.
Для удаления фото на странице полноразмерной фотографии поместим веб-форму
с кнопкой отправки данных, щелчок на которой и выполнит удаление.
Обе веб-формы — добавления нового изображения и удаления существующего —
будут отправлять данные методом POST. Веб-форма добавления изображения
будет кодировать данные с применением метода, multipart/form-data (это единст-
венный способ отправить серверу файл).
Потребуется добавить в приложение гри дополнительных контроллера — выпол-
няющихся при получении следующих путей:
♦ /add (HTTP-метод GET) — addPage() — выдающий страницу с веб-формой для
занесения добавляемого изображения:
addPage(<объект клиентского запроса>, <о&ъект серверного ответа>)
Веб-форма будет содержать элементы управления со следующими наименова-
ниями:
• title — поле ввода — название изображения (обязательно к заполнению);
• desc — область редактирования — описание изображения;
• photo — поле выбора файла — файл с изображением (обязательно к заполне-
нию, доступны для выбора лишь файлы с расширением jpg).
Еще в веб-форме будет кнопка отправки данных;
♦ / (HTTP-метод POST) — add о — добавляющий изображение, заданное в веб-
форме, в фотогалерею и производящий перенаправление на главную страницу.
Перенаправление — указание веб-обозревателю выполнить переход по другому
интернет-адресу.
Формат вызова контроллера add о такой же. как и у контроллера addPage();
♦ /<идентификапюр>/де\е1е (ПТТР-метод POST) — del О — удаляющий изобра-
жение с заданным идентификатором и производящий перенаправление на
главную страницу:
del(<идентпфпкатор>, <объект клиентского запроса>,
<о&ьект серверного ответа>)
В качестве идентификатора используется имя файла, в котором хранится изо-
бражение. без расширения.
Все контроллеры также объявим в отдельных модулях, имена которых совпадут
с именами контроллеров, и сохраним в папке src\controllers.
Для получения POST-параметров мы применим готовое решение, реализованное
в виде дополнительной программной библиотеки formidable. Эту' библиотеку мы
установим в состав нашего Node-проекта.
Внимание!
Библиотека formidable может обрабатывать лишь POST-параметры, закодированные
методом multipart/form-data
Полное описание этой библиотеки можно найти по интернет-адресу
https://www.npmjs.com/package/formidable
I. Найдем в папке 29\ех29 4 сопровождающего книгу файлового архива (см. при-
ложение 5) папку photogallery (папка проекта) и скопируем куда-либо на локаль-
ный диск.
Сначала установим в проект веб-приложения библиотеку formidable.
2. Запустим утилиту Terminal или командную строку, в этой утилите перейдем
в папку проекта и введем команду:
npm install formidable@-3.5
Мы указали установить версию 3.5 этой библиотеки (се использовал автор при
написании кнш и) или совместимую с ней.
3. Откроем файл package json и найдем следующий фрагмент кода:
{
"dependencies": {
"formidable": "-3.5"
)
}
Атрибут dependencies манифеста проекта содержит поддокумент, хранящий
перечень всех дополнительных библиотек, которые были установлены в состав
проекта. Имя атрибута этого поддокумента совпадает с именем соответствую-
щей библиотеки, а значение представляет собой указание на требуемую версию
этой библиотеки, заданное при установке.
Кроме того, в папке проекта были созданы:
• папка node_modules — хранящая все установленные библиотеки и все библио-
теки. требуемые им для работы (и устанавливаемые автоматически). В нашем
случае эта папка содержит саму библиотеку formidable и еще 5 библиотек,
которые она использует;
• файл package-lockjson — реестр проекта (список всех без исключения уста-
новленных библиотек с их точными версиями).
Теперь внесем исправления в маршрутизатор.
4. Откроем файл src\router.js и исправим код следующим образом:
import error from './controllers/error.js1;
xmport addPage from './controllers/addpage.js1;
import, add from './controllers/add.js’;
import del from /controllers/del.js';
const reDetail = /л\/([0-9] +)$/;
const reStatic = /л\/ ([-_C-9a-z] +\,[a-z]{2,3})$/;
const reDelete = /л\/([0-9]+)\/delete$/; // ......................... 1
export default function router(req, res) {
const if = reDetail.exec(requestedPath);
const rS = reStatic.exec(requestedPath);
const rDel = reDelete, exec (requestedPath) ; // ................ 2
if (rD)
detail(rD[l], req, res);
else if (rS)
staticFile(rS[l], req, res);
else if (rDel && req.method = 'POST') // ........................ 3
del (rDel [1] , req, res) ;
else if (requestedPath = '/add') // ............................. 4
addPage(req, res);
else if (requestedPath =- '/')
if (req.method = 'POST') // .................................. 5
add(req, res);
else // ..................................................... 6
index(req, res);
else
error('Страница не найдена', 404, req, res);
}
Понадобится создать еще одно регулярное выражение— определяющее шаблон
для пути, который ссылается па контроллер, удаляющий изображение (поз. 1
в приведенном коде). И. разумеется, проверить путь, полученный с очередным
запросом, на соответствие этому регулярному выражению (поз. 2).
Если очередной путь соответствует только что добавленному регулярному вы-
ражению И запрос был выполнен с использованием HTTP-метода POST, вызы-
ваем контроллер del о, удаляющий изображение (поз. 3). Название НТТР-
метода в виде строки в верхнем регистре можно получить из свойства method
объекта запроса.
Если был получен запрос по пути /add, вызываем контроллер addPage () (поз. 4).
Если был получен запрос по пути / («корень» приложения), то в случае выпол-
нения запроса HI IP-методом POST вызываем контроллер add О, добавляющий
изображение в галерею (поз. 5), в противном случае — как и раньше, контроллер
index () (поз. 6).
Напишем контроллер addPageо, который выведет страницу с веб-формой для
занесения добавляемого изображения.
5. Создадим файл src\controllers\addpage js и занесем в него код из листинга 29,9.
Листинг 29.9 Контроллер, выводящий страницу для добавления изображения
(src\controllers\addpage.js)
export default function addPage(req, res) {
ccnst s = '<!doctype html>' +
'<html>' +
' chead>' +
' <meta charset="UTF-8">' +
' cmeta name="viewport" ' +
'content="width=device-width, ' +
'initial-scale=l.0">' +
' clink href="/styles.css" rel="?tylesheet">' +
' <title>flo6aaneHwe :: Фотогалереяс/Ь1Ь1е>’ +
' </head>’ +
' <body>' +
' <Ы>Фотогалерея</Ы>' +
' сЬ2>Добавление изображенияс/Н2>' +
' cform action’'"/" methoa="post" ’ +
’enctype^"multipart/torm-data"^1 +
' Ср>Название: cinput name="citle" required>c/p>' +
' <р>Описание: cbrxrextarea name="desc">' +
'c/textarea>c/p>' +
' ср>Изображение: cinput type="file" ' +
’name="photo" accept=".jpg" ’ +
'required>c/p>' +
' Cpxinput type="submit" value="flo6aBMTb">c/p>'
' c/form>' +
' c/body>’ +
'</html>';
res.end(s);
}
Этот контроллер очень прост. Чего не скажешь о контроллере add о, который
сохраняет заданное изображение в фотогалерее и который мы сейчас напишем.
6. Создадим файл src\contfollers\add.js и занесем в него код из листинга 29.10.
Листинг 29.10. Контроллер, сохраняющий заданное изображение
(src\controllers\add Js)
import { rename, writeFile } from 'node:fs/promises';
import formidable from 'formidable'; // ................................. 1
const form = formidable!! upi.oadDir: 'data' }); // ..................... 2
export default async function add(req, res) {
const fileBaseName = (new Date()).getTime(); // ................. 3
const jpgFileName = data/${fileBaseName).jpg ; // .............. 4
const jsonFileName = data/${fileBaseName}.json';
const [postParams, files] = await form.parse(req); // .......... 5
await rename(files.photo[0].filepath, jpgFileName); // ......... 6
const fileDesc = { // .......................................... 7
title: postParams.title(0],
desc: postParams.desc[0]
};
const fileDescStr = JSON.stringify(fileDesc); // ............... 8
await writeFile(jsonFileName, fileDescStr); // ................. 9
res.setHeader('Location', // ........................... 10
res. statusCode = 302; // ..................................... 11
res.endO; // .................................................. 12
}
Программное ядро библиотеки formidable— фабрика объектов formidable ().
Она подвергнута экспорту' по умолчанию. Чтобы импортировать сущность из
дополнительной библиотеки, установленной в составе проекта, в выражении
импорта следует записать имя нужной библиотеки (поз. 1 в листинге 29.10).
Фабрика объектов formidable(), будучи вызванной, возвращает объект обработ-
чика POST-параметров, настроенный в соответствии с заданными параметрами.
Параметры задаются в виде простого объекга (как обычно бывает в JavaScript).
Мы указали лишь один параметр — uploadDir, задающий путь к папке, в кото-
рой будут временно сохраняться полученные от веб-обозревателя файлы
(поз. 2).
В теле контроллера создаем базовое имя для графического и JSON-файлов,
представляющее собой временную отметку, которая выражена количеством
миллисекунд, прошедших с полуночи 1 января 1970 года (поз. 3). На основе ба-
зового имени создаем пути JPG- и JSON-файлов (поз. 4).
Объект обработчика POST-параметров, возвращенный фабрикой объектов
formidable(), поддерживает асинхронный метод parse(<запрос>), выполняющий
обработку POST-параметров из заданного запроса. Он выдает массив из двух
элементов— простых объектов, первый из которых хранит значения всех POST-
параметров, не являющиеся файлами, второй — содержимое файлов. Имена
свойств этих объектов совпадают с наименованиями элементов управления, а
значения свойств представляют собой массивы из значений, занесенных в эти
элементы управления. Если в элемент управления занесено лишь одно значение,
свойство все равно будет хранить массив — из единственного элемента (поз. 5).
Следует отметить, что метод parseo объекта обработчика также сохраняет по-
лученные файлы в папке с заданным ранее путем. Так что нам этого делать не
придется
Однако сохраняются полученные файлы под именами, сгенерированными самой
библиотекой. Так что нам придется их переименовать. Для этого применим
асинхронную функцию rename (<старый путь» <новый путь>) из встроенного мо-
дуля fs/promises. Новый путь для JPEG-файла мы сформировали ранее, а ста-
рый можно получить из свойства filepath объекта, представляющего сохранен-
ный файл (поз. 6).
Осталось сохранить сведения о добавленном изображении в JSON-файлс. Соз-
даем простой объект, хранящий эти сведения (поз. 7). и преобразуем его в стро-
ку с JSON-кодом (поз. 8). Дтя сохранения заданной строки в файле используем
асинхронную функцию writeFile (<луть к файлу» <строка>) из встроенного мо-
дуля fs/promises (поз. 9).
Перенаправление по другому пути выполняется в три этапа. На первом этапе
следует занести в серверный ответ заголовок Location, указав у него в качестве
значения целевой путь. Выполняется это вызовом у серверного ответа метода
setHeader() (ПОЗ. 10):
setHeader (<гя4я заголовка» <значенпе заголовка»
На втором этапе (поз. 11) у серверного ответа указывается код статуса 302 (вре-
менное перенаправление). 11а третьем этане производится отсылка пустого отве-
та вызовом метода end о без параметров (поз. 12).
Последний контроллер, который мы напишем. — это del о, удаляющий задан-
ное изображение.
7. Создадим файл src\controllers\del js и занесем в него код из листинга 29.1 I
Листинг 29.11. Контроллер, удаляющий изображение (src\controllersidel.js)
import { rm } from 'none:fs/promises';
export default async function del(id, req, res) {
const jpgFilePath = data/${id}.jpg ;
const descFilePath = 'data/${id}.json';
await rm(jpgFilePath);
await rm(descFilePath);
res.setHeader('Location', '/’);
res.statusCode = 302;
res.end();
Для удаления файла используем асинхронную функцию rm(<путь к файлу» из
встроенною модуля fs/promises. Остальное нам знакомо.
Теперь нужно добавить на страницу выбранного изображения пустую веб-
форму с кнопкой отправки данных Удалить, которая запустит удаление изо-
бражения. Эта веб-форма отправит данные HTTP-методом POST.
8. Откроем файл src\controllers\detail.js и внесем следующие правки:
export default async function detail(id, req, res) {
s—<•---------J—<-/bedy>'—h
s += * <form action="/${id}/delete" niethod=,,post" ' +
1 class="inline">’ +
’ Cinput type="submit" value="Удалить">1 +
’ </form>' +
' </body>' +
'</html>';
res.end(s);
}
Осталось добавить на главную страницу гиперссылку, ведущую на страницу
добавления изображения в галерею.
9. Откроем файл src\controliers\mdex js и добавим код, создающий гиперссылку на
страницу добавления изображения (путь /add), самостоятельно.
Запустим приложение, откроем веб-обозреватель и выполним переход по интернет-
адресу http://localhost:8000/. Добавим в фотогалерею произвольное изображение
с произвольными названием и описанием, а потом удалим его.
Полезно знать
♦ Утилита npm загружает устанавливаемые библиотеки из собственного хранилища,
находящегося по интернет-адресу: https://www.npmjs.com/.
♦ 1 [анку node_modules. в которую записываются устанавливаемые дополнительные биб-
лиотеки. можно удалить, а впоследствии восстановить — вместе со всем содержи-
мым. Для этого достаточно запустить утилиту Terminal или командную строку,
в среде этой утилиты перейти в папку проекта и подать команду:
npm install
♦ Но возможности утилита npm будет устанавливать те версии библиотек, что были
указаны в реестре проекта, чтобы воссоздать проект в том виде, в котором он был
создан ранее.
29.6. Самостоятельное упражнение
Сделайте так, чтобы при попытке удалить изображения щелчком на кнопке Уда-
лить на странице с этим изображением предварительно выводилось окно-
предупреждение и удаление выполнялось лишь после нажатия кнопки ОК этого
окна.
Необходимый для этого веб-сценарий можете сделать как внутренним, так и внеш-
ним. поместив файл с этим сценарием в папку static.
Урок 30
Двухзвенные веб-приложения.
Разработка бэкендов
Традиционные веб-приложения обрабатывают данные и представляют их в при-
годном для вывода виде (в виде веб-страниц) в одном месте — на стороне сервера,
отчего и носят название однозвенных.
Достоинство традиционных веб-приложений: низкая нагрузка на клиент (поскольку
клиент занимается лишь выводом страниц, сгенерированных приложением) и низ-
кая сложность разработки (т. к. необходимо написать лишь код. работающий на
стороне сервера). Недостаток: высокая нагрузка на сервер, т. к. приложение, поми-
мо обработки данных, еще должно генерировать страницы с результатами.
30.1. Введение
в двухзвенные веб-приложения и REST
30.1.1. Двухзвенные веб-приложения
Напротив, двухзвенные приложения обрабатывают данные на стороне сервера,
а представляют — на стороне клиента.
Двухзвенное веб-приложение — состоит из двух частей (или звеньев), из кото-
рых одна работает на стороне сервера, другая — на стороне клиента.
Бэкенд (или веб-служба) — часть двухзвенного веб-приложения, которая рабо-
тает на стороне сервера и обрабатывает данные.
Фронтенд — часть двухзвенного веб-приложения, которая работает на стороне
клиента и представляет пользователю данные, полученные от бэкенда и зако-
дированные в каком-либо компактном формате (обычно JSON).
Достоинство двухзвенных приложений: низкая нагрузка на сервер, поскольку он
занимается лишь обработкой данных и выдачей результатов в простом компактном
формате. Недостатки: повышенная сложность разработки (ведь необходимо писать,
помимо серверного кода, еще и клиентский) и высокая нагрузка на клиент (которо-
му придется заниматься преобразованием данных, полученных от бэкенда. в вид,
пригодный для вывода).
В настоящее время в связи с повышением производительности компьютеров и мо-
бильных устройств высокой популярностью различных интернет-служб и связан-
ной с этим необходимостью всемерно снижать нагрузку на серверы, двухзвенные
приложения становятся все более популярными.
30.1.2. REST
REST (Representational State Transfer, репрезентативная передача состояния) —
набор принципов, согласно которым реализуется взаимодействие между фрон-
тендом и бэкеидом.
REST основан на следующих двух принципах:
♦ каждая единица информации, которую может предоставить приложение {интер-
нет-ресурс), идентифицируется ее интернет-адресом;
♦ каждое действие, выполняемое над таким интернет-ресуреом, идентифицирует-
ся НТТР-методом.
В качестве примера рассмотрим двухзвенное приложение фотогалереи, построен-
ное на основе REST.
♦ Iкремень изображений — идентифицируется путем / (слеш):
• выдача перечня — обозначается НТТР-методом GET;
• добавление нового изображения в перечень — НТТР-методом POST;
♦ отдельное изображение с заданным идентификатором — идентифицируется
путем формата 1<идентификатор>:
• выдача изображения — обозначается НТТР-методом GET;
• удаление изображения — НТТР-методом DELETE.
Полезно знать
♦ REST включает в себя еще один принцип: все сведения о клиенте хранятся на сто-
роне клиента. В частности, если приложение реализует разграничение доступа
(в этой книге не рассматривается), список прав пользователя на доступ к интернет-
ресурсам должен храниться фронтендом, а не бэкендом.
♦ Правка интернет-ресурса обозначается НТТР-мет одом PUT или PATCH (обычно
PUT — его название короче).
30.2. Упражнение. Разработка бэкенда
Превратим традиционное веб-приложсние фотогалереи, написанное при выполне-
нии упражнений 29.1-29.5, в бэкенд, выдающий данные в формате JSON и постро-
енный по принципам REST. Перечень изображений и отдельные изображения
в этом приложении будут идентифицироваться путями, а выполняемые над ними
дейст вия HTTP-методами, как описано в разд. 30.1.2.
Перечень изображений будет представлять собой массив J SON-доку ментов с атри-
бутами: id (идентификатор — имя файла изображения без расширения), title
(название изображения) и href (полный интернет-адрес файла с изображением).
Сведения об отдельном изображении будут представлять собой отдельный JSON-
документ, содержащий те же атрибуты плюс атрибут desc (описание изображения).
I. Найдем в папке 29\ех29 5 сопровождающего книгу файлового архива (см. при-
ложение 5) папку photogallery (нанка проекта) и скопируем ее куда-либо па ло-
кальный диск.
Сразу же удалим ненужную больше таблицу стилей.
2. Удалим панку static, содержащую файл styles css.
Переделку приложения начнем с маршрутизатора.
В разрабатываемом бэкенде, чтобы получить изображение с заданным иденти-
фикатором, следует отправить по пути формата 1<идентификатор> запрос
HTTP-методом GET. а для удаления изображения — запрос по тому же пути, но
с применением HTTP-метода DELETE. Для анализа пути в обоих случаях мы
будем использоват ь одно и то же регулярное выражение — хранящееся в кон-
станте reDetail, тем самым немного упростив код.
Кроме того, поскольку бэкенд не будет выводить страницу с веб-формой для
указания добавляемого изображения, мы можем удалить код, который вызывает
соответствующий контроллер, и сам этот контроллер.
3. Откроем файл src\router js в текстовом редакторе и внесем такие правки:
import error from ’./controllers/error.js';
imporc addPuge from 1./controllers/addpagQ.js';
import add from './controllers/add.js’;
const reDetail = /Л\/([0-9]+)$/;
const reStatic = /Л\/([-_'J-9a-zj+\. [a-z] (2,3}) $/;
const reDoloto - /Л\/([0-9]+)\/deletG$/?
export default function router(req, res) {
const rD = reDetail.exec(requestedPath);
const rS = reStatic.exec(requestedPath);
const rDel - reDelete. exec ( requestedPath;,
if (rD)
if (req.method = 'DELETE')
del(rD[l] , req, res) ;
else
detail(rD[l], req, res);
else if (rS)
staticFile(rS[l], req, res);
else if—(-rDe-1- -&&- -req. method — ' POST' )
acl(rDoi[1|; roqz res);
о loo if—(requestedPath '/add')
addPuqo treq,—eesP-r
else if (requestedPath = '/')
if (req.method = 'POST')
add(req, res);
else
index(req, res);
else
error('Страница не найдена', 404, req, res);
}
4. Удалим файл src\controllers\addpage.js. содержащий код контроллера, который вы-
водит страницу добавления нового изображения.
Контроллер, ранее генерировавший главную страницу, отныне будет формиро-
вать и отправлять клиенту JSON-массив опубликованных изображений.
5. Откроем файл src\controilers\mdex.js в текстовом редакторе и внесем необходимые
исправления:
export default async function index(req, res) {
Lot l>----^^-deefeype——i-
<main class-"photos ">1;
const data = [];
const dir = await opendir('data');
for await (let file of dir) {
const { name, ext } = parse(file.name);
if (ext — '.jpg') (
if (!search || (title.includes(search) ||
desc.includes(search)))
s и-- '-----<artic-lo>'—-
J--------г/ort 1 cl o^'}
data.push({
id: name,
title: fileDesc.title,
href: http://${req headers host}/$(file.name}'
}) ;
}
}
--------------'----;/main>' 4-
J—</body>' 4-
' </htial>' /
ros.ond(s);
res.setHeader('Content-Type', 1 application/j son');
res.end(JSON. stnngify (data)) ;
}
Данные в формате JSON следует заносить в ответ преобразованными в строко-
вый вид. А перед этим необходимо добавить r ответ заголовок Content-Type
со значением application/json, тем самым указав веб-обозревателю, что ему от-
правлены JSON-данные.
Контроллер, который ранее выдавал страницу с изображением, теперь будет
генерировать JSON-документ со сведениями об этом изображении.
6. Откроем файл src\controllers\detail.js в текстовом редакторе и исправим его сле-
дующим образом:
export default async function detail(id, req, res) {
j-ot s ------'<) doc typo html»1—ь
res.end(s)I
const data = {
id,
title: fD.title,
desc: fD.desc ?? ’',
href: 4 http://${req.headers.host}/${id}.jpg'
};
res.setHeader('Content-Type', 1 application/json');
res.end(JSON.stringify(data));
}
Как вы, уважаемые читатели, наверное, уже успели заметить, код бэкенда за-
метно компактнее кода аналогичного по функциональности традиционного при-
ложения.
Контроллер, добавляющий изображение в фотогалерею, теперь будет возвра-
щать пустой ответ с кодом статуса 201 (ресурс успешно создан).
7. Откроем файл src\controllers\add js в текстовом редакторе и исправим его так:
export default async function add(req, res) {
ros. notHoador (1 Lo< \ition',—' / 1 j ;
recjstatusCode - 302;
res.statusCode = 201;
res.endO ;
}
Контроллер, удаляющий изображение, теперь будет возвращать пустой ответ
с кодом статуса 204 (в нашем случае — ресурс успешно удален).
8. Откроем файл src\controllers\del js в текстовом редакторе и исправим таким обра-
зом:
export default async function del(id, req, res) {
res.set Header('Locat ion1 /—1 / 1);
res.scatusCodc- ~ 302;
res.statusCode = 204;
res.end();
}
Что касается контроллера, ранее выдававшего страницу с сообщением об ошиб-
ке, то теперь пусть он выдает пустой ответ с кодом статуса, переданного кон-
троллеру вторым параметром.
9. Откроем файл src\controllers\error.js в текстовом редакторе и внесем в него такие
правки:
export default function error(message, statusCcde, req, res) {
res.statusCode = statusCode;
const о ~------'Odoctypo ntiuJ/*1—►
’</html>';
res.end is);
res.end() ;
Запустим бэкенд, откроем веб-обозреватель и выполним переход по интернет-
адресу http://localhost:8000/. Веб-обозреватель выведет JSON-код перечня изобра-
жений. выданный бэкендом (рис. 30.1).
[{"id":"1701430022944","title":"Одинокое
дерево","href":"http://localhost:8000/1701430022944 jpg"},
("id":"1701430039287","title":"Цветущая
клубника","href":"http://localhost 8000/1701430039287.jpg"},
("id":"1701430051223","title":"Тротуар","href":"http://localhost 8000/1701430051223.jpg"},
("id":"1701430163374","title":"Зимний закат","href":"http://localhost‘8000/1701430163374.jpg"},
("id":"1701430177487","title":"Цветы","href":"http.//localhost:8000/1701430177487.jpg"},
{"id":"1701764800735","title":"До*дь и
солнце","href":"http://localhost 8000/1701764800735.jpg"},
("id":"1701764988325","title":"Памятник доктору
Айболиту","href":"http://Iccalhost:8000/1701764988325 jpg"}]
Рис. 30.1. Результат выполнения упражнения 30 2 JSON-код перечня изображений
Попробуем вывести сведения об одном из изображений, выполнив переход по ин-
тернет-адресу http://localhost:8()0U/l 701430022944. Веб-обозреватель отобразит
JSON-код этих сведений (рис. 30.2).
("id":"1701430022944","title":"Одинокое дерево","desc":"Г. Волжский, возле
филармонии","href”:"http://localhost:8000/1701436022944,jpg"}
Рис 30.2. Результат выполнения упражнения 30 2
JSON-код сведений о выбранном изображении
Данные, представленные в формате JSON, не отличаются наглядностью. Зато они
прекрасно обрабатываются средствами JavaScript. Что очень пригодится нам во
время программирования фронтенда — т. е. очень скоро.
Однако на уроках 31 и 32, когда мы начнем разрабатывать фронтенд для нашего
бэкенда, то столкнемся с проблемой. Веб-обозреватель не сможет прочитать дан-
ные, отправленные бэкендом, и выдаст ошибку. И вот почему...
Фронтенд будет обслуживаться обычным веб-сервером http-server, который по
умолчанию работает через ТСР-порт 8080. Л бэкенд будет работать, как мы усло-
вились, через ТСР-порт 8000. Таким образом, запрос от фронтенда бэкенду станет
межхостовым.
А чтобы веб-обозреватель смог считать содержимое серверного ответа, полученно-
го от бэкенда, который работает на другом хосте, бэкенд должен отправить в соста-
ве ответа указание, что отправленные им данные заслуживают доверия. Кроме того,
бэкенд должен явно разрешит ь фронтенду выполнять расширенные межхостовые
запросы.
30.3. Поддержка межхостовых запросов
Межхостовые запросы бывают двух видов’ прост ые и расширенные.
30.3.1. Простые межхостовые запросы
Простой межхостовый клиентский запрос— межхостовый запрос, выполнен-
ный с применением HTTP-метода GET, POST или HEAD, содержащий в себе
заголовки: Accept, (предпочитаемые типы данных). Accept-Language (предпочи-
таемые ЯЗЫКИ), Content-Language (наиболее предпочитаемый ЯЗЫК). Contenr-Type
(тип отправляемых данных) и Range (требуемая часть данных, если данные за-
гружаются по частям).
В заголовке Content-Type допускается указание лишь типов: application/x-www-
form-urlencoded, multipart/form-data ИЛИ text/plain (обычный TCKCT).
На выполнение таких запросов веб-обозреватель не обязан запрашивать у бэкенда
каких бы то ни было дополнительных разрешений. Тем не менее он должен отпра-
вить в составе запроса заголовок Origin, указав в нем адрес клиентского хоста, на
котором работает.
Бэкенд, получив запрос с заголовком Origin, обязан отправить фронтенду в составе
ответа указание, что высланные им в этом ответе данные заслуживают доверия.
Для этого он должен вставить в ответ заголовок Access-Controi-Aiiow-Origin и ука-
зать в качестве его значения одно из двух:
♦ символ * (звездочка):
if (reg.headers.origin)
res.setHeader('Access-Control-Allow-Origin', '*');
Гак бэкенд сообщит, что отправленные им данные доступны любому фронтенду.
Обычно подобным образом поступают при отправке данных, не являющихся
секретными.
Заголовки полученного запроса можно извлечь из свойств простого объекта,
хранящегося в свойстве headers объекта запроса. Так. значение заголовка Origin
можно извлечь из свойства origin;
♦ адрес хоста клиента — взяв его из заголовка Origin полученного запроса:
if (req.headers.origin)
res.setHeader('Access-Control-Allow-Origin1, req.headers.origin);
Тем самым бэкенд сообщит фронтенду, что отправленные данные предназнача-
ются именно ему. и предот вратит перехват важных данных злоумышленниками.
Если бэкенд не отправит такого указания, веб-обозреватель не сможет прочитать
данные из полученного ответа, что приведет к ошибке.
При разработке бэкенда следует иметь в виду, что заголовок Origin отправляется не
во всех запросах, а только в межхостовых.
30.3.2. Расширенные межхостовые запросы
Расширенный межхостовый клиентский запрос — межхостовый запрос, не
удовлетворяющий требованиям, которые предъявляются к простым запросам
(см. разд. 30.3.1).
Перед отправкой межхостового запроса веб-обозреватель самостоятельно отправ-
ляет бэкенду проверочный запрос с целью получить разрешение на отправку рас-
ширенного запроса. Проверочный запрос отправляется с применением НТТР-
метода options и содержит следующие важные заголовки:
♦ Access-Control-Request-Method — название НПР-метода, которым планируется
выполнить расширенный запрос;
♦ Access-Control-Request-Headers — перечень имен заголовков, которые будут
присутствовать в расширенном запросе, разделенных последовательностями из
запятой и пробела.
В ответ бэкенд, чтобы разрешить фронтенду выполнить расширенный запрос, дол-
жен отправить пустой ответ с заголовками:
♦ Access-Control-Allow-Origin — описанным в разд. 30.1.7;
♦ Access-Control-Allow-Method — с названием допустимого в запросе НТ1 Р-метода;
♦ Access-Control-Allow-Headers — с перечнем имен допустимых в запросе заго-
ловков. разделенных последовательностями из запятой и пробела;
♦ Access-Control-Мах-Аде — с временем, в течение которого разрешение, отправ-
ленное веб-обозревателю, останется актуальным. По истечении этого времени
веб-обозревателю придется получить у бэкенда новое разрешение. Время зада-
ется в виде числа в секундах. Если заголовок не указан, разрешение будет хра-
ниться 5 с.
У отправляемого ответа следует указать код статуса 204 (г нашем случае он обо-
значает, что ответ пуст). Впрочем, как показывает практика, можно указать и код
статуса 200.
Если бэкенд намеревается запретить расширенный запрос, он должен отправить
фронтенду пустой ответ с кодом статуса 405 (недопустимый HTTP-метод) или 403
(в этом случае — недопустимый заголовок).
Пример:
if (req.headers.origin)
res.setHeader('Access-Control-Allow-Origin', ;
if (req.method == ’OPTIONS') {
if (req.headers['access-control-request-mechod'] = 'DELETE' &&
reg.headers['access-control-request-headers']
.includes('X-IAmNoHacker')) {
res.setHeader('Access-Control-Allow-Method', 'DELETE');
res.setHeader('Access-Control-Allow-Headers',
'X-IAmNoHacker, X-IAmGood’);
res.setHeader('Access-Control-Max-Age', '86400');
} else
res.statusCode = 405;
res.end();
}
30.4. Упражнение.
Реализация поддержки межхостовых запросов
Добавим бэкенду, написанному при выполнении упражнения 30.2, поддержку
межхостовых запросов.
Ради безопасности отправляемые данные будем помечать как доступные только
хосту, который их запрашивал. Сделаем допустимым только HTTP-метод delete.
I. Найдем в папке 30\ех30 2 сопровождающего книгу файлового архива (см. при-
ложение 5) папку photogallery (папка проекта) и скопируем ее куда-либо на ло-
кальный диск.
Код, отправляющий фронтенду необходимые уведомления и разрешения, по-
местим непосредственно в модуль маршрутизатора, благо этот код невелик.
2. Откроем модуль src\router.js в текстовом редакторе и вставим в него следующий
код:
export default function router(req, res) {
if (req.headers.origin)
res.setHeader('Access-Control-Allow-Origin',
req.headers.origin);
if (req.method = ’OPTIONS') {
res.setHeader('Access-Control-Allow-Methods', 'DELETE');
res setHeader('Access-Control-Max-Age', '86400’);
res.statusCode = 204;
res end() ;
return;
}
const requestedPath = new URL(req.url,
'http://${req.headers.host}').pathname;
}
Проверим исправленный бэкенд в работе (как это сделать, описано в упражне-
нии 30.2).
Урок 31
Разработка фронтендов, часть 1.
Компоненты
Бэкенд работает «за кулисами», обрабатывая данные и пересылая фронтенду невра-
зумительные. с точки зрения человека. J SON-доку менты Фронтенд же блистает
«на сцене», превращая полученные от бэкенда данные в эффектные веб-страницы.
Фронтенд— это по большому счету набор сложных веб-сценариев, которые взаи-
модействуют с бэкендом, обрабатывают и отображают полученные данные.
31.1. Принципы разработки фронтендов
Практически всегда фронтенд является одностраничным.
Одностраничный фронтенд — фронтенд, реализованный в виде единственной
страницы, на которой в различные моменты времени выводятся разные экраны.
Экран — отдельная часть пользовательского интерфейса одностраничного фрон-
тенда, генерируемая программно.
Экран можно рассматривать как аналог отдельной страницы традиционного веб-
приложения Например, фронтенд может содержать экран перечня изображений,
экран выбранного изображения, экран для добавления изображения и др.
Главный экран —экран, выводящийся при переходе по пути / (в «корень» при-
ложения).
Навигация — переход с одного экрана на другой.
Навигация может выполняться как пользователем (щелчками на гиперссылках или
кнопках), гак и самим фронтендом (так, после успешного добавления изображения
фронтенд самостоятельно выполняет перенаправление на экран с перечнем изо-
бражений).
Интерфейс фронтенда часто строится из компонентов.
Компонент — часть интерфейса фронтенда. независимая от любого внешнего
кода и. как правило, пригодная для повторного использования.
Компонент может выводить сколь угодно сложное содержимое (в том числе вклю-
чающее другие компоненты) и включать в себя сколь угодно сложную логику.
В качестве компонента можно реализовать практически всё что угодно: от простой
кнопки до целого экрана.
Компоненты практически всегда реализуются в виде классов. Обычно пишут базо-
вый класс, содержащий базовую логику компонента, а на ею основе создают про-
изводные классы конкретных компонентов.
31.2. Упражнение. Базовый класс компонента
Напишем базовый класс Component, реализующий базовую логику компонента. Он
будет содержать следующие объект ные методы:
♦ конструктор. Должен принимать единственный параметр — простой объект
с настройками компонента. Необязателен к указанию, значение по умолча-
нию — пустой объект.
В принимаемом конструктором простом объекте можно указать следующие на-
стройки:
• baseElement — ссылка на элемент страницы, в котором будет выведен компо-
нент после создания (базовый элемент страницы). Если не указана, в качест-
ве базового элемента выступит секция тела страницы;
• любые другие настройки, поддерживаемые конкретным компонентом.
Конструктор должен присваивать ссылку на базовый элемент, полученную в со-
ставе объекта с настройками, объектному свойству baseElement, простой объект
с остальными настройками— объектному свойству settings, после чего вызы-
вать метод createContent() (описан далее);
♦ createContent () — должен загружать данные с бэкенда, создавать элементы, со-
ставляющие содержимое компонента, выводить их в базовом элементе, привя-
зывать обработчики событий к созданным элементам и др. Результата возвра-
щать не должен.
Этот метод вызывается конструктором. Также может быть вызван изнутри ком-
понента.
Еще класс должен содержать объектные свойства: baseElement, хранящее ссылку на
базовый элемент, и settings, содержащее простой объект со всеми настройками, за
исключением baseElement.
В производных классах, реализующих конкретные компоненты, для реализации
необходимой функциональности будет перекрываться метод createContent ().
Объявление класса поместим в модуль 31 2 js. Сам класс подвергнем экспорту по
умолчанию.
I. Создадим в какой-либо папке локального диска файл 31 2 js и запишем в него
код из листинга 31.1.
Листинг 31.1. Базовый класс компонента Component (31.2.js)
export default class Component {
baseElement
settings
constructor(settings={}) {
const: ' baseE]ement^document.boay, ...others } = settings;
this.baseElement = baseElement;
this.settings = others;
this.creareContent();
}
createContent() {}
}
Проверим класс в работе, создав простой компонент, выводящий приветствие.
2. Найдем в папке 31\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файл 31 2.html (пустая страница) и скопируем ее куда-либо на ло-
кальный диск.
3. Откроем копию файла 31.2 html в текстовом редакторе и вставим в секцию тела
тег <script> такого вида:
<bcdy>
<script type="module">
</scnpt>
</body>
В теге <script> необходимо указать атрибут type со значением module, чтобы
исполняющая среда посчитала код. помещенный в этот тег, модулем JavaScript
(см. урок 14) и позволила использовать в нем выражения импорта.
4. Запишем во вставленном теге следующий код:
import Component from 1./31.2.js’;
class TestComponent extends Component {
createContent() {
thi s.ba seE1ernent.inne rHTML =
'<Ы>Привет, S (this.settings.name} !</hl>' ;
}
}
const testcomponent = new TestComponent({ name: ’читатель' });
Запустим веб-сервер http-server, указав ему в качестве корневой папку, в которой
находятся файлы примера. Откроем веб-обозреватель и выполним переход по ин-
тернет-адресу http://localhost:8080/31.2. html. На странице должно появиться при-
ветствие.
31.3. Упражнение.
Компонент перечня изображений
Имея базовый класс компонента, можем начать программирование фронтенда
фотогалереи. Напишем компонент index, выводящий перечень изображений и пре-
доставляющий пользователю возможность их поиска по заданной подстроке.
Компонент не будет поддерживать никаких дополнительных настроек.
1, Найдем в папке 31\!sources сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 31 3 html (пустая страница) и 31 3.css (таблица стилей
с оформлением). Скопируем их куда-либо на локальный диск.
2. Найдем в папке 31\ех31.2 сопровождающего книгу' файлового архива (см. при-
ложение 5) файл 31 2js (код базового класса компонента) и скопируем его в ту
же папку, что и предыдущие файлы.
Чтобы отправить бэкенду запрос на загрузку данных, нам понадобится интер-
нет-адрес бэкенда. Запишем его в отдельном модуле 31 3.1.js, в константе
baseURL, и подвергнем ее множественному экспорту (на тот случай, если нам по-
надобится объявить в этом модуле еще какие-либо сущности).
3. Создадим в папке с файлами примера файл 31.3.1 .js, откроем его в текстовом ре-
дакторе и запишем в него код из листинга 3 1.2.
Листинг 31.2. Модуль с базовым интернет-адресом бэкенда (31.3.1.js)
const baseURL = 'http://localhost:8000/’;
export { baseURL };
Класс компонента Index объявим в модуле 31 3 2 js и подвергнем экспорту по
умолчанию.
4. Создадим r той же папке файл 31 3.2.js, откроем его в текстовом редакторе и
запишем в него код из листинга 31 3.
Листинг 31.3. Класс компонента Index (31.3.2.js)
import Component f rom './31.2.js’;
import { baseURL } from ’./31.3.1.js';
export default class Index extends Component {
async createContent() {
this.baseEiement.innerHTML = ’';
let url;
if (this.search) {
const params = new URLSearchParams({search: this.search));
url = ’${baseURL’?${params)';
} else
url = baseURL;
const r = await window.fetch(url);
const data = await r.json();
let s = ’<Ь1>Фотогалереч</Ь1>' +
’<form class=”inline">’ +
' <input rype="search" name="search" ' +
’value="${this.search ?? +
’ cinput type="submit" value="Найти">' +
’</form>' +
'<p><a ЬгеГ="#">Добавить изоЬражение</а></р>' +
’<main class="photos">’;
for (let item of data)
s += ’ <article>' +
<a href="#">' +
<h2->$ {item. title}</h2>' +
<img src="${item.href}">' +
’ </a>' +
' </article>';
s += '</main>';
this.baseElement.innerHTML = s;
const searchForm = this.baseElement.queryselector('form');
searchForm.addEventListener('submit', evt => {
evt.preventDefault();
const searchField = evt.target.elements[0J;
this.search = searchField.value.trim() ;
this.createContent();
});
Подстроку для поиска, указанную пользователем, будем хранить в свойстве
search. Это свойство создадим при первом выполнении поиска, в обработчике
события submit веб-формы поиска. Создавать его заранее, непосредственно
в объявлении класса, нет смысла, т. к. метод createContent(), будучи вызванным
из конструктора базового класса, все равно не сможет обратиться к нему (по-
скольку это свойство отсутствует в базовом классе, подробности —
в разд. 11.3.2).
Объявление класса Index содержит лишь код перекрытого метода createContent (),
выполняющего несколько действий. Этот метод сделан асинхронным, поскольку
в нем используются операторы ожидания.
• Во-первых, метод удаляет из базового элемента все имеющееся содержимое.
• Во-вторых, он загружает с бэкенда перечень изображений. При этом, если
свойство search уже существует и в нем хранится непустая подстрока для по-
иска, он отправляет ее бэкенду с GET-параметром search.
• 15-третьих, после загрузки перечня изображений метод выводит в базовом
элементе заголовок, веб-форму поиска, гиперссылку на страницу добавления
изображений и сам перечень изображений. Гиперссылки, присутствующие
в составе выводимого изображения, пока сделаны «пустыми» (без интернет-
адресов), поскольку мы еще не реализовали во фронтенде маршрутизацию
(этим займемся на уроке 32).
• В-четвертых, он привязывает к веб-форме поиска обработчик события submit,
который представляет собой стрелочную функцию, указанную непосредст-
венно в вызове метода addEventListener (). Если бы мы реализовали обработ-
чик события в виде свойства, которому присвоена стрелочная функция (как
посту пали ранее), метод createContent (), вызываемый из конструктора базо-
вого класса, посчитал бы. что это свойство не существует (поскольку это
свойство также отсутствует в базовом классе).
Обработчик события submit веб-формы поиска заносит заданную в поле ввода
подстроку в свойство search (попутно создавая его, если оно еще не существует)
и вызывает метод createContent (), чтобы «обновить» компонент.
Осталось добавить в код страницы 31.3.html сценарий, создающий экземпляр
компонента index, чтобы проверить его в работе.
5. Откроем копию файла 31 3 html в текстовом редакторе и вставим в секцию тела
тег <script> со сценарием:
<script type="module">
import Index from './31.3.2.j s';
const index = new Index(document.body);
</script>
Найдем в папке 30\ex30 4 сопровождающего книгу файлового архива (см. приложе-
ние 5) папку photogahery (папка проекта бэкенда) и скопируем ее куда-либо на ло-
кальный диск. Запустим копию бэкенда на выполнение.
Запустим веб-сервер http-server, указав ему в качестве корневой папку, в которой
находятся файлы фронтенда. Откроем веб-обозреватель и выполним переход по
интернет-адресу http://localhost:8080/31.3.html. Проверим, выводится ли перечень
изображений (см. рис. 29.1). Зададим какую-либо подстроку поиска в веб-форме,
нажмем кнопку Найти и посмотрим, что получится.
31.4. Упражнение. Компонент отдельной позиции
перечня изображений
Наш компонент index, выводящий перечень изображений, слишком сложен. Давай-
те упростим его. перенеся часть кода во вновь созданный компонент item, выводя-
щий отдельную позицию перечня изображений.
Компонент будет поддерживать дополнительную настройку data, посредством
которой передается простой объект со сведениями о выводимой позиции.
I. Найдем в папке 31\ех31 3 сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 31 3 html (страница фронтенда), 31 3 css (таблица стилей),
31 2 js (базовый класс компонента), 31 3 1 .js и 31.3 2 js (код фронтенда). Скопиру-
ем их куда-либо на локальный диск.
Класс компонент item объявим в модуле 31 4 js и подвергнем экспорчу но умол-
чанию.
В перекрытом методе createContent () следует сформировать нужное содержи-
мое и добавить его к содержимому, имеющемуся в базовом элементе.
2. Создадим в папке с файлами примера файл 31.4.js, откроем его в текстовом
редакторе и запишем в него код из листинга 31.4.
Листинг 31.4. Класс компонента item (31_4.js)
import Component from './31.2.js ';
export default class Item extends Component {
createContent() {
const data = this.settings.data;
const s = '<article>' +
* <a href="#">' +
<h2>$(data.title}</h2>' +
<img src="$(data.href}">' +
' </a>’ +
'</article>';
this.baseElement.insertAdjacentHTML('beforeend', sj;
}
}
Для добавления содержимого, описанного HTML-кодом, в элемент страницы
применяем метод insertAdjacentHTML о (см. разд. 18.5).
Осталось исправить компонент index таким образом, чтобы он для вывода пози-
ций перечня применял компонент item.
3. Откроем копию файла 31 3 2js в текстовом редакторе и внесем в нее следующие
исправления:
import Item from './31.4.js';
export default class Index extends Component {
createContent() {
let s = ’<Ь1>Фотогалерея</Ь1>' +
»1 ass ~ "phetos /
' <main class="photos'‘X/main> 1 ;
this baseElement.innerHTML = s;
const photos = this.baseELement querySelector('main.photos');
for (let item of data)
s f-—1—<articlG>’ +
c/articlo^1i
new Item({ baseElement: photos, data: item });
э——---------' /m-л j in-')
this.basoElomonc. innorHTML--s-r
const searchForm = this.baseElement.querySelector('form');
}
}
При создании каждого экземпляра компонента item указываем в настройке
baseElement ссылку на семантическое основное содержимое (тег <main>), сфор-
мированное ранее. В результате все созданные компоненты будут выведены
в этом элементе, сформировав гем самым перечень изображений.
Компонент item занимается исключительно выводом, не добавляя никакой
интерактивности. Поэтому хранить объекты этого компонента в переменных,
массиве или где-либо нет необходимости.
Если не сделали этого ранее, найдем в папке 30\ех30.4 сопровождающею книгу
файлового архива (см. приложение 5) папку photogallery (папка проекта бэкенда),
скопируем ее куда-либо на локальный диск и запустим бэкенд на выполнение.
Запустим веб-сервер http-server, указав ему в качестве корневой папку, в которой
находятся файлы фронтенда. Откроем веб-обозреватель и выполним переход
по интернет-адресу http://locaihost:S080/31.3.html Фронтенд должен выглядеть и
фу нкционировать так же, как и ранее.
Урок 32
Разработка фронтендов, часть 2.
Маршрутизация
К этому момент}' наш фронтенд имеет всего лишь один экран — выводящий пере-
чень изображений. Добавить остальные два экрана: с выбранным изображением и
веб-формой для добавления изображения — не составит груда, достаточно объ-
явить еще два компонента.
Сложнее реализовать навигацию между экранами. Безусловно, можно использовать
для этого гиперссылки. Только куда они должны вести? На другие пути? Например,
так:
<а href--"/add'^Добавить изображение*:/а>
<hl >Фотогалереж/Ы>
са href="/17O1430022944"^Одинокое дерезо</а>
Но при щелчке на такой гиперссылке веб-обозреватель отправит веб-серверу
запрос с путем, записанным в гиперссылке, и. поскольку, скорее всего, никакого
файла по этому пути не окажется, веб-сервер вполне ожидаемо вернет ответ с со-
общением об «ошибке 404».
32.1. Маршрутизация на стороне клиента
Выходом может послужить указание в гиперссылках не путей, а якорей. При пере-
ходе на другой якорь, если остальные составляющие текущего интернет-адреса не
изменяются, то никакие запросы веб-серверу не отсылаются. Переход на другой
якорь можно обработать полностью на стороне клиента.
При переходе на другой якорь в объекте текущего окна веб-обозревателя возника-
ет событие hashchange. Объект этого события создается на основе класса
HashChangeEvent, производного от класса Event. Класс HashChangeEvent поддержива-
ет два свойства, доступные только для чтения:
♦ oldURL— интернет-адрес, с которого был выполнен переход, в виде строки:
♦ newURL — интернет-адрес, на который был выполнен переход, в виде строки.
Кроме того, якорь, на который был выполнен переход, с начальным символом
решетки можно получить из свойства hash объекта класса Location, который пред-
ставляет текущий интернет-адрес и хранится в свойстве location объекта окна
(см. разд. 21 2).
В обработчике события hashchange обычно выполняется .маршрутизация на стороне
клиент — выяснение, какой компонент следует вывести при переходе на якорь
того или иного формата.
Сразу после открытия страницы событие hashchange не возникает, и это необходи-
мо учитывать при программировании.
Свойство hash объекта текущего интернет-адреса доступно не только для чтения,
но и для записи. Если присвоить ему другой якорь, без начального символа решет-
ки или с таковым, будет выполнен переход по этому якорю. Таким образом можно
выполнять перенаправление на стороне клиента.
32.2. Упражнение. Маршрутизация
и компонент выбранного изображения
Добавим во фронтенд, который начали разрабатывать на уроке 31, второй экран,
выводящий выбранное изображение.
Реализуем этот экран в виде компонента Detail. Этот компонент будет принимать
дополнительную настройку id, хранящую идентификатор подлежащего выводу
изображения
Также реализуем во фронтенде маршрутизацию, чтобы при переходе по якорю
формата:
♦ пустая строка — выводился экран с перечнем изображений:
♦ <иденпшфикатор> — выводился экран с изображением, идентификатор кото-
рого был задан в составе пути.
1. Найдем в папке 31\ех31 4 сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 31 3 html (страница фронтенда), 31 3 css (таблица стилей),
31 2js (базовый класс компонента), 31.31.js, 31.32js и 31 4js (код фронтенда).
Скопируем их куда-либо на локальный диск.
Начнем с класса компонента Detail, который объявим во вновь созданном мо-
дуле 32 2 js и подвергнем экспорту но умолчанию.
В перекрытом методе createContent () сформируем содержимое компонента,
включая веб-форму для удаления изображения, и выведем его в базовом эле-
менте.
Событие submit веб-формы удаления изображения будем обрабатывать, как и
ранее, в стрелочной функции, указанной в вызове метода addEventListener о, —
чтобы избежать проблем с доступом к свойствам производного класса из мето-
дов. вызванных из кода базового класса. В обработчике события мы отправим
бэкенду запрос на удаление изображения и в случае успешного удаления произ-
ведем перенаправление на экран перечня изображений.
2. Создадим в папке с файлами примера файл 32.2.js, откроем его в текстовом
редакторе и запишем в него код из листинга 32.1.
Листинг 32.1. Класс компонента Detail (32.2.js)
import Component from *./31.2.js';
import { baseURL } from ’./31.3.1.js';
export default class Detail extends Component (
async createContent() {
this.baseElement.innerHTML =
const url = '${baseURL}S{this.settings.id} ;
const r = await window.fetch(url);
const data = await r.jsonO;
let s = ’<Ь1>Фстогалереж/Ь1>' +
'<h2>$(data.title}</h2>‘ +
’<section class="photo">' +
<imo src="${data.href}">' +
’</section>';
if (data.desc)
s += '<div>${data.desc}</div>';
s += '<form class="inline"> +
’ cinput type="submit" уа1ие="Удалить">’ +
’</form>’;
this.baseElement.innerHTML = s;
const deleteForm = this.baseElement.querySelec.tor ('form') ;
deleteForm.addEventListener (' submit ’, async evt => {
evt.preventDefault();
const r = await window.fetch(url, { method: 'delete' });
if (r.ok)
location.hash = '';
});
}
}
Теперь нужно указать в гиперссылках, присутствующих в составе позиций
перечня изображений, якоря, указывающие на экраны этих изображений. Ис-
правления следует внести в компонент item, формирующий отдельную позицию
перечня изображений.
3. Откроем файл 31 4.js в текстовом редакторе и исправим хранящийся в нем код
следующим образом:
export default class Item extends Component {
createContent() {
const s = '<article>' +
о hrct-"ff"
<а href="#$(data.id}">’ +
'</article>';
Осталось реализовать маршрутизацию. Поместим необходимый веб-сценарий
непосредственно в страницу.
4. Откроем файл 31 3 html в текстовом редакторе, удалим все содержимое присутст-
вующего в HTML-коде тега <script> и впишем в него следующий сценарий:
import Index from './31.3.2.js’;
import Detail from './32.2.js';
const reDetail = /#([0-9]+)$/;
let screen;
function Iouter() {
const rD = reDetail.exec(location.hash);
if (rD)
screen = new Detail({ id: rD[l] });
else
screen = new Index();
}
window.addEventListener(’hashchange’, router);
router () ;
Код, выполняющий маршрутизацию во фронтенде, аналогичен маршрутизатору
из состава традиционного веб-приложения (см.упражнение 29.3) и бэкенда
(см. упражнение 30.2). Этот код мы реализовали в виде обработчика события
hashchanqe окна.
Не забываем, что сразу после открытия фронтенда событие hashchange не возни-
кает. Поэтому в конце кода мы явно вызвали обработчик этого события. Если
этого не сделать, сразу после открытия фронтенда всегда, независимо от присут-
ствующего в интернет-адресе якоря, будет выводиться главный экран
Найдем в папке 30\ех30 4 сопровождающего книгу файлового архива (см. приложе-
ние 5) папку photogallery (папка проекта бэкенда) и скопируем ее куда-либо на
локальный диск. Запустим копию бэкенда на выполнение.
Запустим веб-сервер http-server, указав ему в качестве корневой папку, в которой
находятся файлы фронтенда. Откроем веб-обозреватель и выполним переход по
интернет-адресу http://localhost:8080/31.3.html. Щелкнем на какой-либо из пози-
ций перечня изображений, чтобы вывести страницу с выбранным изображением
(см. рис. 29.2).
32.3. Упражнение. Добавление изображений
Осталось добавить в нашу фотогалерею средства для занесения новых изображе-
ний. Сделаем это.
Экран добавления нового изображения станет выводиться при переходе по якорю
add и будет реализован компонентом Add, не принимающим никаких дополнитель-
ных настроек.
1. Найдем в папке 32\ех32 2 сопровождающего книгу файлового архива (см. при-
ложение 5) файлы 313.html (страница фронтенда), 31 3 css (таблица стилей),
31 2.js (базовый класс компонента), 31 3 1.js. 31 3 2 js. 31 4 js и 32 2 js (код фронтен-
да). Скопируем их куда-либо на локальный диск.
Класс компонента Add объявим во вновь созданном модуле 32 3.js и подвергнем
экспорту по умолчанию.
В перекрытом методе createContent () сформируем веб-форму для добавления
изображения и выведем ее в базовом элементе.
Событие submit веб-формы добавления изображения и здесь будем обрабаты-
вать в стрелочной функции, которую укажем непосредственно в вызове метода
addEventListener().
2. Создадим в папке с файлами примера файл 32 3 js. откроем его в текстовом
редакторе и запишем в него код из листинга 32.2.
Листинг 32.2. Класс компонента Add (32.3.js)
import Component f rom './31.2.j s';
Import { baseHRL } from ’./31.3.1.js';
export default class Aad extends Component (
createContent() {
this.baseElement.innerHTML =
const s = ’<Ы>Фотогалерея</Ы>’ +
*сЬ2>Добавление изо0раженияс/Ь2>' +
’<form>’ +
' ср>Название: cinput name="title" requi redx/p>' +
' ср>Описание: cbrxtextarea name="desc">' +
'</textareax/p>' +
' <р>Изображение: cinput type="file" ' +
’name="photo" accept=".jpg" ' +
’ reqvi redx/p>' +
' cpxinput type="submit" value="flo&aEMTb"x/p>' +
'c/form>';
this.baseElement.innerHTML = s;
const addForm = this.baseElement.queryselector('form');
addForm.addEventListener('submit’, async evt => {
evt.preventDefault();
const fd = new FormData(addForm);
const r = await window.fetch(baseURL,
{ method: 'post', body: fd });
if (r.ok)
location.hash = '';
});
}
}
Исправим интернет-адрес в гиперссылке, ведущей на экран добавления изобра-
жения. Этот экран формирует компонент index.
3. Откроем копию файла 31 3 2 js в текстовом редакторе и внесем такие правки:
export default class Index extends Component {
async createContent() {
let s = '<Ы>Фотогалерея</Ь1>' +
Ьго£-"#">Добавить иоображоиио</а></р>’—*
'<pXa href="#add''>flo6aBHTb изображение</аХ/р>' +
'rmain class="photos"x/main>';
}
}
Осталось исправить код, выполняющий маршрутизацию, таким образом, чтобы
при переходе по якорю add на странице выводился компонент Add.
4. Откроем копию файла 31 3.html в текстовом редакторе, найдем тег <script> и
исправим содержащийся в нем сценарий следующим образом:
import Index from './31.3.2.js';
import Detail from './32.2.js';
import Add from './32.3.js';
function router() {
const rD = reDetail.exec(location.hash);
if (rD)
screen = new Detail({ id: rD[l] });
else if (location.hash = '#add')
screen = new Add();
else
screen = new Index();
}
Если не сделали этого ранее, найдем в папке 30\ех30 4 сопровождающего книгу
файлового архива (ем. приложение 5) папку photogallery (папка проекта бэкенда),
скопируем куда-либо на локальный диск и запустим бэкенд на выполнение.
Запустим веб-сервер http-server, указав ему в качестве корневой папку, в которой
находятся файлы фронтенда. Откроем веб-обозреватель и выполним переход по
интернет-адресу http://localhost:8080/31.3.html. Щелкнем на гиперссылке Доба-
вить изображение, на появившемся экране укажем само добавляемое изображе-
ние, сведения о нем и нажмем кнопку Добавить. Добавим таким же образом еще
одно изображение.
Заключение
На этом книга, посвященная языку программирования JavaScript, закончена. Мы
изучили все наиболее востребованные инструменты самого языка. DOM, ВОМ,
Web API и даже «шапочно» познакомились с исполняющей средой Node.js. Мы за-
помнили множество новых терминов, языковых конструкций, классов, свойств и
методов. Мы написали десятки учебных веб-страниц, в том числе пару вполне
функциональных интер!ют-фотогалерей. И теперь с гордостью можем называть се-
бя веб-програм м истам и.
За рамками рассмотренного в книге осталось не так уж и много:
♦ специфические, редко используемые инструменты класс сведений о веб-обо-
зревателе Navigator, класс истории веб-обозревателя History, средства геолока-
ции, программного создания окон. HIML-компоненты и др.;
♦ инструменты, недавно введенные в стандарт языка, пока существующие в чер-
новых редакциях и поддерживаемые не всеми веб-обозревателями: работа с уст-
ройствами Bluetooth, NFC, режим «картинка-в-картинке» и т. п.;
♦ устаревшие инструменты, оставленные для совместимости и в настоящее время
нерекомендуемые к использованию.
Автор полагает, что если читатель этим заинтересуется, то без особых проблем
сможет найги нужную информацию в Сети. Благо он, после прочтения этой книги,
уже имеет знания, необходимые для знакомства с упомянутыми инструментами.
На языке JavaScript написан ряд библиотек, упрощающих выполнение типичных
задач веб-программирования, наподобие привязки обработчиков к событиям
(jQuery, Prototype, rxjs и многие другие), сложные библиотеки (фреймворки) для
написания фронтендов (Angular, React. Vue. Lit) и бэкендов, рассчитанных на рабо-
ту в Node.js (Express, Meteor и др.).
Существуют даже производные языки, призванные устранить некоторые недостат-
ки JavaScript: TypeScript, CoffeeScript и Dail. Программы, написанные на этих язы-
ках, преобразуются в обычный JavaScript-код, исполняемый веб-обозревателями
или исполняющей средой Node.js.
У JavaScript бурное настоящее и многообещающее будущее. И изучать его, безу-
словно, стоит!
А чтобы изучение проходило эффективно и увлекательно — воспользуйтесь сле-
дующими полезными интернет-ресурсами по теме книги:
♦ Ьирх://(1еуе1орег.то7П1а.огй/ги/<1ос8/\\еЬ/.)а¥а8сп|К — исчерпывающее руко-
водство и справочник по JavaScript, включающий недавно введенные в стандарт
инструменты, пока еще не поддерживаемые всеми веб-обозревателями. Большая
часть материалов написана на английском языке:
♦ https://nodejs.org/en/ — «домашний» сайт Node.js. Дистрибутивы, документация
по Node.js API, руководства по разработке;
♦ https://www.w3schools.com/ — крупный англоязычный сайт, посвященный все-
возможным интернет-технолог иям: HTML, CSS, JavaScript. Node.js и пр. Статьи,
опубликованные на этом ресурсе, отличаются лаконичностью и доходчивостью;
♦ https://learn.javascript.ru/ — хороший русскоязычный ресурс по JavaScript;
♦ https://nodejsdev.ru/— неплохой русскоязычный ресурс по Node.js;
♦ https://vk.com/javascript forum — сообщество JavaScript-программистов в со-
циальной сети «ВКонтакте». Много полезных руководств;
♦ https://vk.com/js_hub — другое сообщество социальной сети «ВКонтакте», по-
священное JavaScript;
♦ https://vk.com/html5js — сообщество «ВКонтакте», посвященное HTML, CSS
и JavaScript;
♦ https://vk.com/nodcjs_lih — сообщество «ВКонтакте» для Node-разработчиков.
На этом автор прощается с вами, уважаемые читатели! До свидания и успехов на
ниве JavaScripi-nporpaMMHpoBaHHa!
В юдимир Дронов
ПРИЛОЖЕНИЯ
С Приложение 1. Приоритет операторов
О Приложение 2. Node js подготовка к работе
О Приложение 3. Веб-сервер http-server: установка и использование
О Приложение 4 Устаревшие программные инструменты JavaScript,
применяющиеся до сих пор
Э Приложение 5 Описание файлового архива
Приложение 1
Приоритет операторов
Приоритет Оператор Описание Пример
17 ( ... ) Круглые скобки (10 4 2) / 3
16 . (точка) Доступ к элементу объекта arr.length
[] arr[1 length']
е 9 Условный доступ к эле- менту объекта obj.arr?.length
[] Доступ к элементу массива arr[1]
0 Вызов функции func(l, 2)
new Создание объекта с указа- нием параметров new Date(2023, 11, 31)
import Импоот import Cis from './m.js';
15 new Создание объекта без ука- зания параметров new Date()
14 ++ Инкремент, поставленный после переменной i++
— Декремент, поставленный после переменной count—
13 -Ы- Инкремент, поставленный перед переменной
— Декремент, поставленный перед переменной —count
I Логическое НЕ ! flag
— Двоичное НЕ -3
+ Унарный плюс +str
- Унарный минус -val
typeof Получение типа typecf s
void Поглощение void(handler())
delete Удаление свойства delete obj.prop
await Ожидание await window.fetch('/')
12 * * Возведение в степень 5 ** 3
(окончание)
Приоритет Оператор Описание Пример
11 * Умножение 4 * 3
/ Деление 10/3
% Остаток от деления 10 % 3
10 + Сложение 1+2
- Вычитание 2-1
9 « Сдвиг влево 3 « 2
» Сдвиг вправо знаковый 12 » 2
»> Сдвиг вправо беззнаковый 12 »> 3
8 < Меньше х < у
<= Меньше или равно X <= у
> Больше X > у
>= Больше или равно X >= у
in Проверка существования свойства 'prop1 in obj
instanceof Принадлежность классу d instanceof Date
7 — Равно x == у
=== Строго равно X = у
I = Не равно x != у
I == Строго не равно x !у
6 & Двоичное И 3 & 2
5 A Дв >ичное исключающее И 3 л 2
4 I Двоичное ИЛИ 3 | 2
3 && Логическое И flagl && flag2
2 11 Логическое ИЛИ flagl II flag2
?? Выбор delimiter ?? ’, '
1 . । n' II л IL * л . „ , „• и 11 n 1 < - 1 V . — V II li- г. . II II II „ -И f oi> | 41 Л л £ Присваивание простое и комбинированное x = у
??= Условное присваивание delimiter ??=
? ... : Условный (flag) ? x : у
return Возврат результата return i
yield Возврат с приостановкой yield i
yield’ Вызов генератора yield’ gen()
Упаковка-распаковка [...arrl, ...arr2]
Приложение 2
Node.js: подготовка к работе
П2.1. Установка Node.js
Перед началом разработки на языке JavaScript веб-приложений, предназначенных
для работы в исполняющей среде Node.js, следует установить эту среду на свой
компьютер.
I. Перейдем на «домашний» сайт Node.js, расположенный по интернет-адресу
htlps://nock* *js.org/en/, — откроется главная страница «домашнего» сайта
(рис. 112.1).
ABOUT LEARN DOWNLOAD DOCS GET INVOLVED CERTIFICATION NEWS
1
Node.js’ is an open source, cross platform JavaScript runtime environment
Download Node.js®
20.9.0 LTS 21.1.0 Current
Recommended For Most users M Latest Features
Other Downloads! Changelog API Docs Other Downloads, Changelog' API Docs
For information about supported releases, see the release schedule.
Рис П2.1. Главная веб-страница «домашнего» веб-сайта Node.js
Для загрузки здесь доступны две редакции дистрибутива Node.js:
• текущая (Current)— реализует все последние нововведения, однако может
содержать программные ошибки.
Обычно используется для программирования и эксплуатации не очень ответ-
ственных решений (в частности, некоммерческих веб-приложений с относи-
тельно невысокой нагрузкой) и экспериментов;
• с увеличенным периодом технической поддержки (LTS)— несколько уста-
ревшая по отношению к текущей, не реализующая последние нововведения,
однако гарантированно не содержащая ошибок.
Рекомендуется к применению при разработке и эксплуатации наиболее от-
вете! венных и высоконагруженных решений (в первую очередь— коммерче-
ских приложений).
Загрузка дистрибутива нужной редакции выполняется щелчком на одной из
больших темно-зеленых кнопок. Щелчок на левой кнопке вызывает загрузку
редакции с увеличенным периодом поддержки, щелчок на правой — текущей
редакции. Номер версии загружаемой редакции написан непосредственно на со-
ответствующей кнопке.
Для учебных целей можно выбрать любую редакцию. Автор в процессе написа-
ния книги использовал текущую (Current).
2. Щелкнем на кнопке, соответствующей выбранной для загрузки редакции.
Веб-приложение, реализующее функциональность «домашнего» сайта Node.js,
самостоятельно определит операционную систему, установленную на компью-
тере, и предоставит редакцию, подходящую для нее. Так. автор книги зашел на
сайт с компьютера, работающего под управлением 64-разрядной редакции
Microsoft Windows, и получил 64-разрядную Windows-редакцию Node.js.
3. По окончании загрузки файла с дистрибутивом —запустим его на выполнение.
Откроется первое окно установщика (рис. П2.2), на котором не показано ничего
интересного. Поэтому сразу...
4. ...нажмем в этом окне кнопку Next (Далее).
Откроется второе окно (рис. П2.3)— с лицензионным соглашением. Можем
прочитать его при желании, после чего...
5. ...установим флажок I accept the terms in the License Agreement (Я принимаю
положения лицензионного соглашения) и нажмем кнопку Next.
Рис. П2.2 Первое окно установщика Node js — сразу пропускаем
? Node.js Setup X
Fnd-User License Agreement n*de
please read die following license agreement carefully
Node.js is licensed for use as follows;
Copyright Node.js contributors. AH rights reserved
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software"), to deal in the Software without restriction, including
without limitation the nghts co use, copy, modify, merge, publish,
distribute, subhcense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
tha fnllnu/inn rnnditinnc’
ai accept the terms In the License Agreement
print Back Next Cancel
______________________________________________________________________________________!
Рис. П2.3. Bi прое окно установщика Node js лицензионное соглашение
Рис. П2.4. Третье окно установщика Node .js задание пути установки
Откроется третье окно (рис. 112.4) — в котором указывается путь установки.
Но умолчанию Node.js устанавливается по пути C:\Program Files (x86)\nodejs
(32-разрядная редакция) или CAProgram Files\nodejs (64-разрядная редакция),
и указывать другой путь следует только в каких-либо особых случаях. Задать
другой путь можно, вручную введя его в поле ввода, имеющемся в окне, или
нажав расположенную ниже кнопку C hange (Изменить) и указав целевую панку
в открывшемся на экране стандартном диалоговом окне выбора папки.
6. В любом случае — нажмем кнопку Next.
Откроется четвертое окно (рис. П2.5)— где выбираются устанавливаемые ком-
поненты исполняющей среды.
Рис П2.5. Четвертое окно установщика Nodejs выбор устанавливаемых компонентов
Слева представлен иерархический список устанавливаемых компонентов. По
умолчанию устанавливаются все наличествующие компоненты, и изменять их
состав следует только в крайне специфических случаях.
7. Нажмем кнопку Next.
Откроется пятое окно (рис. П2.6)— где имеется возможность указать установку-
компилятора.
Некоторые дополнительные библиотеки для Node.js содержат в своем составе
модули, написанные на языке C++, которые после установки для успешной ра-
боты должны быть откомпилированы в машинный код. Для этого требуется
компилятор из состава пакета Microsoft Visual Studio C++ и некоторые дополни-
тельные программы (в частности, исполняющая среда языка Python). По умол-
чанию они не устанавливаются.
Библиотек, содержащих код на C++, существует немного, в этой книге они не
используются и не рассматриваются, поэтому компилятор Visual Studio C++
устанавливать не нужно. Если же планируется задействовать такого рода биб-
лиотеки. следует дать установщику указание инсталлировать компилятор, уста-
новив флажок Automatically install the necessary tools (Автоматически устано-
вить необходимые инструменты).
8. В любом случае — нажмем кнопку Next.
Рис. П2.6. Пятое окни устансьщика Nodejs указание устанчаки компилятора C++
Шестое окно (рис. П2.7) сообщит, что вопросов у установщика больше нет,
и предложит начать собственно установку.
9. Нажмем кнопку Install (Установить) и положительно ответим на предупрежде-
ние подсистемы НАС.
Установка занимает немного времени (у автора— несколько секунд). После
чего появится седьмое, последнее окно (рис. 112.8)— с сообщением об успеш-
ном завершении установки.
10. Нажмем кнопку Finish (Завершить).
Установщик завершит свою работу.
ПОЛЕЗНО ЗНАТЬ
Если требуется загрузить редакцию Node.js, отличную от предлагаемой сайтом, следует
щелкнуть на гиперссылке Other Downloads (Другие загрузки), расположенной под со-
ответствующей темно-зеленой кнопкой (см. рис. П2.1). Откроется странице! загрузок,
на которой можно выбрат ь нужную редакцию дистрибутива.
Рис. П2.7. Шестое окне установщика Node.js все готоводля установки
1 Node.js Setup
Completed the Node.js Setup Wizard
Node.js has been successfully installed.
Click the Finish button to exit the Setup Wizard.
Hack tmish
Cancel
Рис П2 8. Седьмое окно установщика Node js установка завершена1
П2.2. Проверка работоспособности Node.js
11роверить работоспособность установленной Node.js очень просто.
1. Запустим утилиту Terminal (в Windows 1 Г) или командную строку (в предыду-
щих версиях Windows).
Для запуска утилиты Terminal следует открыть меню Пуск, щелкнуть на кнопке
Все приложения и выбрать в появившемся меню пункт Terminal
Для запуска командной строки откройте меню Пуск и выберите в подменю
Служебные— Windows пункт Командная строка.
2. Наберите в утилите Terminal или командной строке команду:
node -v
В ответ будет выведена версия установленной исполняющей среды Node.js. на-
пример.
V21.1.0
Приложение 3
Веб-сервер http-server:
установка и использование
http-server — это компактный веб-сервер, написанный на JavaScript и работающий
под управлением Node.js. Он может применяться как для публикации простых сай-
тов и веб-приложений, так и для их отладки.
На заметку
Здесь приведено сокращенное описание http-server Полное описание можно найти по
интернет-адресу http://tinyurl.com/mwyrc3kt
П3.1. Установка http-server
Для установки http-server следует запустить утилиту Terminal или командную ci ро-
ку (как это сделать, описано в разд. П2.2) и подать в ней команду:
npm install http-server -g
Командный ключ -g предписывает выполнить установку на глобальном уровне —
в папку пользовательского профиля. Благодаря этому установленную таким обра-
зом программу можно будет запуст ить откуда угодно.
П3.2. Запуск веб-сайта с помощью http-server
Для запуска веб-сайта с целью отладки посредством http-server следует открыть
утилиту Terminal или командную строку, перейти в корневую папку отлаживаемого
сайта и набрать команду формата:
http-server [<путь к корневой папке сайта>] [—port|-p <номер ТСР-порта>]
Пример последовательности команд, запускающих отладку сайта, корневая папка
которого находится по пути d:\work\projects\simpiesite:
d:
cd work\projects\simplesite
http-server
По умолчанию сайт, обслуживаемый веб-сервером, доступен через ТСР-порт 8080.
Следовательно, открыть сайт можно, перейдя по интернет-адресу http://
Iocalhost:8080.
Можно не выполнять предварительный переход r корневую папку сайта, а указать
путь к ней непосредственно при запуске http-server Путь к корневой папке может
быть как абсолютным:
http-server d:\work\projects\simplesite
гак и относительным — отсчитываемым от текущей папки:
d:
cd work\projects
http-server simplesite
Веб-сервер http-server поддерживает полезный командный ключ —port (или -р).
задающий номер TCP-порта, через который будет досту пен обслуживаемый сайт:
http-server d:\work\projects\simplesite —port 80
ПЗ.З. Останов http-server
Остановить веб-сервер http-server можно, переключившись в экземпляр утилиты
Terminal или командной строки, в котором он запущен, и нажав комбинацию кла-
виш <Ctrl>+<C>. Также можно просто закрыть этот экземпляр Terminal или ко-
мандной строки.
Приложение 4
Устаревшие программные
инструменты JavaScript,
применяющиеся до сих пор
Язык JavaScript непрерывно совершенствуется, обзаводясь новыми программными
инструментами, более удобными в использовании, нежели старые. Тем не ме-
нее ряд устаревших программных инструментов находит применение до сих пор
(в основном в коде старых разработок).
Это приложение посвящено устаревшим программным инструментам и предназна-
чено читателям, занимающимся сопровождением старого кода.
На заметку
Описанные здесь старые программные инструменты поддерживаются в JavaScript до
сих пор — для совместимости
П4.1. Условное присваивание и выбор
До появления в JavaScript операторов условного присваивания ??= и выбора ?? для
аналогичной цели применялся оператор логического И11И 11.
У оператора | | есть любопытная особенность. Если оба его операнда не являются
логическими величинами, оператор преобразует первый операнд к логическому
типу. Если в результате получится true, оператор 11 вернет изначальное значение
первого операнда, если false — изначальное значение второго операнда.
I Тримеры:
> let langl
> let lang2 = 'JavaScript'
> langl || 'Python'
< 'Python'
> lang2 I| 'Python'
< 'JavaScript'
Для условного присваивания можно использовать оператор комбинированного
присваивания 11=:
> langl ||= 'Pythcn'
> langl
< 'Python'
> lang2 ||= 'Python'
> 1апд2
< 'JavaScript'
П4.2. Необязательные параметры функций
Для объявления функций с необязательными параметрами ранее использовалась
одна из особенностей JavaScript: если при вызове функции указать меньшее коли-
чество параметров, чем присутствует в ее объявлении, то не указанным в вызове
параметрам будет присвоено значение undefined. Далее в теле функции проверя-
лись значения необязательных параметров, и если они были равны undefined, го
таким параметрам присваивались какие-либо значения по умолчанию. Такая про-
верка обычно производилась с помощью оператора логического ИЛИ или соответ-
ствующего оператора комбинированного присваивания (см. разд. 114.1).
Пример функции, принимающей необязательный параметр и написанной в «старом
стиле»:
> function getTag(content, tagName) {
tagName ||= 'div';
return '<' + tagName + + content + '</' + tagName + ’>';
> getTag('Хамачи', 'h2')
< '<Ь2>Хамачи</Ь2>'
> getTag('Цена: 65 руб.')
< '<div>UeHa: 65 py6.</div>'
П4.3. Функции
с произвольным количеством параметров
Оператор упаковки-распаковки ... существенно упрощает написание функций,
принимающих произвольное количество параметров. Однако он появился в JavaScript
не так давно, а ранее для объявления таких функций использовались другие сред-
ства.
При выполнении тела функции исполняющая среда JavaScript создает локальную
переменную arguments. Эта переменная .хранит объект класса Arguments, который
представляет собой коллекцию из всех параметров, переданных текущей функции
при вызове.
Как правило, функция, получающая значения параметров посредством коллекции
из переменной arguments, объявляется как не принимающая параметров вообще —
для простоты.
Пример функции average(), принимающей произвольное количество чисел и вы-
числяющей их среднее арифметическое:
> function average() {
let sum = 0;
for (let i = 0; i < arguments.length; i++)
sum += arguments[i];
return sum / arguments.length;
}
> averaged, 2, 3, 4, 5)
< 3
> average(465, 87, 3, 9045, 82)
< 1936.4
He забываем о том. что коллекция из локальной переменной arguments хранит все
параметры, полученные функцией, и учитываем это при программировании.
Пример функции getTag(), принимающей обязательное имя тега и произвольное
количество строк, объединяющей их и возвращающей строку с готовым HTML-
кодом:
> function getTag() {
const tagName = arguments[0];
let s = '';
for (let i = 1; i < arguments.length; i++) {
if (i > 1)
s += ' ' ;
s += arguments[i];
}
return ’<’ + tagName + ’>’ + s + ’</’ + tagName +
}
> getTag('div', 'Цена', ’65', ’руб')
< '<.div>UeHa 65 py6</div>'
> getTag(1p', 'Заходите', 'в', 'наш', 'суши-бар’, 'почаще!')
< '<р>Заходите в наш суши-бар почаще!</р>'
П4.4. Объявление классов
В настоящее время для объявления классов применяется простой и удобный син-
таксис, использующий ключевое слово class. Ранее объявить свой класс в JavaScript
было сложнее, и код такого объявления не отличался наглядностью.
Далее описывается объявление классов, создание в них свойств, методов, динами-
ческих свойств, объектных и классовых, и наследование классов в «старом стиле».
П4.4,1. Объявление функции-конструктора
Для объявления класса необходимо написагь для него функцию-конструктор. Она
будет выполняться при создании каждого объекта объявляемого класса
Конструктор может принимать произвольное количество параметров, но не должен
возвращать результат. Имя конструктора, представляющего объявляемый класс,
станет именем этого класса.
Ссылка на создаваемый объект хранится в переменной this, создаваемой испол-
няющей средой и доступной в теле конструктора.
В качестве конструктора может быть задействована только именованная или ано-
нимная функция. Стрелочную функцию использовать нельзя, т. к. в ее теле пере-
менная this ссылается не на текущий объект, а на глобальный объект текущею
окна.
Пример функции-конструктора, объявляющей класс Point:
> function Point() {
this.x = 100;
this.у = this.x * 2;
)
Создать объект класса, объявленного таким способом, можно так же, как и объект
любого другого класса:
> consс р = new Point
> р.х
< 100
> Р-У
< 200
П4.4.2. Создание свойств и методов
Создать свойства и методы в классе, объявляемом функцией-конструктором, мож-
но двумя способами. Какой способ выбрать — дело личных предпочтений.
П4.4.2.1. Объявление свойств и методов
в функции-конструкторе
Свойства можно создать непосредственно в конструкторе — просто присвоив им
нужные значения.
Создать метод можно, создав свойство и присвоив ему функцию, реализующую
метод. Допускается использовать лишь именованные и анонимные функции (при-
чина объяснена в разд. IJ4.4 /).
Пример:
> // Объявляем функцию, которая реализует метод getPoint () класса Point
> function Point getPoint() {
return 'Точка [' + this.x + + this.у + ']';
}
> // Объявляем сам класс Point
> function Point(x, у) {
this.x = x;
this.у = у;
this. getPoint = Point._getPoint;
> const р = new Point(100, 300)
> p.x
< 100
> Р-У
< 300
> p.getPoint()
< 'Точка [100, 300J'
П4.4.2.2. Объявление свойств и методов в прототипе
Еще свойства и методы, которые требуется объявить в классе, можно занести в его
прототип.
Прототип — простой объект, чьи свойства и методы копируются в каждый
объект класса, которому принадлежит этот прототип. Копирование свойств и
методов из прототипа в создаваемый объект выполняется перед исполнением
ф> нкции-констр} ктора.
Ссылка на прототип хранится в статическом свойстве prototype владеющего им
класса. Изначально прототип объявляемого класса пуст.
В прототипе имеет смысл создавать свойства, значения которых не изменяются
в конструкторе (в противном случае уместнее создать их в конструкторе).
Пример:
> // Объявляем класс Point
> function Point(х, у) {
thi s.х = х;
this.у = у;
> // Добавляем в его прототип свойство color...
> Point.prototype.color = 'black';
> // ...и метод getPoint ()
> Point.prototype.getPoint = function () {
return 'Точка [' + this.x + ', ' + this.у + ']';
}
> const p = new Point(100, 300)
> // Свойство и метод, созданные в прототипе класса, ничем не отличаются
> // от созданных в его конструкторе
> р.color
< 'black'
> р.color = 'blue'
> р.color
< 'blue'
> p.getPoint()
< 'Точка [100, 300]'
П4.4.3. Создание динамических свойств
Динамическое свойство в заданном прототипе можно создать вызовом статического
метода defineProperty () класса Object в формате:
defineProperty(<прототин>, <ммя свойства>, <о&ъект конфигурации^.
Имя свойства задается в виде строки. Простой о&ъект конфигурации должен иметь
свойства get и (или) set. задающие соответственно геттер и (или) сеттер.
Пример:
> // Объявляем сам класс, содержащий свойство color, в котором будет
> // храниться значение создаваемого динамического свойства color
> function Point(х, у) {
this.x = х;
this.у = у;
this, color = 'black';
> // Объявляем геттер для динамического свойства color
> function Point gecColor() {
return this. color.toUpperCase();
}
> // Объявляем сеттер для динамического свойства color
> function Point secColor(val) {
this, color = val.toLowerCase();
}
> 1/ Создаем само динамическое свойство color
> Object.defineProperty(Point.prototype, 'color', {
get: Point_getColor,
set: Point_.setColor
})
> const p = new Point(30C, 100)
> p.color
< 'BLACK'
> p.color = 'blUE'
> p.color
< 'BLUE'
Если у создаваемого динамического свойства не указать сеттер, свойство будет
доступно только для чтения, если не указать геттер — только для записи.
П4.4.4. Создание статических свойств и методов
Статические свойства и методы можно реализовать в виде добавленных свойств
объекта самой функции-конструктора.
Пример’
> // Объявляем класс
> function Point(х, у) {
this.x = х;
this.у = у;
this.color = 'black';
}
> // Создаем у этого класса два статических свойства...
> Point.blackColor = 'black';
> Point.blueColor = 'blue';
> // ...и статический метод
> Point.getBluePoint = function (x, y) {
const p = new Point(x, y);
p.color = 'blue';
return p;
> const p = Point.getBluePoint(300, 100)
> p.color — Point.blueColor
< true
П4.4.5. Наследование классов
Чтобы создать производный класс на основе указанного базового класса, необхо-
димо последовательно выполнить три действия-
I. Создать копию прототипа базового класса — вызовом статического метода
create () класса Object:
create (<копируемый объекту)
В качестве результата метод возвращает ссылку на копию заданного объекта.
С помощью метода created можно создать копию любого объекта — не только
прототипа.
2. Присвоить ссылку на конструктор базового класса свойству constructor копии
прототипа базового класса, полученной ранее.
3. Присвоить полученную копию прототипа базового класса свойству prototype
производного класса.
Чтобы в коде производного класса в процессе переопределения метода вызвать
принадлежащий базовому классу:
♦ конструктор — следует вызвать непосредственно конструктор базового класса,
использовав метод са11() или apply о (см. разд. 8.8.2) и указав первым парамет-
ром ссылку на текущий объект производного класса:
♦ любой другой метод — следует вызвать этот метод у прототипа базового класса,
который хранится в его свойстве prototype, также использовав метод са11() или
apply о и указав первым параметром ссылку на текущий объект производного
класса.
Пример:
> // Объявляем базовый класс с двумя свойствами и методом
> function Point(х, у) {
this.x = х;
this.у = у;
}
> Point.prototype.getPoint = tunction () {
return 'Точка [’ + this.x + ', ’ + this.у + ’]';
}
> // Создаем копию прототипа базового класса и заносим в нее
> // конструктор базового класса
> const proto = Object.create(Point.prototype)
> proto.constructor = Point
> /l Объявляем производный класс с дополнительным свойством
function ColorPoint (х, у, color) {
//В теле конструктора производного класса вызываем конструктор
// базового класса
Point.call(this, х, у);
this.color = color;
)
> // Указываем созданную ранее копию прототипа базового класса в качестве
> // прототипа производного класса
> ColorPoint.prototype = proto
> // Создаем в производном классе дополнительный метод
> ColorPoint.prototype.getPoint = function () {
//В теле метода производного класса вызываем метод базового класса
return Point.prototype.getPoint.call(this) + ', ' + this.color;
}
> const cp - new ColorPoint(200, 500, 'yellow')
> cp.getPoint()
< 'Точка [200, 500J, yellow'
П4.5. Загрузка данных с веб-сервера
Ранее загрузка данных с веб-сервера производилась с применением технологии
AJAX.
AJAX (Asynchronous JavaScript и XML, асинхронные JavaScript и XML1) — тех-
нология, позволяющая загружать файлы с веб-сервера, на текущий момент
устаревшая.
Реализация AJAX включает несколько последовательно выполняемых этапов, рас-
сматриваемых далее.
XML (extensible Markup Language, расширяемый язык разметки) — я;ык для кодирования произ-
вольных данных. Похож на H TML — XML-данные представляются в виде набора вложенных друг
в друга парных тегов и их атрибутов. Когда-то применялся для кодирования пересылаемых данных,
но в середине 2000-х годов был вытеснен более компактным и простым в обработке форматом JSON.
В этой книге XML не рассматривается.
П4.5.1. Подготовка объекта AJAX
Загрузка данных но технологии AJAX выполняется объектом класса XMLHttpRequest.
Этот объект создается выражением формата:
new XMLHttpRequest()
П4 5.2. Указание интернет-адреса загружаемого файла
Интернет-адрес загружаемого файла (и пара других ггараметров) задается в вызове
метода open() созданного ранее объекта AJAX:
open (< HTTP-методу, <мнтернет-адрес>, ^асинхронная залрузкаТУ)
Название HTTP-метода указывается в виде строки в верхнем регистре (например.
'GET')- интернет-адрес загружаемого файла также задается в виде строки. Третьим
параметром передается логическая величина:
♦ false — будет выполнена синхронная загрузка, при которой исполнение кода
приостановится, пока запрошенный файл не будет загружен;
♦ true— будет выполнена асинхронная загрузка, при которой сразу после отправ-
ки веб-серверу запроса на загрузку исполнение кода продолжится.
Применение синхронной загрузки упрощает программирование, однако страница
на момент выполнения загрузки перестает реагировать на действия пользователя.
При использовании асинхронной загрузки страница не «подвисает», но программи-
рование несколько усложняется. Тем не менее в последнее время чаще всего при-
меняется асинхронная загрузка.
Пример асинхронной загрузки файла:
const ajax = new XMLHttpRequestO;
ajax.open('GET', '/others/html/fragment1.html', true);
Синхронная загрузка 6} дет рассмотрена в разд. 114.5.6.
Метод open () просто задает параметры загрузки, но не запускает ее. Для запуска
загрузки следует вызвать метод send о, речь о котором пойдет в разд. 114.5.4.
П45.3. Получение загруженного файла
При асинхронной загрузке момент получения запрошенного файла предугадать
невозможно. Поэтому о его получении и обработке следует подумать заранее, еще
до отправки запроса на загрузку.
Получение загруженного файла реализуется в обработчике события readystatechange
объекта AJAX, выполняющего загрузку. Это событие возникает при каждом изме-
нении состояния загрузки, поэтому при каждом его возникновении необходимо
проверять значения двух свойств того же объекта:
♦ свойство readystate должно хранить значение 4;
♦ свойство status должно хранить значение 200.
Если оба этих условия выполняются, строковое содержимое загруженного файла
можно извлечь из свойства respcnseText того же объекта AJAX:
ajax.addEventListener('readystatechange', function () {
if (this.readyState == 4 && this.status = 200)
// Выводам содержимое загруженного HTML-файла в элементе output
output.innerHTML = this.responseText;
});
Свойство readystate хранил числовой код состояния объекта AJAX в виде одного
из следующих числовых значений:
♦ 0 — запрос не инициализирован (еще не был вызван метод open ());
♦ 1 — запрос инициализирован, но не отправлен (метод open о был вызван, метод
send О — еще нет);
♦ 2 — получена начальная часть серверного ответа с запрошенным файлом;
♦ 3 — идет загрузка запрошенного файла:
♦ 4 — запрошенный файл полностью загружен.
Свойство status объекта AJAX хранит числовой код статуса ответа, присланного
сервером, а свойство statusText — храпит строку с описанием статуса.
П4.5.4» Отправка веб-серверу запроса
на получение файла
Отправку запроса на загрузку файла производит метод send о объекта AJAX, кото-
рый в этом случае вызывается без параметров:
aj ах.send();
П4.5.5. Отправка данных веб-серверу
в POST-параметрах
Если требуется отправить веб-серверу в составе запроса набор POST-параметров
с какими-либо данными, следует использовать следующий формат вызова метода
sendO объекта AJAX:
send «отправляемые данные>)
Отправляемые данные могут быть указаны в виде объекта класса URLSearchParams
(см. разд. 28.1.2. /) или FormData (см. разд. 28.I 2.2).
В отправляемый ответ будет добавлен заголовок Content-Type со значением соот-
ветственно application/x-www-form-urlencoded ИЛИ multipart/form-data.
Пример:
const frm = document.forms[0];
const fd = new FormData(frnu ;
const aj ax = new XMLHttpRequest();
ajax.addEventListener('readystatechange', function () {
if (this.readyState = 4 && this.status == 201)
// Изображение было успешно добавлено в галерею
});
ajax.open('POST', true);
ajax.send(fd);
П4.5.6. Синхронная загрузка файлов
по технологии AJAX
Если методу ореп() объекта Л.1ЛХ передать третьим параметром значение false,
веб-обозреватель выполнит синхронную загрузку. В этом случае исполнение сце-
нария приостановится на вызове метода send(), пока запрашиваемый файл не будет
получен Содержимое загруженного файла станет доступным сразу же после вызо-
ва метода send (). 11 ри мер:
ajax.open('GET', '/others/html/fragmentl.html', false);
ajax.send();
output.innerHTML = ajax.responseText;
Тем не менее рассмотренный ранее подход — с обработкой события
readystacechanged — будет работа! ь и при синхронной загрузке.
Синхронную загрузку рекомендуется использовать, когда время загрузки файла
гарантированно невелико, а посетители сайта готовы немного подождать.
П4.6. Работа с GET-параметрами
До появления в составе Web ЛР1 класса URLSearchParams, представляющего набор
GET-параметров в удобном для обработки виде, для работы с GET-параметрами
применялись следующие встроенные функции:
♦ encodeURi (<сорока>) — кодирует заданную строку таким образом, чтобы ее мож-
но было вставить в интернет-адрес в качестве значения GET-параметра. и воз-
вращает результат. Символы двоеточия, слеша, вопросительного знака, ампер-
санда и равенства не кодируются. Пример:
> const sourcel = 'язык программирования';
> let encodedl = encodeURi(sourcel)
> encoded]
< '%Dli8F’.bO%B7%Dl s8B!’,DOaBA' 2O.DO''.BF%D1 80%D0< BE\D0nB3 !.D1%8OSD0-.B0 DC’b
%BC%D0%BC%D0%B8%Dl%80 !iD0%BE%D0%B2%D0%B0%D0%BD%D0%B8%Dl%8F'
> const source2 = 'https://bhv.ru/';
> let encoded2 = encodeURi(source2)
> encoded2
< 'https://bhv.ru/'
♦ decodeURi (<строка>} — декодирует заданную строку, ранее закодированную
функцией encodeURi (), и возвращает результат:
> decodeURI(encoded])
< 'язык программирования’
♦ encodeURiComponent (<строка>) — аналогична encodeURK), только дополнительно
кодирует символы двоеточия, слеша, вопросительного знака, амперсанда и ра-
венсз ва:
> encoded2 = encodeURIComponent(source2)
< 'https 3A%2F%2Fbhv.ru%2F'
Эта функция пригодится, если в качестве значения какого-либо GET-параметра
необходимо указать интернет-адрес;
♦ decodeURicomponent (<строка>} — декодирует заданную строку, ранее закодиро-
ванную функцией encodeURTComponent (), и возвращает результат:
> decodeURICcmponent (encoded2)
< 'https://bhv.ru/’
Пример формирования строки GET-параметров:
const getParams = ’?search=* + encodeURI(search) + '&page=10';
Получить декодированное значение GET-параметра, находящегося в заданной
строке и имеющего указанное имя, можно с помощью функции getParamO:
ge t Par am (< строка СЕТ-параметров>, <имя GET-параметра^
Объявление этой функции:
function getParam(params, name) {
if (params[01 == '?')
params = params.slice(1);
const val = params.split('&’)
.find(pair => pair.startsWith(name + ’ = ',)
?.split(’=’)[1];
return val ? decodeURI(val) : val;
Пример использования этой функции:
const s = ' ?search='tDl 8F' DO*B7!^D1 ДЕТ DO ' BASpage-10' ;
const search = getParairus, ’search'); // 'язык'
Приложение 5
Описание файлового архива
Файловый архив, сопровождающий книгу, размещен на сервере издательства
«БХВ» по интернет-адресу: https://zip.bhv.ru/9785977519427.zip. Ссылка на него
доступна и со страницы книги на сайте https://hhv.ru/.
Список папок и файлов, имеющихся в архиве (вложенность папок и файлов показа-
на отступами):
♦ <номер урока> — папка с материалами урока с укатанным номером. Состав папки:
• 'sources — папка с исходными материалами, необходимыми для выполнения
упражнений в текущем уроке;
♦ ех<номер раздела^ — результаты выполнения упражнения, приведенного
в разделе с указанным номером;
• з<номер раздела> — результаты выполнения самостоятельных упражнений,
приведенных в разделе с указанным номером;
♦ readme txt — текстовый файл с описанием электронного архива.
Предметный указатель
А
abort 343
abort() 297
AbortController 297
AbortSignal 297
abs() 107
acos() 107
acosh() 108
activeElement 316
add!) 184. 327,349
addColorStop() 449
addEventListener!) 293
add Path!) 447
after!) 33 1
afterprint 368
AggregateError 398
AJAX 553
alertf) 372
all() 397
allSettledO 397
altKey 302
animationcancel 300
animationend 300
Animation Event 303
animationiteration 300
animationNamc 303
animationstart 300
any!) 398
append! ) 330. 464, 466. 469
appendChild!) 331
apply() 164
arc() 443
arcTo() 443
arguments 547
Arguments 547
Array 167
as 266, 268, 269
asin() 107
asinclterator 405
asinh 108
assign!) 370
async 399, 400. 407
at() 127. 169
atan() 107
atan2() 108
atanh() 108
author 483
avail I leight 371
avail Width 371
await 401,406
В
badlnput358
before!) 330
beforeprint 368
beforeunload 367
beginPath!) 4.39
behavior 323
bezierCurveTo!) 445
Biglnt 115
BiglntQ 116
Biglnt64Array 181
BigL'int64Array 181
bind() 164
Blob 472
blob() 472
block 322
blocksize 381
blur 349. 367
blur!) 348. 367
body 315,462,468, 470
bodyL'scd 472
BOM 366
Boolean 81
Boolean!) 85
borderBoxSize 381
bottom 322
boundingClientRect 377
box .380
break 88. 89, 94
bubbles 309
button 302
byteLength 182
BYTESPER ELEMENT 182
С
cache 462
call() 163
camelCase 78
cancel 363
cancellable 313
canplay 342
canplaythrough 342
CanvasGradient 449
CanvasPattern 452
CanvasRenderingContext2D 432
capture 294. 297
case 88
catch 274
catch() 396
cbrt{) 107
ceil() 107
change 349
characterSet 337
charAt!) 127
charCodeAt() 127
checked 348
checkValidity() 360
childElementCount 318
children 318
class 211,224, 230
classList 326
className 326
clear!) 185, 188,282,427
clearData() 418
clearlntervaK) 385
clearRect() 433
clearTimeout() 385
click 299
clientHeight 320
clientwidth 320
clientX 301
clientY 301
clip() 457
ClipboardEvent 302
close 363
closet) 362, 391
closePath() 441
code 302
colorDepih 371
compactDisplay 103
complete 339
concat() 126, 170
confirm!) 372
console 281
Console 281
const 77
constructor 214, 217. 552
contains!) 318. 327
contentBoxSize 381
contentRect 381
contextmenu 299
continue 94
cookie 429, 430
Cookie 428
copy 300
copy Within!) 175
cos() 107
cosh() 108
created 552
createConicGradiendl) 45 I
createElementd 330
creaieLinearGradientd 4-18
createObjectURLd 415
createPattern!) 452
createRadialGradientd 450
createReadStream!) 493
createServer!) 485
createT extNode() 330
CSSStyleDeclaration 327
Ctrl Key 302
currency 104
currencyDisplay 104
currencySign 104
currentSrc 339, 341
currentTarget 301
currentTime 340
customError 358
cut 349
D
data 391
dataset 320
DataT ransfer 417
data-атрибут 320
Date 134
day 138
day Period 138
dblclick 299
debug!) 281
decodeURK) 556
dccodeL'RIComponent!) 557
default 88,265
defaultChecked 348
defaultMuted 341
defaultPlaybackRate 341
defaultPrevented 313
defaultSelected 348
defaultValue 347
defer 64
definePropertyO 551
delete 197
delete!) 185, 188,465,467,469
deltaMode 302
deltaX 302
deltaY 302
dependencies 500
description 209, 483
devicePixelContentSize 381
Direni 492
disconnect() 376,380
document 315
DOM 315
domain 430
DOMRect 322
DOMStringMap 320
DOMTokenList 326
done 239
drag 417
Drag'n’drop 417
dragend 421
dragenter 420
DragEvent 417
dragleave 420
dragover 419
dragsiart 417
draw Image!) 454
drop 420
dropEffect 420
duration 340
durationchange 342
E
E 106
effectAllowed 419
elapsedTime 302, 303
elements 353
ellipse!) 444
else 86. 87
encode!’Rl() 556
encodel RIComponeni!) 557
end() 485
ended 341, 343
endsWith() 125
entries!) 175, 185. 188.202,210. 465,
467, 469
EPSILON 101
era 138
error 339, 343
Error 273
error!) 281
errors 398
Event 301
eventPhase 309
every!) 172
exec() 256, 258
existsSync() 493
exmpl() 108
exp() 108
expires 429
export 265, 266
ext 492
extends 224. 230
F
fastSeek() 342
fetch!) 461,470
File 413
Filel ist 413
filepath 504
tiles 348.413.423
fill!) 174, 440.441.447
fillRect!) 433
fillStyle 433. 449
fillText!) 435
filter!) 173
finally 274
finally!) 396
find!) 172
findlndexQ 172
findLast!) 172
findLastindexO 172
firstElementChiid 318
flags 260
fiat!) 173
fiatMap!) 173
Float32Array 181
Float64 Array 181
floor!) 107
focus 349. 367
focus() 348. 367
FocusEvent 349
focusin 349
focusout 349
font 435
fontKerning 436
fontStretch 435
fontVariantCaps 435
for 406
forEach!) 173, 184. 187.465.467
form 347
format!) 106. 139
formdata 354
formData 354
Form Data 468
FormDataEvent 354
formidable 500
formidable!) 503
forms .316
fractionalSecondDigits 139
freeze!) 204
from 267-269
from() 176
fromCharCode() 127
function 141
Function 147
function* 245.407
G
get215,217
get() 187.465.467. 469
gclAU()467,469
getBoundingClientRect!) 322
getContextf) 432
getDalat) 420
getDate!) 136
getDay!) 136
getElementByld!) 316
getElementsByClassName() 317
getElementsByName!) 316
getElementsByTag]\ameO 316
getFullYear() 136
getHoursf) 136
getltem() 427
getLineDash!) 442
getMilliseconds() 136
getMinutes!) 136
getMonthf) 136
geiOwnPropertyNaines!) 203, 210
getOwnProperty Symbols!) 210
getSeconds!) 136
getSetCookie!) 472
getTime!) 136
getTimezoneOffset!) 136
getUTCDay!) 136
GET-параметр 466. 556
global 260
globalAlpha 455
globalCompositeOperation 455
groups 257
H
has! ) 184, 187.465, 467. 469
hash 370. 478
hashchange 368, 524
HashChangeEvent 524
haslndices 260
hasOwn() 195, 196, 209
hasOwnProperty!) 195, 196,209
head 315
headers 462,471.490
Headers 464
height 322.371,432
hidden 337
host 370, 478
hostname 370, 478
hour 138
hour 12 138
href 369. 478
HTMLAudioElement 340
HTMLBodj Element 315
HTMLCanvasElement 432
HTMLCollection 316
HTMLDialogElement 362
H TMLDocument 315
HTMLElement 317
H TMLFormEleinent 353
HTMLHeadElement315
HTMLImageElement 339
HTMLProgressElement 352
H TMLVideoElement 340
http-server 544
hypotf) 107
1
if 86, 87
ignoreCase 260
images 315
miageSmoothmgEnabled 454
imageSmoothingQuality 455
import 267-269
in 194, 209
includes!) 124. 171
index 256. 348
indexOf!) 125, 171
indices 257
Infinity 97
-Infinity 97
info!) 281
inlineSize 381
mnerlleight 366
innerHTML 328
innerText 321,329
innerWidth 366
input 256, 349
insertAdjacentElement!) 332
insertAdjacentHTML() 329
insert AdjacentText!) 329
insertBefore() 331
instanceof 201
Ini 16 Array 181
lnl32Array 181
lnt8 Array 181
Intersectionobserver 374
IntersectionObserverEntry 376
intersectionRatio 376
intersectionRect 377
Inti.DateTimeFormat 139
Intl.NumberFormat 106
invalid 349
isArray() 176
isEqualNode!) 323
isExtensibleO 203
isFinite!) 100
isFrozen() 204
islnteger() 100
islntersecting 377
isNaNQ 1оГ
isPoinllnPaih() 448
isPointlnStrokeO 448
isSafelnteger!) 101
isSameNode() 323
isSealed!) 204
iterator 240
J
join!) 174
JSON 474. 475
json() 472
К
kebab-case 320
key 302
Key boardEvent 302
key down 299
keys!) 175, 185, 188. 203,210.465,467,470
key up 299
key word 483
L
language 371
lastElementChild 318
lastindex 258
lastlndexOfi) 125, 171
lastModified 337, 414
left 322, 323
length 124, 169,348,353
let 76
letterspacing 436
license 483
ImeCap 441
lineDashOffset 442
linejoin 442
lineTo!) 440
lineWidth 441
links 316
listen!) 485
LN10 106
LN2 106
load 300. 339, 367
load!) 342
loadeddata 342
loadedmetadata 342
loadstar! 342
localeCompare!) 128
local Storage 426
location 369
Location 369
log!) 108,281
Iogl0() 108
Ioglp() 108
Iog2() 108
LOG2E 106
M
main 483
Map 186
map!) 173
match!) 257, 258
matchAHQ 258
Math 106
max 352
inax{) 107
MAX SAFE INTEGER 101
MAX VALUE 101
max-age 429
maximumFractionDigits 105
maximumSignificantDigits 105
maxLength 357
measureTextf) 435
message 274, 391
MessageEvent 391
method 461, 501
min() 107
MIN SAFE INTEGER 101
MIN VALUE 101
minimumFractionDigits 105
minimuinlntegerDigits 105
minimumSignificantDigits 105
minLength 357
minute 138
miterLimit 442
mode 462
month 138
mousedown 299
inouseenter 299
MouseEvent 301
mouseleave 299
mousemove 299
mouseout 299
mouseover 299
mouseup 299
move I o() 440
multiline 260
muted 340
N
name 274,413,483,492
NaN 97
naturalHeight 339
naturalWidth 339
navigator 371
Navigator 371
networkstate 341
new 191
newURL 524
next() 239
nextElementSibling 318
Node 481
Node.js 481
Node.js API 481
NodeList 316
Node-приложение 482
notation 102
npm 483
null 206
Number 96, 99
Numher() 112
О
Object 190, 229
observed 376. 379
offsetHeight 320
offsetLeft 320
offsetParent 320
offset! op 320
offset Width 320
offsetX 301
offsetY 301
ok 471
oldURL 524
on() 485
once 293
open 362
open() 554
opendir() 492
options 348
origin 370. 478
outerHeight 366
ou ter Width 366
P
padEnd() 127
padStart() 126
pagehide 367
pageshow 367
pageX 301
pageXOffset 367
pageY 301
pageYOffset 367
parentElement 318
parsed 492, 503
parseFloat() 112
parselntt) 112
PascalCase 212
passive 294
paste 349
path 430
Path2D 447
pathname 370, 478
patternMismatch 358
pause 343
paused 342
paused 341
Pl 106
pipe() 493
play 342
play() 342
playbackRate 340
playing 342
pop!) 170
port 370. 478
position 353
postMessaget) 390
POST-параметр 468
pow() 108
prepend() 330
preservesPitch 340
preventDefault() 313
preventExtensions!) 203
previousElementSibling 318
print() 367
progress 342
promise 399
Promise 394
PromiseRejectionEvent 399
prompt!) 372
propertybiame 302
protocol 370. 477
prototype 237, 550
push!) 170
Q
quadraticCurveTo!) 445
querySelector() 317
query SelectorAll() 317
R
race!) 398
random!) 108
RangeError 273
rangeOverflow 358
rangeUnderflow 358
ratechange 343
raw 122
read File() 492
ready State 341, 555
readystatechange 554
reason 397. 399
rect() 446
reduce!) 173
reduceRight!) 174
ReferenceError 273
referrer 337
RegExp 248. 249
reject!) 399
rejectionhandled 399
related farget 302, 349
reload!) 370
remove!) 327. 333,349
removeChild() 334
removeEventListener! ) 297
removeltem() 427
rename!) 504
repeat 302
repeat!) 127
replace!) 126, 259,327,370
replaceAll!) 126. 260
replaceChild!) 333
replaceChildren!) 333
replaceWith!) 332
reportvalidity!) 360
request 485
Request 470
reset 353
reset!) 353
resize 367
ResizeObserver 379
ResizeObserverEntry 380
resolve!) 399
Response 471
responseText 555
REST 507
restore!) 458
return 142
return!) 240
return Value 362
reverse!) 177
revokeObjectURL!) 415
right 322
rm() 504
root 374, 376
rootBounds 377
rootMargin 374,376
rotate!) 454
round!) 107
roundRect!) 446
s
save!) 458
scale!) 453
screen 371
Screen 371
screen Left 366
screen Top 366
screenX 301.367
screenY 301,367
scripts 316. 483. 486
scroll 300
scroll!) 323, 367
scrollBy!) 323, 367
scrollHeight 321
scrolIlntoViewO 322
scroIlLeft 321
scrollTo!) 323, 367
scrolllop 321
scroll Width 321
scrollX 367
scrollY 367
seal!) 203
search 370. 478
search!) 250
searchParams 478
second 139
seeked 343
seeking 341,343
select 349
select!) 348
selected 348
selectedlndex 348
selectedOptions 348
selectionEnd 347
selectionStan 347
self 391
send() 555
sessionStorage 426
set 216, 217
Set 183
set() 187.465.466. 469
setCustomValidity!) 358
sctDaraO 418
setDate() 136
setDraglmage!) 418
setF till Year!) 136
set! leader!) 504
setHours() 136
sellnterval() 385
setltem() 427
setLineDash() 442
setMilliseconds!) 136
setMinutes!) 136
setMonth!) 136
setSeconds!) 136
setTime!) 136
setTimeout!) 385
shadowBlur 434
shadowColor 434
shadowOffsetX 434
shadowOffsetY 434
shift!) 170
shiftKey 302
show!) 362
showModal!) 362
sign!) Ю7
signal 294, 297, 463
signDisplay 103
sin() 107
sinh() 108
size 184, 187.414
slice() 174
some() 172
sort() 176, 468
source 260
splice!) 170
split!) 126,260
sqrt() 107
SQRT1 2 106
SQRT2 106
stalled 343
starts With!) 125
static 217
status 397, 471,555
statusC ode 493
statusText 471,555
stepDown() 348
stepMismatch 358
stepUpO 348
stoplmmediatePropagation() 310
stopPropagation!) 309
Storage 426
String 118, 124
String!) 129
stroke!) 439. 447
strokeRect!) 433
strokeStyle 433. 449
strokeText() 435
structuredClone!) 180,201
style 103,327
submit 353
submit!) 353
SubmitEvent 354
substring!) 125. 126
super 227
suspend 343
switch 88
Symbol 209
Symbol!) 209
Symbol.asynclterator!) 405
Symbol.iterator!) 240, 241
Symbol.toPrimitive() 234
SyntaxError 273
т TransitionEvent 302 iransitionrun 300
tagName 322 takeRecordsf) 377 tan() 107 tanh() 108 target 301.376. 381 terminated 391 lest() 250 text 348 7 ext 330 text()414, 472 textAlign 436 textBaseline 436 textContent 321,328 TextMetrics 435 lextRendering 437 then!) 395 this 80. 162, 199,212,213,217. 294, 295,549 threshold 375, 376 throw 276 throw!) 240 lime 377 timeupdate 342 timeZone 138 timeZoneName 139 title 337 toDateStrmg!) 137 toExponential() 100 toFixedl) 100 toggled 327 toISOStringd 137 toLocaleDateStringO 137 loLocaleLowerCased 124 toLocaleString!) 102, 137, 175 toLocaleTimeStringO 137 toLocaleUpperCaseQ 124 toLowerCase() 124 tooLong 358 tooShon 358 top 322, 323 toPrecision() 100 toPriinitive 234 loReversed!) 177 toSortedO 177 toSplicedd 171 toString!) 99, 137, 235. 467 toTimeStringd 1.37 toUpperCased 124 toUTCString() 137 transitioncaneel 300 transitionend 300 transitionsiart 300 translate!) 453 trim() 124 trimEnd() 124 trim Start() 124 truncd 107 try 274 type 301.. 348. 390. 413.483 ТуреЕггог 273 typeMismatch 358 typeof 70. 78 u Uintl6Array 181 Uint32Array 181 Uint8Array 181 Uint8ClampedArray 181, 182 undefined 71 unhandledrejection 399 unicode 260 unit 104 unit Display 104 unobserved 376. 380 unshift!) 170 uploadDir 503 UR [Error 274 url 471,490 URL 337. 477 URLSearchParams 466.468 useGrouping 103 V valid 358 validationMessage 361 validity .358 VahdiiyState 358 value 239, 347, 353. 397 valueAsDate 347 valueAsN umber 347 valueMissing 358 valueOft j 234 values!) 175, 185, 188,203.210,465,467,470 var 77 version 483 videoHeight 341 video Width 341 visibility change 337 visibilityState 337
void 299 volume 340 volumechange 342 Worker 390 WorkerGIobalScope 391 write() 329 writeFile() 504
w writelnQ 330
wailing 343 warn() 281 Web API 413 weekday 138 wheel 299, 302 WheelEvem 302 width 322, 371,432, 435 X x 322,446 XMLHttpRequest 554
winvalidate 361 window 79.366 Window 366 with() 169 wordSpacing 436 у 322.446 year 138 yield 245 yield* 246
A В
Атрибут 474 Атрибут тега 0 draggable 417 0 height 432 0 max 357 0 inaxLength 357 0 min 357 0 minLength 357 0 pattern 357 0 required 357 0 src 62 0 step 357 0 type 264 0 width 432 Валидация 356 Вариант 252 Веб-приложение 0 двухзвенное 506 0 клиентское 54 0 однозвенное 481 0 традиционное 481 Веб-служба 506 Веб-сценарий 45. 62 0 внешний 62 0 внутренний 62 Возбуждение исключения 273 Возврат результата 28 Возобновление 286 Временная отметка 38 Вызов функции 36
Б Выражение 23, 59 0 блочное 67
Битовая маска 110 Битовое поле 110 Блок 67 0 статической инициализации 218 Большое целое число 115 Ьженд 506 0 вложенное 61 0 выбора 88 0 пу стое61 0 сложное 33 0 управляющее 33 0 условное 34, 86 0 условное множественное 87 Выход из функции 286
И
Генератор 245 0 асинхронный 407 0 класс 247 0 мет од 246 0 объект 247 0 функция 245 Генерирование исключения 273 Геттер 215 Г радиент 448 0 конусный 451 0 линейный 448 0 радиальный 450 0 угловой 451 Графическая закраска 452 Графический контекст 432 Группа 254 Импорт 264 0 гибридный 269 0 множественный 268 0 по умолчанию 267 Индекс элемента 42 Инкремент 99 Инспектор DOM 288 Интернет-ресурс 507 Интерпретатор 23 Исключение 273 0 необработанное 276 Исполняющая среда 23 Источник событ ия 301 Итератор 239 0 асинхронный 405 Итерация цикла 90
0 именованная 255 0 нумерованная 254 К
д Канва 432 Квантификатор 252
Двоичное 0 И 108 0 ИЛИ 109 0 исключающее ИЛИ 109 0 НЕ 108 0 отрицание 108 0 сложение 109 0 умножение 108 Декремент 99 Деструктурирование 0 массива 177 0 объекта 204 Детектор 0 видимости 374 0 изменения размеров 379 Диалог 362 0 модатьный 362 0 немодальный 362 Документ 474 Кеш 462 Кешированная копия 462 Кешированный файл 462 Класс 38 0 анонимный 230 0 базовый 190 0 встроенный 41 0 итерируемый 241 0 производный 190 Клиентский запрос 461 0 кросс-доменный 462 0 межсайтовый 462 0 межхостовый 462 0 межхостовый простой 512 0 межхостовый расширенный 513 0 проверочный 513 Ключ 185 Ключевая точка 448 Ключевое слово 30 Код 45
3 0 символа 118 0 статуса 471
Заголовок 0 клиентского запроса 462 0 серверного ответа 471 Закомментирование 66 Замыкание 156 0 однократного вызова 158 Значащее представление объекта 234 Коллекция 315 Комментарий 50, 65 Компиляция 64 Компонент 516 Конкатенация 123 Консоль 23 0 веб-обозревагеля 23
Константа 32, 77
Конструктор 214
Контекст выполнения 53. 80. 162
Конт роллер 487
Конфликт имен 77
Копирование
0 глубокое 180
0 неглубокое 180
Кривая Безье 444
0 квадратическая 444
0 кубическая 444
Л
Модуль 263
0 внешний 264
0 внутренний 264
0 встроенный 484
0 стартовый 264
н
Навигация 516
Hai ру зка 394
Наследование 190
Нулевая ссылка 206
Лайтбокс 334
Логическая величина 30, 81
Логическое
0 И 83
О ИЛИ 83, 546
<> НЕ 83
О отрицание 83
О сложение 83. 546
0 умножение 83
Локаль 102
м
Манифест проекта 482
Маршрутизатор 487
Маршрутизация 487, 525
Маска 457
Массив 42
О вложенный 168
0 пустой 166
0 типизированный 181
Машинный код 65
Метасимвол 250
Метод 37
0 асинхронный 400
0 волшебный 234
0 закрыт ый 219
0 итерируемый 241
0 классовый 40
0 магический 234
0 общедоступный 219
0 объектный 40, 213
0 открытый 219
0 статический 40
О частный 210
Множество 182
Модификатор 248
О
Область видимости 75, 373
0 глобальная 76, 142
0 уровня блока 76
0 уровня функции 142, 147
Обработка исключения 274
Обработчик
0 исключения 274
0 исключения умолчанию 276
0 события 52
0 события по умолчанию 312
Обратная ссылка 255
Обращение к переменной 32, 78
Объект 37
0 вложенный 192
0 глобальный 47. 79
0 замороженный 204
0 запечатанный 203
0 итерируемый 241
С нерасширяемый 203
0 простой 41. 197
0 пустой 199
0 текущий 38
Объектный адрес 415
Объявление
0 класса 211,548
0 переменной 31, 75
0 функции 36, 141.206
Окружность
0 конечная 450
0 начатьная 450
Операнд 28
Оператор 28
0 арифметический 98
0 бинарный 68
0 возврата результата 142
О возврата с приостановкой 245
О выбора 74, 546
О вызова генератора 246
О вызова функции 140
0 двоичный I08
О доступа к элементу объекта 192
О досту на к элементу последовательности
I22
О комбинированного присваивания 73
0 конкатенации строк 123
О логический 83
0 объявления переменных 75, 76
0 ожидания 401
О поглощения 299
О получения типа 70, 78
О прерывания 89, 94
° итерации 94
О принадлежности классу 201
0 присваивания 73
О проверки существования свойства 194,
209
О сдвига 109
0 создания объекта 191
0 сравнения 81
0 тернарный 68
О удаления свойства 197
О унарный 68
О упаковки-распаковки 146, 179,206,547
0 условного доступа 193
0 условною присваивания 74, 546
0 условный 87
Описание статуса 471
Отладка 279
Отладчик 282
Отложенная загрузка 374
Ошибка
О времени выполнения 280
0 времени компиляции 279
п
Папка проекта 482
Параметр функции 36
0 необязательный 145. 547
Перекрытие
0 метода 225
0 свойства 225
Переменная 31, 76
О блочная 76
0 глобальная 76, 77
0 локальная 142, 145
Перенаправление 499
Переопределение метода 226
Перетаскивание 417
0 источник 417
0 приемник 417
Перо 440
Поддокумент 474
Подквантификатор 254
Подкласс 190
Под набор 251
Подстрока 40
Поиск
0 без учета регистра 249
0 глобальный 257
0 жадный режим 254
0 многострочный 259
0 обычный 256
0 щедрый режим 254
Полифилл 237
11оследовагельность 44
0 выполнения операторов 29
Потеря точности 113
Поток 390, 493
0 основной 390
0 фоновый 390
Пошаговое выполнение 285
Предел 375
Преобразование 453
Преобразование типа 30
0 неявное 30
0 явное 30
Прерыватель 297
Прерывающий сигнал 297
Привязка внешнего веб-сценария 62
Приорит ет операторов 29
Присваивание 31.72
0 комбинированное 73
0 простое 72
0 условное 74
Прогон 271
Ilpoi рамма 45
Про1 раммный код 45
0 асинхронный 386
0 параллельный 391
0 синхронный 385
Проект 482
Промис 394
Пространство имен 236
Прототип 550
Проход цикла 90
Р
Размер массива 42
Раскомментирование 66
Распространение события 308
Регулярное выражение 248
Реестр проекта 500
Рекурсия 160
О бесконечная 161
С войство 37
О динамическое 215
О добавленное 196
О закрытое 219
0 классовое 40
6 общедоступное 219
О объектное 40, 212
0 открытое 219
0 статическое 40
0 частное 219
Сдвиг
0 влево 109
0 вправо беззнаковый 109
О вправо знаковый 109
Серверный ответ 471
О пустой 471
Сеттер 215
Символ
0 пробельный 59
0 специальный 119
Слайдер 303
Словарь 185
Событие 51
О всплывающее 308
0 невсплывающее 309
Состояние промиса
О ожидания 394
0 отклоненное 394
О подтвержденное 394
Состояние холста 458
Спойлер 149
Стек вызовов 280
Строгий режим 66
Строка 29, 118
0 помеченная 120
0 пустая 120
0 шаблонная 120
Суперкласс 190
Сценарий 45
0 проекта 486
Счет чик цикла 90
Таймер 385
0 многократный 385
0 однократный 385
0 периодический 385
Тег
0 canvas 432
0 script 62. 64, 264
Тело
0 функции 36
6 цикла 35
Тип данных 28, 70
0 большое целое число 115
0 значащий 71
0 логический 30. 81
0 неопределенный 71
0 объектный 191
0 примитивный 71
0 ссылочный 72
0 строковый 29. 1 18
0 функциональный 147. 229
0 числовой 28. 96
0 элементарный 71
0 эмблем 209
Точка останова 283
Трассировка 285
У
У гол начальный 451
Унарный минус 99
Унарный плюс 111
Условие 30
0 истинное 30
0 ложное 30
ф
Фабрика
0 классов 231
0 объектов 201
0 функции 157
Фаза события 308
0 всплытия 308
0 источника 303,308
0 погружения 308
Фотогалерея 334
Фреймворк 531
Фронтенд 506
О одностраничный 516
Функция 36
О анонимная 153
О асинхронная 399
0 вложенная 147
0 встроенная 37
0 глобальная 142
О именованная 153
О итерируемая 240
О нехраняшая 153
О однократного вызова 155
О пометка 120
0 рекурсивная 160
О стрелочная 154,400
О хранящая 156
X
Холст 432
Хранилище 426
О локальное 426
О постоянное 426
0 сессионное 426
ц
Цикл 35
О бесконечный 94
0 перебора последовательности 93
О перебора последовательности с
ожиданием 406
О по свойствам объекта 93. 210
0 с постусловием 92
0 с предусловием 92
0 со счетчиком 90
ч
Числа Фибоначчи 242
Число
0 вещественное 96
0 с плавающей точкой 96
ш
Шаг
0 с заходом 286
0 с обходом 286
э
Экран 516
0 главный 516
Экспорт 263
0 гибридный 267
0 множественный 265
0 по умолчанию 265
Элемент
6 объекта 190
0 массива 42
пустой 166
0 страницы базовый 517
Эмблема 208
Я
Якорь 49