/
Теги: информационные технологии вычислительная техника обработка данных программирование
ISBN: 978-5-507-49267-1
Текст
В. В. ЯНЦЕВ
JAVASCRIPT.
КРЕАТИВНОЕ
ПРОГРАММИРОВАНИЕ
УЧЕБНОЕ ПОСОБИЕ
Издание второе, стереотипное
•САНКТ-ПЕТЕРБУРГ•МОСКВА•КРАСНОДАР•
2024
УДК 004
ББК 32.973.4я73
Я 65
Янцев В. В. JavaScript. Креативное программирование : учебное пособие
для вузов / В. В. Янцев. — 2-е изд., стер. — Санкт-Петербург : Лань,
2024. — 232 с. : ил. — Текст : непосредственный.
ISBN 978-5-507-49267-1
Можно смело утверждать: JavaScript — это язык программирования, который
предоставляет разработчику богатый набор неожиданных, неординарных и нетривиальных приемов создания кода. Знаете ли вы, что в условном операторе в качестве
самого условия можно использовать вызов функции? Что один и тот же цикл не составит труда направить в любую сторону — как на увеличение значения, так и на
уменьшение? Что в роли аргумента функции способна выступать другая функция?
Если вы начинающий программист, то с уверенностью можно сказать, что о подобных фокусах вы ни разу не слышали. Между тем, перечисленные методы — лишь
некоторые из многочисленных способов нестандартного программирования. Этим
способам и посвящена данная книга. На ее страницах рассказывается о самых разных приемах креативной разработки на JavaScript. Кроме того, в помощь начинающему есть полезная информация по оптимизации и тестированию сценариев, а также по исправлению логических ошибок. Продемонстрирован ряд готовых программ,
использующих нестандартный подход при создании кода.
Рекомендовано в качестве дополнительной литературы для студентов вузов,
обучающихся по направлению «Информатика и вычислительная техника».
К книге прилагаются дополнительные материалы, доступные в электронной
библиотечной системе «Лань» по ссылке или QR-коду, указанным ниже.
УДК 004
ББК 32.973.4я73
Обложка
П. И. ПОЛЯКОВА
© Издательство «Лань», 2024
© В. В. Янцев, 2024
© Издательство «Лань», художественное
оформление, 2024
1. ВВЕДЕНИЕ
Книг о программировании на JavaScript много. Я бы даже сказал, очень
много. У желающих изучить этот язык большой выбор: можно почитать отечественного автора, а можно — иностранного. Можно выбрать издание, где акцент делается исключительно на теории, а можно найти книгу, где показано
освоение языка на практических примерах. Каждая из этих книг по-своему хороша, интересна и полезна. И хотя создается впечатление, что недостатка в информации о JavaScript нет, автор решил написать еще одну книгу. Возникают
резонные вопросы: почему и зачем? Какая ниша данной тематики оказалась незаполненной и как в нее вписывается новое учебное пособие? Постараюсь ответить на эти вопросы.
1.1. О чем эта книга
Сразу скажу, что полноценным учебным пособием данную книгу всетаки назвать нельзя. Ибо она не охватывает все базовые возможности
JavaScript. Скорее, это дополнение к любому другому изданию, где излагаются
основы языка. Что же тогда предстоит узнать читателю из новой книги?
Можно смело утверждать: JavaScript — это язык программирования, который предоставляет разработчику богатый набор неожиданных, неординарных
и нетривиальных приемов создания кода. Именно об этих нестандартных приемах написания кода я и хочу рассказать.
Знаете ли вы, что в условном операторе в качестве самого условия можно
использовать вызов функции? Что один и тот же цикл не составит труда направить в любую сторону — как на увеличение значения, так и на уменьшение?
Что в роли аргумента функции способна выступать другая функция? Что в простом таймере setTimeout() иногда записывают весьма сложные инструкции?
Что обработчики можно регистрировать в цикле, который проходит по списку
элементов? Если вы начинающий программист, то с уверенностью можно сказать, что о подобных фокусах вы ни разу не слышали. Между тем, упомянутые
методы — лишь некоторые из многочисленных способов нестандартного программирования. Этим способам и посвящена данная книга. На ее страницах
рассказывается о самых разных приемах креативной разработки на JavaScript.
Кроме того, в помощь начинающему есть полезная информация по оптимизации и тестированию сценариев, а также по исправлению логических ошибок.
И, конечно, продемонстрирован ряд законченных программ, использующих нестандартный подход при создании кода.
В разных поисковых системах очень популярен запрос «скачать JavaScript». Многие из интересующихся программированием даже не подозревают,
что ничего скачивать не надо — JavaScript уже есть на вашем компьютере.
Обработчик кода этого языка изначально встроен в ваш браузер, какой бы
«марки» он у вас не был. Поэтому разработка сценариев на JavaScript — один
из самых интересных и творческих процессов в web-программировании. В этом
вы неоднократно убедитесь на страницах данной книги.
—3—
1.2. Особенности изложения материала
В книге пятнадцать глав.
Первая — «Введение». Вы сейчас читаете ее.
Во второй мы познакомимся со способами размещения сценариев. На эту
тему у программистов большой разброс мнений, и многие даже опытные разработчики допускают серьезные отклонения от стандартов, принятых Консорциумом Всемирной Паутины — World Wide Web Consortium (W3C). Внесем в
этот вопрос полную ясность.
Глава 3 рассказывает о том, что такое комментарии, зачем они нужны и
как их можно размещать в коде программ. В том числе рассмотрим необычные
способы добавления комментариев.
Из главы 4 вы узнаете всевозможную информацию о переменных, а также о логических и арифметических операторах, которые позволяют менять значения переменных.
Глава 5 посвящена массивам — как простым, так и многомерным. На
конкретных примерах сценариев мы увидим, как можно получить простую и
сложную анимации с помощью массивов.
В шестой мы подробно разберем разнообразные функции, включая до сих
пор мало распространенные стрелочные, а также выполним обещание, данное
выше: покажем, как аргументом может выступать массив или другая функция.
В седьмой рассмотрим проверку условий. Помните, автор упоминал, что
в скобках условия можно вызвать функцию? Посмотрим, как это сделать.
Тема восьмой главы — циклы. Здесь из разряда нестандартных будут
примеры реверсивного запуска цикла и вызова функции в условии цикла.
В главе 9 вспомним о различных таймерах и увидим, как вместо имени
функции в них можно помещать простой и даже сложный код.
Строкам и манипуляциям с ними посвящена десятая глава. Узнаем, какие
методы существуют для обработки строк, что такое интерполяция и шаблонные
литералы.
Глава 11 содержит подробный рассказ о способах регистрации обработчиков. Покажем, как можно временно остановить обработчик при наступлении
того или иного события, как отменить привязку обработчика и вновь зарегистрировать его или перезапустить. Интересен будет раздел, рассказывающий о
передаче аргументов в обработчик при его регистрации.
Двенадцатая глава посвящена управлению элементами HTML-страницы:
их созданию, настройке атрибутов и свойств, удалению атрибутов и свойств,
«ликвидации» элементов.
Глава 13 рассказывает о том, как при создании кода можно легко обойтись без различных библиотек. Проиллюстрируем такой подход набором программ, которые написаны на «чистом» JavaScript и создают весьма интересные
визуальные эффекты при работе с изображениями. Все примеры содержат подробные объяснения, чтобы даже начинающий программист смог разобраться в
работе сценариев.
—4—
Много полезной информации в главе 14. Вы узнаете, как оптимизировать
код и обнаружить в нем синтаксические ошибки, как проверить работоспособность вашего сценария и добиться задуманной логики его выполнения.
И в самой короткой главе 15 подведем итоги нашей работы.
Книгу сопровождает zip-архив со всеми рассмотренными программами.
Его можно скачать по адресу https://testjs.ru/JS.zip.
Работать с архивом очень просто. Если описание какой-то программы
начинается с указания «Файл», значит ее страница находится в той или иной
папке архива. Понять, где найти программу, легко. Вот пример:
Файл chapter2/script2.4.1.html
Здесь до слеша указано название папки, где размещается необходимый
вам файл. Цифра в имени папки соответствует номеру главы, к которой эта
папка относится. Дальше идет имя файла — script2.4.1.html. Первая цифра в
его имени — номер главы, вторая — номер раздела, третья — порядковый номер программы, под которым она описана в разделе.
Глядя на пример выше, понятно, что перед нами сценарий из второй главы, четвертого раздела. В данном разделе этот сценарий разбирается первым.
Надо посмотреть код? Откройте страницу или сценарий в каком-либо из
специализированных редакторов. Хотите посмотреть сценарий в действии?
Просто запустите ту или иную страницу в браузере.
На что еще хотелось бы обратить внимание читателя: автор использовал
терминологию по языку JavaScript, представленную на страницах такого авторитетного сайта, как https://developer.mozilla.org/ (сайт некоммерческой организации Mozilla Foundation; сайт представляет подробную информацию для
разработчиков).
1.3. Оформление сценариев
Сценарии имеют различное типографское оформление в соответствии с
их размещением в тексте.
Если код в книге выделен в отдельный блок, то он оформлен моноширинным шрифтом, например, так:
for(let i=1; i<10; i++)
{
let b="i"+i;
if(e.id!=b)
{
let p=document.getElementById(b).style;
p.transition="opacity 1s";
p.opacity=0;
}
}
Если фрагменты сценария внедрены непосредственно в текст, то в этом
случае части кода выделены полужирным шрифтом, например, так:
если условие e.id!=b истинно, то
—5—
Обратите внимание: в некоторых блоках программ сделан перенос
части кода на вторую и даже на третью или четвертую строку (из-за недостатка ширины страницы в книге). В реальном сценарии код записывается одной строкой. Запомните правило: все переносы строк кода существуют только в их типографском воспроизведении. Если вы в дальнейшем
столкнетесь с подобной ситуацией, учитывайте данный аспект.
Еще один момент. Обычно таблицы стилей какой-либо страницы принято
записывать следующим образом (пример):
.im {
position: absolute;
width: 300px;
border: 1px solid #000000;
cursor: pointer;
z-index: 1;
}
Однако подобный код занимает в книге слишком много места. Поэтому
автор применил сокращенную форму записи стилей (например, так):
.im {position: absolute; width: 300px;
border: 1px solid #000000;
cursor: pointer; z-index: 1;}
Как видите, экономия налицо: в этом случае вместо семи строк на запись потрачено только три.
В описании программ нередко имеет смысл не приводить весь код, который относится к разбираемому фрагменту, а показывать только ту его часть,
что важна в контексте данных объяснений. Поэтому в таких случаях автор вводит сокращение в виде трех точек:
...
Например, сокращенный вариант для предыдущего примера с настройками стилей может выглядеть так:
.im {position: absolute;...}
Тем самым мы показываем, что на данном этапе описания нам важно обратить
внимание исключительно на позиционирование элемента.
И последнее. Все принципиально важные фрагменты текста или кода в
отдельных блоках, на которые надо обратить особое внимание, выделены полужирным шрифтом.
—6—
2. РАЗМЕЩЕНИЕ СЦЕНАРИЕВ
В этой главе мы познакомимся со способами размещения сценариев. На
данную тему у программистов большой разброс мнений, и многие даже опытные разработчики допускают серьезные отклонения от стандартов, принятых
Консорциумом Всемирной Паутины — World Wide Web Consortium (W3C).
Хотя JavaScript предоставляет разработчику множество возможностей,
а браузеры — еще больше, в среде программистов существует одно негласное
правило: код должен быть отделен от разметки. Подчеркну: правило не является обязательным, но его соблюдение считается хорошим тоном. Вот только
каждый понимает его по-разному.
Поэтому внесем полную ясность в вопрос оптимального размещения сценариев и покажем, как нельзя, как можно и как лучше всего.
2.1. Ошибочный метод
Сейчас я покажу вам, как поступать нельзя, и объясню, почему. И сразу
отмечу, что тем не менее многие разработчики поступают именно так, и это
сходит им с рук. А виной всему браузеры. В них все очень тщательно продумано и предусмотрено. В результате, даже получив некачественный код, браузеры
понимают, чего хотел добиться программист, и демонстрируют правильную
web-страницу.
Итак, в чем же состоит ошибка, о которой я хотел рассказать? В том, что
некоторые специалисты буквально понимают тезис о разделении разметки и
кода, а в результате рождаются вот такие программы, где сценарии расположены после закрывающего тэга </html>:
Файл chapter2/script2.1.1.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
</head>
<body>
<p><input type="button" id="but" value="CLICK"></p>
</body>
</html>
<script>
let i=1;
document.getElementById("but").
addEventListener("click", function()
{
if(i<16)
{
let pict='<p><img
src="../chapter13/img1/im'+i+
—7—
'.jpg" id="im1" alt="Горный пейзаж"></p>';
document.body.
insertAdjacentHTML("beforeend", pict);
i++;
}
});
</script>
А что здесь такого, спросит неискушенный читатель? Вроде все правильно: разметка отдельно, код отдельно. И тем не менее так делать нельзя.
Другой вариант. Теперь тот же самый сценарий находится в отдельном
файле, а его подключение выполняется после печати всего документа.
Файл chapter2/script2.1.2.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
</head>
<body>
<p><input type="button" id="but" value="CLICK"></p>
</body>
</html>
<script src="script2.1&2.js"></script>
И так тоже делать нельзя.
При этом, если запустить в браузере первую и вторую страницу, то обе
окажутся вполне работоспособными, что хорошо видно на рисунке 2.1.1.
Рис. 2.1.1
Страница из серии «так делать нельзя» тем не менее успешно работает
В чем же проблемы обоих примеров?
Консорциум Всемирной Паутины (W3C)
разрабатывает стандарты
HTML и CSS. Так вот, согласно этим стандартам размещение сценария или его
—8—
вызов разрешен только в заголовочной части документа (внутри тегов
<head>...</head>) или в теле документа (внутри тегов <body>...</body>).
Сейчас мы сильно забежим вперед, так как разговор о стандартах Консорциума Всемирной Паутины нам предстоит гораздо позже. Следующие действия лучше было бы разбирать в главе 14, но ничего не поделаешь, рассуждения надо подкрепить фактами.
Итак, Консорциум Всемирной Паутины для проверки разметки страниц
предоставляет специальный валидатор. Вот его адрес в сети: https://validator.
w3.org/.
Давайте откроем страницу с примером script2.1.1.html в каком-нибудь
специализированном редакторе и скопируем ее код. Теперь запустим упомянутый валидатор, кликнем вкладку “Validate by Direct Input” и поместим в текстовое поле скопированный код. Нажмем кнопку “Check” и посмотрим на результат
тестирования нашей страницы. Он показан на рисунке 2.1.2. Как видите, размещение сценария за пределами документа объявлено фатальной ошибкой.
Проверим вторую страницу. Валидатор покажет тот же результат. Вызов
сценария за пределами документа также объявлен фатальной ошибкой.
Рис. 2.1.2
Валидатор сообщает, что выбранный способ размещения сценария — ошибочный
Можно уже подвести итог наших экспериментов в данном разделе. Некоторые разработчики размещают сценарии или их вызовы вне описания документа. И хотя браузеры «проглотят» такой код и «не поперхнутся», это будет
—9—
неправильный подход, так как он явно нарушает стандарты web-программирования.
В завершение раздела добавим «кстати». Вы обратили внимание, что в
листинге есть несколько случаев переноса кода на следующие строки:
document.getElementById("but").
addEventListener("click", function()
или
let pict='<p><img
src="../chapter13/img1/im'+i+
'.jpg" id="im1" alt="Горный пейзаж"></p>';
Это как раз тот момент, что мы обсуждали во «Введении». Все переносы кода
существуют только в их типографском воспроизведении. В реальных программах данные инструкции записываются одной строкой.
2.2. Сценарий в теле документа
Рассмотрение вариантов правильного размещения начнем со случая, когда тот же самый сценарий расположен в теле документа. В такой ситуации
сценарий наиболее разумно поместить непосредственно перед закрывающим
тегом </body>, иначе код потеряется среди разметки.
Вот один из примеров:
Файл chapter2/script2.2.1.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
</head>
<body>
<p><input type="button" id="but" value="CLICK"></p>
<script>
let i=1;
document.getElementById("but").
addEventListener("click", function()
{
if(i<16)
{
let pict='<p><img
src="../chapter13/img1/im'+i+
'.jpg" id="im1" alt="Горный пейзаж"></p>';
document.body.
insertAdjacentHTML("beforeend", pict);
i++;
}
});
</script>
</body>
</html>
— 10 —
В следующем листинге сценарий располагается во внешнем файле, а его
вызов происходит перед завершением печати страницы:
Файл chapter2/script2.2.2.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
</head>
<body>
<p><input type="button" id="but" value="CLICK"></p>
<script src="script2.1&2.js"></script>
</body>
</html>
Рис. 2.2.1
Страница прошла успешную проверку в валидаторе
Для контроля «законности» наших действий проверим в валидаторе первый сценарий (второй можете проверить самостоятельно). Как видите, проверка не дает ошибок (рис. 2.2.1). Мы получили сообщение “No errors or warnings...”. Значит, данный подход верный.
Хотя мы убедились, что размещение кода в теле документа разрешено стандартами, все-таки рассмотренные нами варианты тоже нельзя считать совсем
удачными. Дело в том, что все равно сценарий или его вызов смешаны с разметкой, пусть и не очень сильно. Поэтому имеет смысл рассмотреть еще два примера.
Но прежде чем мы это сделаем, будет одна важная оговорка. Все сценарии имеют разное количество кода. Очень громоздкие программы могут сильно
замедлять загрузку страницы. Поэтому в таких случаях лучше всего располагать большой сценарий во внешнем файле, а вызывать его перед завершением
— 11 —
печати страницы. Два варианта, которые мы рассмотрим дальше, больше подходят для небольших скриптов.
2.3. Сценарий в заголовочной части
Мы постепенно приближаемся к наилучшему варианту, хотя рассмотренный ниже пример еще не является идеальным.
Чтобы отделить код от разметки страницы, можно поместить сценарий в
заголовочную часть документа:
Файл chapter2/script2.3.1.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
<script>
addEventListener("load", function()
{
let i=1;
document.getElementById("but").
addEventListener("click", function()
{
if(i<16)
{
let pict='<p><img
src="../chapter13/img1/im'+i+
'.jpg" id="im1" alt="Горный пейзаж"></p>';
document.body.
insertAdjacentHTML("beforeend", pict);
i++;
}
});
});
</script>
</head>
<body>
<p><input type="button" id="but" value="CLICK"></p>
</body>
</html>
Обратите внимание, что в коде есть два фрагмента, выделенные серым
фоном. Они появились не случайно. Объясним.
Как читает страницу браузер? Файл разбирается сверху вниз. В результате блок кода
document.getElementById("but").
addEventListener("click", function()
{
...
});
— 12 —
просто не будет работать. Почему? Дело в том, что регистрация обработчика
document.getElementById("but").
addEventListener("click", function()
происходит до того, как построена объектная модель документа. Получается,
что данные о кнопке с id="but" еще отсутствуют в анализаторе браузера, а мы
уже пытаемся привязать к ней событие щелчка. Естественно, что этот процесс
закончится неудачно и сценарий просто не будет работать. Поэтому мы добавляем в программу условие, при котором обработчик регистрируется только после загрузки всего документа:
addEventListener("load", function()
{
...
});
Вот теперь страница будет функционировать как положено.
Метод размещения сценария, показанный в этом разделе, относится к
разряду правильных. Из экономии места автор не стал показывать картинку с
отчетом валидатора. Вы можете сами проверить страницу script2.3.1. Адрес валидатора вы уже знаете, методику проверки — тоже.
И все же этот метод не самый лучший. На взгляд автора, идеален вариант,
который мы рассмотрим в следующем разделе.
2.4. Самое правильное место
Предложенный ниже вариант, по мнению автора, является самым оптимальным. Здесь мы располагаем сценарий в отдельном файле, а его вызов помещаем в заголовочную часть документа, тем самым максимально отделяя код
от разметки страницы. Вот так:
Файл chapter2/script2.4.1.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
<script src="script2.4.js"></script>
</head>
<body>
<p><input type="button" id="but" value="CLICK"></p>
</body>
</html>
Сценарий покажем отдельно:
Файл chapter2/script2.4.js
addEventListener("load", function()
— 13 —
{
let i=1;
document.getElementById("but").
addEventListener("click", function()
{
if(i<16)
{
let pict='<p><img
src="../chapter13/img1/im'+i+
'.jpg" id="im1" alt="Горный пейзаж"></p>';
document.body.
insertAdjacentHTML("beforeend", pict);
i++;
}
});
});
Обратите внимание: в файле с кодом JavaScript теги <script>...</script>
отсутствуют. Во внешних скриптах они не нужны.
Вообще в хорошем, качественном проекте, будь он индивидуальным или
корпоративным, стили и сценарии размещаются в отдельных файлах, а подключаются они в заголовочной части документов внутри тегов <head>...</head>.
Еще на что хочется обратить внимание: раз подключение файла с кодом
выполняется в заголовочной части, то есть до построения объектной модели
документа, то во внешнем сценарии, как и в сценарии из предыдущего раздела,
все обработчики регистрируются после загрузки страницы:
addEventListener("load", function()
{
...
document.getElementById("but").
addEventListener("click", function()
{
...
});
});
Вообще, имейте в виду на будущее: во всех сценариях, размещенных или
вызывающихся в заголовочной части документа, регистрацию обработчиков
необходимо производить после окончательной загрузки элементов страницы.
Вот так:
addEventListener("load", function()
{
Регистрация обработчика 1
Регистрация обработчика 2
...
Регистрация обработчика N
...
});
Когда разумнее всего размещать сценарии в заголовочной части документа, а когда — в отдельном файле? На этот вопрос есть резонный ответ. Если
ваш сценарий очень простой и короткий, можно записать его в заголовочной
части. Если сценарий большой — размещайте весь код во внешнем файле.
— 14 —
У сценария в теле документа есть только одно (и не самое важное) преимущество: не надо записывать обработчик загрузки страницы.
Будем ли мы в дальнейшем следовать приведенным выше рекомендациям
по размещению сценариев? Нет. И на это есть объективные причины. Примеры
из остальных глав книги у нас построены следующим образом.
1. В полноценных программах сценарии размещаются в заголовочной части документа, как бы велики они не были (впрочем, чрезмерно многострочные
сценарии у нас просто-напросто отсутствуют). Такой подход выбран из тех соображений, что все компоненты страницы удобнее изучать в одном файле, чем
бесконечно переключаться между документом, таблицей стилей и сценариями.
2. В коротких скриптах, демонстрирующих отдельные простейшие синтаксические нюансы, HTML-разметка вообще отсутствует. В таких случаях
весь документ — это теги <script>...</script> с кодом внутри. Тем самым мы
максимально упрощаем ряд примеров.
2.5. Взаимодействие сценариев
Нередко бывают ситуации, когда часть сценария размещается непосредственно в документе, а другая — в одном или нескольких внешних файлах.
Обеспечить взаимодействие таких частей очень просто. Важно, чтобы был доступ из одного сценария к переменным и функциям другого.
Разберем такой пример. У нас есть страница с фотографией и две кнопки
для просмотра еще двух снимков:
Файл chapter2/script2.5.1.html
<p><input type="button" id="but1" value="1">
<input type="button" id="but2" value="2"><br><br>
<img id="ins" src="../chapter13/img1/im3.jpg"
alt="..."></p>
При нажатии кнопки вместо исходного загружается изображение с номером,
указанным на кнопке. Этим процессом управляет сценарий, распределенный по
трем файлам:
<!DOCTYPE html>
...
<script src="script2.5.1_1.js"></script>
<script src="script2.5.1_2.js"></script>
<script>
addEventListener("load", function()
{
document.getElementById("but1").
addEventListener("click", func1);
document.getElementById("but2").
addEventListener("click", func2);
});
</script>
...
</html>
— 15 —
Непосредственно в документе сценарий выполняет только регистрацию
обработчиков нажатия кнопок:
document.getElementById("but1").
addEventListener("click", func1);
document.getElementById("but2").
addEventListener("click", func2);
Сами функции описаны в двух остальных файлах:
<script src="script2.5.1_1.js"></script>
<script src="script2.5.1_2.js"></script>
«Начинка» файлов очень похожа:
Файл chapter2/script2.5.1_1.js
function func1()
{
document.getElementById("ins").src=
"../chapter13/img1/im1.jpg";
}
Файл chapter2/script2.5.1_2.js
function func2()
{
document.getElementById("ins").src=
"../chapter13/img1/im2.jpg";
}
В процессе загрузки страницы функции из внешних файлов помещаются в
память компьютера и становятся доступны при вызове из основного документа.
Другая ситуация — когда документ работает только с внешними файлами. Между ними тоже можно наладить «взаимодействие».
Внесем изменения в предыдущий пример, распределив весь код по двум
файлам — script2.5.2_1.js и script2.5.2_2.js. Теперь наша страница содержит
только HTML-разметку:
Файл chapter2/script2.5.2.html
<!DOCTYPE html>
...
<script src="script2.5.2_1.js"></script>
<script src="script2.5.2_2.js"></script>
...
<p><input type="button" id="but1" value="1">
<input type="button" id="but2" value="2"><br><br>
<img id="ins" src="../chapter13/img1/im3.jpg"
alt="..."></p>
...
</html>
Как и в предыдущем примере, внешние сценарии имеют много общего:
Файл chapter2/script2.5.2_1.js
addEventListener("load", function()
— 16 —
{
document.getElementById("but2").
addEventListener("click", func2);
});
function func1()
{
document.getElementById("ins").src=
"../chapter13/img1/im1.jpg";
}
Файл chapter2/script2.5.2_2.js
addEventListener("load", function()
{
document.getElementById("but1").
addEventListener("click", func1);
});
function func2()
{
document.getElementById("ins").src=
"../chapter13/img1/im2.jpg";
}
Обратите внимание на такую особенность: в файле script2.5.2_1.js в качестве обработчика регистрируется функция func2, описание которой находится в
файле script2.5.2_2.js. И наоборот, в файле script2.5.2_2.js в качестве обработчика регистрируется функция func1, описание которой находится в файле
script2.5.2_1.js. Запустите файл chapter2/script2.5.2.html в браузере. Вы увидите, что и при таком распределении кода сценарии работают вполне успешно
(рис. 2.5.1).
Рис. 2.5.1
Страница с двумя внешними сценариями. Загружено фото номер 2
— 17 —
3. КОММЕНТАРИИ ОБЫЧНЫЕ И НЕ ОЧЕНЬ
Как вы понимаете, комментарии размещают в коде программы, чтобы пояснить ее отдельные фрагменты. Также комментарии могут использоваться в
качестве различных временных заметок, необходимых в процессе написания
сценария. Такие заметки по завершении разработки обычно удаляют.
Пояснение фрагментов кода целесообразно по двум причинам.
1. По прошествии времени разработчику иногда трудно вспомнить, что
делают те или иные части сценария. Наличие комментариев упрощает этот
процесс.
2. Если вы работаете в IT-компании, необходимо, чтобы ваш код понимали
не только вы, но и другой программист, которому, вполне возможно, придется
использовать готовый сценарий в качестве составной части какого-либо проекта.
Особенность комментариев в том, что они нужны только специалистам,
читающим сценарии в редакторе. Это происходит во время набора программы,
ее изменения или соединения с другими частями той или иной разработки. Любой браузер, выполняя инструкции сценария, не воспринимает комментарии
как часть кода, а просто игнорирует их.
3.1. Традиционные комментарии
В языке JavaScript существует два способа размещать комментарии непосредственно в теле сценария.
Однострочный комментарий выделяется двумя наклонными чертами —
слешами:
// Это комментарий
Например:
function func()
{
// Объявляем переменную - счетчик кликов на кнопке
let i=0;
...
}
Многострочный обрамляется слешем со звездочкой в его начале и звездочкой со слешем в конце:
/* Это комментарий,
который состоит
из нескольких строк */
Например:
function func(t)
{
let k=0;
/* Запускаем цикл из 4 проходов и проверяем
переданное в функцию значение. Если оно
меньше текущего значения i,
увеличиваем счетчик k на единицу */
— 18 —
for(let i=0; i<5; i++)
{
if(t<i)
{
k++;
}
}
...
}
Несколько простых советов по использованию комментариев.
1. Не загромождайте код комментариями к тем частям, назначение которых понятно без лишних слов. Пример:
// Регистрируем обработчик щелчка на изображении
document.getElementById("im").
addEventListener("click", func);
Здесь комментарий явно лишний — любой программист, увидев данную
строку, сразу поймет ее назначение.
2. Если код достаточно сложный, можно дать комментарии не только к
отдельным фрагментам, но и ко всей программе в целом, поместив соответствующие разъяснения в самом начале сценария.
3. Старайтесь вставлять комментарии к блокам программы, а не к каждой
ее строке. Пример неудачного расположения комментариев:
// Объявляем функцию func
function func(t)
{
// Регистрируем счетчик кликов
let k=0;
// Запускаем цикл из 4 проходов
for(let i=0; i<5; i++)
{
// Проверяем переданное в функцию значение
if(t<i)
{
// Если переданное значение меньше i,
увеличиваем счетчик на единицу
k++;
}
}
}
В этом фрагменте комментируется каждое действие, в результате чего строки
кода «теряются» среди строк пояснений. А это, в свою очередь, усложняет восприятие программы. Лучше сгруппировать все пояснения в многострочный
комментарий и расположить его перед объявлением функции:
/* Объявляем функцию func и регистрируем
счетчик кликов k.Запускаем цикл из 4 проходов
и проверяем переданное в функцию значение.
Если оно меньше текущего значения i,
увеличиваем счетчик k на единицу */
function func(t)
— 19 —
{
let k=0;
for(let i=0; i<5; i++)
{
if(t<i)
{
k++;
}
}
}
При таком «построении» и тело функции «как на ладони», и сами комментарии
вполне понятны.
4. Другой вариант — добавлять комментарии в конце строк кода:
function func(t)
{
let k=0;
for(let i=0; i<5; i++)
{
if(t<i)
{
k++;
}
}
}
// Объявляем функцию func
// Регистрируем счетчик
// Запускаем цикл
// Проверяем значение
// Если значение меньше i,
// увеличиваем счетчик
Получилось очень удобно: и функция выглядит понятно, и комментарии
привязаны к нужным строкам.
5. Если вы создаете многострочный комментарий, отделяйте его от блока
кода пустой строкой. Так легче с первого взгляда определить границу между
комментарием и текстом программы.
6. Вставляйте комментарий сразу по мере написания кода, не оставляя это
дело «на потом». Пройдет немного времени и может оказаться, что вам трудно
восстановить собственный ход рассуждений во время разработки того или иного фрагмента программы. А в результате — лишняя трата сил и времени на то,
чтобы вспомнить заложенные в скрипте идеи.
Необходимо сказать еще об одном важном моменте, когда символы комментариев могут оказаться полезными — это временное отключение части уже
написанного кода от его выполнения.
Немного забегая вперед, продемонстрируем часть сценария, который мы
будем разбирать при обсуждении многомерных массивов. Вот этот фрагмент:
let q=document.querySelectorAll(".tim");
for(let n=0; n<q.length; n++)
{
q[n].style.height=d[1][i]+"px";
q[n].style.borderRadius=d[2][i]+"px";
}
Он формирует достаточно сложную анимацию. Предположим, что, настраивая
анимационный эффект, мы хотим временно отключить одну из инструкций,
чтобы было удобнее проследить, как выполняются остальные. Сделать это
очень просто — добавим символ комментария перед строкой, которую необходимо «исключить»:
— 20 —
let q=document.querySelectorAll(".tim");
for(let n=0; n<q.length; n++)
{
q[n].style.height=d[1][i]+"px";
// q[n].style.borderRadius=d[2][i]+"px";
}
Теперь в показанном фрагменте будет выполняться только первая инструкция.
Понятно, что при необходимости отключить несколько строк кода надо
«обрамлять» их символами многострочного комментария:
function race()
{
let t="";
for(let a=0; a<=d[0][i]; a++)
t+="<div class='tim'></div>";
document.getElementById("loa").innerHTML=t;
/*
let q=document.querySelectorAll(".tim");
for(let n=0; n<q.length; n++)
{
q[n].style.height=d[1][i]+"px";
q[n].style.borderRadius=d[2][i]+"px";
}
*/
i++;
if(i==d[0].length)
i=0;
setTimeout(race, 200);
}
3.2. И такое возможно
Информация данного раздела лишь в редких случаях может быть использована на практике. Тем не менее автор все-таки решил продемонстрировать
ряд нестандартных примеров, чтобы показать, какая необычайная гибкость заложена в JavaScript.
Выше мы видели пример расположения комментариев в одних строках с
кодом, где сначала шли инструкции, а потом их описание:
function func(t)
{
let k=0;
for(let i=0; i<5; i++)
{
if(t<i)
{
k++;
}
}
}
// Объявляем функцию func
// Регистрируем счетчик
// Запускаем цикл
// Проверяем значение
// Если значение меньше i,
// увеличиваем счетчик
— 21 —
Так вот, JavaScript позволяет поменять местами код и комментарии:
/* Объявляем функцию func */
/* Регистрируем счетчик */
/* Запускаем цикл */
/* Проверяем значение */
/* Если значение меньше i, */
/* увеличиваем счетчик */
function func(t)
{
let k=0;
for(let i=0; i<5; i++)
{
if(t<i)
{
k++;
}
}
}
Как видите, разница только в том, что мы «окружили» каждое пояснение символами многострочного комментария /* */, чтобы дальнейшая часть строки
воспринималась браузером как часть кода.
Ну и еще несколько совсем уже экзотических примеров.
Вот так можно присвоить значение переменной:
let i= /* Объявляем счетчик проходов */ 0;
И еще вот так:
let i= // Объявляем счетчик проходов
0;
Комментарии можно вставлять в инструкции и операции. Например, приведенный ниже код будет отлично работать:
let a=1;
let b=2;
let c=a /* Получаем сумму начальных переменных */ + b;
И этот обработчик будет запущен корректно:
document.getElementById("but").
/* Регистрируем обработчик */
addEventListener("click", function()
{
...
});
А это примеры размещения комментариев в условиях цикла
for (let a=0; /* Запускаем цикл */ a<=d[0][i]; a++)
t+="<div class='tim'></div>";
и в списке аргументов функции:
function func(/* Список аргументов */ x, y, z)
{
...
}
Для полной достоверности продемонстрированных в данном разделе креативных методов размещения комментариев покажем конкретный пример сценария, где эти комментарии расположились самым причудливым образом:
Файл chapter3/script3.2.1.html
— 22 —
<!DOCTYPE html>
<html lang="ru">
<head>
...
<script>
addEventListener("load", function()
{
let i // Комментарий
=1;
document.getElementById("but" /* Комментарий */).
addEventListener("click", function()
{
if(i< /* Комментарий */ 16)
{
let pict='<p><img src="../chapter13/img1/im'+
i /* Комментарий */ +'.jpg" id="im1"
alt="Горный пейзаж"></p>';
document.body. /* Комментарий */
insertAdjacentHTML("beforeend", pict);
i++;
}
});
});
</script>
</head>
<body>
<p><input type="button" id="but" value="CLICK"></p>
</body>
</html>
Вы уже, наверное, поняли: вариантов размещения комментариев очень
много. Конечно, перечисленные в этом разделе способы, как уже было сказано,
весьма экзотические и вряд ли пригодятся вам в работе, разве что в каких-то
совсем неординарных случаях.
— 23 —
4. ПЕРЕМЕННЫЕ И ИХ КЛАССИФИКАЦИИ
В предыдущих разделах в некоторых примерах мы уже пользовались переменными. Поговорим о них подробнее.
Как вы, наверное, помните из книг по синтаксису JavaScript, переменная —
это участок памяти, предназначенный для хранения каких-либо значений или
данных. Программа к такой переменной обращается по ее имени, которое может
состоять из латинских букв, цифр, символов подчеркивания и знаков $. При этом
существует правило, по которому имя переменной не должно начинаться с цифры и содержать пробелы. Некоторые программисты, привыкшие работать с PHP
или Perl, ставят знак $ в начале каждой переменной. В этом случае надо быть
внимательным, чтобы не перепутать код на «чистом» JavaScript с кодом, работающим под управлением библиотеки jQuery, которая также использует знак доллара. Также обратите внимание, что знак $ еще используется при интерполяции
выражений (${...}), о которой мы поговорим в главе 10.
Переменную уместно сравнить с контейнером. Он может быть пустой, в
него можно положить один или несколько предметов, потом добавить еще какие-то вещи, затем часть содержимого удалить. Точно такие же манипуляции
производят и с переменными.
4.1. Правильные имена переменных
В этом очень коротком разделе приведем несколько вариантов правильных имен переменных.
let
let
let
let
let
let
let
let
let
let
let
let
myname
MYNAME
MYname
myName
my_name
$myname
$MY_name
_my_name_
my$name
my_$_name
myname1
my55name
Думаю, примеров достаточно — алгоритм назначения имен переменных
читателю понятен.
Обратите внимание, что JavaScript чувствителен к регистру букв.
MYNAME и myname — это разные переменные. При этом автор советует не
назначать разным переменным одинаковые имена, отличающиеся только регистром. Так ведь можно и запутаться в программе.
4.2. Типы данных в переменных
Особенность переменных в том, что хранимая в них информация может
неоднократно меняться, причем не только значения, но и тип данных. Напри— 24 —
мер, вы можете создать переменную и сохранить в ней на начальной стадии
число, а затем поместить в переменную текстовую строку. Или наоборот.
Перед использованием в сценарии переменную необходимо объявить.
Например, так:
let i=1;
или так:
let i="Текст";
или так:
let i;
Здесь let — это оператор объявления переменных. О таких операторах мы поговорим в следующем разделе.
В первых двух случаях мы сразу присваиваем переменной текущее значение, а в последнем случае это значение будет присвоено позже на каком-то из
этапов выполнения программы.
JavaScript позволяет объявить сразу несколько переменных, например,
так:
let i, t, b;
Так:
let i, t=0, b;
И даже вот так:
let a=b=c=5;
В последнем случае каждая переменная получит значение 5.
Поменять тип данных, сохраненных в переменной на предыдущем этапе,
очень просто:
let i=1;
i="Строка";
или
let i="Строка";
i=1;
Если вам необходимо преобразовать числовое значение из переменной в
строку, то сделать это можно, «сложив» число из переменной с пустой или заполненной строкой. Хотя бы вот так:
let i=1;
let b=i+" января - Новый год!";
Для преобразования строк в числа применяйте специальные встроенные
функции языка JavaScript — parseFloat
let i="3.14";
let t=parseFloat(i);
или parseInt:
let i="5";
let t=parseInt(i);
— 25 —
Если необходимо определить тип переменной, воспользуйтесь оператором typeof, например, таким образом:
Файл chapter4/script4.2.1.html
<script>
let a="Строка";
let b=3;
let c=true;
let d=[1, 2, 3, 4, 5];
alert("Тип переменной a
alert("Тип переменной b
alert("Тип переменной c
alert("Тип переменной d
</script>
-
"+typeof
"+typeof
"+typeof
"+typeof
a);
b);
c);
d);
Здесь мы последовательно узнаем тип данных, хранящихся в четырех переменных.
4.3. Переменные: var, let
Давайте теперь обсудим еще одну тему: операторы объявления переменных. В предыдущих разделах вы видели, что все переменные в наших сценариях
объявлялись оператором let. Однако во многих программах, написанных несколько лет назад, а также во многих даже современных книгах по программированию на JavaScript вы можете встретить операторы объявления переменных
var.
Дело в том, что долгие годы у разработчиков существовал только один
способ объявить переменную — оператором var. В 2015 г. к этому оператору
добавился еще один — let, что породило некоторый разброс в написании программ.
Так все-таки var или let? Как же правильно объявлять переменную? Заметить разницу между этими объявлениями в простых программах чаще всего невозможно. И в одном, и в другом случае сценарии, приведенные во всех главах
этой книги, будут работать одинаково, каким бы способом мы не объявляли переменные.
Но в чем же принципиальное отличие? Вот что на эту тему сообщает
Mozilla Foundation на сайте https://developer.mozilla.org/: «Директива let объявляет переменную с блочной областью видимости с возможностью инициализировать ее значением. Оператор var объявляет переменную, инициализируя ее
при необходимости». И еще: «Когда let используется внутри блока, то область
видимости переменной ограничивается этим блоком. Напомним, что отличие
заключается в том, что областью видимости переменных, объявленных директивой var, является вся функция, в которой они были объявлены».
Разница все еще не очень ясна? Тогда вот вам несколько сценариев, которые наглядно покажут различия.
Посмотрите два html-файла:
— 26 —
Файл chapter4/script4.3.1.html
<script>
var i=1;
if(true)
{
var i=2;
}
alert(i);
</script>
Файл chapter4/script4.3.2.html
<script>
let i=1;
if(true)
{
let i=2;
}
alert(i);
</script>
По очереди запустите эти сценарии в браузере. Что вы видите? В первом
случае в диалоговом окне появится цифра 2, а во втором — цифра 1. А все потому, что у переменной, объявленной оператором var, область видимости вся
функция. Поэтому, объявив такую же переменную с другим значением в условии, мы по сути просто изменили значение переменной, но не создали новую.
Во втором листинге вторая переменная let i=2 объявлена внутри блока if и ее
значение — 2 — видно только внутри этого блока.
Еще два примера разных способов объявления переменных.
Файл chapter4/script4.3.3.html
<script>
var i=1;
alert(i);
var i=2;
alert(i);
</script>
В этой программе второе объявление var будет воспринято анализатором
кода как простое изменение значения переменной с 1 на 2. В результате диалоговые окна поочередно покажут нам эти числа.
В другом примере никаких диалоговых окон мы не увидим:
Файл chapter4/script4.3.4.html
<script>
let i=1;
alert(i);
let i=2;
alert(i);
</script>
— 27 —
Здесь проблема в следующем: анализатор кода посчитает, что мы пытаемся
оператором let вторично объявить уже существующую переменную, а
это нарушение синтаксиса, в чем можно убедиться, заглянув в консоль браузера
(рис. 4.3.1).
Рис. 4.3.1
Сообщение об ошибке в консоли браузера
Надеюсь, теперь главная и принципиальная разница ясна. Что касается
областей видимости, глобальных и локальных переменных — об этом мы поговорим чуть позже.
Но уже сейчас можно сделать вывод: оператор let — более современный.
Опытные разработчики советуют использовать объявления let. Я придерживаюсь аналогичного мнения и рекомендую везде применять let.
Теперь вернемся к первым двум примерам, где у нас объявлялись одноименные глобальные и локальные переменные. Точнее, к примеру с объявлением переменной let:
<script>
let i=1;
if(true)
{
let i=2;
}
alert(i);
</script>
Такой сценарий хорош только для демонстрации разницы между операторами
var и let, но в реальных программах я бы не советовал в разных областях видимости использовать одноименные переменные — велика вероятность допустить
ошибку в коде. Пусть лучше у каждой переменной будет свое уникальное имя.
Уже было сказано, что во многих случаях никакой разницы между способами объявления переменных вы можете даже не заметить. Вот два примера,
подтверждающих этот тезис:
Файл chapter4/script4.3.5.html
— 28 —
<script>
var i=1;
if(true)
{
i=2;
}
alert(i);
</script>
Файл chapter4/script4.3.6.html
<script>
let i=1;
if(true)
{
i=2;
}
alert(i);
</script>
Это немного измененные сценарии script4.3.1.html и script4.3.2.html. Как
видите, мы убрали объявление переменной i в блоках условий. Теперь в обоих
скриптах i — это одна и та же переменная и в функции, и в блоке. Поэтому результат выполнения сценариев будет одинаковым, оба раза откроются диалоговые окна с цифрой 2.
В одном сценарии можно использовать разное объявление разных переменных: одни объявлять let, другие — var. Например:
Файл chapter4/script4.3.7.html
<script>
var a=1;
alert("Значение переменной a - "+a);
let b=2;
alert("Значение переменной b - "+b);
</script>
Но в одном сценарии не может быть одноименных переменных, объявленных разным способом:
Файл chapter4/script4.3.8.html
<script>
var i=1;
alert(i);
let i=2;
alert(i);
</script>
Файл chapter4/script4.3.9.html
<script>
let i=1;
alert(i);
var i=2;
alert(i);
</script>
— 29 —
Если запустить эти сценарии, оба выдадут ошибку, описание которой вы можете посмотреть в консоли.
4.4. Константы const
Наконец, поговорим про оператор объявления констант const. Он позволяет присвоить переменной какое-либо значение, но только один раз. Повторное присвоение значения переменной, объявленной оператором const, приведет
к возникновению ошибки в программе.
Вот пример корректного объявления константы:
Файл chapter4/script4.4.1.html
<script>
const b=1;
alert(b);
</script>
А здесь при попытке изменить значение константы возникает ошибка:
Файл chapter4/script4.4.2.html
<script>
const b=1;
alert(b);
b=2;
alert(b);
</script>
Если запустить данный скрипт, то вы увидите первое диалоговое окно с
установленным значением константы, но не увидите второе, так как попытка
изменения значения будет являться нарушением синтаксиса. В этом вы можете
убедиться, заглянув в консоль (рис. 4.4.1).
Рис. 4.4.1
Сообщение об ошибке в консоли браузера
— 30 —
Оператор const удобно использовать для сокращения программы, если в
ней, например, происходят многократные обращения к одному и тому же элементу разметки:
const v=document.getElementById("im").style;
v.width="100px";
v.opacity="0.5";
v.border="2px solid #0000CC";
Еще пример с данным оператором — в цикле:
for(let k=1; k<3; k++)
{
let s="im"+k;
const v=document.getElementById(s).style;
v.width="100px";
v.opacity="0.5";
v.border="2px solid #0000CC";
}
В этом случае константа v создается каждый раз заново при каждом проходе,
поэтому в данном примере ошибка не возникнет.
Корректно будет работать и такой сценарий:
function func(z)
{
let s="im"+z;
const v=document.getElementById(s).style;
v.width="100px";
v.opacity="0.5";
v.border="2px solid #0000CC";
}
Здесь значение константы может меняться в зависимости от значения аргумента z функции. Но поскольку константа создается заново при каждом вызове функции, то ошибки тоже не происходит.
Константы могут быть локальными и глобальными, как и переменные,
объявленные операторами let или var.
И еще два момента:
при объявлении константы ей сразу должно быть присвоено значение;
имя константы не может совпадать с именами функций или переменных
let (и var) той же области видимости.
Если переменные let или var можно просто объявить, не присваивая им
значений, то с константой такой номер не пройдет, что подтверждает вот такой
короткий сценарий:
Файл chapter4/script4.4.3.html
<script>
const b;
alert(b);
</script>
— 31 —
Запустив его, вы не увидите диалогового окна, а только пустую страницу.
Как всегда, ошибку смотрим в консоли.
По поводу const в среде программистов бытуют разные мнения. Например, такое: const может быть полезна для предотвращения случайного изменения некоторых постоянных данных. По этому поводу у автора есть вот такое
мнение: программист, который случайно и незапланированно меняет переменную, мягко говоря, не очень профессиональный. Хороший разработчик всегда
должен четко отслеживать, что происходит с данными в его сценариях. Кроме
того, напомню, что при попытке изменить константу программа будет прервана, и вы даже не сразу поймете, почему. Только заглянув в консоль браузера,
можно обнаружить причину ошибки. И вообще, были же времена, когда в
JavaScript существовала только переменная var. И ничего, программисты как-то
справлялись с наблюдением за тем, какие переменные можно менять, а какие —
нет.
4.5. Глобальные и локальные переменные
Наконец, вспомним о том, что для переменных важен не только тип сохраненных данных. Не менее значимой является область видимости. Последний
термин определим так: область видимости — это часть сценария, в которой
значения данной переменной могут быть использованы программой.
Глобальные переменные доступны в любой части сценария, для любой
функции. Обычно глобальные переменные объявляются примерно так:
<script>
let i=1;
let t=0;
код сценария
</script>
В результате переменные i и t доступны в самых разных частях сценария. Учитывайте, что их значения могут быть изменены в теле любой функции, которая
оперирует с этими переменными.
Локальные переменные доступны только внутри конкретной функции,
условия или цикла. Объявляются они, например, так:
function func()
{
let a=1;
...
}
или так:
for(...)
{
let a=1;
...
}
— 32 —
В этих примерах значение переменной a доступно только внутри функции или
цикла. Обращение к переменной a из другой функции или не из цикла закончится неудачей.
Наконец, в «недрах» функции может быть своя иерархия переменных, то
есть одни из них оказываются глобальными внутри данной функции, а другие — локальными внутри цикла или условия, являющихся частью данной
функции. Для иллюстрации подобных ситуаций у нас есть два файла. Вот первый:
Файл chapter4/script4.5.1.html
<script>
func();
function func()
{
let a=1;
if(a==1)
{
let v=0;
alert(v);
}
alert(v);
}
</script>
Запустите страницу в вашем браузере. Что вы увидите? Откроется первое окно,
на котором будет представлено значение переменной v — это число 0. Нажмите
«ОК» (или «Закрыть» в Яндекс. Браузере). Все. Хотя у нас в сценарии есть еще
вызов второго диалогового окна, оно не появится, так как переменная v — локальная для тела функции и видна только внутри условия if(a==1) {...}.
Все то же самое будет происходить и с переменной, созданной внутри
цикла:
Файл chapter4/script4.5.2.html
<script>
func();
function func()
{
for(let c=0; c<2; c++)
alert(c);
alert(c);
}
</script>
В этом случае сначала откроется первое окно, на котором будет представлено начальное значение переменной c — число 0. Нажмите «ОК». Появится второе окно с новым значением переменной c — единицей. На этом выпол— 33 —
нение цикла завершается. Снова жмем «ОК» и убеждаемся, что третье диалоговое окно не появилось, так как переменная c — локальная для тела функции и
видна только внутри цикла for(...) {...}.
4.6. Меньше или больше переменных?
Ответ на этот вопрос зависит от того, какой фрагмент кода мы возьмемся
рассматривать.
Помните, в предыдущем разделе у нас был пример с оператором const?
Мы говорили, что его удобно использовать для сокращения программы, если в
ней, например, происходят многократные обращения к одному и тому же элементу разметки:
const v=document.getElementById("im").style;
v.width="100px";
v.opacity="0.5";
v.border="2px solid #0000CC";
v. float="right";
Согласитесь, что вариант кода, приведенный ниже, более громоздкий:
document.getElementById("im").style.width="100px";
document.getElementById("im").style.opacity="0.5";
document.getElementById("im").style.border="2px solid #0000CC";
document.getElementById("im").style .float="right";
Как мы видим, в первом случае создание необязательной переменной v
полностью оправдано. Зато в следующем примере объявленные константы
окажутся явно лишними:
const v=document.getElementById("im1").style;
v.width="100px";
const w=document.getElementById("im2").style;
w.width="200px";
Если в программе происходит только однократное обращение к элементу, проще переписать данный пример так:
document.getElementById("im1").style.width="100px";
document.getElementById("im2").style.width="200px";
В таком варианте налицо явная экономия кода.
Пример другого порядка. В теле функции есть 2 цикла:
function func()
{
...
let d;
for(d=0; d<5; d++)
{
...
}
...
— 34 —
let f;
for(f=0; f<3; f++)
{
...
}
...
}
Поскольку циклы работают по одному разу и последовательно друг за другом,
получается, что переменная d после выполнения соответствующего блока кода
уже не нужна. Так может имеет смысл использовать ее снова — теперь уже во
втором цикле? Реализуем данный подход, переписав тело функции:
function func()
{
...
let d;
for(d=0; d<5; d++)
{
...
}
...
for(d=0; d<3; d++)
{
...
}
...
}
Тем самым мы чуть-чуть сэкономим на памяти, которая теперь занята не двумя
переменными, а только одной.
Приведенные фрагменты сценариев показывают, как, грамотно манипулируя переменными, можно сделать код более лаконичным. Впрочем, приемам
оптимизации у нас будет посвящен раздел 14.5.
4.7. Многострочный код в переменных
Бывают ситуации, когда программисту необходимо в одну переменную
поместить большой объем данных. Например, HTML-код нескольких фотографий, которые необходимо добавить на страницу. Как поступить в этом случае?
Присваивать переменной очень длинную строку не совсем хорошо. Такую
строку будет неудобно и писать, и читать. Понятно, что у данной проблемы
есть решение, но какое?
Начнем с традиционного приема. Он состоит в том, что сначала в переменную записывают только часть данных. Затем в следующей строке к этой же
переменной добавляют вторую порцию информации. В третьей строке — третью часть. И так далее, до окончательного заполнения переменной. В практическом примере это может выглядеть так:
— 35 —
Файл chapter4/script4.7.1.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", function()
{
let pict='<p><img src="../chapter13/img1/im1.jpg"
id="im1" alt="Горный пейзаж">';
pict+='<img src="../chapter13/img1/im2.jpg"
id="im2" alt="Горный пейзаж">';
pict+='<img src="../chapter13/img1/im3.jpg"
id="im3" alt="Горный пейзаж"></p>';
document.body.
insertAdjacentHTML("beforeend", pict);
});
});
</script>
</head>
<body>
<p><input type="button" id="but" value="CLICK"></p>
</body>
</html>
Здесь мы в три «подхода» заполняем переменную HTML-кодом трех
изображений.
Обратите внимание: в операциях присвоения данных переменным сделаны типографские переносы кода на вторую строку.
Прием, использованный в предыдущем сценарии, нельзя считать полностью рациональным. Видите, каждую строку мы вынуждены начинать с имени
переменной, символа + и знака равенства. Даже в этом коротком примере уже
набегает некоторое количество знаков, которые увеличивают код. А что будет,
если строк с данными окажется много? Сценарий неоправданно «утяжелится».
Между тем есть подход, при котором мы избавляемся от лишних операций, присваивая переменной многострочное значение за один раз. В основу
этого метода положена интересная особенность JavaScript: переход на следующую строку после оператора + (и некоторых других) не считается новой строкой и не вызывает ошибку в анализаторе кода. Написав
Файл chapter4/script4.7.2.html
let a=1+
2+
3;
— 36 —
мы выполним корректную операцию. Запустите данную страницу в браузере, и
вы убедитесь в работоспособности такого кода.
Этим же методом мы можем за один раз наполнить любую переменную
любым количеством многострочных данных. Следуя этому принципу, скорректируем наш сценарий добавления снимков:
Файл chapter4/script4.7.3.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", function()
{
let pict='<p><img src="../chapter13/img1/im1.jpg"
id="im1" alt="Горный пейзаж">'+
'<img src="../chapter13/img1/im2.jpg"
id="im2" alt="Горный пейзаж">'+
'<img src="../chapter13/img1/im3.jpg"
id="im3" alt="Горный пейзаж"></p>';
document.body.insertAdjacentHTML("beforeend", pict);
});
});
</script>
</head>
<body>
<p><input type="button" id="but" value="CLICK"></p>
</body>
</html>
Проверьте страницу в браузере и убедитесь, что все отлично работает.
Чтобы было понятнее, как мы наращивали объем данных в переменной,
приведем сокращенный фрагмент этого процесса:
let pict='<p><img ...>'+
'<img ...>'+
'<img ...></p>';
Есть еще один вариант создания многострочной переменной. О нем мы
поговорим в главе 10.
4.8. Операторы для переменных
Один из важнейших аспектов выполнения сценариев на JavaScript — операции с переменными. Для этого существуют несколько видов операторов.
Арифметические операторы. Для начинающего наиболее важны те, что
представленные в таблице 4.8.1.
— 37 —
Таблица 4.8.1
Арифметические операторы
Оператор
Действие
Пример
Примечание
=
Присваивание
x=1;
x="Текст";
Присваивает переменной данные
любого типа
+
Сложение
x+y
Применяется также к переменным,
содержащим строки; результатом
будет новая строка
-
Вычитание
x-y
*
Умножение
x*y
/
Деление
x/y
**
Возведение в степень
x**y
%
Взятие остатка от деления
x%y
Результат — остаток от целочисленного деления
Действие последнего оператора можно хорошо понять, если запустить в
браузере следующий файл:
Файл chapter4/script4.8.1.html
<script>
alert(5%2);
</script>
Остатком от деления будет число 1 (на 2 нацело делится 4, один в остатке).
Обычно результат выполнения операции присваивается либо новой переменной
let z=x+y;
либо одной из переменных, участвующих в данной операции:
x=x+y;
Для второго случая существуют сокращенные формы записи операций
присваивания, представленные в таблице 4.8.2.
Таблица 4.8.2
Сокращенная форма записи операций
Сокращенная запись
Аналог
x+=y;
x=x+y;
x-=y;
x=x-y;
x*=y;
x=x*y;
x/=y;
x=x/y;
x**=y;
x=x**y;
x%=y;
x=x%y;
Примечание
Применяется также к переменным, содержащим строки; результатом будет новая строка
— 38 —
Еще две важные операции: инкремента и декремента. Разъяснения к ним
даны в таблице 4.8.3.
Таблица 4.8.3
Операторы инкремента и декремента
Оператор
Название
Действие
Аналог
Пример
++
Оператор инкремента
Увеличение значения
переменной на 1
x=x+1;
x++;
--
Оператор декремента
Уменьшение значения
переменной на 1
x=x-1;
x--;
В некоторых случаях с операторами инкремента и декремента надо обращаться очень аккуратно. Почему? Дело в том, что формы записи данных оператором могут иметь два вида. Например, оператор инкремента: x++; и ++x;.
Оператор декремента: x--; и --x;.
В каких ситуациях форма записи играет решающее значение? Когда вы
хотите для сокращения кода выполнить сразу два действия: увеличить переменную и присвоить ее значение другой переменной. Здесь возможны следующие варианты.
1. Сначала присваиваем текущее значение первой переменной (назовем ее
x) второй переменной (назовем ее y), а потом увеличиваем на единицу переменную x, то есть используем форму записи x++:
Файл chapter4/script4.8.2.html
<script>
let x=1;
let y=x++;
alert("y равен "+y+", x равен "+x);
</script>
2. Сначала увеличиваем текущее значение переменной x на единицу, то есть
используем форму записи ++x, а потом присваиваем ее значение переменной y:
Файл chapter4/script4.8.3.html
<script>
let x=1;
let y=++x;
alert("y равен "+y+", x равен "+x);
</script>
Запустив данные файлы в своем браузере, вы можете убедиться, что перемена мест оператора инкремента влияет на результат. Аналогично поведет
себя сценарий, где используются операторы декремента.
В 2021 г. в JavaScript были добавлены три новых оператора сокращенного
логического присваивания.
1. Логическое И присваивание &&=. Записывается: x &&= y. Переменной
x присваивается новое значение только, если x — истинно. Аналог: x=x && y.
— 39 —
Пример:
Файл chapter4/script4.8.4.html
<script>
let a=5;
a &&= 3;
alert(a);
let a2=0;
a2 &&= 3;
alert(a2);
// Аналоги
let b=5;
b=b && 3;
alert(b);
let b2=0;
b2=b2 && 3;
alert(b2);
</script>
Так как в первом блоке переменная a истинна, то произойдет присвоение
ей нового значения. В результате вы увидите в диалоговом окне цифру 3.
Во втором блоке a2 равна 0, что интерпретируется как ложь. Поэтому
диалоговое окно выведет 0 — присвоения не произошло.
Аналоги из третьего и четвертого блоков дадут такие же результаты.
2. Логическое ИЛИ присваивание ||=. Записывается: x ||= y. Переменной x
присваивается новое значение только, если x — ложно. Аналог: x=x || y.
Пример:
Файл chapter4/script4.8.5.html
<script>
let a=5;
a ||= 3;
alert(a);
let a2=0;
a2 ||= 3;
alert(a2);
let b=5;
b=b || 3;
alert(b);
let b2=0;
b2=b2 || 3;
alert(b2);
</script>
Так как в первом блоке переменная a истинна, то присвоения ей нового
значения не происходит, и она сохраняет исходное значение. В результате вы
увидите в диалоговом окне цифру 5.
— 40 —
Во втором блоке a2 равна 0, что интерпретируется как ложь. Поэтому диалоговое окно выведет 3 — выполнено присвоение переменной нового значения.
Естественно, что аналоги дают точно такие же результаты.
3. Логическое нулевое присваивание ??=. Записывается: x ??= y. Переменной x присваивается новое значение только, если x — null или undefined.
Аналог: x=x ?? y.
Пример:
Файл chapter4/script4.8.6.html
<script>
let a;
a ??= 3;
alert(a);
let a2=5;
a2 ||= 3;
alert(a2);
let b;
b=b ?? 3;
alert(b);
let b2=5;
b2=b2 ?? 3;
alert(b2);
</script>
Так как в первом блоке переменная a не определена, то ей присваивается
новое значение. Диалоговое окно покажет цифру 3.
Во втором блоке a2 определена и равна 5. Поэтому диалоговое окно выведет 5 — новое значение переменной не присваивается.
Те же самые результаты дают выражения-аналоги.
Данные операторы можно использовать по-разному. Забегая вперед, покажем один пример с участием цикла. Допустим, перед нами стоит задача прокрутить один и тот же цикл три раза подряд с изменением значения от 2 до 0.
Традиционно подобная задача решается так:
Файл chapter4/script4.8.7.html
<script>
let i=1;
let sum="";
for(let a=2; i<=9; a--)
{
sum+=a+"\n";
i++;
if(a==0)
a=3;
}
alert(sum);
</script>
Когда в результате трех проходов переменная a достигает значения 0, запускается инструкция из условного выражения:
— 41 —
if(a==0)
a=3;
Значение переменной a вновь увеличивается и цикл уходит на второй круг. Переменная i задает 9 проходов или три полных цикла.
Теперь внесем изменения в сценарий, заменив условие if на сокращенный
оператор логического присваивания ||=. У нас получится вот такая программа:
Файл chapter4/script4.8.8.html
<script>
let i=1;
let sum="";
for(let a=2; i<=9; a--)
{
sum+=a+"\n";
i++;
a ||= 3;
}
alert(sum);
</script>
Как мы уже знаем, присвоение нового значения переменной в таком операторе происходит только когда значение этой переменной становится ложным.
В данном случае значение переменной a станет ложным при нуле. После этого
a опять увеличится, и цикл пойдет по следующему кругу.
Запустите по очереди оба файла. Вы увидите, что мы получили совершенно одинаковые результаты (рис. 4.8.1). Но во втором примере, заменив
условный оператор if на логический ||=, мы сэкономили 6 символов.
Рис. 4.8.1
Диалоговое окно показывает результаты выполнения сценария
Завершая этот раздел, осталось добавить, что мы перечислили далеко не
все операторы, а только наиболее часто применяющиеся.
— 42 —
5. МАССИВЫ ТАКИЕ И ЭТАКИЕ
Представьте, что вы собрались полностью обновить свое компьютерное
окружение. Прежде чем приобрести новое оборудование, составим перечень
необходимых покупок. Назовем этот список «Комплектация»:
1) ноутбук;
2) подставка под ноутбук;
3) мышь;
4) коврик для мыши;
5) Wi-fi роутер;
6) МФУ три в одном — принтер, сканер и копировальное устройство;
7) звуковые колонки.
Будем считать, что этого достаточно. А теперь попробуем выполненную
работу перевести на язык программирования. Тогда можно сказать, что мы создали массив по имени «Комплектация», включающий 7 элементов. Думаю, это
очень наглядное сравнение для понимания того, что собой представляют массивы в JavaScript. Поговорим на эту тему обстоятельнее.
5.1. Простые массивы
Итак, массив — это переменная, содержащая в своих «недрах» нумерованный список элементов. В качестве элементов могут быть данные любого типа, которые поддерживаются в JavaScript: строки, числа, символы и другие.
Массив может также одновременно содержать данные разных типов.
Объявить массив очень просто:
let mas=[элемент 1, элемент 2, ..., элемент n];
Можно также создать пустой массив, который будет заполнен в процессе
выполнения сценария:
let mas=[];
Для получения отдельного элемента по его индексу используются записи
следующего вида:
mas[0]
mas[1]
...
mas[n]
Чтобы узнать, сколько элементов в массиве, надо обратиться к свойству
length:
let d=mas.length;
Будьте внимательны при написании сценариев: индексы элементов массива начинаются с 0, а их количество отсчитывается с 1.
Учитывая это, пример с перечнем оборудования под названием «Комплектация» необходимо переписать таким образом:
0) ноутбук;
— 43 —
1) подставка под ноутбук;
2) мышь;
3) коврик для мыши;
4) Wi-fi роутер;
5) МФУ три в одном — принтер, сканер и копировальное устройство;
6) звуковые колонки.
Здесь у нас 7 элементов с индексами от 0 до 6.
Что интересно, элементами массивов могут быть переменные и даже сами
массивы. О последнем случае мы поговорим позже, а пока продемонстрируем
вариант, когда переменные служат элементами массива:
Файл chapter5/script5.1.1.html
<script>
let a=1;
let b=2;
let c=3;
let mas=[a, b, c];
alert(mas);
</script>
Если запустить этот сценарий в браузере, вы увидите, что у нас получился массив со значениями 1, 2 и 3 (рис. 5.1.1).
Рис. 5.1.1
Массив, составленный из значений переменных
Добавить новый элемент массива тоже очень просто:
mas[n+1]=элемент n+1;
Существует множество методов обработки массивов. Посмотрим некоторые из них.
1. concat() — объединяет несколько исходных массивов и создает новый.
Например, так:
let a=[1, 2, 3];
let b=["A", "B", "C"];
let x=a.concat(b);
Обратите внимание: в методе перемена мест массивов приводит и к перемене мест их элементов. Эту особенность хорошо иллюстрирует следующий
сценарий:
— 44 —
Файл chapter5/script5.1.2.html
<script>
let a=[1, 2, 3];
let b=["A", "B", "C"];
let x=a.concat(b);
alert(x);
let y=b.concat(a);
alert(y);
</script>
Запустите файл в браузере, и вы убедитесь, что мы получаем массивы с
разным порядком следования элементов (рис. 5.1.2).
Рис. 5.1.2
От перемены мест объединяемых массивов меняется и порядок следования элементов
Как видите, принцип такой: в новом массиве элементы исходного массива
сохраняют свои индексы, а элементы присоединяемого массива получают следующие индексы.
Методом concat() можно соединить не только 2 массива, но и больше.
Вот пример последовательного слияния трех массивов, выполняемых одной
инструкцией:
Файл chapter5/script5.1.3.html
<script>
let a=[1, 2, 3];
— 45 —
let b=["A", "B", "C"];
let c=["String1", "String2"];
let x=a.concat(b, c);
alert(x);
</script>
Результат слияния массивов:
1,2,3,A,B,C,String1,String2
2. filter() — создает новый массив с элементами, прошедшими проверку,
условия которой заданы в передаваемой функции. Поясним на примере:
Файл chapter5/script5.1.4.html
<script>
let ivan=["Иван", "Иванов", 25,
"Екатеринбург", "программист"];
let res=ivan.filter(test => typeof test=="string");
alert(res);
</script>
Здесь мы фильтруем данные из массива по типу. Строки «одобряем», все
остальное отбрасываем. Результат такой фильтрации:
Иван,Иванов,Екатеринбург,программист
Что за функция использована нами внутри круглых скобок, мы выясним в
главе 6.
3. indexOf() — выполняет поиск элемента в массиве. Возвращает индекс
первого совпадающего элемента. Пример:
Файл chapter5/script5.1.5.html
<script>
let ivan=["Иван", "Иванов", 25,
"Екатеринбург", "программист"];
let res=ivan.indexOf("Екатеринбург");
alert(res);
</script>
Результат:
3
Противоположный ему метод lastIndexOf() возвращает индекс последнего совпадающего элемента:
Файл chapter5/script5.1.6.html
<script>
let mas=["JavaScript", "Ruby", "PHP",
"Python", "JavaScript", "Perl"];
let res= mas.lastIndexOf("JavaScript");
alert(res);
</script>
— 46 —
Результат:
4
4. join() — формирует из элементов массива текстовую строку с разделителем, переданным аргументом.
Файл chapter5/script5.1.7.html
<script>
let mas=["JavaScript", "Ruby", "PHP",
"Python", "JavaScript", "Perl"];
let res=mas.join("\n");
alert(res);
</script>
В результате выполнения этой программы мы увидим в диалоговом окне
названия языков программирования, расположенных на отдельных строках.
5. map() — выполняет замену элементов текущего массива на элементы,
получаемые в результате выполнения функции, указанной в круглых скобках.
Пример:
Файл chapter5/script5.1.8.html
<script>
let mas=[1, 2, 3, 4, 5];
let res=mas.map(test => test*10);
alert(res);
</script>
В этом сценарии исходные элементы увеличивают значения в 10 раз. Результат:
10,20,30,40,50
Если элементы какого-либо массива необходимо заменить на полностью
новые, можно использовать один из двух подходов:
создать новый массив с новыми элементами и в последующих частях
программы использовать этот массив вместо старого;
удалить все элементы из текущего массива и заполнить его новыми
данными:
Файл chapter5/script5.1.9.html
<script>
let mas=[1, 2, 3, 4, 5];
mas=["JavaScript", "Ruby", "PHP", "Python", "Perl"];
alert(mas);
</script>
По сути, данная операция аналогична той, что мы видели в предыдущей
главе, когда меняли текущее значение переменной на новое.
6. pop() — удаляет последний элемент массива и возвращает этот элемент. Например, вот так:
— 47 —
Файл chapter5/script5.1.10.html
<script>
let mas=["JavaScript", "Ruby", "PHP",
"Python", "Perl"];
let res=mas.pop();
alert(res);
alert(mas);
</script>
Данный сценарий удалит язык программирования Perl и сохранит в массиве mas первые четыре элемента.
7. push() — добавляет новые элементы в конец массива и возвращает его
новую длину:
Файл chapter5/script5.1.11.html
<script>
let mas=["JavaScript", "Ruby", "PHP",
"Python", "Perl"];
let res=mas.push("HTML", "CSS");
alert(res);
alert(mas);
</script>
Сценарий вернет новое количество элементов — 7 — и увеличенный массив.
8. reduce() — применяет заданную функцию к каждому элементу массива, перебирая их слева направо, и возвращает конечный результат.
Файл chapter5/script5.1.12.html
<script>
let mas=[1, 2, 3, 4, 5];
let res=mas.reduce((result, test) => result+test*5);
alert(res);
</script>
Вернет суммарный результат 71.
9. reduceRight() — применяет заданную функцию к каждому элементу
массива, перебирая их справа налево, и возвращает конечный результат.
Файл chapter5/script5.1.13.html
<script>
let mas=[1, 2, 3, 4, 5];
let res=mas.reduce((result, test) => result+test*5);
alert(res);
</script>
Вернет суммарный результат 55.
10. reverse() — выполняет сортировку массива в обратном порядке. Вот
так:
Файл chapter5/script5.1.14.html
— 48 —
<script>
let mas=[1, 2, 3, 4, 5];
mas.reverse();
alert(mas);
</script>
Теперь последовательность элементов массива будет выглядеть так:
5,4,3,2,1
11. shift() — удаляет элемент массива с индексом 0 и возвращает этот
элемент. Чтобы посмотреть метод в действии, запустите следующий файл:
Файл chapter5/script5.1.15.html
<script>
let mas=[1, 2, 3, 4, 5];
let res=mas.shift();
alert(res);
alert(mas);
</script>
12. sort() — производит сортировку элементов массива по заданной
функции сравнения. Если функция не указана, массив сортируется в соответствии со значениями кодовых точек каждого символа Unicode.
Файл chapter5/script5.1.16.html
<script>
let mas=[1, 3, 7, 2, 5, 4, 6];
mas.sort();
alert(mas);
</script>
Получим массив с элементами, расположенными по порядку возрастания
чисел.
И еще один пример:
Файл chapter5/script5.1.17.html
<script>
let mas=["JavaScript", "Ruby", "PHP",
"Python", "Perl"];
mas.sort();
alert(mas);
</script>
Результат сортировки:
JavaScript,PHP,Perl,Python,Ruby
Пример с данными, записанными кириллицей:
Файл chapter5/script5.1.18.html
<script>
let mas=["Псков", "Волгоград", "Самара",
"Казань", "Тюмень"];
— 49 —
mas.sort();
alert(mas);
</script>
Результат:
Волгоград,Казань,Псков,Самара,Тюмень
13. slice() — возвращает новый массив, содержащий часть исходного массива. В скобках указывается индекс, с которого начинается отсчет, или интервал копирования.
Пример 1 — копирование элементов, начиная с третьего и до конца массива:
Файл chapter5/script5.1.19.html
<script>
let mas=["Псков", "Волгоград", "Самара",
"Казань", "Тюмень"];
let res=mas.slice(3);
alert(res);
</script>
Результат:
Казань,Тюмень
Пример 2 — копирование элементов с первого по третий:
Файл chapter5/script5.1.20.html
<script>
let mas=["Псков", "Волгоград", "Самара",
"Казань", "Тюмень"];
let res=mas.slice(1, 3);
alert(res);
</script>
Результат:
Волгоград,Самара
Как видите из данного примера, в новый массив не включается элемент,
индекс которого обозначен вторым аргументом в методе slice. Таким образом,
мы получили массив res с элементами массива mas, у которых индексы 1 и 2.
14. splice() — удаляет часть массива; удаляет часть массива, а на его место добавляет новые элементы; вставляет новые элементы в указанную позицию. В первых двух случаях возвращает удаленные элементы.
Рассмотрим на примерах все три варианта.
Файл chapter5/script5.1.21.html
<script>
let mas=[1, 2, 3, 4, 5];
let res=mas.splice(2, 2);
alert(mas);
alert(res);
</script>
— 50 —
В этом сценарии мы удаляем два элемента, начиная со второй позиции.
Результат:
«остатки» массива:
1,2,5
удаленные элементы:
3,4
Следующий скрипт демонстрирует замену двух элементов начиная с индекса 1:
Файл chapter5/script5.1.22.html
<script>
let mas=[1, 2, 3, 4, 5];
let res=mas.splice(1, 2, 12, 13);
alert(mas);
alert(res);
</script>
Результат:
1,12,13,4,5
2,3
Третий сценарий — вставка новых элементов в указанную позицию.
В этом случае количество удаляемых элементов — 0:
Файл chapter5/script5.1.23.html
<script>
let mas=[1, 2, 3, 4, 5];
mas.splice(2, 0, 25, 58);
alert(mas);
</script>
Результат:
1,2,25,58,3,4,5
15. unshift() — добавляет элементы в начало массива и возвращает его
новую длину. Иллюстрирует метод вот такой пример:
Файл chapter5/script5.1.24.html
<script>
let mas=[1, 2, 3, 4, 5];
let res=mas.unshift("HTML", "CSS");
alert(mas);
alert(res);
</script>
Результат:
HTML,CSS,1,2,3,4,5
7
— 51 —
16. includes() — ищет определенный элемент в массиве и возвращает true
или false в зависимости от результатов поиска. Например:
Файл chapter5/script5.1.25.html
<script>
let mas=[1, 2, 3, 4, 5];
let res=mas.includes(3);
alert(res);
res=mas.includes(8);
alert(res);
</script>
Результат:
true
false
17. every() — с помощью указанной функции проверяет все элементы
массива. Возвращает true, если все элементы соответствуют заданному критерию, и false в противном случае. Пример:
Файл chapter5/script5.1.26.html
<script>
let mas1=[1, 2, 3, 4, 5];
let mas2=[1, 2, "text", 4, 5];
let res=mas1.every(test => typeof test=="number");
alert(res);
res=mas2.every(test => typeof test=="number");
alert(res);
</script>
Результат:
true
false
18. some() — с помощью указанной функции проверяет все элементы
массива. Возвращает true, если хотя бы один элемент соответствует заданному
критерию, и false в противном случае. Пример:
Файл chapter5/script5.1.27.html
<script>
let mas1=["a", "b", "c", 4, "d"];
let mas2=["a", "b", "c", "d", "e"];
let res=mas1.some(test => typeof test=="number");
alert(res);
res=mas2.some(test => typeof test=="number");
alert(res);
</script>
Результат:
true
false
— 52 —
19. find() — возвращает из массива первый элемент, который соответствует заданному критерию. Пример использования метода:
Файл chapter5/script5.1.28.html
<script>
let mas=["a", "b", "c", 4, "d"];
let res=mas.find(test => typeof test=="number");
alert(res);
</script>
Результат:
4
20. findIndex() — возвращает из массива индекс первого элемента, который соответствует заданному критерию. Пример:
Файл chapter5/script5.1.29.html
<script>
let mas=["a", "b", "c", 4, "d"];
let res=mas.findIndex(test => typeof test=="number");
alert(res);
</script>
Результат:
3
21. forEach() — вызывает заданную функцию для каждого элемента массива. Пример:
Файл chapter5/script5.1.30.html
<script>
let mas=[1, 2, 3];
mas.forEach(test => alert(test*3));
</script>
Результат:
3
6
9
22. isArray() — проверяет, является ли объект массивом. Возвращает true
в случае положительного исхода проверки и false в противном случае. Синтаксис:
Array.isArray(проверяемый объект)
Пример:
Файл chapter5/script5.1.31.html
<script>
let mas=["a", "b", "c", "d", "e"];
let a=5;
— 53 —
let res=Array.isArray(mas);
alert(res);
res=Array.isArray(a);
alert(res);
</script>
Результат:
true
false
В методы, оперирующие числами или строками, можно передавать значения переменных или вызовы функций, которые вычисляют параметры обработки массива. Эти подходы иллюстрирует следующий пример:
Файл chapter5/script5.1.32.html
<script>
let mas=["Псков", "Волгоград", "Самара", "Казань", "Тюмень"];
let a=3;
let b=1;
alert(mas.slice(a));
alert(mas.slice(b, a));
function func1()
{
let x=1;
x+=2;
return x;
}
alert(mas.slice(func1()));
function func2()
{
let y=0;
y++;
return y;
}
alert(mas.slice(func2(), func1()));
</script>
5.2. Варианты применения
Обычно массивы применяют для хранения определенного набора данных,
которые затем обрабатываются сценарием. Например, информации о клиентах:
let ivan=["Иван", "Иванов", 25];
let petr=["Петр", "Петров", 31];
let denis=["Денис", "Денисов", 28];
Учитывая, что в массиве нумерация элементов последовательная, значениями массивов очень удобно оперировать в циклах:
— 54 —
Файл chapter5/script5.2.1.html
<script>
let ivan=["Иван", "Иванов", 25,
"Екатеринбург", "программист"];
for(let i=0; i<ivan.length; i++)
document.write(ivan[i]+"<br>");
</script>
В результате данные Ивана Иванова будут распечатаны на странице «в
столбик».
Массивы могут пригодиться и для создания определенных визуальных
эффектов. Покажем два таких примера.
В первом случае мы создадим таймер загрузки данных на сервер.
У нас есть форма с данными, которые необходимо передать серверной
программе:
Файл chapter5/script5.2.2.html
<p><input id="sum1" value="Иван"><br>
<input id="sum2" value="Иванов"><br>
<input id="sum3" value="25"><br>
<input type="button" id="but"
value=" ЗАГРУЗИТЬ ДАННЫЕ "></p>
И есть контейнер, в котором появляются графические элементы, указывающие
на выполнение загрузки:
<div id="loa"></div>
Работает программа очень просто.
Есть идентификатор процесса, который меняет свое значение после получения ответа от сервера
let h=1;
счетчик элементов массива
let i=0;
и сам массив
let d=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
9, 8, 7, 6, 5, 4, 3, 2, 1];
Сценарий запускается сразу после нажатия кнопки «ЗАГРУЗИТЬ
ДАННЫЕ»:
document.getElementById("but").
addEventListener("click", function()
{
...
});
Для упрощения примера мы лишь имитируем отправку информации. Поэтому, запустив таймер
race();
— 55 —
даем ему поработать 5 секунд, а затем имитируем ответ сервера «ЗАГРУЗКА
ПРОШЛА УСПЕШНО»:
setTimeout(()=>
{
h=2;
document.getElementById("loa").innerHTML=
"ЗАГРУЗКА ПРОШЛА УСПЕШНО";
},
5000);
При этом идентификатор h получает значение 2, и работа таймера прекращается.
Функция race, управляющая таймером, вызывается каждые 80 миллисекунд
function race()
{
...
setTimeout(race, 80);
}
благодаря чему в контейнер с равными (и очень короткими) промежутками выводятся очередные показания:
document.getElementById("loa").innerHTML=
t+" ЗАГРУЗКА "+t;
В исходном состоянии переменная t «пустая»:
let t="";
Ее заполнение производится в цикле
for(let a=0; a<=d[i]; a++)
t+="<div class='tim'></div>";
При каждом вызове функции race количество проходов цикла определяется числом из очередного элемента массива:
a<=d[i]
Внутри цикла переменная t заполняется кодом, который при выводе на
страницу образует картинку из нескольких штрихов
t+="<div class='tim'></div>";
по бокам от надписи «ЗАГРУЗКА». Количество штрихов равно очередному
числу из массива.
При каждом вызове функции счетчик элементов массива увеличивается
на единицу:
i++;
Когда функция переберет все элементы массива, счетчик i обнулится, и
процесс начнется заново:
if(i==d.length)
i=0;
— 56 —
Поскольку значения элементов массива сначала идут по возрастающей,
а достигнув числа 10 начинают убывать, индикатор выглядит как непрерывно
бегущие от минимума к максимуму и обратно штрихи (рис. 5.2.1).
Как видите, массив оказался полезен в таком необычном качестве.
Рассмотрим другой пример. В новой программе плавно меняется цвет
текста. Цветовые параметры сценарий получает из массива. Поскольку чернобелая печать не передает заявленный эффект, вам будет необходимо запустить
нужный файл в браузере.
На странице имеется надпись JavaScript:
Файл chapter5/script5.2.3.html
<div id="col">JavaScript</div>
Функция изменения цвета надписи запускается через 3 секунды после загрузки страницы:
Рис. 5.2.1
Таймер загрузки данных на сервер. Сейчас обрабатывается элемент массива с индексом 7,
благодаря чему мы видим по бокам от надписи по 7 штрихов
addEventListener("load", function()
{
...
setTimeout(color, 3000);
...
});
Необходимые данные содержатся в массиве ar:
let ar=["#0000CC", "#006600", "#CC0099", "#FF9900",
"#0099CC", "#FF5050", "#33CC33", "#CC0000"];
Также в работе сценария участвует счетчик проходов:
let i=0;
Функция color содержит несколько простых инструкций.
— 57 —
В течение 3 секунд плавно меняем исходный цвет надписи на новый, полученный из очередного элемента массива
document.getElementById("col").style.transition=
"color 3s";
document.getElementById("col").style.color=ar[i];
Увеличиваем значение счетчика
i++;
Проверяем, не превысило ли оно индекс последнего элемента в массиве. Если
«да», обнуляем переменную i:
if(i>7)
i=0;
Последняя операция — повторный запуск функции по истечении 3 секунд:
setTimeout(color, 3000);
Таким образом, процесс смены цвета текста происходит непрерывно по кругу
до тех пор, пока открыта страница. Как видите, и здесь массив оказался к месту.
Обратите внимание: в обоих примерах из данного раздела мы использовали таймер setTimeout, являющийся частью функционала JavaScript. О таймерах нам предстоит более подробный разговор в главе 9.
5.3. Многомерные массивы
В предыдущем разделе у нас был пример с тремя массивами, которые содержали данные трех разных клиентов. А если объединить эти массивы в один?
Например, так:
let mas=[["Иван", "Иванов", 25],
["Петр", "Петров", 31], ["Денис", "Денисов", 28]];
В результате этого объединения мы получили многомерный массив, каждым
элементом которого, в свою очередь, тоже является массив:
Файл chapter5/script5.3.1.html
<script>
let mas=[["Иван", "Иванов", 25],
["Петр", "Петров", 31], ["Денис", "Денисов", 28]];
alert(mas);
alert(mas[1]);
alert(mas[0][1]);
</script>
Запустив страницу в браузере, мы последовательно увидим три диалоговых окна с выбранными данными:
все элементы многомерного массива:
Иван,Иванов,25,Петр,Петров,31,Денис,Денисов,28
все элементы массива с индексом 1:
Петр,Петров,31
— 58 —
элемент с индексом 1 массива с индексом 0:
Иванов
Как вы уже поняли, чтобы получить значение того или иного элемента из
вложенного массива, нужно указать после имени многомерного массива два
оператора [ ]. В первом задаем индекс вложенного массива, во втором — индекс элемента из вложенного массива:
mas[0][1]
Мы рассмотрели пример двухмерного массива. Но уровней вложенности
может быть больше — три, четыре и так далее. Чтобы убедиться в этом, запустите в браузере следующий файл:
Файл chapter5/script5.3.2.html
<script>
let mas=[[["Иван", "Иванов", 25],
["Екатеринбург", "программист"]],
[["Петр", "Петров", 31],
["Самара", "преподаватель"]],
[["Денис", "Денисов", 28],
["Казань", "экскурсовод"]]];
alert(mas);
alert(mas[1]);
alert(mas[0][1]);
alert(mas[0][1][0]);
</script>
Заполнить многомерный массив можно, сразу указав все его составляющие при создании или поочередно заполняя элементы массива:
let mas=[];
mas[0]=["Иван", "Иванов", 25];
mas[1]=["Петр", "Петров", 31];
mas[2]=["Денис", "Денисов", 28];
Как и в случае с простыми массивами, элементами многомерных массивов могут быть переменные, содержащие массивы:
Файл chapter5/script5.3.3.html
<script>
let a=[1, 2, 3];
let b=["A", "B", "C"];
let c=[false, true];
let mas=[a, b, c];
alert(mas);
alert(mas[1]);
alert(mas[1][2]);
</script>
— 59 —
К многомерным массивам или отдельным элементам вложенных массивов применимы все те же методы, что мы рассматривали в первом разделе данной главы. Вот несколько примеров, подтверждающих заявленный принцип.
Метод concat():
Файл chapter5/script5.3.4.html
<script>
let a=[[1, 2, 3], ["JavaScript", "HTML"]];
let b=[["A", "B", "C"], [99, 88, 77, 66]];
let x=a.concat(b);
alert(x);
</script>
Метод filter():
Файл chapter5/script5.3.5.html
<script>
let mas=[[1, 2, 3], [22, "JavaScript", "HTML", 15]];
let res=mas[1].filter(test => typeof test=="string");
alert(res);
</script>
Метод indexOf():
Файл chapter5/script5.3.6.html
<script>
let mas=[[1, 2, 3], [22, "JavaScript", "HTML", 15]];
let res=mas[1].indexOf("HTML");
alert(res);
</script>
Думаю, алгоритм применения известных нам методов к многомерным
массивам или их элементам понятен.
С помощью многомерных массивов можно создавать еще более «навороченные» примеры визуальных эффектов, чем с помощью одномерных. Продемонстрируем это двумя сценариями.
Для начала усложним пример с бегущими штрихами. При этом сделаем
проще условия задачи. Наш таймер в новой версии не будет связан с формой и
станет запускаться сразу после загрузки страницы:
Файл chapter5/script5.3.7.html
addEventListener("load", race);
Вновь будет счетчик проходов:
let i=0;
А вот массив станет трехмерным:
let d=[[0, 1, 2, 3, 4, 5, ..., 0],
[7, 10, 13, 16, 19, 22,..., 7],
[0, 0, 0, 2, 4, 6,..., 0]];
— 60 —
При этом первый вложенный массив отвечает за количество штрихов, второй — за высоту штриха, третий — за скругление штриха.
Функция race вызывается каждые 200 миллисекунд:
function race()
{
...
setTimeout(race, 200);
}
В начале каждого прохода создаем переменную
let t="";
которую в цикле заполняем количеством штрихов, соответствующим числу из
очередного элемента массива:
a<=d[0][i]
for(let a=0; a<=d[0][i]; a++)
t+="<div class='tim'></div>";
Затем эти штрихи появляются в контейнере:
document.getElementById("loa").innerHTML=t;
Следом в новом цикле меняем каждому штриху высоту и скругление на
основе данных, полученных из второго и третьего вложенного массивов:
let q=document.querySelectorAll(".tim");
for(let n=0; n<q.length; n++)
{
q[n].style.height=d[1][i]+"px";
q[n].style.borderRadius=d[2][i]+"px";
}
Увеличиваем значение счетчика
i++;
Когда функция переберет все элементы массивов, счетчик i обнулится, и
процесс начнется заново:
if(i==d[0].length)
i=0;
Что мы получим в итоге? Мы увидим, как на странице появляются удлиненные штрихи, количество которых увеличивается, а сами штрихи при этом
становятся толще, превращаясь из штрихов в круги. Затем процесс идет вспять.
И так до тех пор, пока загружена страница (рис. 5.3.1).
Следующий пример чуть проще, так как использует многомерный массив
с двумя вложенными компонентами. В то же время достигаемый эффект смотрится, по мнению автора, интереснее.
Сценарий запускается после загрузки страницы:
Файл chapter5/script5.3.8.html
addEventListener("load", function()
{
...
— 61 —
});
Создаем двухмерный массив:
let ar=[["#0000CC", "#006600", "#CC0099", "#FF9900",
"#0099CC", "#FF5050", "#33CC33", "#CC0000"],
["20px -20px 10px #000000",
"-20px -20px 10px #000000",
"-20px 20px 10px #000000",
"20px 20px 10px #000000"]];
Рис. 5.3.1
Разные стадии работы таймера
Вложенный массив с индексом 0 отвечает за цветовое оформление текста, массив с индексом 1 — за расположение тени от букв.
Создаем две переменные — счетчики элементов вложенных массивов:
let i=0;
let b=0;
Через 1 секунду после загрузки документа запускается функция color:
setTimeout(color, 1000);
В ней запрограммировано три процесса:
повторяющиеся вызовы функции через каждые 3 секунды:
setTimeout(color, 3000);
медленное изменение цвета надписи:
document.getElementById("col").style.transition=
"color 3s, ...";
document.getElementById("col").style.color=ar[0][i];
медленное изменение положения тени от букв:
document.getElementById("col").style.transition=
"..., text-shadow 3s";
...
— 62 —
document.getElementById("col").style.
textShadow=ar[1][b];
При каждом запуске функции значения счетчиков увеличиваются на единицу:
i++;
...
b++;
Так как набор цветов содержит 8 значений, а набор положений тени —
только 4, то при достижении максимальных показателей счетчики обнуляются
if(i>7)
i=0;
...
if(b>3)
b=0;
и все начинается по новой.
В итоге мы получим страницу, на которой одновременно плавно меняется
цвет текста, а тень не менее плавно совершает полный оборот вокруг каждой из
букв (рис. 5.3.2).
Рис. 5.3.2
Страница второго примера
Во втором примере есть интересное отличие от первого. В первом примере вложенные массивы имели одинаковую размерность, а во втором — разную.
На этом рассказ о массивах завершен, и мы переходим к описанию функций.
— 63 —
6. ФУНКЦИИ НА ВСЕ СЛУЧАИ ЖИЗНИ
Одни из наиболее важных «действующих лиц» любой программы на
JavaScript — функции. Именно они являются обработчиками событий, именно
в них выполняется большинство операций, вычислений, преобразований и т. д.
По сути, функция — это набор инструкций, объединенных в один блок и
выполняющих те или иные действия по изменению содержания страницы. Приятное свойство целого ряда функций (но не всех) — к ним можно обращаться
неоднократно и на самых разных этапах выполнения программы.
JavaScript предоставляет разработчику несколько вариантов создания
функций. Поговорим о них подробнее.
6.1. Разнообразие функций
Перефразируя известную песню, скажем: функции бывают разные.
Например, по назначению их можно разделить на два основных вида.
Первый — функции, выполняющие действия, связанные с изменением
состояния и параметров различных элементов документа. Пример:
Файл chapter6/script6.1.1.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
<script>
addEventListener("load", function()
{
setTimeout(func, 2000);
function func()
{
document.getElementById("im1").style.width="200px";
document.getElementById("im2").src=
"../chapter13/img1/im3.jpg";
}
});
</script>
</head>
<body>
<p><img src="../chapter13/img1/im1.jpg" id="im1"
alt="Фото">
<img src="../chapter13/img1/im2.jpg" id="im2"
alt="Фото"></p>
</body>
</html>
Задача приведенной выше функции — через 2 секунды после загрузки страницы уменьшить одно изображение и поменять адрес другого.
Вторая разновидность — функции, необходимые для вычислений:
— 64 —
Файл chapter6/script6.1.2.html
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", func);
function func()
{
let z=parseInt
(document.getElementById("sum1").value)+
parseInt
(document.getElementById("sum2").value);
alert(z);
}
});
</script>
...
<p><input type="button" id="but" value="CLICK"></p>
<p><input id="sum1" value="0">
<input id="sum2" value="0"></p>
...
В этом примере функция вычисляет сумму чисел, введенных в текстовых полях.
Естественно, бывают и смешанные варианты, когда инструкции направлены на выполнение требуемых вычислений и на их основе — манипулирование элементами страницы.
Файл chapter6/script6.1.3.html
...
<script>
addEventListener("load", function()
{
setTimeout(func, 2000);
function func()
{
let w=document.documentElement.clientWidth/8;
document.getElementById("im1").style.width=w+"px";
}
});
</script>
...
<p><img src="../chapter13/img1/im1.jpg" id="im1"
alt="Фото"></p>
...
В данном примере через 2 секунды после загрузки определяется ширина
окна браузера, значение делится на 8, и полученный результат присваивается
— 65 —
свойству width изображения. Таким образом, оно пропорционально уменьшается.
По методу обращения к ним функции можно разделить на 3 группы.
1. Именованные — то есть имеющие имя, по которому они могут быть
вызваны:
// Описание функции
function func()
{
...
}
// Вызов функции
func();
Или:
// Описание функции
function func()
{
...
}
// Вызов функции как обработчика события
document.getElementById("im").
addEventListener("click", func);
2. Анонимные функции, то есть не имеющие имени и «приписанные»
непосредственно к регистратору обработчика. Пример:
document.getElementById("im").
addEventListener("click", function()
{
...
});
3. Анонимные функции, изначально присвоенные какой-либо переменной. Допустим, вот так:
let v=function(x)
{
...
}
Вызов этой функции может выглядеть следующим образом (например, если результат необходимо сохранить для дальнейших операций):
let z=v(8);
И даже еще проще (например, если вычисленный результат выводится на страницу):
v(8);
Правда, оговорюсь, что, на мой взгляд, такие функции все же необходимо считать «условно анонимными», ведь к ним можно обратиться по имени переменной. Однако я не уверен, что со мной согласятся все программисты.
Обратите внимание на один важный момент. Если результат выполнения
функции присваивается переменной, то вызов такой функции может происхо— 66 —
дить только после объявления данной переменной и присвоения ей ссылки на
данную функцию:
let v=function(x)
{
...
}
let z=v(8);
И это логично: сначала мы объявляем переменную, а потом уже выполняем
операции с ней.
В то же время именованные функции можно вызывать как после их описания в коде сценария, так и до:
func();
function func()
{
...
}
Часто программисту необходимо использовать рекурсивные функции —
то есть те, что вызывают сами себя. Это можно сделать двумя способами.
Допустим, мы хотим вычислить сумму квадратов чисел от 1 до 5. Напишем такой сценарий:
Файл chapter6/script6.1.4.html
<script>
let a=1;
let sum=0;
func(5);
function func(i)
{
sum+=a**2;
a++;
if(a<=i)
func(i);
}
alert(sum);
</script>
В этом примере переменная a — счетчик повторов функции, sum — переменная для хранения результатов вычислений. Пока a меньше значения аргумента,
функция вызывается вновь:
if(a<=i)
func(i);
Результаты вычислений показываем в диалоговом окне:
alert(sum);
— 67 —
Создавая рекурсивную функцию, обязательно предусмотрите предел ее
выполнения. Если этого не сделать, процесс станет бесконечным и страница с
функцией зависнет. В случае нашего примера такой предел установлен — количество повторов равно 5.
Еще один способ создания рекурсивной функции — использование таймера setTimeout в теле функции (подробный рассказ о таймерах будет позже).
Рассмотрим вариант программы с таймером, а для этого в предыдущий пример
внесем некоторые изменения. Теперь сценарий будет выглядеть так:
Файл chapter6/script6.1.5.html
<script>
let a=1;
let sum=0;
func();
function func()
{
sum+=a**2;
a++;
alert(sum);
if(a<=5)
setTimeout(func, 1000);
}
</script>
Если запустить такую страницу в браузере, вы увидите, как один раз в секунду будет появляться диалоговое окно с промежуточными результатами вычислений.
Теперь о передаче параметров в функцию. Как вы уже видели из упомянутых примеров, это можно сделать разными способами.
1. Передать данные в качестве аргумента (или аргументов, если их много). Вот так:
func(15);
function func(i)
{
// инструкции, выполняемые в зависимости
от значения аргумента i
}
2. Использовать для передачи данных значение глобальной переменной:
let a=15;
function func()
{
// инструкции, выполняемые в зависимости
от значения переменной a
}
— 68 —
3. Еще один вариант, который мы пока не упоминали: передача в качестве
аргумента функции — объекта события (с последующим извлечением данных,
например, id). Рассмотрим этот случай подробнее.
Пусть документ содержит несколько изображений:
Файл chapter6/script6.1.6.html
<p><img id="im1" src="... " alt="...">
<img id="im2" src="..." alt="...">
<img id="im3" src="... " alt="..."></p>
Станем фиксировать щелчки мышью на странице:
addEventListener("click", func);
Обработчиком у нас назначена функция func. Первоначально она получает данные из интерфейса event (его еще называют объектом события):
function func (ev)
{
...
}
Сначала проверим, где произошло событие. Нам важны щелчки на фотографиях:
if(ev.target.tagName=="IMG")
{
...
}
Если условие верно, определим id рисунка
ev.target.id
и увеличим то изображение, на котором произошел клик:
document.getElementById(ev.target.id).
style.width="300px";
Как видите, функция func получает id из аргумента, роль которого выполняет объект события. Запишем функцию целиком:
function func(ev)
{
if(ev.target.tagName=="IMG")
{
document.getElementById(ev.target.id).
style.width="300px";
}
}
4. Наконец, бывает смешанный вариант — это когда одна и та же функция может получать аргументы совершенно разных типов. Например, число
или строку. Частный случай такой функции показан ниже:
Файл chapter6/script6.1.7.html
<script>
func(15);
— 69 —
func("Строка");
function func(i)
{
let a=i+"";
alert("Количество знаков - "+a.length);
}
</script>
Задача функции — вычислять количество знаков в переданном аргументе. Запуск функции происходит 2 раза.
Сразу после загрузки страницы функция получает в качестве аргумента
число
func(15);
которое преобразуется в строку:
a=i+"";
При втором вызове в функцию передается строка:
func("Строка");
В этом случае операция
a=i+"";
ничего не меняет.
Последнее действие — вывод сообщения о количестве знаков в переданном аргументе:
alert("Количество знаков - "+a.length);
Как видите, данная функция прекрасно оперирует с данными разных типов.
Раз в функцию можно передавать какие-то параметры, значит, можно и
получать результаты работы функции. Покажем два способа.
1. Прямое указание в теле функции возвращаемого значения. Для этого
используется оператор return. Вот элементарный пример:
Файл chapter6/script6.1.8.html
<script>
function func()
{
let a=2+3;
return a;
}
let b=func();
alert(b);
</script>
Как видите, здесь прямо указано, что функция возвращает значение переменной a
return a;
— 70 —
которое можно использовать в других частях сценария. При этом сама переменная a является локальной и видна только в функции. Чтобы ее значение было доступно в других частях сценария, мы использовали оператор return и вызывали функцию, присвоив результат ее выполнения глобальной переменной b:
let b=func();
2. Результатом работы функции бывает изменение глобальной переменной. Естественно, что новое значение этой переменной также может использоваться в других операциях:
Файл chapter6/script6.1.9.html
<script>
let a=3;
function func()
{
a+=5;
}
func();
alert(a);
</script>
Здесь новое значение переменной a условно можно считать возвращаемым. Сначала мы объявили глобальную переменную a
let a=3;
затем в теле функции увеличили ее значение, и полученный результат передали
в дальнейшую работу методу alert:
alert(a);
6.2. Стрелочные функции
Есть еще один интересный случай — функции-стрелки. Мы уже применяли их в некоторых примерах из главы, посвященной массивам.
Вообще, стрелочные функции появились в ES6 в 2015 г. Но разработчики
применяют их достаточно редко. Например, валидатор JavaScript, о котором
будет разговор в последней главе, «увидев» в коде стрелочную функцию, до
сих пор сообщает: «синтаксис функции стрелки (=>) доступен только в ES6
(используйте версию 6)». Хотя на момент создания книги уже больше года существовала 12 версия (вышла в июне 2021 г.).
Таким образом, функции-стрелки можно с некоторыми оговорками отнести к разряду пока еще креативных методов разработки.
Особенность стрелочных функций — более короткая и простая запись.
Функции-стрелки объявляются двумя последовательными символами =>. Один
из возможных вариантов такой функции:
Файл chapter6/script6.2.1.html
— 71 —
let func= ()=>
{
let a=5+3;
alert(a);
};
Вызвать данную функцию можно так:
func();
Обратите внимание — перед оператором => мы поставили круглые скобки. Они нужны на случай, если мы передаем в функцию какие-то аргументы:
Файл chapter6/script6.2.2.html
<script>
let func= (arg)=>
{
let a=arg+3;
alert(a);
};
func(2);
</script>
Функции-стрелки могут быть записаны гораздо проще, без фигурных
скобок, если выполняют всего одну операцию. Вот пример:
Файл chapter6/script6.2.3.html
<script>
let func= ()=>alert(5**2);
func();
</script>
И еще пример — теперь с передачей в функцию аргументов:
Файл chapter6/script6.2.4.html
<script>
let func= (arg1, arg2)=>alert(arg1**arg2);
func(3, 5);
</script>
Если стрелочная функция оперирует одним аргументом, то круглые скобки перед оператором => можно не ставить (что мы наблюдали в ряде примеров
из главы 5):
Файл chapter6/script6.2.5.html
<script>
let func= arg =>
{
let sum=arg+5;
alert(sum);
— 72 —
}
func(3);
</script>
Если аргументов несколько, такой номер не пройдет. В этом случае необходимы круглые скобки:
Файл chapter6/script6.2.6.html
<script>
let func= (arg1, arg2, arg3)=>
{
let sum=arg1+arg2+arg3;
alert(sum);
};
func(1, 2, 3);
</script>
К стрелочной функции, как и к любой другой, можно обращаться неоднократно и на разных стадиях выполнения программы. Главное, чтобы сначала
была создана переменная со ссылкой на функцию, а уже потом выполнялись
обращения к ней. Модернизируем предыдущий сценарий:
Файл chapter6/script6.2.7.html
<script>
let func= (arg1, arg2, arg3)=>
{
let sum=arg1+arg2+arg3;
alert(sum);
};
func(1, 2, 3);
alert("Пауза между запусками стрелочной функции");
func(10, 11, 12);
</script>
Функции-стрелки относятся к разряду анонимных функций.
В примерах, разобранных выше, мы присваивали результат выполнения
стрелочной функции переменной. Это так называемый случай условной анонимности. Полную анонимность функции-стрелки демонстрируют, когда выполняют роль обработчика события:
Файл chapter6/script6.2.8.html
...
<script>
addEventListener("load", function()
{
let a=0;
document.getElementById("but").
— 73 —
addEventListener("click", ()=>
{
a++;
alert("Количество щелчков - "+a);
});
});
</script>
...
<p><input type="button" id="but" value="CLICK"></p>
...
На данной странице производится подсчет кликов, сделанных на кнопке.
Как видите, стрелочная функция запускается в качестве обработчика. Никаких
иных способов привести ее в действие не существует.
К достоинствам стрелочных функций можно отнести тот факт, что они
отнимают у компьютера меньшее количество системных ресурсов, чем обычные функции. Но есть у функций-стрелок и недостаток — их нельзя использовать в конструкторах при создании новых объектов объявленного класса.
6.3. Массив как аргумент функции
До сих пор мы рассматривали ситуации, когда функции получали в качестве аргументов переменные с одним значением. Однако в целом ряде сценариев удобнее использовать несколько иной подход.
Давайте рассмотрим типичную ситуацию, когда функция обрабатывает
данные пользователя. Допустим, это имя, фамилия и возраст. Типичный сценарий для выполнения заявленных действий будет выглядеть так:
Файл chapter6/script6.3.1.html
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", ()=>{func(a, b, c);});
let a="Иван";
let b="Иванов";
let c=25;
function func(x, y, z)
{
alert("Вас зовут "+x+" "+y+", вам "+z+" лет.");
}
});
</script>
...
<p><input type="button" id="but" value="CLICK"></p>
...
После нажатия кнопки мы передаем функции три аргумента:
...func(a, b, c);...
— 74 —
...
let a="Иван";
let b="Иванов";
let c=25;
Сценарий написан корректно, но не совсем рационально. Мы использовали три переменных в качестве трех аргументов. А если собрать данные в массив? Получится одна переменная, причем в более короткой записи. Сможет ли
функция работать с массивом? Конечно, да! Смотрим такой вариант:
Файл chapter6/script6.3.2.html
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", ()=>{func(a);});
let a=["Иван", "Иванов", 25];
function func(d)
{
alert("Вас зовут "+
d[0]+" "+d[1]+", вам "+d[2]+" лет.");
}
});
</script>
...
<p><input type="button" id="but" value="CLICK"></p>
...
В отличие от предыдущего сценария, здесь функция оперирует элементами массива с выбранными индексами.
Как видите, в данном сценарии нет ничего экстраординарного, но тем не
менее автору не приходилось встречать в литературе по JavaScript для начинающих подобных примеров. Что ж, вы можете взять данный метод передачи аргументов себе на вооружение. Тем более, что он бывает весьма полезен: работает вполне эффективно, при этом сокращает код и упрощает чтение программ.
Кроме того, в функцию можно передать массив с любым количеством элементов, а оперировать только некоторыми из них.
Еще один важный момент. Передав в функцию аргумент в виде массива,
можно менять его отдельные элементы, а также удалять какие-то из них или
добавлять новые. Пример таких действий показан в следующем сценарии:
Файл chapter6/script6.3.3.html
<script>
let a=["Иван", "Иванов", 25];
let b=
["Петр", "Петров", 31, "Самара", "преподаватель"];
function func(d)
{
— 75 —
if(d.length<4)
d[3]="город неизвестен";
else
d.pop();
alert(d[0]+" "+d[1]+", возраст - "+d[2]+", "+d[3]+
", количество данных - "+d.length);
}
func(a);
func(b);
</script>
У нас есть два массива с данными клиентов. У Иванова они неполные —
только имя, фамилия и возраст. У Петрова — полный набор, включая город
проживания и профессию. Допустим, нам в дальнейшем необходимы только четыре параметра, а профессия нас не интересует. Функция func унифицирует информацию, сводя ее к четырем основным элементам. Когда в качестве аргумента
передан массив a, к данным Иванова добавляется запись о том, что город его
проживания неизвестен. Когда в качестве аргумента передан массив b, из данных Петрова удаляется элемент, содержащий информацию о профессии. Для
наглядности получившиеся результаты мы выводим методом alert. В дальнейшем можно использовать измененные массивы в других операциях.
6.4. Функция как аргумент функции
До сих пор мы передавали функциям в качестве аргументов числа, строки, объекты событий, переменные, массивы. Но список этот будет неполным,
если не рассказать о том, что в качестве аргумента одной функции может выступать другая функция.
Сейчас мы разберем несколько элементарных примеров.
Вариант первый — функция, выполняющая роль аргумента, сама вызывается без аргументов:
Файл chapter6/script6.4.1.html
<script>
let a=3;
function func()
{
a+=2;
return a;
}
function test(c)
{
c*=2;
alert(c);
}
test(func());
</script>
— 76 —
Здесь аргументом функции test служит функция func. Когда мы вызываем основную функцию
test(func());
то получаем из func возвращаемое значение — вычисленное значение переменной a:
a+=2;
return a;
Далее в основной функции умножаем значение из аргумента на 2 и показываем,
что у нас получилось на экране:
c*=2;
alert(c);
Если результат выполнения функции присваивается какой-то переменной, то роль аргумента будет выполнять эта переменная:
Файл chapter6/script6.4.2.html
<script>
let f=function()
{
let a=3;
a+=2;
return a;
};
function test(c)
{
c*=2;
alert(c);
}
test(f());
</script>
Данная функция очень похожа на предыдущую, только переменная a теперь не
глобальная, а локальная.
А если аргументом используется функция, сама получающая аргументы?
Все будет происходить похожим образом.
Перепишем наши примеры в соответствии с задачей.
Передача аргумента по имени функции, которая сама получает аргументы:
Файл chapter6/script6.4.3.html
<script>
function func(a)
{
a+=2;
return a;
}
function test(c)
— 77 —
{
c*=2;
alert(c);
}
test(func(3));
</script>
Передача в основную функцию аргумента по имени переменной, которая
получает результат из функции с аргументом:
Файл chapter6/script6.4.4.html
<script>
let f=function(a)
{
a+=2;
return a;
};
function test(c)
{
c*=2;
alert(c);
}
test(f(3));
</script>
В каждом из перечисленных выше сценариев у нас была только одна
функция-аргумент. На самом деле ограничений по количеству функцийаргументов нет. Их может быть несколько, что прекрасно демонстрирует следующий пример. Здесь две таких функции. Каждая производит свои вычисления, которые затем суммируются основной функцией:
Файл chapter6/script6.4.5.html
<script>
let a=3;
let b=5;
function func1()
{
a+=2;
return a;
}
function func2()
{
b+=7;
return b;
}
function test(c, g)
{
c+=g;
alert(c);
— 78 —
}
test(func1(), func2());
</script>
Пока мы рассматривали варианты абстрактных сценариев. Сейчас нам
предстоит разобрать новый скрипт, который показывает, как функцииаргументы могут быть полезны в реальных условиях.
Начнем с экспериментов. Загрузите в браузер страницу chapter6/
script6.4.6.html. Вы увидите две кнопки. Нажмите верхнюю. На экране появится прямоугольник. Продолжайте нажимать эту кнопку. Прямоугольник будет
увеличиваться в ширину (рис. 6.4.1). После седьмого клика прямоугольник
уменьшится до исходного состояния. Можете продолжать щелкать на данной
кнопке — фигура опять будет становится все шире и шире, а затем возвращаться в исходное состояние.
Рис. 6.4.1
Управляем прямоугольником с помощью кнопки WIDTH
Теперь нажмите нижнюю кнопку. Произойдет сразу два события: прямоугольник станет шире и одновременно приобретет цветовую окраску
(рис. 6.4.2). Продолжайте кликать нижнюю кнопку. Фигура будет одновременно менять размеры и цвет фона.
Поэкспериментируйте и посмотрите на результаты. Они будут весьма интересными и разнообразными. Можете первой нажимать кнопку
WIDTH+COLOR. Можете нажимать кнопки поочередно. Можете вообще использовать любую последовательность нажатия кнопок.
Наблюдаемые вами эффекты стали возможны благодаря использованию
одной функции в качестве аргумента другой.
— 79 —
Рис. 6.4.2
Управляем прямоугольником с помощью кнопки WIDTH+COLOR
Приступим к разбору сценария.
Итак, на странице есть две кнопки:
Файл chapter6/script6.4.6.html
<p>
<input type="button" id="but1" value="WIDTH">
...
<input type="button" id="but2" value="WIDTH+COLOR">
</p>
которые управляют поведением прямоугольника, расположенного под ними:
<div id="square"></div>
Следующий момент не принципиальный для работы программы, но его необходимо пояснить. В исходном состоянии прямоугольник скрыт. Нажатие любой
из кнопок приводит к его появлению на экране. Для этого у нас есть идентификатор
let v=1;
по значению которого программа определяет, надо ли показать прямоугольник
или он уже виден. Этот «разбор» происходит в начале функции func:
function func()
{
if(v==1)
{
sq.visibility="visible";
v=2;
}
...
}
— 80 —
Переменная sq — ссылка на свойства стиля прямоугольника, которые неоднократно будут меняться в процессе выполнения программы:
let sq=document.getElementById("square").style;
Значения цвета сценарий получает из массива с шестью элементами:
let ar=["#FFFFFF", "#FF9900", "#CC0000",
"#0000CC", "#006600", "#660066"];
Переменная i служит счетчиком кликов:
let i=0;
Основную работу выполняют две функции. Зарегистрируем их в качестве
обработчиков. Для кнопки WIDTH обработчиком является функция func:
document.getElementById("but1").
addEventListener("click", func);
Для кнопки WIDTH+COLOR — функция con. Обратите внимание, как мы регистрируем обработчик для этой кнопки:
document.getElementById("but2").
addEventListener("click", ()=>{con(func());});
Здесь аргументом функции con служит функция func:
con(func());
Разберем основные инструкции функции func.
После того, как фигура появилась на экране, мы задаем ей начальный
размер, который зависит от значения счетчика:
sq.width=100+i*100+"px";
Поскольку после первого клика значение счетчика все еще равно 0, ширина
прямоугольника будет 100 пикселей. После второго клика счетчик станет равен 1, и ширина увеличится до 200 пикселей. После третьего клика ширина фигуры станет 300 пикселей.
Задав размер прямоугольника после очередного клика, мы присваиваем
текущее значение счетчика переменной res:
let res=i;
Она хранит возвращаемое значение на тот случай, когда функция func будет
использована в качестве аргумента:
return res;
Теперь увеличиваем счетчик на единицу:
i++;
Если его значение достигло предела, равного 6, то сбрасываем счетчик в исходное состояние, чтобы вернуть фигуре начальные размеры:
if(i==6)
i=0;
Нажатие кнопки WIDTH+COLOR запускает функцию con. Она состоит
всего из одной инструкции:
— 81 —
function con(c)
{
sq.background=ar[c];
}
В этой инструкции мы задаем текущий цвет фону прямоугольника. Индекс
элемента массива мы получаем из возвращаемого значения функции func. При
этом одновременно выполняются и основные инструкции функции-аргумента.
Таким образом, нажатие кнопки WIDTH+COLOR приводит к одновременному
изменению размера и цвета фигуры.
Если задать какой-то цвет кнопкой WIDTH+COLOR, а потом нажимать
только кнопку WIDTH, то вы увидите, что меняющий размеры прямоугольник
все время остается окрашенным в выбранный цвет.
К сказанному выше нужно добавить, что функции в качестве аргументов
используются также при создании обратных вызовов в синхронном и асинхронном программировании. В следующем разделе мы познакомимся с таким
примером.
6.5. Обратный вызов
Как мы до сих пор передавали функцию в качестве аргумента? Вот упрощенный пример:
Файл chapter6/script6.5.1.html
<script>
let a=3;
function func()
{
a*=2;
return a;
}
function test(c)
{
alert(c);
}
test(func());
</script>
Получалась такая последовательность операций:
вызываем основную функцию с аргументом в виде вспомогательной
функции:
test(func());
производим вычисления во вспомогательной функции:
function func()
{
a*=2;
return a;
}
— 82 —
результаты вычислений используем в основной функции:
function test(c)
{
alert(c);
}
Теперь рассмотрим обратный вызов. Это ситуация, когда функцияаргумент вызывается по завершении какого-либо процесса:
Файл chapter6/script6.5.2.html
<script>
let a=3;
function func(b)
{
alert(b);
}
function test(c)
{
a*=2;
c(a);
}
test(func);
</script>
Как видите, при обратном вызове используется другой алгоритм:
вызываем основную функцию с аргументом в виде имени вспомогательной функции:
test(func);
производим вычисления в основной функции:
function test(c)
{
a*=2;
c(a);
}
по завершении вычислений передаем результаты во вспомогательную
функцию:
function func()
{
alert(a);
}
Надо обратить внимание на еще одно важное отличие между первым и
вторым примером. В сценарии script6.5.1.html мы записывали в аргументе
именно вызов функции:
test(func());
То есть рядом с именем функции-аргумента мы ставили круглые скобки.
При обратном вызове аргументом служит только имя функции:
— 83 —
test(func);
В этом случае рядом с именем функции-аргумента скобки не ставятся.
Обратные вызовы особенно удобно использовать, если в теле основной
функции запланирован вызов разных функций. Рассмотрим пример, в котором
на этапах выполнения сценария происходит несколько обратных вызовов. При
этом имена функций, передаваемых аргументами, — разные:
Файл chapter6/script6.5.3.html
<script>
let a=3;
function func(b)
{
alert(100+b);
}
function res(d)
{
alert(200+d);
}
function sum(k)
{
alert(300+k);
}
function test(c)
{
a*=2;
c(a);
}
test(func);
test(res);
test(sum);
</script>
Если запустить этот файл в браузере, вы увидите, что основная функция
test по очереди передает данные сначала в func, затем в res, а следом в sum.
В результате будут последовательно показаны три диалоговых окна с цифрами 106, 212 и 324. Получается так потому, что мы трижды запускали функцию
test, каждый раз меняя значение аргумента.
6.6. Обещанного три года ждут
Эта поговорка вспоминается автору всякий раз, когда речь заходит о промисах. Впрочем, о них чуть позже. А пока — важная предварительная часть.
Раз уж мы упомянули в предыдущем разделе про асинхронное программирование, продолжим эту тему.
— 84 —
По умолчанию все инструкции в сценарии JavaScript выполняются синхронно — то есть по очереди одна за другой. Пока не будет выполнена первая
инструкция, вторая не запустится. Если возникает ситуация, что какой-то блок
кода выполняется долго, может произойти «зависание» браузера. Все остальные операции в нем прекратятся до завершения текущего процесса.
Чтобы избежать такой ситуации, в программах используют асинхронный
код.
Типичный пример асинхронной операции — загрузка данных с сервера,
выполняющаяся по технологии Ajax.
Как обычно выглядит эта процедура? Для демонстрации такого подхода
напишем страницу, в которой будут одновременно выполняться две операции:
подсчет времени с момента запуска страницы в браузере и загрузка данных с
сервера.
Итак, у нас есть документ, в котором два поля для вывода результатов.
Показания таймера демонстрируются в верхнем:
Файл chapter6/script6.6.1.html
<p id="tm">0</p>
Информация об успешной или неудачной загрузке — в нижнем:
<p id="ins">Идет загрузка...</p>
Запускаем таймер:
let sum=0;
setInterval(tim, 1000);
function tim()
{
sum++;
document.getElementById("tm").innerHTML=sum;
}
Переменная sum выполняет роль счетчика, значение которого увеличивается на
единицу каждую секунду.
Одновременно начинаем загрузку файла с сервера:
pro();
function pro()
{
...
}
Процесс выглядит так. Создаем новый экземпляр объекта XMLHttpRequest:
let re=new XMLHttpRequest();
Формируем запрос
re.open("POST", "img.doc", true);
и отправляем его серверной программе
re.send();
— 85 —
Ставим сценарию задачу вывести ответ в соответствующее поле:
re.addEventListener("load", function()
{
...
});
В этом блоке две инструкции. Первая — на случай удачной загрузки:
if(this.readyState == 4 && this.status == 200)
document.getElementById("ins").innerHTML=
"Загрузилось";
Вторая инструкция — на случай какого-либо сбоя:
else
document.getElementById("ins").innerHTML=
"Не загрузилось";
Как видите, все просто.
К сожалению, у разобранного сценария (как, впрочем, и у следующего,
который нам только предстоит рассмотреть) два недостатка.
Во-первых, данная программа будет работать только на локальном или
реальном хостинге. Но мы не создавали локальный хостинг, так как для всех
остальных сценариев он не нужен: другие страницы запускаются как обычно —
двойным щелчком на ярлыке.
Во-вторых, даже если вы создадите на своем компьютере локальный хостинг, загрузка файла с вашего компьютера на ваш же компьютер произойдет
так быстро, что вы не сможете ощутить всей «прелести» асинхронной работы
скрипта. Надпись, подтверждающая успешную загрузку, и первое показание
счетчика появятся практически одновременно.
Если вам все же интересно посмотреть, как эти процессы выполняются в
реальных условиях, загляните на полностью аналогичную страницу по адресу
https://testjs.ru/PROM/script6.6.1.html. Вы увидите, что таймер отсчитает несколько секунд, прежде чем файл будет загружен.
В следующем примере мы воспользуемся «услугами» промиса. Что это
такое и с чем его «едят»?
Термин произошел от английского «promise», что переводится как «обещание». По сути промис — это объект, предназначенный для сохранения значения, которое будет получено в результате выполнения асинхронной операции. Хотя в начале такой операции результат ее выполнения еще отсутствует,
промис «обещает», что при любом исходе сценарий получит то или иное значение. Если операция выполнена успешно, промис подтверждается. Если из-за
какой-либо ошибки операция не была выполнена, промис отклоняется. До тех
пор, пока результат не получен, промис находится в состоянии ожидания. Поэтому поговорку, вынесенную в заголовок этого раздела, можно тоже назвать
житейским промисом с предполагаемым временем ожидания.
Синтаксис промиса:
new Promise(function(resolve, reject) {...});
или
— 86 —
new Promise((resolve, reject)=> {...});
Здесь аргумент resolve — имя функции, которая будет вызвана при
успешном завершении операции. Аргумент reject — имя функции, которая будет вызвана при неудаче.
Для обработки результата промиса используется три метода:
then — выполняет заданную функцию при подтверждении промиса;
catch — выполняет заданную функцию, если промис отклонен;
finally — выполняет заданную функцию вне зависимости от значения
промиса.
Теперь посмотрим, как изменится наш предыдущий сценарий, если мы
используем в нем объект Promise.
Опять на странице два поля:
Файл chapter6/script6.6.2.html
<p id="tm">0</p>
<p id="ins">Идет загрузка...</p>
Все та же функция управляет работой таймера:
let sum=0;
setInterval(tim, 1000);
function tim()
{
sum++;
document.getElementById("tm").innerHTML=sum;
}
А функция загрузки данных опять называется pro:
pro();
function pro()
{
...
}
Но теперь у нее наряду со «старыми» появились новые инструкции.
Создаем переменную prom, через которую мы будем обращаться к результатам выполнения промиса:
let prom=new Promise((resolve, reject)=>
{
...
});
Следующие три инструкции нам уже знакомы:
let re=new XMLHttpRequest();
re.open("POST", "img.doc", true);
re.send();
Обрабатывает ответ сервера анонимная функция:
re.addEventListener("load", function()
{
— 87 —
...
});
Если промис подтвержден, вызываем функцию resolve:
if(this.readyState == 4 && this.status == 200)
resolve("Загрузилось");
Если промис отклонен, вызываем функцию reject:
else
reject("Не загрузилось");
Обрабатываем результат, используя методы then и catch:
prom .then(c=>document.getElementById("ins").
innerHTML=c)
.catch(d=>document.getElementById("ins").
innerHTML=d);
Если вам хочется посмотреть, как второй сценарий выполняется в реальных условиях, загляните на полностью аналогичную страницу по адресу
https://testjs.ru/PROM/script6.6.2.html. Вы увидите, что программа дает такой
же результат, как и в первом сценарии.
— 88 —
7. ПРОВЕРЯЕМ УСЛОВИЯ
Понятие условий очень хорошо иллюстрирует следующий пример. Проходит соревнование филателистов. Надо определить победителя по критерию
«у кого коллекция больше». У Олега 110 марок, а у Сергея — 118. Как видим,
сравнение приведет к тому, что победителем будет объявлен Сергей. Здесь проверяется простое условие: если у Олега больше всех марок, значит он победитель. Если у Сергея больше марок — значит приз достанется ему. Истинным
оказалось второе утверждение, и члены жюри награждают Сергея.
Примерно то же самое происходит и при ветвлении программного кода.
Проверка различных условий — важнейшая составляющая JavaScript. Очень
часто сценарий решает задачу: какие действия выполнить в зависимости от
значения какой-то переменной или от истинности какого-то выражения. Цель
проверки условий — направить программу в то или иное русло (иногда направлений может быть гораздо больше двух).
В этой главе мы разберем как стандартные подходы при формулировании
условий в сценариях, так и не совсем обычные.
7.1. Операторы сравнения и логические операторы
Начнем рассказ об условиях с операторов. В первую очередь перечислим
операторы сравнения, которые представлены в таблице 7.1.1.
Таблица 7.1.1
Операторы сравнения
Оператор
Проверяемое условие
Пример
==
Равно
x==y
!=
Не равно
x!=y
>
Больше
x>y
>=
Больше или равно
x>=y
<
Меньше
x<y
<=
Меньше или равно
x<=y
===
Строго равно
x===y
!==
Строго не равно
x!==y
Про последние два оператора хотелось бы рассказать подробнее. Назначение данных операторов можно продемонстрировать на четырех примерах.
Пример 1.
Файл chapter7/script7.1.1.html
<script>
let a=0;
if(a==0)
— 89 —
{
alert("Условие истинно");
}
else
{
alert("Условие ложно");
}
</script>
Пример 2.
Файл chapter7/script7.1.2.html
<script>
let a="0";
if(a==0)
{
alert("Условие истинно");
}
else
{
alert("Условие ложно");
}
</script>
Если по очереди запустить эти файлы в браузере, вы убедитесь: несмотря
на различие типов данных, программа считает, что в обоих случаях значение
переменной x равно 0 и, соответственно, выводит подтверждающее сообщение.
А все потому, что мы использовали оператор простого сравнения ==. Чтобы избежать подобных ошибок, и существуют операторы строгого сравнения. Применив один из них (===) в третьем и четвертом примерах, мы получим требуемый результат.
Пример 3.
Файл chapter7/script7.1.3.html
<script>
let a="0";
if(a===0)
{
alert("Условие истинно");
}
else
{
alert("Условие ложно");
}
</script>
В этом примере диалоговое окно выведет сообщение Условие ложно, так
как условие строгого равенства не выполнено.
Пример 4.
Файл chapter7/script7.1.1.html
<script>
let a=0;
— 90 —
if(a===0)
{
alert("Условие истинно");
}
else
{
alert("Условие ложно");
}
</script>
В этом случае мы получим сообщение об истинности выражения, так как
условие строгого равенства выполнено.
Вторая таблица 7.1.2 содержит перечень логических операторов.
Таблица 7.1.2
Логические операторы
Оператор
Действие
Возвращаемый результат
&&
Логическое умножение (И)
true, если все операнды истинны, и false, если хотя бы один операнд ложен
!
Логическое отрицание (НЕ)
true, если операнд ложен, и false, если операнд истинен
||
Логическое сложение (ИЛИ)
true, если хотя бы один операнд истинен, и
false, если все операнды ложны
Теперь вернемся к операторам сравнения. Обращаю ваше внимание, что
запись двойного сравнения, к которой мы привыкли в школе, в JavaScript не
сработает. Так писать нельзя: 1<x<10.
Правильная запись: x>1&&x<10. В этом варианте выполняется корректная проверка обоих условий, а затем логическое умножение, о котором мы говорили только что.
7.2. Проверка условий
Попробуем дать краткую формулировку применению условных выражений: они необходимы для проверки определенных данных на соответствие некоторым критериям.
Вариант наиболее простого использования условного оператора выглядит
так:
if(условие)
{
инструкции
}
Здесь инструкции будут выполняться в случае истинности условия.
Часто необходимо предусмотреть альтернативные инструкции, если проверка закончилась неудачей. В этом случае используют следующую форму
записи:
if(условие)
{
инструкции 1
— 91 —
}
else
{
инструкции 2
}
Инструкции блока else будут выполняться в случае ложности основного
условия.
Бывает, надо проверить несколько условий. В этой ситуации используют
такую конструкцию:
if(условие 1)
{
инструкции 1
}
else if(условие 2)
{
инструкции 2
}
...
else if(условие n)
{
инструкции n
}
else
{
инструкции на случай ложности всех условий
}
Иногда требуется проверить нескольких условий, но при этом не выполнять никаких действий, если все условия окажутся ложными. Представим, что у
нас есть переменная a, которая может принимать разные числовые значения, но
для нас важны только три из них: 1, 2 и 3. В этом случае условные выражения
можно записать так
if(a==1)
{
инструкции 1
}
if(a==2)
{
инструкции 2
}
if(a==3)
{
инструкции 3
}
и даже так:
if(a==1)
{
инструкции 1
}
else if(a==2)
{
инструкции 2
}
else if(a==3)
— 92 —
{
инструкции 3
}
Обратите внимание: в последнем примере мы отказались от использования блока else с альтернативными инструкциями в случае ложности условий.
Действенность такого подхода иллюстрирует следующий пример.
У нас есть страница с текстовым полем и кнопкой:
Файл chapter7/script7.2.1.html
<input id="inp">
<input type="button" value="CLICK" id="but">
В поле можно ввести число или строку. Нажатие кнопки запускает функцию
func:
document.getElementById("but").
addEventListener("click", func);
function func()
{
...
}
Первая операция функции — получение значения, введенного в поле:
let a=document.getElementById("inp").value;
Далее следует определение, введено ли одно из трех чисел: 1, 2 или 3. Для
этого мы использовали проверку трех условий, но без альтернативного блока
else:
if(a==1)
{
alert("ОДИН");
}
else if(a==2)
{
alert("ДВА");
}
else if(a==3)
{
alert("ТРИ");
}
В случае истинности одного из условий на экране появляется диалоговое окно.
Если же введено любое другое число или строка, на странице ничего не произойдет.
Обычно в литературе по синтаксису JavaScript авторы не сообщают о необязательности блока else для проверки множественных условий. Что ж, мы
ликвидировали этот пробел.
Еще один способ проверки — оператором выбора switch. Он может быть
полезен как альтернатива рассмотренным выше случаям проверки нескольких
условий. Выражения с использованием данного оператора выглядят так:
switch (выражение)
{
— 93 —
case значение 1:
инструкции 1
break;
case значение 2:
инструкции 2
break;
...
case значение n:
инструкции n
break;
default:
инструкции на случай отсутствия совпадений
}
Здесь значение из каждого блока последовательно сравнивается с заданным выражением. В случае совпадения выполняются инструкции соответствующего блока. На случай отсутствия совпадений предусмотрены инструкции в
блоке default.
Посмотрим, как можно, немного изменив предыдущий сценарий, использовать оператор switch:
Файл chapter7/script7.2.2.html
let a=parseInt(document.getElementById("inp").value);
switch (a)
{
case 1:
alert("ОДИН");
break;
case 2:
alert("ДВА");
break;
case 3:
alert("ТРИ");
break;
default:
alert("Нет совпадения");
}
Если при отсутствии совпадений никакие действия не нужны, то блок
default можно не использовать:
switch (a)
{
case 1:
инструкции 1
break;
case 2:
инструкции 2
break;
case 3:
инструкции 3
}
Кроме того, блоки можно объединять, если при разных значениях должны выполняться аналогичные инструкции:
switch (a)
— 94 —
{
case 1:
инструкции 1
break;
case 2:
case 3:
инструкции 2
}
Оператор break не является обязательным, но без него вы столкнетесь с
одним неприятным эффектом: после того, как будет найдено совпадение, продолжат выполняться все последующие блоки, включая default. Данную проблему иллюстрирует следующий пример:
Файл chapter7/script7.2.3.html
switch (a)
{
case 1:
alert("ОДИН");
case 2:
alert("ДВА");
case 3:
alert("ТРИ");
default:
alert("Нет совпадения");
}
Запустите в браузере этот файл и введите число 1. Вы убедитесь, что диалоговое окно появится все четыре раза, невзирая на то, что совпадение всего
одно. Согласитесь, это совсем не то, что нам требуется от программы. Вот почему так важен оператор break. Он прерывает выполнение оператора switch
срезу после того, как найдено первое совпадение.
Однако, отказавшись в отдельных частях выражений выбора от break,
можно получить интересные эффекты. Предположим, нам надо, чтобы при совпадении первого значения выполнялась первая инструкция, а при совпадении
второго значения — вторая и одновременно третья инструкции. При совпадении третьего значения — только третья. Такой подход реализован в следующем
сценарии:
Файл chapter7/script7.2.4.html
switch (a)
{
case 1:
alert("ОДИН");
break;
case 2:
alert("ДВА");
case 3:
alert("ТРИ");
break;
default:
alert("Нет совпадения");
}
— 95 —
Данный метод может оказаться полезным в сценариях со сложной программой выбора исполняемых инструкций.
7.3. Нужны ли фигурные скобки?
Если у вас после проверки условия выполняется только одна инструкция,
то можно использовать сокращенную форму записи выражения:
if(условие)
инструкция
или
if(условие)
инструкция 1
else
инструкция 2
или
if(условие 1)
инструкция 1
else if(условие 2)
инструкция 2
...
else if(условие n)
инструкция n
else
инструкция на случай ложности всех условий
Как видите, в таких условных выражениях можно не заключать единственную инструкцию в фигурные скобки.
Чтобы продемонстрировать данный подход, создадим новый сценарий,
внеся изменения в файл script7.2.1.html:
Файл chapter7/script7.3.1.html
if(a==1)
alert("ОДИН");
else if(a==2)
alert("ДВА");
else if(a==3)
alert("ТРИ");
else
alert("Нет совпадения");
Здесь во всех блоках условий мы использовали сокращенную запись.
Возможен также смешанный вариант, когда в условном выражении у
одиночных инструкций фигурные скобки отсутствуют, а в блоке с несколькими
инструкциями скобки есть. Данную ситуацию демонстрирует такой сценарий:
Файл chapter7/script7.3.2.html
if(a==1 || a==2 || a==3)
alert("Есть совпадение");
else
{
— 96 —
alert("Нет совпадения");
alert("Попробуйте еще раз");
}
Автор использовал в книге следующий подход: во всех условиях, где после проверки выполняется только одна инструкция, фигурные скобки не ставятся.
Еще один случай, когда можно обойтись сокращенной записью: иногда
удобнее выполнять проверку условий тернарным оператором. Он принимает
три операнда: условие, инструкцию, которая выполняется, если условие истинно, и инструкцию, которая выполняется, если условие ложно. Его запись выглядит так:
let a=условие ? инструкция 1 : инструкция 2;
Как видите, результат выполнения тернарного оператора можно присвоить переменной.
Пример реального использования тернарного оператора, частично основанный на предыдущем сценарии:
Файл chapter7/script7.3.3.html
function func()
{
let a=document.getElementById("inp").value;
(a==1 || a==2 || a==3) ?
alert("Есть совпадение") :
alert("Нет совпадения");
}
7.4. Функция как условие в условии
И еще один интересный метод при использовании условных операторов.
Обозначим этот прием так: в условные выражения можно передавать возвращаемые значения из внешних функций.
Рассмотрим три варианта:
вызов функции в левой части условного выражения;
вызов функции в правой части условного выражения;
вызов функции в качестве всего условного выражения.
Двигаемся по порядку.
Первый вариант демонстрируется в следующем примере:
Файл chapter7/script7.4.1.html
<script>
function func()
{
return Math.random();
}
if(func()>0.5)
— 97 —
alert(true);
else
alert(false);
</script>
Очень простой сценарий. После загрузки страницы регистрируем функцию, которая возвращает случайное число от 0 до 1:
function func()
{
return Math.random();
}
А затем выполняем код, состоящий из двух условных блоков:
if(func()>0.5)
alert(true);
else
alert(false);
Как видите, здесь в левой части условного выражения происходит вызов функции func, и на основе возвращаемого ею значения выполняется оценка истинности или ложности сравнения.
Запустите страницу в браузере и, регулярно обновляя ее, посмотрите, как
меняется сообщение в диалоговом окне.
Рассмотрим похожий сценарий, в котором вызов функции происходит в
правой части условного выражения:
Файл chapter7/script7.4.2.html
<script>
function func()
{
return Math.random();
}
if(0.5>func())
alert(true);
else
alert(false);
</script>
В этой программе вполне успешно сравнивается значение из левой части выражения с возвращаемым значением из функции.
Наконец, третий вариант. Сейчас у нас вызов функции выполняет роль
условного выражения. Заодно мы передаем в эту функцию аргумент, на основе
которого формируется возвращаемое значение. Для демонстрации данного
примера разместим на странице две кнопки
Файл chapter7/script7.4.3.html
<input type="button" value="TRUE" id="bu1">
<input type="button" value="FALSE" id="bu2">
которые запускают одну и ту же функцию but:
document.getElementById("bu1").
— 98 —
addEventListener("click", but);
document.getElementById("bu2").
addEventListener("click", but);
...
function but(ev)
{
let t= parseInt(ev.target.id.substr(2));
if(func(t))
alert(true);
else
alert(false);
}
Первым делом в функции but из id выделяется число:
let t=parseInt(ev.target.id.substr(2));
А затем проверяется условие
if(func(t))
alert(true);
else
alert(false);
истинность или ложность которого вычисляется путем вызова функции func с
полученным числом в качестве аргумента:
function func(a)
{
if(a==1)
return true;
if(a==2)
return false;
}
Таким образом, на истинность проверяется значение, возвращаемое из функции.
Если запустить в браузере указанную страницу, то вы увидите следующую картину: после нажатия первой кнопки появится диалоговое окно с сообщением «TRUE». Если нажать вторую кнопку, то сообщение будет «FALSE».
Читатели должны понимать, что перечисленные сценарии — демонстрационные и не имеют практической пользы. Но данные программы разъясняют
принцип, которым можно воспользоваться в реальных разработках, если это
необходимо.
— 99 —
8. ЗАПУСК ЦИКЛОВ
Операторы циклов необходимы для перебора некоторых значений в заданных пределах и поочередного выполнения определенных инструкций, иногда зависящих от значения на очередном проходе, а иногда — нет. Циклы
удобно использовать в тех случаях, когда какую-то часть программного кода
надо выполнить многократно.
Например, с помощью цикла удобно проходить по всем элементам массива или выполнять одинаковые (а иногда и неодинаковые) изменения свойств
группы элементов страницы. И даже регистрировать обработчики событий
(в чем мы убедимся, изучая главу 11).
8.1. Простые циклы
Наиболее универсален оператор for. Количество проходов в таком цикле
фиксируется переменной-счетчиком. Ограничивает число проходов — условие.
Структура цикла for обычно выглядит так:
for(исходное значение; условие; приращение)
{
инструкции
}
Исходное значение — это значение, установленное перед началом цикла
переменной-счетчику. Условие — выражение, с помощью которого проверяется, может ли выполняться цикл далее после очередного прохода (или может ли
он выполняться вообще). Приращение — значение, на которое увеличивается
счетчик после каждого прохода.
В реальном сценарии цикл оформляют, например, следующим образом.
Допустим, у нас на странице есть три фотографии:
Файл chapter8/script8.1.1.html
<img id="im1" class="im"
src="../chapter13/img1/im1.jpg" alt="Фото">
<img id="im2" class="im"
src="../chapter13/img1/im2.jpg" alt="Фото">
<img id="im3" class="im"
src="../chapter13/img1/im3.jpg" alt="Фото">
Каждая — размером 200 пикселей:
.im {width: 200px;}
Под ними кнопка
<input type="button" value="CLICK" id="but">
нажатие которой запускает сценарий увеличения размера картинок:
document.getElementById("but").
addEventListener("click", func);
— 100 —
Функция содержит цикл, который перебирает по id все фотографии и меняет их
размеры:
function func()
{
for(let i=1; i<4; i++)
{
document.getElementById("im"+i).
style.width="300px";
}
}
В цикле for допустимо объявлять сразу несколько переменных-счетчиков
и одновременно производить над ними различные операции. Изменим страницу, поместив в ее разметку еще три фото
Файл chapter8/script8.1.2.html
<img id="image4" class="im2"
src="../chapter13/img1/im4.jpg" alt="Фото">
<img id="image5" class="im2"
src="../chapter13/img1/im5.jpg" alt="Фото">
<img id="image6" class="im2"
src="../chapter13/img1/im6.jpg" alt="Фото">
большего размера:
.im2 {width: 300px;}
Также внесем изменения в описание цикла, добавив в него еще одну переменную:
for(let i=1, k=6; i<4; i++, k--)
{
document.getElementById("im"+i).
style.width="300px";
document.getElementById("image"+k).
style.width="200px";
}
Здесь одновременно выполняются две операции: на каждом проходе значение
счетчика i возрастает, а значение счетчика k убывает. Переменная i участвует в
увеличении размеров первых трех снимков, а переменная k — в уменьшении
размеров трех последних. Результат работы программы показан на рисунке 8.1.1.
Рис. 8.1.1
Вид страницы до и после запуска сценария
— 101 —
Следующий сценарий демонстрирует, что в цикле можно использовать
условия, состоящие из нескольких частей. Кроме того, мы заодно увидим ситуацию, о которой было сказано выше: в зависимости от того, как сформулировано условие, цикл может выполняться или нет.
Вновь у нас три рисунка, кнопка, а между ними текстовое поле для ввода
числа — количества фотографий, размеры которых должны быть изменены:
Файл chapter8/script8.1.3.html
<input id="inp">
Если число не введено, сценарий не сработает.
Итак, изображений — три. Если мы введем число 1, то увеличится первая
картинка. Введем 2 — первая и вторая. Введем 3 — увеличатся все снимки.
А если ввести отрицательное число, 0 или больше 3? Цикл не будет запущен,
так как в нем есть два условия:
for(let i=1; i<=a && a<4; i++)
{
document.getElementById("im"+i).
style.width="300px";
}
Первое условие
i<=a
ограничивает количество фотографий, которые участвуют в процессе. Второе
условие ограничивает значение, введенное пользователем:
a<4
Присвоив счетчику начальное значение 1, мы тем самым установили нижнюю
границу вводимого числа.
Иногда необходимо прервать цикл или пропустить какой-то из его проходов.
Для первого случая используется оператор break. Следующая программа
увеличивает только два снимка, прерывая цикл после второго прохода:
Файл chapter8/script8.1.4.html
for(let i=1; i<4; i++)
{
document.getElementById("im"+i).
style.width="300px";
if(i==2)
break;
}
Чтобы пропустить один из проходов цикла, воспользуйтесь оператором
continue. В примере ниже пропускается второй проход, в результате чего увеличиваются только первый и третий снимок.
Файл chapter8/script8.1.5.html
— 102 —
for(let i=1; i<4; i++)
{
if(i==2)
continue;
document.getElementById("im"+i).
style.width="300px";
}
Исходное значение, условие и приращение не являются обязательными в
установках цикла.
Можно использовать в качестве счетчика внешнюю переменную:
Файл chapter8/script8.1.6.html
let i=1;
for(; i<4; i++)
{
document.getElementById("im"+i).style.width="300px";
}
Условие можно проверять в теле цикла:
Файл chapter8/script8.1.7.html
for(let i=1; ; i++)
{
document.getElementById("im"+i).
style.width="300px";
if(i==3)
break;
}
Приращение значения счетчика также может выполняться в теле цикла:
Файл chapter8/script8.1.8.html
for(let i=1; i<4;)
{
document.getElementById("im"+i).
style.width="300px";
i++;
}
Наконец, возможен даже такой вариант, когда установки цикла не содержат вообще ничего:
Файл chapter8/script8.1.9.html
let i=1;
for(; ;)
{
document.getElementById("im"+i).
style.width="300px";
i++;
— 103 —
if(i>3)
break;
}
В следующем сценарии покажем, как для перебора элементов массива
можно в цикл for в качестве условия вставить переменную, которая содержит
массив:
Файл chapter8/script8.1.10.html
function func()
{
let ar=[1, 2, 3];
for(let i=0; ar; i++)
{
document.getElementById("im"+ar[i]).
style.width="300px";
}
}
Здесь цикл выполняется, пока не закончатся элементы массива. Начальное значение 0 присвоено счетчику из соображений, что отсчет индексов элементов массива начинается с нуля.
Теперь рассмотрим оператор с похожим названием и не похожим внутренним устройством — for...of. Его структура имеет следующий вид:
for(переменная of объект)
{
инструкции с участием переменной
}
В операторе for...of на каждом проходе переменной присваивается очередной
элемент объекта, после чего с переменной выполняются определенные действия.
Работу данного цикла мы разберем на примере обхода элементов массива. Приведенный ниже сценарий вычисляет сумму квадратов чисел, полученных из массива:
Файл chapter8/script8.1.11.html
<script>
let sum=0;
let ar=[1, 2, 3, 4, 5];
for(let i of ar)
{
sum+=i**2;
}
alert(sum);
</script>
В этой программе на каждом проходе цикла очередное значение из массива помещается в переменную i, с которой и производятся последующие манипуляции.
— 104 —
Еще один случай — обработка строки (подробнее о строках — в главе 10):
Файл chapter8/script8.1.12.html
<script>
let sum="";
let text="JavaScript";
for(let t of text)
{
sum+=t+" ";
}
alert(sum);
</script>
В данном сценарии на каждом проходе переменной t присваивается очередной
символ из строки, после чего к символу добавляется пробел.
Третий оператор цикла, который мы рассмотрим, — while. Он устроен
несколько проще, чем предыдущие:
while(условие)
{
инструкции
}
Чтобы показать, как цикл может быть использован в реальных условиях,
напишем новую программу на основе сценария из файла script8.1.1.html, заменив в функции func оператор for на while:
Файл chapter8/script8.1.13.html
function func()
{
let i=1;
while(i<4)
{
document.getElementById("im"+i).
style.width="300px";
i++;
}
}
В этом примере роль счетчика выполняет переменная, объявленная вне
тела цикла. Но ее значения меняются именно в цикле.
Для перебора элементов массива в цикл while в качестве условия также
можно вставить переменную, которая содержит массив. Этот подход демонстрирует вот такой скрипт, созданный на основе предыдущих:
Файл chapter8/script8.1.14.html
function func()
{
let i=0;
— 105 —
let ar=[1, 2, 3];
while(ar)
{
document.getElementById("im"+ar[i]).
style.width="300px";
i++;
}
}
Наконец, четвертый оператор цикла: do...while. Этот оператор имеет одно
принципиальное отличие от while. В цикле while сначала проверяется условие,
а потом, если оно истинно, выполняются инструкции. Таким образом, если
условие изначально ложно, ни одного прохода не состоится. Оператор
do...while сначала выполняет инструкции, а потом проверяет условие. Поэтому
даже при изначально ложном условии инструкции будут выполнены один раз.
Форма записи оператора do...while:
do
{
инструкции
}
while(условие)
Вновь изменим неоднократно модифицированный сценарий из этого раздела. Теперь у нас на странице опять появилось текстовое поле для ввода числа.
Программа увеличивает все три картинки, если в текстовое поле введено любое
число больше 0. Но если мы введем отрицательное значение или 0, цикл все
равно совершит один проход и увеличит первое изображение:
Файл chapter8/script8.1.15.html
let a=parseInt(document.getElementById("inp").value);
let i=1;
do
{
document.getElementById("im"+i).
style.width="300px";
i++;
}
while(a>0 && i<=3);
Условие
a>0 && i<=3
определяет, что значение, введенное в текстовое поле, должно быть больше нуля, а количество проходов — не более трех.
В описании цикла do...while главный акцент сделан на том, что этот оператор обеспечивает минимум однократное выполнение инструкций. Однако
справедливости ради надо сказать, что с помощью цикла for иногда можно достичь точно такого же эффекта. Это утверждение иллюстрирует следующий
пример:
— 106 —
Файл chapter8/script8.1.16.html
let a=parseInt(document.getElementById("inp").value);
for(let i=1; ; i++)
{
document.getElementById("im"+i).
style.width="300px";
if(a<=1 || i==3)
break;
}
Мы применили цикл, в котором истинность условия проверяется в теле
цикла. Сначала один раз выполняется инструкция, а затем проверяется, может
состояться следующий проход или нет:
if(a<=1 || i==3)
break;
Если введенное пользователем число меньше единицы, то выполнение цикла
прерывается. Если больше единицы, то совершаются следующие проходы, пока
счетчик не достигнет верхнего значения:
i==3
Запустите файл в браузере, и вы увидите, что мы получили результат, совершенно аналогичный тому, что был достигнут с помощью цикла do...while.
А за счет того, что в цикле for счетчик был объявлен в установочной части,
этот вариант оказался на одну строку короче, чем в случае с do...while. Для организации работы цикла for нам понадобилось 6 строк кода, а для цикла
do...while — 7.
8.2. Вновь про фигурные скобки
Как и в случае с условными операторами, если в теле цикла выполняется
только одна инструкция, то можно использовать сокращенную форму записи:
for(...)
инструкция
Данный подход иллюстрирует следующий пример:
Файл chapter8/script8.2.1.html
<script>
let a=1;
for(let i=0; i<5; i++)
a+=i;
alert(a);
</script>
В этом цикле на каждом проходе к предыдущей сумме добавляется очередное значение счетчика.
Тот же принцип можно использовать в случае оператора for...of:
— 107 —
Файл chapter8/script8.2.2.html
<script>
let a=1;
let ar=[0, 1, 2, 3, 4];
for(let i of ar)
a+=i;
alert(a);
</script>
Сокращенная форма записи подойдет и для оператора while:
Файл chapter8/script8.2.3.html
<script>
let i=0;
while(i<5)
i++;
alert(i);
</script>
По такой же методике можно написать сокращенную форму цикла
do...while:
Файл chapter8/script8.2.4.html
<script>
let i=0;
do
i++;
while(i<5);
alert(i);
</script>
8.3. Функция как условие в цикле
Как и условные операторы, циклы for и while могут получать условия и
приращения из внешних функций. Вот пример на данную тему:
Файл chapter8/script8.3.1.html
<script>
let sum=1;
for(let a=func(); a<7; a++)
sum+=sum*a;
function func()
{
let b=2;
— 108 —
b**=2;
return b;
}
alert(sum);
</script>
В этом сценарии исходное значение счетчика оператор цикла получает из
функции.
Следующая программа получает из функции значение для использования
его в условном выражении:
Файл chapter8/script8.3.2.html
<script>
let sum=1;
for(let a=4; a<func(); a++)
sum+=sum*a;
function func()
{
let b=2;
b+=5;
return b;
}
alert(sum);
</script>
Третий сценарий этого раздела демонстрирует вызов функции вместо выражения приращения:
Файл chapter8/script8.3.3.html
<script>
let a=4;
let sum=1;
for(; a<7; func())
sum+=sum*a;
function func()
{
a++;
}
alert(sum);
</script>
Четвертый скрипт показывает, как значение приращения может задаваться функцией:
Файл chapter8/script8.3.4.html
<script>
let sum=1;
— 109 —
for(let a=4; a<7; a+=func())
sum+=sum*a;
function func()
{
return 1;
}
alert(sum);
</script>
Вызовы функций могут присутствовать во всех установочных частях
цикла:
Файл chapter8/script8.3.5.html
<script>
let a;
let sum=1;
for(a=func1(); a<func2(); func3())
sum+=sum*a;
function func1()
{
let b=2;
b**=2;
return b;
}
function func2()
{
let с=2;
с+=5;
return с;
}
function func3()
{
a++;
}
alert(sum);
</script>
8.4. Реверсивный запуск цикла
Обычно один цикл работает в одном направлении: значение счетчика либо увеличивается, либо уменьшается. Получение приращения из внешней
функции может быть полезно, чтобы «заставить» работать один и тот же цикл в
разных «направлениях». Вот код страницы, демонстрирующей такой прием:
Файл chapter8/script8.4.1.html
...
<script>
addEventListener("load", function()
— 110 —
{
document.getElementById("bup").
addEventListener("click", but);
document.getElementById("bum").
addEventListener("click", but);
});
let a=1;
let b=function(bt)
{
if(bt=="bup")
a++;
if(bt=="bum")
a--;
};
function but(ev)
{
let c=ev.target.id;
for(; a>0 && a<6; b(c))
alert(a);
if(a==6)
a=5;
if(a==0)
a=1;
}
</script>
...
<input type="button" value="Вперед" id="bup">
<input type="button" value="Назад" id="bum">
...
В этом документе у нас две кнопки
<input type="button" value="Вперед" id="bup">
<input type="button" value="Назад" id="bum">
привязанные к одному обработчику — функции but:
document.getElementById("bup").
addEventListener("click", but);
document.getElementById("bum").
addEventListener("click", but);
Первая инструкция функции but определяет id нажатой кнопки и помещает его значение в переменную c:
let c=ev.target.id;
После этого запускается цикл:
for(; a>0 && a<6; b(c))
alert(a);
Он получает приращение из функции b, аргументом которой является переменная c:
— 111 —
let b=function(bt)
{
if(bt=="bup")
a++;
if(bt=="bum")
a--;
};
Эта функция в зависимости от id нажатой кнопки либо увеличивает значение счетчика, либо уменьшает.
Чтобы глобальная переменная a не вышла за рамки, ограничивающие количество проходов в обоих направлениях, в функции but предусмотрены два
условных выражения:
if(a==6)
a=5;
if(a==0)
a=1;
Запустим страницу в браузере и нажмем кнопку «Вперед». После этого
5 раз подряд откроются диалоговые окна, поочередно демонстрирующие цифры от 1 до 5. Теперь нажмем кнопку «Назад». Снова поочередно откроются
шесть диалоговых окон, но теперь на каждом из них цифры будут идти по убывающей — от 5 до 1.
— 112 —
9. УПРАВЛЕНИЕ ТАЙМЕРАМИ
Таймер — это устройство, выполняющее определенные действия с равными промежутками времени или задающее интервал между какими-либо событиями.
В обычной жизни мы имеем дело с таймерами повсеместно. Заводите дома будильник, чтобы встать пораньше — тем самым включаете таймер. Судья,
дав свисток в начале футбольного матча, запускает секундомер — это тоже
таймер. Изображение на экране компьютера переписывается 60 раз в секунду — этим процессом также управляет таймер. Скорость автомобиля показывается на спидометре — для этого измеряется пройденный путь за промежуток
времени, заданный таймером.
Естественно, что такая функциональная возможность, как вызов таймеров,
есть и в JavaScript. Таймеры используются для многократного повторения некоторых фрагментов кода либо для задержки выполнения какой-то операции.
9.1. Запускаем таймер
Мы с вами рассмотрим три варианта таймеров.
Периодический таймер setInterval. Многократно срабатывает через выбранный промежуток времени и выполняет заданную функцию. Форма записи:
setInterval(функция, интервал времени, аргументы);
Пример использования данного таймера с аргументами:
Файл chapter9/script9.1.1.html
<script>
let a=1;
setInterval(func, 1000, 3, 2);
function func(arg1, arg2)
{
if(a<100000)
{
a*=arg1+arg2;
document.getElementById("sum").innerHTML+=a+" ";
}
}
</script>
Если запустить этот файл в браузере, вы увидите, как с промежутком в
1 секунду на странице появятся числа от 5 до 390625.
Пример использования данного таймера без аргументов:
Файл chapter9/script9.1.2.html
<script>
addEventListener("load", ()=>
{
— 113 —
setInterval(tim, 1000);
});
function tim()
{
let cha=new Date();
let min=new Date();
let sek=new Date();
let m=min.getMinutes();
let mi=""+m;
if(mi.length==1)
{
m="0"+m;
}
let s=sek.getSeconds();
let se=""+s;
if(se.length==1)
{
s="0"+s;
}
document.getElementById("tm").innerHTML=
cha.getHours()+":"+m+":"+s;
}
</script>
Перед вами классический вариант часов на web-странице. Показания выводятся в строке:
<p id="tm"></p>
Автор не стал мудрить с оформлением страницы: часы выглядят очень
просто — как показано на рисунке 9.1.1.
Рис. 9.1.1
Часы на основе таймера setInterval
Таймер запускается сразу после загрузки страницы:
addEventListener("load", ()=>
{
setInterval(tim, 1000);
});
Интервал в 1 секунду выбран, чтобы точность наших часов соответствовала обычной точности, характерной любым подобным механическим или
электронным устройствам, показывающим часы, минуты и секунды.
Для определения даты и времени в JavaScript существует объект Date. Он
содержит информацию о количестве миллисекунд, прошедших с 1 января 1970 г.
— 114 —
Создадим три новых экземпляра этого объекта:
let cha=new Date();
let min=new Date();
let sek=new Date();
Они нужны для вычисления часа, минут и секунд. Первым делом мы определяем количество минут методом getMinutes():
let m=min.getMinutes();
Затем переводим числовой показатель в строку:
let mi=""+m;
Теперь вычисляем длину строки с показаниями минут:
mi.length
Если число минут состоит из одной цифры, добавляем перед ней цифру 0:
if(mi.length==1)
{
m="0"+m;
}
Благодаря этому получается, что таймер все время показывает двухзначное
число минут, что выглядит аккуратнее. Кроме того, теперь таймер имеет стабильный внешний вид, а не смещается в стороны, когда число минут меняется с
одной цифры на две.
Такие же манипуляции проделываем и с показаниями секунд:
let s=sek.getSeconds();
let se=""+s;
if(se.length==1)
{
s="0"+s;
}
Методом getSeconds() определяем количество секунд, переводим показания в строку и добавляем 0, если строка состоит из одной цифры.
Когда предварительная подготовка завершена, выводим данные на страницу в строку с id="tm":
document.getElementById("tm").innerHTML=
cha.getHours()+":"+m+":"+s;
Как видите, текущий час мы вычисляем методом getHours() непосредственно
при выводе показаний:
cha.getHours()
Следующий на очереди — таймер setTimeout. Он срабатывает однократно, запуская указанную функцию через выбранный промежуток времени. Форма записи:
setTimeout(функция, интервал времени, аргументы);
Пример данного таймера с аргументами:
Файл chapter9/script9.1.3.html
— 115 —
<script>
setTimeout(func, 3000, 3, 2);
function func(arg1, arg2)
{
alert(arg1**arg2);
}
</script>
Здесь вычисления будут выполнены через 3 секунды после загрузки страницы.
Пример данного таймера без аргументов:
Файл chapter9/script9.1.4.html
<script>
setTimeout(func, 3000);
function func()
{
document.getElementById("im").src=
"../chapter13/img1/im2.jpg";
}
</script>
Этот сценарий через 3 секунды после загрузки документа меняет исходную фотографию
<img src="../chapter13/img1/im1.jpg" id="im"
alt="Фото">
на новую
document.getElementById("im").src=
"../chapter13/img1/im2.jpg";
С помощью таймера setTimeout можно многократно запускать одну и ту
же функцию. У нас уже был подобный пример — файл script6.1.5.html. Тогда
мы написали сценарий, в котором таймер располагался в теле функции. Упомянутый пример демонстрировал рекурсивную функцию с ее повторным вызовом
через определенный промежуток времени. Таймер, вызывающий функцию из ее
тела, называется вложенным.
Чтобы еще раз продемонстрировать данный случай таймера, возьмем
нашу программу с часами и заменим setInterval на setTimeout. В первую очередь придется переписать код регистрации обработчика загрузки страницы. Теперь функция tim будет запускаться сразу после загрузки страницы:
Файл chapter9/script9.1.5.html
addEventListener("load", tim);
Таймер разместим в самом конце тела функции, после инструкции, выводящей показания на страницу:
function tim()
{
...
— 116 —
document.getElementById("tm").innerHTML=
cha.getHours()+":"+m+":"+s;
setTimeout(tim, 1000);
}
В результате таймер будет запускать функцию tim из нее самой, каждую
секунду получая из объекта Date() новое время.
В качестве третьего таймера мы рассмотрим метод requestAnimationFrame.
У него специфическое назначение. Он необходим для синхронизации кода, выполняющего анимацию, и частоты перерисовки содержимого в окне браузера.
Благодаря применению этого метода достигается плавное изменение анимированных параметров объекта на странице. Форма записи:
requestAnimationFrame(функция);
Главное отличие от предыдущих таймеров — в requestAnimationFrame
мы не задаем интервал времени между срабатываниями. Частота срабатывания,
повторимся, синхронизируется с частотой перерисовки картинки на экране.
Для лучшего понимания принципа действия requestAnimationFrame
напишем простой сценарий, который управляет движением геометрической
фигуры по странице.
Запустите в браузере файл script9.1.6.html. Вы увидите две ограничительные линии, оранжевый прямоугольник, расположенный у левой линии, и
кнопку Старт. Нажмите ее. Прямоугольник плавно переместится к правой линии и остановится (рис. 9.1.2).
Рис. 9.1.2
Анимация с помощью requestAnimationFrame — прямоугольник двигается
от левой ограничительной линии к правой
Посмотрим код программы. На странице есть элемент p, которому придан
вид прямоугольника оранжевого цвета:
Файл chapter9/script9.1.6.html
— 117 —
<p id="bx"></p>
#bx {position: absolute; left: 2px; top: 50px;
width: 80px; height: 50px; background: #FF9900;}
Он находится внутри контейнера div шириной 600 пикселей и видимыми границами по бокам:
div {position: relative; margin: auto; width: 600px;
height: 190px; border-left: 1px solid #000000;
border-right: 1px solid #000000;}
Сценарий, управляющий движением фигуры, очень короткий:
addEventListener("load", function()
{
let k=2;
document.getElementById("but").
addEventListener("click", anim);
function anim()
{
k+=2;
if(k<520)
{
document.getElementById("bx").style.left=k+"px";
requestAnimationFrame(anim);
}
}
});
После загрузки страницы создаем переменную k
k=2;
которая выполняет роль счетчика смещения прямоугольника по горизонтали.
Ее начальное значение соответствует смещению фигуры относительно левого
края контейнера на 2 пикселя:
#bx {... left: 2px; ...}
Нажатие кнопки Старт запускает функцию anim:
document.getElementById("but").
addEventListener("click", anim);
Счетчик увеличивает свое значение
k+=2;
и прямоугольник смещается вправо. Происходит это так. При перерисовке кадра на экране новая горизонтальная координата фигуры оказывается на 2 пикселя правее координаты фигуры на предыдущем кадре.
Синхронизацию выполняет таймер requestAnimationFrame. Он запускается, если прямоугольник не достиг правого края контейнера:
if(k<520)
{
...
}
— 118 —
Сначала выполняется смещение прямоугольника
document.getElementById("bx").style.left=k+"px";
а затем вызывается метод requestAnimationFrame:
requestAnimationFrame(anim);
При этом метод запускает функцию, внутри которой он расположен.
9.2. Останавливаем таймер
Примеры с часами из предыдущего раздела демонстрируют корректную
работу бесконечного таймера.
А вот в сценарии script9.1.1.html есть один очень серьезный недостаток:
мы остановили выполнение расчетов, но таймер по-прежнему работает, при
этом не совершая никаких полезных действий, но отнимая ресурсы центрального процессора.
Чтобы избежать подобных ситуаций, в JavaScript предусмотрены механизмы остановки таймеров. Рассмотрим их по порядку.
Остановка таймера setInterval выполняется методом clearInterval. Покажем его работу на примере программы-секундомера.
На странице есть табло секундомера
Файл chapter9/script9.2.1.html
<p id="tm">0</p>
и две кнопки для пуска и остановки измерителя времени:
<input type="button" id="but1" value="Старт">
<input type="button" id="but2" value="Стоп">
В теле сценария объявлены две переменные:
let st;
let sum=0;
Первая служит идентификатором таймера setInterval
st=setInterval(...);
а вторая — счетчиком времени.
Нажатие кнопки Старт запускает секундомер:
document.getElementById("but1").
addEventListener("click", ()=>
{
sum=0;
document.getElementById("tm").innerHTML=0;
st=setInterval(tim, 1000);
});
Первым делом счетчик и показания табло обнуляются
sum=0;
document.getElementById("tm").innerHTML=0;
— 119 —
после чего запускается метод setInterval, который каждую секунду вызывает
функцию tim. Ее задача — увеличивать значение счетчика на единицу и выводить показания на табло:
function tim()
{
sum++;
document.getElementById("tm").innerHTML=sum;
}
После нажатия кнопки Стоп запускается метод clearInterval, который
вызывает остановку таймера:
document.getElementById("but2").
addEventListener("click", ()=>
{
clearInterval(st);
});
После этого показания времени фиксируются. Если теперь нажать Старт, на
табло опять появится 0, и процесс измерения начнется заново.
Остановка таймера setTimeout выполняется методом clearTimeout. Проиллюстрируем его на примере все той же программы-секундомера.
Нажатие кнопки Старт запускает функцию, практически аналогичную
предыдущей. Только метод setInterval заменен на setTimeout:
Файл chapter9/script9.2.2.html
document.getElementById("but1").
addEventListener("click", ()=>
{
sum=0;
document.getElementById("tm").innerHTML=0;
setTimeout(tim, 1000);
});
В функции tim добавилась одна инструкция:
function tim()
{
...
st=setTimeout(tim, 1000);
}
Так как setTimeout — однократный таймер, то мы зацикливаем процесс, помещая этот метод с вызовом функции tim внутрь нее самой (как мы уже делали в
сценарии script9.1.5.html).
Нажатие кнопки Стоп приводит к вызову метода clearTimeout, который
останавливает секундомер:
document.getElementById("but2").
addEventListener("click", ()=>
{
clearTimeout(st);
});
— 120 —
Остановку таймера requestAnimationFrame выполняют методом cancelAnimationFrame. Покажем его использование, модернизировав файл
script9.1.6.html.
Добавим на страницу кнопку Стоп:
Файл chapter9/script9.2.3.html
<input type="button" id="but2" value="Стоп">
В сценарии появится новая переменная
let rq;
Она служит идентификатором таймера:
function anim()
{
...
if(k<520)
{
...
rq=requestAnimationFrame(anim);
}
}
Нажатие кнопки Стоп запускает метод cancelAnimationFrame, который
останавливает таймер:
document.getElementById("but2").
addEventListener("click", ()=>
{
cancelAnimationFrame(rq);
});
Вы можете остановить и возобновить перемещение фигуры в любой момент.
9.3. Функции в таймере
До сих пор мы использовали в таймерах вызовы внешних функций. Однако тело функции можно поместить и непосредственно в таймер. Покажем такой подход на нескольких очень простых сценариях.
Случай для метода setInterval:
Файл chapter9/script9.3.1.html
<script>
let a=1;
setInterval(()=>
{
if(a<1000)
{
a*=2;
document.getElementById("sum").innerHTML+=a+" ";
}
}, 1000);
</script>
— 121 —
В этой программе функция, помещенная непосредственно в таймер, поочередно умножает предыдущее значение переменной a на 2. Результаты последовательно один за другим выводятся на страницу.
Покажем точно такую же программу для метода setTimeout:
Файл chapter9/script9.3.2.html
<script>
let a=1;
let st;
setTimeout(st=()=>
{
if(a<1000)
{
a*=2;
document.getElementById("sum").innerHTML+=a+" ";
setTimeout(st, 1000);
}
}, 1000);
</script>
В этом сценарии мы присвоили стрелочную функцию переменной st, чтобы можно было запускать ее таймером setTimeout из нее самой.
Наконец, последний пример — для метода requestAnimationFrame.
Упростим файл script9.1.6.html, удалив кнопку запуска. Теперь анимация
начинает работать сразу после загрузки страницы.
Файл chapter9/script9.3.3.html
<script>
let k=0;
let rq;
requestAnimationFrame(rq=()=>
{
k+=2;
if(k<520)
{
document.getElementById("bx").style.left=k+"px";
requestAnimationFrame(rq);
}
});
</script>
Как и в предыдущем случае, для циклической работы функции мы присвоили ее переменной.
— 122 —
10. МАНИПУЛИРУЕМ СТРОКАМИ
Что представляет себе обычный человек при упоминании термина «строка»? Скорее всего, последовательность слов от левого до правого края страницы книги или столбца газеты. Но эти представления не годятся, если речь заходит о программировании. В JavaScript строка — это набор любых разрешенных
символов, заключенный в кавычки.
Строкой может быть как набор из множества знаков или слов, так и всего
одно слово или даже один знак. Более того, строка может быть не просто текстом, а фрагментом HTML-кода.
В этой главе мы рассмотрим некоторые аспекты создания и обработки
строк.
10.1. Про кавычки
Создать строку очень просто: необходимо заключить набор символов в
кавычки.
В JavaScript используются кавычки трех типов:
одинарные ' ';
двойные " ";
обратные ` `.
Про обратные кавычки мы поговорим в разделе 10.3, а пока разберемся с
одинарными и двойными.
Посмотрим некоторые примеры.
Строка в одинарных кавычках:
'Это строка'
Строка в двойных кавычках:
"Это еще одна строка"
Иногда бывает необходимо использовать кавычки внутри строки. Это
можно сделать двумя способами.
1. Использовать разные кавычки «снаружи» строки и внутри нее:
'Роман "Три мушкетера" написал Александр Дюма'
"Писатель О'Генри автор юмористических рассказов"
2. Использовать одинаковые кавычки, экранируя внутренние:
"Роман \"Три мушкетера\" написал Александр Дюма"
'Писатель О\'Генри автор юмористических рассказов'
Использование разных кавычек весьма актуально, если ваша строка представляет собой фрагмент HTML кода:
'<img src="img1/im1.jpg" id="im" alt="Фото">'
Строку можно непосредственно вывести на страницу:
Файл chapter10/script10.1.1.html
— 123 —
<!DOCTYPE html>
...
<script>
addEventListener("load", ()=>
{
document.getElementById("txt").
insertAdjacentHTML("beforeend",
'Роман "Три мушкетера" написал Александр Дюма');
});
</script>
</head>
<body>
<p id="txt">Пример вывода строки на страницу:<br></p>
</body>
</html>
Файл chapter10/script10.1.2.html
<!DOCTYPE html>
...
<script>
addEventListener("load", ()=>
{
document.getElementById("txt").
insertAdjacentHTML("beforeend",
'<img src="../chapter13/img1/im1.jpg" alt="Фото">');
});
</script>
</head>
<body>
<p id="txt">Пример вывода строки на страницу:<br></p>
</body>
</html>
Строку можно присвоить переменной и потом уже выполнять с нею необходимые действия:
Файл chapter10/script10.1.3.html
<!DOCTYPE html>
...
<script>
addEventListener("load", ()=>
{
let t=
"Писатель О'Генри автор юмористических рассказов";
document.getElementById("txt").
insertAdjacentHTML("beforeend", t);
});
</script>
</head>
<body>
<p id="txt">Пример вывода строки на страницу:<br></p>
</body>
</html>
— 124 —
Файл chapter10/script10.1.4.html
<!DOCTYPE html>
...
<script>
addEventListener("load", ()=>
{
let t=
'<img src="../chapter13/img1/im2.jpg" alt="Фото">';
document.getElementById("txt").
insertAdjacentHTML("beforeend", t);
});
</script>
</head>
<body>
<p id="txt">Пример вывода строки на страницу:<br></p>
</body>
</html>
10.2. Методы обработки строк
В этом разделе мы поговорим об одном важном свойстве любой строки и
о некоторых методах, используемых при работе со строками.
Часто при обработке строки необходимо вычислить ее длину или, иначе
говоря, количество символов. Для этой цели есть свойство length. Формат его
записи:
строка.length;
Например:
'Роман "Три мушкетера" написал Александр Дюма'.length;
Или:
let t=
"Писатель О'Генри автор юмористических рассказов";
t.length;
Результат можно вывести на страницу:
document.getElementById("txt").
insertAdjacentHTML("beforeend", t.length);
Или присвоить переменной и использовать ее в дальнейшем:
let len=
'Роман "Три мушкетера" написал Александр Дюма'.length;
document.getElementById("txt").
insertAdjacentHTML("beforeend", len);
Обратите внимание: отсчет количества символов в строке начинается с
единицы, а отсчет индексов, указывающих на положение символа в строке, —
с нуля.
Рассмотрим основные методы работы со строками.
— 125 —
1. charAt() — метод возвращает из строки символ, порядковый номер которого указан в скобках.
Файл chapter10/script10.2.1.html
<script>
alert("string".charAt(4));
</script>
Результат:
n
То же самое можно получить немного проще:
alert("string"[4]);
В этом случае строка рассматривается как массив символов.
2. concat() — объединяет несколько строк и возвращает новую строку.
Файл chapter10/script10.2.2.html
<script>
alert("string1".concat("string2", "string3"));
</script>
Результат:
string1string2string3
Этот метод можно заменить простым суммированием трех строк:
alert("string1"+"string2"+"string3");
3. includes() — определяет, находится ли одна строка внутри другой. Возвращает true или false.
Файл chapter10/script10.2.3.html
<script>
alert("New string".includes("str"));
alert("New string".includes("abc"));
</script>
Результат:
true
false
4. indexOf() — возвращает индекс первого вхождения в строке указанных
символов или -1, если вхождений нет.
Файл chapter10/script10.2.4.html
<script>
alert("New string".indexOf("str"));
alert("New string".indexOf("abc"));
</script>
— 126 —
Результат:
4
-1
5. lastIndexOf() — возвращает индекс последнего вхождения в строке
указанных символов или -1, если вхождений нет.
Файл chapter10/script10.2.5.html
<script>
alert("New string, old string".lastIndexOf("str"));
alert("New string, old string".lastIndexOf("abc"));
</script>
Результат:
16
-1
6. match() — выполняет сопоставление регулярного выражения в строке.
Возвращает совпадающий фрагмент или null, если совпадений нет.
Файл chapter10/script10.2.6.html
<script>
let r=/^[a-z][a-z\d.-]+[a-z\d]@[a-z\d][a-z\d-]+
[a-z\d]\.[a-z]{2,13}$/i;
let adr1="test.999@yandex.ru";
let adr2="999.test@yandex.ru";
alert(adr1.match(r));
alert(adr2.match(r));
</script>
Результат:
test.999@yandex.ru
null
Здесь мы проверяем адреса электронной почты на соответствие требованиям российских почтовых сервисов. В первом случае почта соответствует
нормам, поэтому сценарий выводит в диалоговое окно этот адрес. Во втором
случае адрес почты начинается с цифр, что запрещено отечественными сервисами. Поэтому совпадения с регулярным выражением не происходит.
7. padStart() — вставляет в начало строки указанную подстроку столько
раз и так, чтобы итоговая строка достигла заданной длины. Если подстрока не
указана, вставляются пробелы.
Файл chapter10/script10.2.7.html
<script>
let t="TEXT";
alert(t.padStart(15));
alert(t.padStart(10, "abc"));
alert(t.padStart(8, "abc"));
— 127 —
alert(t.padStart(9, 1));
</script>
Результат:
TEXT
abcabcTEXT
abcaTEXT
11111TEXT
Обратите внимание: в третьем случае, чтобы получить новую строку заданной длины, в исходную строку вставляется полная подстрока abc и еще
один символ из нее — первый.
8. padEnd() — вставляет в конец строки указанную подстроку столько
раз и так, чтобы итоговая строка достигла заданной длины. Если подстрока не
указана, вставляются пробелы.
Файл chapter10/script10.2.8.html
<script>
let t="TEXT";
alert(t.padEnd(15)+0);
alert(t.padEnd(10, "abc"));
alert(t.padEnd(8, "abc"));
alert(t.padEnd(9, 1));
</script>
Результат:
TEXT
TEXTabcabc
TEXTabca
TEXT11111
0
При первом вызове метода padEnd мы завершили новую строку символом 0, чтобы были видны добавленные пробелы.
9. repeat() — возвращает строку, состоящую из указанного в скобках количества повторений исходной строки.
Файл chapter10/script10.2.9.html
<script>
alert("Text".repeat(3));
</script>
Результат:
TextTextText
10. replace() — в случае совпадения части строки с искомой подстрокой
заменяет первое совпадение новой подстрокой.
Файл chapter10/script10.2.10.html
<script>
alert("text text".replace("e", "E"));
</script>
— 128 —
Результат:
tExt text
11. replaceAll() — в случае совпадения части строки с искомой подстрокой заменяет все совпадения новой подстрокой.
Файл chapter10/script10.2.11.html
<script>
alert("text text".replaceAll("e", "E"));
</script>
Результат:
tExt tExt
12. search() — выполняет поиск совпадения регулярного выражения со
строкой. Возвращает индекс первого совпадения с регулярным выражением
или -1, если совпадений нет.
Файл chapter10/script10.2.12.html
<script>
let r=/^[a-z][a-z\d.-]+[a-z\d]@[a-z\d][a-z\d-]+
[a-z\d]\.[a-z]{2,13}$/i;
let adr1="test.999@yandex.ru";
let adr2="999.test@yandex.ru";
alert(adr1.search(r));
alert(adr2.search(r));
</script>
Результат:
0
-1
13. slice() — извлекает часть строки и возвращает новую строку, не меняя
исходную. Индексы начала извлекаемого фрагмента и его конца указываются в
скобках. Если второй индекс не указан, то извлекается фрагмент от указанного
индекса и до конца строки. Если указаны отрицательные числа, то отсчет символов ведется с конца строки. Символ из позиции, указанной вторым аргументом, в новую строку не включается
Файл chapter10/script10.2.13.html
<script>
alert("text text".slice(2, 7));
</script>
Результат:
xt te
14. split() — разбивает строку на несколько строк с помощью заданного
разделителя.
— 129 —
Файл chapter10/script10.2.14.html
<script>
let ar="text1,text2,text3".split(",");
alert(ar[0]+"\n"+ar[1]+"\n"+ar[2]);
</script>
Результат:
text1
text2
text3
15. startsWith() — определяет, начинается ли строка с указанных символов. Возвращает true или false.
Файл chapter10/script10.2.15.html
<script>
alert("text text".startsWith("te"));
alert("text text".startsWith("xt"));
</script>
Результат:
true
false
16. endsWith() — определяет, заканчивается ли строка указанными символами. Возвращает true или false.
Файл chapter10/script10.2.16.html
<script>
alert("text text".endsWith("xt"));
alert("text text".endsWith("te"));
</script>
Результат:
true
false
17. substring() — возвращает все символы из строки, начиная с позиции,
указанной первым аргументом, до позиции, указанной вторым аргументом, но
не включая символ из позиции, указанной вторым аргументом. Если конечная
позиция не задана, то возвращаются символы до конца строки.
Файл chapter10/script10.2.17.html
<script>
alert("text text".substring(2, 7));
alert("text text".substring(3));
</script>
Результат:
xt te
t text
— 130 —
18. toLowerCase() — переводит символы в строке в нижний регистр. Не
меняет регистр символов исходной строки.
Файл chapter10/script10.2.18.html
<script>
let t="ИЗУЧАЕМ JAVASCRIPT";
alert(t.toLowerCase());
alert(t);
</script>
Результат:
изучаем javascript
ИЗУЧАЕМ JAVASCRIPT
19. toUpperCase() — переводит символы в строке в верхний регистр. Не
меняет регистр символов исходной строки.
Файл chapter10/script10.2.19.html
<script>
let t="изучаем javascript";
alert(t.toUpperCase());
alert(t);
</script>
Результат:
ИЗУЧАЕМ JAVASCRIPT
изучаем javascript
20. trim() — удаляет пробелы и символы перевода строк в начале и в
конце строки.
Файл chapter10/script10.2.20.html
<script>
let t="\n
text
\n";
alert(0+t+0);
alert(0+t.trim()+0);
</script>
Результат в первом диалоговом окне:
0
0
text
Здесь мы добавили цифры 0, чтобы наглядно показать, что в исходной строке
присутствуют пробелы и символы переноса.
Результат во втором диалоговом окне:
0text0
21. trimStart() — удаляет пробелы и символы перевода строк в начале
строки.
— 131 —
Файл chapter10/script10.2.21.html
<script>
let t="\n
text
\n";
alert(0+t+0);
alert(0+t.trimStart()+0);
</script>
Результат в первом диалоговом окне:
0
text
0
Результат во втором диалоговом окне:
0text
0
22. trimEnd() — удаляет пробелы и символы перевода строк в конце
строки.
Файл chapter10/script10.2.22.html
<script>
let t="\n
text
\n";
alert(0+t+0);
alert(0+t.trimEnd()+0);
</script>
Результат в первом диалоговом окне:
0
text
0
Результат во втором диалоговом окне:
0
text0
В методы, оперирующие числами или строками, можно передавать значения переменных или вызовы функций, которые вычисляют параметры обработки строки. Эти подходы иллюстрирует следующий пример:
Файл chapter10/script10.2.23.html
<script>
let a=2;
let b=7;
alert("text text".substring(a));
alert("text text".substring(a, b));
function func1()
{
let x=1;
— 132 —
x++;
return x;
}
alert("text text".substring(func1()));
function func2()
{
let y=3;
y+=4;
return y;
}
alert("text text".substring(func1(), func2()));
</script>
10.3. Интерполяция и шаблонные литералы
В разделе 4.7 мы обсуждали приемы создания многострочных переменных и упоминали, что нам предстоит разобрать еще один способ наполнения
переменных несколькими строками текста или кода. Настало время рассмотреть этот способ.
Если нам необходимо «упаковать» в переменную несколько строк, можно
воспользоваться обратными кавычками ` `, о которых мы уже говорили в разделе 10.1. При этом никаких дополнительных операторов между строками использовать не надо. Содержимое, образованное несколькими строками и заключенное в обратные кавычки, принято называть многострочным литералом.
Чтобы посмотреть такой литерал на конкретном примере, изменим сценарий script4.7.3.html, удалив операторы суммирования и заключив код трех
изображений в обратные кавычки:
Файл chapter10/script10.3.1.html
<!DOCTYPE html>
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", function()
{
let pict=`<p><img src="../chapter13/img1/im1.jpg"
id="im1" alt="...">
<img src="../chapter13/img1/im2.jpg"
id="im2" alt="...">
<img src="../chapter13/img1/im3.jpg"
id="im3" alt="..."></p>`;
document.body.
insertAdjacentHTML("beforeend", pict);
});
});
</script>
...
</html>
— 133 —
Как вы можете убедиться, использование обратных кавычек является
наилучшим способом присвоения одной переменной сразу нескольких строк.
Это позволяет создавать наиболее компактный код.
У строк, заключенных в обратные кавычки, есть еще одно важное преимущество: в них можно подставлять переменные и выражения. Комбинация
строковых литералов с подставленными в них выражениями принято называть
шаблонными литералами. Подстановку выражений еще называют интерполяцией.
Пример интерполяции покажем, немного изменив предыдущий сценарий:
Файл chapter10/script10.3.2.html
<!DOCTYPE html>
...
<script>
addEventListener("load", function()
{
let str='<img src="../chapter13/img1/im2.jpg"
id="im2" alt="...">';
document.getElementById("but").
addEventListener("click", function()
{
let pict=`<p><img src="../chapter13/img1/im1.jpg"
id="im1" alt="...">
${str}
<img src="../chapter13/img1/im3.jpg"
id="im3" alt="..."></p>`;
document.body.
insertAdjacentHTML("beforeend", pict);
});
});
</script>
...
</html>
Следующий очень простой сценарий демонстрирует подставновку в
строку выражения:
Файл chapter10/script10.3.3.html
<script>
let a=2;
let b=3;
alert(`Текст ${a+b} текст`);
</script>
И еще один пример — подстановка в строку вызова функции:
Файл chapter10/script10.3.4.html
— 134 —
<script>
function func()
{
let a=2;
let b=5;
return a+b;
}
alert(`Текст ${func()} текст`);
</script>
— 135 —
11. ОБРАБОТЧИКИ СОБЫТИЙ
События на страницах сайта происходят почти непрерывно. Загружаете
ли вы документ, нажимаете кнопку мыши, прокручиваете страницу или просто
перемещаете указатель — все это для браузера события. Их принято идентифицировать по типу (иногда говорят не тип события, а имя события).
Обработчик события — это некая функция, которая выполняет определенный набор инструкций в ответ на действия посетителя. Чтобы эта функция
заработала, ее необходимо зарегистрировать или связать с конкретным событием.
11.1. События на HTML-страницах
Перечислим некоторые события, которые обрабатываются наиболее часто.
Событие DOMContentLoaded происходит сразу после загрузки объектной модели документа, но не дожидаясь окончания загрузки таблиц стилей,
изображений и фреймов. В ряде случаев прослушивать данное событие предпочтительнее, чем событие полной загрузки документа.
Событие load происходит при загрузке страницы в браузер. Оно генерируется сразу после того, как элементы HTML-разметки, тексты, фреймы и рисунки (картинки, изображения, фото) полностью отображаются на экране компьютера. Такое же событие происходит и при получении ответа от сервера, на
котором стоит программа, вызываемая с применением технологии Ajax.
Событие click возникает при щелчке кнопкой мыши на элементе разметки
страницы. Это могут быть ссылки, кнопки, изображения, текстовые блоки, слои
(событие click, как, впрочем, и некоторые другие, может также происходить
даже на визуально пустом месте документа).
Событие mouseover происходит, когда указатель мыши перемещается
внутрь границ элемента.
Событие mouseout противоположно предыдущему и возникает, когда
указатель мыши покидает границы элемента.
Событие mousemove генерируется в продолжение всего времени, пока
указатель мыши перемещается над элементом.
Событие mousedown происходит при нажатии кнопки мыши, когда ее
указатель находится над элементом.
Событие mouseup возникает при отпускании кнопки мыши (при этом
указатель должен находиться над элементом, для которого генерируется событие).
Событие dragstart происходит, когда посетитель начинает перетаскивать
элемент по странице.
Событие dragover возникает в процессе перетаскивания элемента по
странице.
Событие drop происходит в момент отпускания перетаскиваемого элемента в точке назначения.
— 136 —
Событие resize возникает при изменении размеров окна браузера.
Событие focus случается при получении элементом фокуса (например,
когда курсор будет установлен в текстовом поле).
Событие blur происходит, когда элемент теряет фокус.
Событие submit сопровождает отправку формы, например, нажатием
кнопки. Обычно программисты к данному событию привязывают проверку
данных, введенных пользователем.
Событие change происходит на элементах <select>, <input> и <textarea>,
когда меняются их значения.
Событие scroll возникает при прокручивании страницы в окне браузера
или во фрейме.
Событие paste происходит в момент вставки содержимого буфера обмена
в элемент документа в позицию курсора (например, в однострочные или многострочные текстовые поля).
Событие keydown происходит в момент нажатия любой кнопки на клавиатуре.
Событие keyup возникает в момент отпускания после нажатия клавиши.
Событие keypress — это «симбиоз» двух предыдущих событий (клавиша
нажата и отпущена).
Событие select происходит в текстовых полях документа при выделении в
них какого-либо фрагмента или всего текста в целом.
Событие mouseenter очень похоже на mouseover. Отличие в том, что событие mouseenter не всплывающее и не отменяемое (из этого понятно, что
mouseover всплывающее и отменяемое).
Событие mouseleave, соответственно, похоже на mouseout. Разница такая
же, как и в случае, описанном в предыдущем определении. Событие mouseleave
не всплывающее и не отменяемое.
Разработчик сайта заранее выбирает, какие события на странице должны
вызвать программный отклик, а какие нет.
11.2. Регистрация обработчиков
Сразу сделаем одну важную оговорку — автор исповедует стиль программирования, при котором сценарии на JavaScript располагаются:
либо в головной части документа;
либо во внешнем файле, вызов которого помещается в головной части
документа.
Тем самым программный код отделяется от разметки.
В этой связи возникают определенные особенности регистрации обработчиков. В частности, метод, показанный ниже, не даст никакого результата:
<head>
...
<script>
регистрация обработчика, например,
для клика на изображении
— 137 —
программный код, обрабатывающий щелчок на изображении
</script>
</head>
Почему? Дело в том, что анализатор браузера при загрузке документа
считывает его «сверху вниз». Но у нас обработчик объявлен в заголовочной части, то есть еще до того, как построена объектная модель документа. В результате браузер еще не видит изображение, а мы уже пытаемся привязать к картинке какое-то событие. В итоге обработчик не будет зарегистрирован.
Чтобы все работало корректно, необходимо поступать следующим образом: регистрировать сначала обработчик уровня окна браузера для события
load или DOMContentLoaded, а уже внутри него записывать остальные обработчики. Сделать это можно так
<head>
...
<script>
регистрация обработчика события load уровня окна
{
регистрация обработчика для клика на изображении
}
программный код, обрабатывающий щелчок на изображении
</script>
</head>
или так:
<head>
...
<script>
регистрация обработчика события load уровня окна
{
регистрация обработчика для клика на изображении
программный код, обрабатывающий щелчок на изображении
}
</script>
</head>
При таком подходе браузер сначала загрузит весь документ, следом запустит обработчик события load окна, после чего и будут зарегистрированы необходимые обработчики событий на элементах web-страницы.
Для регистрации обработчиков используется метод addEventListener. Он
принимает три аргумента:
addEventListener(событие, функция,
дополнительные параметры)
Два из них обязательные. Первый — тип события. Второй — анонимная функция, «реагирующая» на событие, или имя внешней функции. Третий аргумент
допустимо не указывать. Он может, например, определять, в какой стадии —
всплытия или погружения — будет вызван обработчик. Кроме того, в качестве
третьего аргумента могут быть иные данные, например, указание разрешать
вызов обработчика не более одного раза (о чем будет речь в разделе 11.4).
— 138 —
Обратите внимание: метод addEventListener обращается к любому событию без использования префикса on: надо писать load, click, focus, submit и так
далее.
Рассмотрим несколько способов применения метода addEventListener.
1. Обработчики событий окна load, scroll, resize зарегистрировать проще
всего. Например, для события load это можно сделать так:
addEventListener("load", start);
и так:
addEventListener("load", function()
{
...
});
и еще так:
addEventListener("load", ()=>
{
...
});
2. Регистрация обработчика как ответ на событие load. Например, так:
addEventListener("load", function()
{
document.getElementById("im").
addEventListener("click", func);
});
или так:
addEventListener("load", function()
{
document.getElementById("im").
addEventListener("click", function()
{
...
});
});
или вот так:
addEventListener("load", function()
{
document.getElementById("im").
addEventListener("click", ()=>
{
...
});
});
3. Регистрация сразу нескольких обработчиков. Пример:
addEventListener("load", function()
{
document.getElementById("im").
addEventListener("click", func);
document.getElementById("im").
addEventListener("mouseout", doc);
— 139 —
document.getElementById("im").
addEventListener("mouseover", res);
});
4. Комбинированная регистрация — когда обработчики одних событий
регистрируются в начале, а другие — в процессе выполнения сценария. Может
выглядеть следующим образом:
addEventListener("load", function()
{
document.getElementById("im").
addEventListener("click", func);
});
function func()
{
...
document.getElementById("te").
addEventListener("click", doc);
}
function doc()
{
...
}
Вообще, метод addEventListener позволяет комбинировать регистрацию
обработчиков весьма разнообразно.
Главное достоинство метода addEventListener в том, что его можно вызывать сколько угодно раз и зарегистрировать с его помощью сколько угодно
обработчиков для одного и того же события на одном и том же элементе страницы:
addEventListener("load", function()
{
document.getElementById("im").
addEventListener("click", func);
document.getElementById("im").
addEventListener("click", doc);
document.getElementById("im").
addEventListener("click", res);
});
Кстати, можно записать и вот так:
addEventListener("load", function()
{
document.getElementById("im").
addEventListener("click", function()
{
func();
doc();
res();
});
});
Правда, такой прием в практике разработчиков встречается крайне редко.
Его единственное достоинство — более короткий код.
— 140 —
Завершая раздел, напишем в качестве примера несложный сценарий, который продемонстрирует некоторые способы регистрации обработчиков. Они
будут «обслуживать» события mouseover, mouseout и click, происходящих на
изображении:
Файл chapter11/script11.2.1.html
<img id="im" src="../chapter13/img1/im1.jpg"
alt="Фото">
Сначала зарегистрируем обработчик события загрузки страницы:
<script>
addEventListener("load", ()=>
{
...
});
</script>
При наведении указателя мыши на рисунок ему добавляется рамка:
document.getElementById("im").
addEventListener("mouseover", function()
{
document.getElementById("im").style.border=
"10px solid #CC0000";
});
Если указатель покидает границы картинки, рамка исчезает:
document.getElementById("im").
addEventListener("mouseout", ()=>
{
document.getElementById("im").style.border=0;
});
Щелчок на фотографии обрабатывают две функции:
document.getElementById("im").
addEventListener("click", size);
document.getElementById("im").
addEventListener("click", rotate);
Первая увеличивает размер изображения:
function size()
{
document.getElementById("im").style.width="400px";
}
Вторая создает эффект перспективы (рис. 11.2.1):
function rotate()
{
document.getElementById("im").style.transform=
"rotateY(30deg)";
}
— 141 —
Рис. 11.2.1
Изображение после запуска обработчиков
Как видите, в этом сценарии мы применили несколько вариантов регистрации обработчиков. Движения мыши обрабатываются анонимными функциями:
традиционной и стрелочной. Щелчки запускают две независимые именованные
функции. Наконец, событие загрузки элементов страницы тоже обрабатывалось
стрелочной функцией.
11.3. Циклы в помощь
В работе программиста часто бывают ситуации, когда в сценарии необходимо зарегистрировать обработчик одного и того же события для множества
однотипных элементов. Чтобы не быть голословными, посмотрим реальный
пример, показанный на рисунке 11.3.1. Документ содержит блок из 50 кнопок с
разными символами. Нажатие любой кнопки приводит к тому, что ее символ
добавляется в текстовое поле, расположенное ниже.
Понятно, что писать 50 строк кода с регистрацией одного и того же обработчика для разных кнопок — крайне нерационально. Хотелось бы по возможности сделать сценарий более компактным. И тут на помощь приходит цикл.
Использовав его, мы способны сократить процесс регистрации всего до нескольких строк.
Именно такой подход реализован в файле, скриншот действия которого
вы увидели на рисунке 11.3.1. Откроем файл script11.3.1.html в каком-либо
текстовом редакторе и посмотрим, как устроена программа.
Итак, на странице 50 кнопок
Файл chapter11/script11.3.1.html
<input type="button" class="sym" value="¥">
...
<input type="button" class="sym" value="χ">
— 142 —
и текстовое поле, куда вставляются символы:
<input id="inp">
Рис. 11.3.1
В этом файле необходимо зарегистрировать обработчик для 50 кнопок
После загрузки страницы мы получаем методом querySelectorAll массив
всех кнопок:
addEventListener("load", ()=>
{
let sym=document.querySelectorAll(".sym");
...
});
Запускаем цикл, количество проходов которого равно количеству элементов
массива:
for(let i=0; i<sym.length; i++)
{
...
}
В теле цикла получаем для каждого элемента его символ
let symbol=sym[i].value;
и регистрируем для каждой кнопки обработчик, передавая в него в качестве аргумента текущий символ:
sym[i].addEventListener("click", ()=>{func(symbol);});
Операцию вставки символов выполняет функция func:
function func(smb)
{
document.getElementById("inp").value+=" "+smb;
}
Подведем итог. Вместо 50 строк кода с регистрацией обработчика для
всех кнопок мы обошлись всего 6 строками:
let sym=document.querySelectorAll(".sym");
for(let i=0; i<sym.length; i++)
{
let symbol=sym[i].value;
sym[i].
addEventListener("click", ()=>{func(symbol);});
}
— 143 —
11.4. Остановка, отмена, перезапуск обработчиков
У программиста часто бывают ситуации, когда необходимо зарегистрировать обработчик, вызывающийся ограниченное количество раз, а не бесконечно. На примере метода addEventListener рассмотрим несколько вариантов,
как можно поступить в этом случае.
Вариант 1. Нам необходимо вызвать обработчик только один раз. Как я
уже говорил выше, метод addEventListener может третьим аргументом принимать указание реагировать на определенное событие на выбранном элементе
однократно. Остальные аналогичные события на этом элементе не вызовут реакции сценария.
Для примера возьмем файл script4.7.3.html. В этой программе каждое
нажатие кнопки добавляет на страницу три фотографии. Модернизируем сценарий, добавив в обработчик третий аргумент с указанием, что реакция на событие щелчка происходит только один раз:
Файл chapter11/script11.4.1.html
document.getElementById("but").
addEventListener("click", function()
{
...
}, {once: true});
Теперь после первого клика все остальные будут проигнорированы сценарием. В результате на страницу добавятся только 3 снимка.
Обратите внимание на форму записи третьего аргумента {once: true} —
он заключен в фигурные скобки.
Если бы мы регистрировали в качестве обработчика именованную функцию, то запись имела бы следующий вид:
document.getElementById("but").
addEventListener("click", func, {once: true});
Вариант 2. Необходимо вызвать обработчик несколько раз, например, 5.
На такой случай есть метод removeEventListener, который отменяет привязку
ранее зарегистрированного обработчика к какому-либо событию при достижении определенного условия. Пример его использования:
<script>
document.getElementById("but").
addEventListener("click", func);
...
// Если выполняется заданное условие,
// привязка обработчика отменяется
if(условие)
document.getElementById("but").
removeEventListener("click", func);
</script>
— 144 —
Важно: содержимое круглых скобок при регистрации обработчика методом addEventListener должно в точности совпадать с содержимым круглых
скобок при вызове метода removeEventListener:
document.getElementById("but").
addEventListener("click", func);
document.getElementById("but").
removeEventListener("click", func);
Чтобы продемонстрировать реальный случай применения метода
removeEventListener, опять модернизируем файл script4.7.3.html, но теперь в
соответствии с новой задачей.
Для начала условимся, что в следующем примере обработчиком будет не
анонимная, а именованная функция func:
Файл chapter11/script11.4.2.html
document.getElementById("but").
addEventListener("click", func);
Введем в сценарий счетчик кликов:
let i=0;
Каждый щелчок увеличивает значение переменной i:
function func()
{
...
i++;
...
}
По достижении предельного значения обработчик удаляется:
if(i==5)
document.getElementById("but").
removeEventListener("click", func);
Исходный обработчик можно не только отменить, но и зарегистрировать
вновь. Более того, процесс регистрации и отмены может быть многократным.
Проиллюстрируем сказанное на примере с перемещением фигуры по странице.
Запустите в браузере файл script11.4.3.html. Вы увидите оранжевый прямоугольник. Наведите на него указатель мыши, нажмите левую кнопку и, не
отпуская ее, перетащите фигуру по странице. Отпустите кнопку. Прямоугольник окажется в новом месте. Проделайте то же самое и переместите фигуру в
другую точку. Так можно экспериментировать, пока не надоест (рис. 11.4.1).
Механизм перемещения обсуждать не станем — в данном примере он не
важен. Обратим внимание на то, что в основе перемещения картинки лежит
процесс многократной регистрации и отмены обработчика.
На странице есть прямоугольник:
Файл chapter11/script11.4.3.html
<div id="bx"></div>
— 145 —
со следующими параметрами:
div {position: absolute; left: 10px; top: 10px;
width: 200px; height: 120px;
background: #FF9900; cursor: pointer;}
Рис. 11.4.1
Перемещаем фигуру по странице
Нажатие указателя мыши над фигурой (событие mousedown) запускает
функцию coor:
document.getElementById("bx").
addEventListener("mousedown", coor);
В ее теле вычисляются текущие координаты прямоугольника и регистрируется обработчик события движения мыши (mousemove):
function coor()
{
...
addEventListener("mousemove", neco);
}
Пока кнопка мыши нажата, функция непрерывно вычисляет текущие координаты указателя и подстраивает под его перемещение движение прямоугольника.
Если теперь отпустить кнопку, произойдет событие mouseup. Его обрабатывает анонимная функция, единственная задача которой — прекратить перемещение рисунка. Достигается этот результат путем отмены привязки обработчика к событию mousemove:
addEventListener("mouseup", function()
{
removeEventListener("mousemove", neco);
});
Дальше схема работы скрипта повторяется. Еще раз нажмем левую кнопку мыши над прямоугольником — и обработчик события mousemove будет зарегистрирован по новой. Перетащим фигуру и отпустим кнопку. Обработчик
события mousemove будет опять отменен. Так может происходить до бесконечности.
— 146 —
Вариант 3. Вновь необходимо вызвать обработчик несколько раз. Возьмем в качестве примера число 3. Способ, который продемонстрирован ниже,
позволяет остановить обработчик, не отменяя его (в который раз за основу программы берем файл script4.7.3.html):
Файл chapter11/script11.4.4.html
let i=0;
document.getElementById("but").
addEventListener("click", func);
function func()
{
if(i<3)
{
...
i++;
}
}
В этом сценарии выполнение функции func останавливается после трех
щелчков по кнопке. Удобство данного метода в том, что на определенном этапе
выполнения программы вы можете обнулить счетчик, и функция func возобновит свою работу при очередном клике на кнопке. Посмотрим скрипт, демонстрирующий «восстановление» работоспособности функции func:
Файл chapter11/script11.4.5.html
let i=0;
document.getElementById("but").
addEventListener("click", func);
function func()
{
if(i<3)
{
...
i++;
}
}
setInterval(()=>{i=0;}, 10000);
Здесь счетчик обнуляется с интервалом 10 секунд. Таким образом, пользователь периодически может добавлять на страницу по три снимка.
Впрочем, аналогичный результат может быть достигнут и таким способом:
Файл chapter11/script11.4.6.html
— 147 —
let i=0;
document.getElementById("but").
addEventListener("click", func);
function func()
{
...
i++;
if(i==3)
document.getElementById("but").
removeEventListener("click", func);
}
setInterval(()=>
{
i=0;
document.getElementById("but").
addEventListener("click", func);
}, 10000);
В последнем варианте после трех щелчков на основном тексте обработчик будет отменен, а через 10 секунд зарегистрирован вновь.
И еще один вариант — когда обработчик может быть отменен или перезаписан:
Файл chapter11/script11.4.7.html
let i=0;
document.getElementById("but").
addEventListener("click", func);
document.getElementById("butrem").
addEventListener("click", ()=>
{
if(i<3)
{
i=0;
document.getElementById("but").
addEventListener("click", func);
}
});
function func()
{
...
i++;
if(i==3)
document.getElementById("but").
removeEventListener("click", func);
}
Если вы щелкнули по основной кнопке три раза, обработчик будет отменен, а кнопка ЕЩЁ деактивирована. Если же щелчков было один или два, то
— 148 —
обработчик можно перезаписать, нажав кнопку ЕЩЁ. После этого можно вновь
нажимать основную кнопку и добавлять новую порцию фотографий.
11.5. Передача аргументов в обработчик
Мы уже несколько раз использовали прием с передачей аргументов в обработчик события. Рассмотрим данную технологию подробнее.
Начнем с простого случая, когда обработчик получает необходимые данные, например, из объекта события. Эту ситуацию иллюстрирует сценарий, показанный ниже. У нас есть страница с очень элементарным тестом — необходимо правильно указать дату полета первого космонавта планеты Юрия Гагарина. Для выбора ответа предназначены три кнопки:
Файл chapter11/script11.5.1.html
<input type="button" id="but1"
value="10 марта 1960 года">
<input type="button" id="but2"
value="12 апреля 1961 года">
<input type="button" id="but3"
value="4 октября 1961 года">
Зарегистрируем один и тот же обработчик для событий нажатия кнопок
document.getElementById("but1").
addEventListener("click", func);
document.getElementById("but2").
addEventListener("click", func);
document.getElementById("but3").
addEventListener("click", func);
и напишем функцию:
let rep=document.getElementById("reply");
function func(ev)
{
let i=ev.target.id;
let d=document.getElementById(i).value;
if(i=="but2")
rep.innerHTML=d+" - правильный ответ";
else
rep.innerHTML=d+" - ответ неправильный";
}
В ее теле мы получаем идентификатор нажатой кнопки и на основе его значения определяем, был ответ правильным или нет:
if(i=="but2")
rep.innerHTML=d+" - правильный ответ";
else
rep.innerHTML=d+" - ответ неправильный";
Вроде бы все просто. Однако бывают ситуации, когда в обработчик необходимо явным образом передать какое-либо значение. Посмотрим три варианта, как это можно сделать.
— 149 —
Теперь наш обработчик будет ориентироваться не на id элемента, а на переданное значение аргумента. В результате функция func приобретет следующий вид:
function func(c)
{
let d=document.getElementById("but"+c).value;
if(c==2)
rep.innerHTML=d+" - правильный ответ";
else
rep.innerHTML=d+" - ответ неправильный";
}
Первый вариант передачи аргументов в обработчик:
Файл chapter11/script11.5.2.html
document.getElementById("but1").
addEventListener("click", function(){func(1);});
document.getElementById("but2").
addEventListener("click", function(){func(2);});
document.getElementById("but3").
addEventListener("click", function(){func(3);});
Здесь мы при регистрации обработчика щелчков по кнопкам использовали анонимные функции, в теле которых всего по одной инструкции: запустить обработчик с определенным аргументом. Например:
function(){func(1);}
Или в более привычной записи:
function()
{
func(1);
}
В следующем варианте обычные анонимные функции мы заменили на
стрелочные:
Файл chapter11/script11.5.3.html
document.getElementById("but1").
addEventListener("click", ()=>func(1));
document.getElementById("but2").
addEventListener("click", ()=>func(2));
document.getElementById("but3").
addEventListener("click", ()=>func(3));
Результат выполнения программы будет точно таким же, как и в предыдущих случаях.
Еще один способ передачи аргументов в обработчик:
Файл chapter11/script11.5.4.html
— 150 —
document.getElementById("but1").
addEventListener("click", func.bind(null, 1));
document.getElementById("but2").
addEventListener("click", func.bind(null, 2));
document.getElementById("but3").
addEventListener("click", func.bind(null, 3));
В этом сценарии при регистрации обработчика мы использовали метод
bind. Он создает новую функцию, которая при вызове устанавливает в качестве
контекста выполнения некоторое значение, а также принимает набор аргументов. В нашем случае аргумент один — число. Поскольку мы не привязываем к
функции контекст выполнения, то первым «пунктом» указываем null.
До сих пор мы разбирали взаимозаменяемые сценарии. В них данные
можно было получать из значения аргумента или из объекта события. Выбор
одного из четырех рассмотренных вариантов — всего лишь дело вкуса. Но бывают случаи, когда передача аргумента в обработчик является наиболее рациональным способом.
Пример, демонстрирующий данный подход, вы увидите, запустив в браузере файл script11.5.5.html. На странице есть три кнопки, которые меняют
оформление текста. Его можно сделать жирным, наклонным или подчеркнутым
(рис. 11.5.1).
Рис. 11.5.1
Страница с программой передачи аргументов в обработчик
Посмотрим код файла. В теле документа кнопки
Файл chapter11/script11.5.5.html
<input type="button" id="but1" value="Жирный">
<input type="button" id="but2" value="Курсив">
<input type="button" id="but3" value="Подчеркнутый">
и текст:
<div id="reply">Дата полета в космос Юрия Гагарина
- 12 апреля 1961 года</div>
При регистрации обработчика мы передаем в него символы HTML-тегов — b,
i, u:
document.getElementById("but1").
addEventListener("click", ()=>func("b"));
— 151 —
document.getElementById("but2").
addEventListener("click", ()=>func("i"));
document.getElementById("but3").
addEventListener("click", ()=>func("u"));
Затем получаем текст:
let i=document.getElementById("reply").innerHTML;
Роль обработчика выполняет именованная функция:
function func(c)
{
document.getElementById("reply").innerHTML=
"<"+c+">"+i+"</"+c+">";
}
Полученный аргумент вставляется в символы тега < >, и таким образом меняется оформление текста.
Завершая тему, вспомним, что передавать аргументы в обработчик также
можно, регистрируя его для разных элементов в цикле. Такой пример мы уже
рассматривали в разделе 11.3.
11.6. Отмена обработки событий по умолчанию
Во всех браузерах есть такой встроенный программный блок — обработчик событий по умолчанию. Он запускается после выполнения обработчиков,
зарегистрированных в сценарии JavaScript. Однако довольно часто программисту необходимо отменить установленную по умолчанию обработку события.
Для этого используется метод preventDefault.
Иллюстрацией такого подхода служат два следующих файла.
Сначала покажем, как функционирует обработчик по умолчанию.
У нас есть страница с фотографией, а под ней три ссылки на картинки
номер 2, 3 и 4:
Файл chapter11/script11.6.1.html
<img src="../chapter13/img1/im1.jpg" id="im"
alt="Фото"><br>
<a href="../chapter13/img1/im2.jpg">2</a>
<a href="../chapter13/img1/im3.jpg">3</a>
<a href="../chapter13/img1/im4.jpg">4</a>
Нажимая любую из ссылок, мы ожидаем, что на месте начального рисунка
окажется другой. Однако вместо этого мы увидим страницу с черным фоном,
на котором и появится выбранный снимок (рис. 11.6.1). При этом исходная
страница будет полностью заменена новой. Произошло так по следующей причине: нажатие ссылки по умолчанию загружает в браузер страницу, указанную
в атрибуте href.
— 152 —
Рис. 11.6.1
По умолчанию браузер загрузит новое фото в новую страницу
Отменим обработчик по умолчанию и добьемся искомого результата. Для
этого добавим сценарий, выполняющий необходимые действия:
Файл chapter11/script11.6.2.html
<script>
addEventListener("load", function()
{
addEventListener("click", (ev)=>
{
let h=ev.target.href;
if(h)
{
document.getElementById("im").src=h;
ev.preventDefault();
}
});
});
</script>
Страница с этим сценарием обеспечивает запланированный эффект — одно фото заменяется другим в текущей странице (рис. 11.6.2).
Анонимная стрелочная функция вызывается при любом щелчке на странице. Но при этом мы сразу проверяем, был ли клик на ссылке:
let h=ev.target.href;
if(h)
{
...
}
— 153 —
Рис. 11.6.2
Отмена обработчика по умолчанию обеспечивает запланированный результат
Если переменная h получает адрес ссылки, то выполняются два действия. Сначала присваиваем текущему изображению новый адрес
document.getElementById("im").src=h;
а затем отменяем обработчик по умолчанию:
ev.preventDefault();
Отмена обработчика по умолчанию может использоваться и во многих
других случаях. Например, к ней в обязательном порядке прибегают, используя
технологию Drag and Drop.
— 154 —
12. ДОБАВЛЯЕМ ЭЛЕМЕНТЫ
Есть расхожее выражение: из ничего создать нечто. Обычно его употребляют в переносном смысле. Понятно, что в реальной жизни такого не может
быть, ведь любое творение рук человеческих создается из какого-то исходного
материала.
Однако JavaScript — язык, в котором возможны подобные чудеса.
Например, перед вами некая web-страница. А на ней кнопка, нажатие которой
приводит к появлению линии, таблицы или фрейма. Только что их не было —
и вот они уже в окне браузера.
Перечисленные элементы взяты лишь для примера. К этому списку можно добавить теги, меняющие оформление текста и создающие ссылки. Единственное, что лишено «магической» подоплеки — добавление на страницу
изображения, так как оно уже хранится на сервере.
Думаю, на этом вступительное слово можно считать завершенным. Уже
ясно, что мы приступаем к изучению приемов создания элементов.
12.1. Создание элементов
Создать новый элемент на странице можно двумя способами:
заранее сформировать его код в сценарии, а затем по мере необходимости внедрить этот код в разметку;
создать элемент непосредственно в процессе выполнения сценария.
Первым вариантом мы уже неоднократно пользовались. Тем не менее,
еще раз продемонстрируем этот прием (в максимально простом варианте):
Файл chapter12/script12.1.1.html
<!DOCTYPE html>
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", function()
{
let pict='<img src="../chapter13/img1/im1.jpg"
id="im1" alt="...">';
document.getElementById("ins").
insertAdjacentHTML("beforeend", pict);
}, {once: true});
});
</script>
</head>
<body>
<p id="ins">
<input type="button" id="but" value="CLICK"><br></p>
</body>
</html>
— 155 —
Здесь код создаваемого элемента уже записан в сценарии — нам только остается внедрить изображение в желаемый момент.
Если элемент надо создать, как иногда говорят, «на лету», программисты
используют метод createElement. В этом случае применяется следующая форма записи:
document.createElement("имя тега");
То есть элемент сначала создается на уровне документа и только потом добавляется в необходимую точку разметки. Кстати, имя создаваемого элемента может заключаться в одинарные кавычки.
Модернизируем предыдущий сценарий, чтобы применить метод
createElement:
Файл chapter12/script12.1.2.html
<!DOCTYPE html>
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", function()
{
let pict=document.createElement("img");
pict.src="../chapter13/img1/im1.jpg";
pict.id="im1";
pict.alt="Горный пейзаж";
document.getElementById("ins").
insertAdjacentElement("beforeend", pict);
}, {once: true});
});
</script>
...
</html>
В этом случае мы создали элемент
let pict=document.createElement("img");
а потом добавили ему три атрибута — адрес, идентификатор и альтернативный
текст:
pict.src="../chapter13/img1/im1.jpg";
pict.id="im1";
pict.alt="Горный пейзаж";
Теперь обратите внимание на важное отличие от предыдущего сценария: сейчас
мы использовали не метод insertAdjacentHTML (он предназначен только для
вставки HTML кода), а метод insertAdjacentElement:
document.getElementById("ins").
insertAdjacentElement("beforeend", pict);
Этот метод позволяет вставлять создаваемые элементы в 4 позиции:
beforebegin — перед открывающим тегом текущего элемента;
afterbegin — сразу после открывающего тега текущего элемента;
— 156 —
beforeend — перед закрывающим тегом текущего элемента;
afterend — сразу после закрывающего тега текущего элемента.
Существует еще три метода вставки элементов на страницу.
Метод insertBefore — его запись выглядит так:
insertBefore(новый элемент,
перед каким элементом разместить);
То есть создаваемый элемент вставляется перед элементом, указанным во втором аргументе. Если вторым аргументом указать null, новый элемент будет помещен в конце текущего элемента:
Файл chapter12/script12.1.3.html
<!DOCTYPE html>
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", function()
{
let pict=document.createElement("img");
...
document.getElementById("ins").
insertBefore(pict, null);
}, {once: true});
});
</script>
...
</html>
Метод appendChild вставляет новый элемент в конец текущего:
Файл chapter12/script12.1.4.html
<!DOCTYPE html>
...
<script>
addEventListener("load", function()
{
document.getElementById("but").
addEventListener("click", function()
{
let pict=document.createElement("img");
pict.src="../chapter13/img1/im1.jpg";
pict.id="im1";
pict.alt="Горный пейзаж";
document.getElementById("ins").appendChild(pict);
}, {once: true});
});
</script>
...
</html>
— 157 —
Наконец, последний метод используется для вставки элементов в выделенную область страницы. Сначала на странице необходимо выделить какую-то
область, например, часть текста. Затем создать элемент. После этого добавить
его к выделению.
Давайте посмотрим пример такого сценария. Запустите в браузере файл
chapter12/script12.1.5.html. Начальный вид страницы показан на рисунке 12.1.1.
Рис. 12.1.1
Начальный вид страницы,
иллюстрирующий создание элементов в тексте
Перед вами текст, а над ним две кнопки: для выделения полужирным и
для вставки изображения. Чтобы воспользоваться первой кнопкой, выделите
весь текст или его фрагмент указателем мыши. Теперь нажмите кнопку.
Оформление текста изменится. Выделите какое-нибудь слово или просто кликните мышью в тексте. Теперь нажмите кнопку IMG. Картинка с горным пейзажем появится в тексте (рис. 12.1.2).
Начнем разбор программы с разметки. Она очень простая:
Файл chapter12/script12.1.5.html
<!DOCTYPE html>
...
<body>
<p><input type="button" id="but1" value=" BOLD ">
<input type="button" id="but2" value=" IMG ">
<br><br>
Перед вами текст, иллюстрирующий ...</p>
</body>
</html>
— 158 —
Рис. 12.1.2
Вид страницы после создания в ней новых элементов
Теперь обратимся к сценарию.
После загрузки документа
addEventListener("load", function()
{
...
});
записываем такую инструкцию:
let sel=document.getSelection();
Метод getSelection, который мы использовали, возвращает объект Selection —
он содержит выделенный пользователем текст. Этот объект создается заново
каждый раз, когда вы делаете очередное выделение.
Если кликнуть на кнопке BOLD, будет запущен следующий обработчик:
document.getElementById("but1").
addEventListener("click", function()
{
...
});
В анонимной функции три инструкции. Сначала создаем элемент b:
let bold=document.createElement("b");
Затем, используя метод getRangeAt, создаем объект Range с диапазоном, равным выделению. Индекс 0 указывает, что выбранный диапазон у нас первый и
единственный:
— 159 —
let rng=sel.getRangeAt(0);
Теперь методом surroundContents добавляем открывающий и закрывающий
теги b перед началом выделения и после конца выделения:
rng.surroundContents(bold);
Все. Оформление выделенного фрагмента изменено.
Если кликнуть на кнопке IMG, будет запущен второй обработчик:
document.getElementById("but2").
addEventListener("click", function()
{
...
});
Создаем элемент img:
let pict=document.createElement("img");
Добавляем необходимые атрибуты:
pict.src="../chapter13/img1/im1.jpg";
pict.id="im1";
pict.alt="Горный пейзаж";
Помещаем рисунок в текст:
let rng=sel.getRangeAt(0);
rng.surroundContents(pict);
Технология, только что разобранная нами, используется при создании визуальных редакторов, которые необходимы администраторам сайтов для изменения или наполнения web-страниц контентом.
А как создать более сложный элемент, например, таблицу? Покажем это
на следующем примере. Традиционная для этого раздела разметка страницы:
Файл chapter12/script12.1.6.html
<!DOCTYPE html>
...
<body>
<p id="ins">
<input type="button" id="but" value="CLICK">
<br><br></p>
</body>
</html>
Щелчок на кнопке запускает анонимную функцию:
document.getElementById("but").
addEventListener("click", function()
{
...
}, {once: true});
Первым делом создаем таблицу:
let tab=document.createElement("table");
— 160 —
Следом запускаем цикл, в котором формируем у таблицы три строки:
for(let i=0; i<3; i++)
{
let tr=document.createElement("tr");
...
tab.appendChild(tr);
}
Внутри этого цикла есть еще один — вложенный. Он создает у каждой строки
по две ячейки:
for(let t=0; t<2; t++)
{
let td=document.createElement("td");
tr.appendChild(td);
}
В результате мы получим таблицу, показанную на рисунке 12.1.3.
Рис. 12.1.3
Создаем таблицу
О том, как придать элементам необходимое оформление, мы поговорим в
следующем разделе.
12.2. Настройка атрибутов и свойств
В предыдущем разделе мы уже видели пример добавления атрибутов
только что созданным элементам img. Напомним, как мы это делали:
let pict=document.createElement("img");
pict.src="../chapter13/img1/im1.jpg";
pict.id="im1";
pict.alt="Горный пейзаж";
Таким же способом можно создавать и другие атрибуты. Вновь продемонстрируем это на примере с фотографией.
Предварительно зарегистрируем для рисунков класс im с определенными
настройками:
Файл chapter12/script12.2.1.html
.im {width: 400px; margin: 5px;}
— 161 —
Как и в предыдущих сценариях, присвоим создаваемому изображению
обязательные атрибуты src и alt:
document.getElementById("but").
addEventListener("click", function()
{
let pict=document.createElement("img");
pict.src="../chapter13/img1/im1.jpg";
pict.alt="Горный пейзаж";
...
}, {once: true});
А потом добавим атрибут title
pict.title="Фотография горного пейзажа";
и атрибут class:
pict.className="im";
Обратите внимание: присвоение имени класса элементу производится через установку свойства, которое называется className. Свойство получило такое название из-за того, что термин class в JavaScript является зарезервированным именем.
Настроить внешний вид и оформление нового элемента можно, добавив
ему атрибут style с определенным набором свойств:
Файл chapter12/script12.2.2.html
document.getElementById("but").
addEventListener("click", function()
{
let pict=document.createElement("img");
...
pict.style="width: 300px;
border: 10px solid #0000CC;
margin-top: 80px; transform: rotate(30deg)";
document.getElementById("ins").
insertAdjacentElement("beforeend", pict);
}, {once: true});
Что у нас получится после запуска этого сценария, видно на рисунке 12.2.1.
Установить атрибуты у созданного элемента можно с помощью метода
setAttribute. Вот пример:
Файл chapter12/script12.2.3.html
pict.setAttribute("src",
"../chapter13/img1/im1.jpg");
pict.setAttribute("alt", "Горный пейзаж");
pict.setAttribute("title",
"Фотография горного пейзажа");
pict.setAttribute("style", "width: 300px;
border: 10px solid #0000CC; margin-top: 80px;
transform: rotate(30deg)");
— 162 —
Рис. 12.2.1
У этого только что созданного элемента был установлен атрибут style
Кстати, устанавливая различные атрибуты с набором значений, можно
настраивать не только новые элементы, но и те, что были в разметке страницы
изначально.
В следующем примере изображение «прописано» в разметке страницы:
Файл chapter12/script12.2.4.html
<p><input type="button" id="but" value="CLICK"><br>
<img src="../chapter13/img1/im1.jpg"
id="im" alt="..."></p>
Сценарий добавления атрибутов выглядит так:
document.getElementById("but").
addEventListener("click", function()
{
let pict=document.getElementById("im");
pict.setAttribute("title",
"Фотография горного пейзажа");
pict.setAttribute("style", "width: 300px;
border: 10px solid #0000CC; margin-top: 80px;
transform: rotate(30deg)");
}, {once: true});
Если нажать кнопку, размер фотографии увеличится, у нее появится рамка,
снимок повернется на 30 градусов, а при наведении указателя мыши на него вы
увидите поясняющую надпись.
— 163 —
12.3. Удаление атрибутов и свойств
Начнем с того, что значения атрибутов и свойств элемента можно изменить.
Делается это теми же способами, что мы использовали при установке атрибутов.
Способ первый. Есть изображение
Файл chapter12/script12.3.1.html
<img src="../chapter13/img1/im1.jpg"
id="im" alt="..." title="Фотография горного пейзажа">
с такими настройками:
img {width: 300px; border: 10px solid #0000CC;
margin-top: 80px; transform: rotate(30deg);}
После нажатия кнопки сценарий меняет изначальные установки:
let pict=document.getElementById("im");
pict.src="../chapter13/img1/im3.jpg";
pict.title="Атрибуты изменены";
pict.style="width: 400px; border: 20px solid #CC0000;
margin-top: 80px; transform: rotate(10deg)";
В итоге на странице появляется новый снимок с новой поясняющей надписью и
с новыми свойствами стиля.
Другой способ — замена атрибутов методом setAttribute:
Файл chapter12/script12.3.2.html
let pict=document.getElementById("im");
pict.setAttribute("src", "../chapter13/img1/im3.jpg");
pict.setAttribute("title", "Атрибуты изменены");
pict.setAttribute("style", "width: 400px;
border: 20px solid #CC0000; margin-top: 80px;
transform: rotate(10deg)");
Для удаления атрибутов и настроек стилей используйте метод
removeAttribute. Допустим, у нас есть вот такой элемент:
Файл chapter12/script12.3.3.html
<img src="../chapter13/img1/im1.jpg" id="im"
alt="..." title="Фотография горного пейзажа"
style="width: 300px; border: 10px solid #0000CC;
margin-top: 80px; transform: rotate(30deg);">
Если нажать кнопку CLICK, атрибуты title и style будут удалены вместе с их
значениями:
let pict=document.getElementById("im");
— 164 —
pict.removeAttribute("title");
pict.removeAttribute("style");
12.4. Ликвидация элементов
Теперь мы рассмотрим пять различных способов удалить один или несколько элементов из документа. А для этого воспользуемся текстом из файла
script12.1.5.html, сделав в нем выделение полужирным (теги <b>...</b>) и
вставив изображение (тег <img...>):
Перед вами <b>текст, иллюстрирующий добавление
элементов</b> в выделенную область.
Вы можете сделать весь текст или его фрагмент
полужирным, а также вставить
<img src="../chapter13/img1/im1.jpg" alt="...">
изображение вместо выделенной области или в точку
в тексте, где вы кликнули мышью.
И, конечно, оставим кнопку, только теперь ее назначение — удалять элементы
со страницы:
<input type="button" id="but" value="ОЧИСТИТЬ">
В примере, который мы разберем первым, нам предстоит удалять элементы, выделяя их в тексте.
Загрузите в браузер файл chapter12/script12.4.1.html. Выделите фрагмент
с полужирным начертанием так, чтобы в выделение попало по несколько букв
слева и справа с обычным начертанием (как это сделать, показано на рис. 12.4.1).
Нажмите кнопку ОЧИСТИТЬ. Полужирное выделение исчезнет, останется
только обычный текст. По той же методике выделите фото, захватив слева и
справа по несколько букв. Снова нажмите кнопку. Снимок исчезнет.
Думаю, уже понятно, что наша программа удаляет элементы, попавшие в
выделенную область. Разберем ее код.
Получаем выделенный фрагмент:
Файл chapter12/script12.4.1.html
let sel=document.getSelection();
Запускаем анонимную функцию, которая обрабатывает щелчок на кнопке:
document.getElementById("but").
addEventListener("click", function()
{
...
});
После нажатия кнопки создаем объект Range:
let rng=sel.getRangeAt(0);
С помощью метода toString получаем текстовое содержимое из выделенной
области
sel.toString()
— 165 —
и на его основе методом createTextNode создаем в документе текстовый узел,
содержимое которого является точной копией содержимого выделенного фрагмента (без тегов HTML):
let cr=document.createTextNode(sel.toString());
Методом deleteFromDocument удаляем выделенный фрагмент
sel.deleteFromDocument();
и на его место вставляем методом insertNode только что созданный текстовый
узел:
rng.insertNode(cr);
Таким образом, HTML элемент удаляется, и остается только текст.
Рис. 12.4.1
Выделяем фрагмент текста, чтобы удалить из него элемент b
В следующем примере мы «одним махом» удалим все элементы из текста. Такой сценарий очень простой:
Файл chapter12/script12.4.2.html
document.getElementById("but").
addEventListener("click", function()
{
let ins=document.getElementById("ins");
ins.innerHTML=ins.innerText;
});
Создаем переменную ins, ссылающуюся на одноименный контейнер:
— 166 —
let ins=document.getElementById("ins");
Получаем текст из контейнера
ins.innerText
и заменяем все содержимое контейнера на этот текст:
ins.innerHTML=ins.innerText;
В результате нажатия кнопки со страницы исчезнут все элементы, в том числе и
сама кнопка. Останется только текст.
Третий пример демонстрирует полное удаление всего содержимого страницы. Он еще проще:
Файл chapter12/script12.4.3.html
document.getElementById("but").
addEventListener("click", function()
{
document.getElementById("ins").innerHTML="";
});
Здесь мы, установив свойству innerHTML пустое значение, полностью очистили контейнер.
Четвертый сценарий показывает удаление изображения методом
removeChild — то есть удаление потомка у родителя:
Файл chapter12/script12.4.4.html
document.getElementById("but").
addEventListener("click", function()
{
document.getElementById("ins").
removeChild(document.getElementById("im1"));
});
Последний сценарий использует для удаления картинки метод remove:
Файл chapter12/script12.4.5.html
document.getElementById("but").
addEventListener("click", function()
{
document.getElementById("im1").remove();
});
Как видите, метод remove — более прямолинейный, чем removeChild. Кроме
того, использование remove делает код более компактным.
— 167 —
13. «ВАНИЛЬНЫЙ» JAVASCRIPT
Vanilla JS — http://vanilla-js.com/ — шуточный сайт кроссплатформенного
фреймворка Vanilla JS, под которым подразумевается чистый JavaScript. Его автор ставит целью напомнить разработчикам, что довольно часто при создании
кода можно обойтись без библиотек и фреймворков. В очень многих случаях
писать программы на чистом JavaScript гораздо продуктивнее и рациональнее.
От имени сайта и образовалось это выражение — «ванильный JavaScript» — то
есть JavaScript в его первозданном виде.
На данную тему автор заговорил не напрасно. Сегодня стало практически
нормой использовать различные библиотеки типа jQuery в самых разных, даже
простых разработках. А написание программ от и до на «ванильном» JavaScript
по нынешним меркам можно назвать креативным программированием.
В этой главе автор хочет показать, как, создавая разные сценарии, можно
вполне обойтись чистым JavaScript. В качестве примеров выбраны программы
работы с изображениями, которые в большинстве случаев пишут с применением компонентов из jQuery. На семи примерах вы убедитесь, что при разработке
самых разных галерей и слайдеров не нужно мудрить. «Ванильный» JavaScript
способен решить любые задачи без посторонней помощи.
13.1. Вращаем изображения
В этой программе мы продемонстрируем, как можно увеличивать маленькие картинки с одним интересным эффектом.
Запустим файл chapter13/script13.1.1.html в браузере. Перед нами 9 фотографий горных пейзажей (рис. 13.1.1). Щелкните указателем мыши на любом
рисунке, и вы увидите, как он плавно увеличится в размерах и сместится в
центр (рис. 13.1.2). Но не просто сместится, а одновременно совершив разворот
на 360о. При этом оставшиеся фото плавно исчезнут (рис. 13.1.3). Чтобы вернуть странице исходный вид, вторично щелкните по увеличенной картинке. Она
уменьшится и займет свое место, совершив обратный разворот, а остальные рисунки вновь станут видны. И опять все изменения произойдут плавно. Так можно увеличивать и просматривать любые картинки, находящиеся на странице.
Перед тем, как мы приступим к изучению принципов работы программы,
несколько слов о посетителях сайтов. Дело в том, что среди них иногда попадаются особо пытливые умы, которые норовят проверить ваши сценарии на
прочность. Например, они могут непрерывно щелкать на вращающемся снимке.
Или после смещения просматриваемой фотографии в центр кликнуть на другой
кадр (фотографии хоть и не видны, но все равно присутствуют на странице).
В обоих случаях произойдет сбой в работе программы. Поэтому необходимо
предусмотреть защитные меры от таких экспериментаторов, что и было сделано автором. Для этого в сценарий внедрены несколько специальных инструкций, которые мы разберем в процессе обсуждения «механизмов» функционирования данной галереи.
— 168 —
Рис. 13.1.1
Страница в исходном состоянии
Рис. 13.1.2
Выбранный снимок начинает увеличиваться и смещаться
к центру с одновременным вращением, а остальные растворяются
Итак, в основе страницы — 9 фотографий:
Файл chapter13/script13.1.1.html
<img id="i1" class="im" src="img1/im1.jpg" alt="...">
...
<img id="i9" class="im" src="img1/im9.jpg" alt="...">
Для всех из них задано абсолютное позиционирование
— 169 —
.im {position: absolute; ...; z-index: 1;}
с привязкой к конкретным точкам:
#i1 {left: 10px; top: 10px;}
...
#i9 {left: 634px; top: 490px;}
Рис. 13.1.3
Изображение заняло центральное положение,
остальные картинки в это время не видны
Фотографии размещены в контейнере:
<div id="basis">
...
</div>
Сценарий начинается с объявления трех глобальных переменных:
let y=1;
let w=0;
let h=0;
Переменная y выполняет роль идентификатора кликов. Она позволяет
сценарию определить, первый или второй щелчок был на рисунке, то есть увеличивать изображение или, наоборот, уменьшать. Если y принимает значение 1,
это означает первый клик, если 2 — второй.
Переменные w и h нужны для временного хранения координат рисунка,
на котором произошел клик, чтобы программа «знала», куда возвращать эту
картинку.
Далее мы регистрируем обработчик события click уровня окна:
addEventListener("click", rotate);
В качестве обработчика у нас выступает функция rotate:
— 170 —
function rotate(ev)
{
...
}
Так как обращений к интерфейсу event будет несколько, сократим код,
объявив переменную e:
let e=ev.target;
Проверяем, где выполнен клик:
if(e.tagName=="IMG")
Если на одной из картинок, то выполняются программные блоки функции.
Первым делом отменяем привязку обработчика, тем самым запрещая повторные клики на время смещения фото в центр:
removeEventListener("click", rotate);
В теле функции мы неоднократно станем обращаться к стилевым параметрам изображения, на котором был сделан щелчок. Поэтому для сокращения
кода объявим переменную t:
let t=document.getElementById(e.id).style;
Если на данной картинке случился первый щелчок
if(y==1)
то последовательно выполняются следующие инструкции — сначала выясняются координаты рисунка относительно границ слоя-контейнера:
let d=document.getElementById(e.id);
w=d.offsetLeft;
h=d.offsetTop;
Затем переменной-идентификатору присваивается новое значение:
y=2;
Дальше стартует цикл, в котором выполняются операции растворения
остальных изображений (здесь мы снижаем уровень непрозрачности до 0):
for(let i=1; i<10; i++)
{
let b="i"+i;
if(e.id!=b)
{
let p=document.getElementById(b).style;
p.pointerEvents="none";
p.transition="opacity 1s";
p.opacity=0;
}
}
Как видите, за счет условия
let b="i"+i;
if(e.id!=b)
{
...
}
— 171 —
растворяются лишь те фото, id которых не совпадает с id выбранного.
Забегая вперед, скажем, что после смещения картинки в центр мы повторно зарегистрируем обработчик. Но тогда пытливый клиент может щелкнуть на свободном участке страницы и попасть на невидимый снимок. Чтобы
такой клик не привел сценарий в действие, мы добавили инструкцию, которая
блокирует активность остальных элементов:
p.pointerEvents="none";
Теперь меняется состояние «главного действующего лица». Сначала увеличивается z-index картинки, чтобы она в процессе движения располагалась
поверх остальных рисунков:
t.zIndex=2;
Затем одновременно выполняем несколько операций.
1. Задаем время изменения размера фотографии
width 1s
время изменения координат
left 1s, top 1s
и время, в течение которого происходит вращение снимка
transform 1s
Получаем вот такую инструкцию:
t.transition=
"width 1s, left 1s, top 1s, transform 1s";
2. Плавно в течение 1 секунды увеличиваем рисунок:
t.width="500px";
3. Также плавно в течение 1 секунды смещаем картинку в центр базового
контейнера:
t.left="250px";
t.top="100px";
4. Не менее плавно поворачиваем изображение на 360 градусов:
t.transform="rotate(360deg)";
Суммарно результат выполнения первой части условного выражения выглядит именно так, как описано в начале раздела: незадействованные снимки
растворяются, а выбранный, вращаясь, увеличивается и смещается в центр, оказываясь в одиночестве на белом фоне.
Когда выбранный снимок расположится в центре документа, восстанавливаем обработчик:
setTimeout(()=>
{
addEventListener("click", rotate);
}, 1000);
Снова щелкнем по увеличенной картинке. Теперь обработка события отменяется вновь и выполняется вторая часть условного выражения:
— 172 —
else
{
...
}
Первым делом меняется значение, хранящееся в идентификаторе:
y=1;
Потом стартует цикл, в котором фоновые снимки проявляются вновь, так
как значение их непрозрачности увеличивается с 0 до 1:
for(let i=1; i<10; i++)
{
let b="i"+i;
let p=document.getElementById(b).style;
p.transition="opacity 2s";
p.opacity=1;
...
}
Время проявления 2 секунды подобрано экспериментально — при меньшем
значении проявление фотографий будет выполняться более резко, чем растворение.
В этом же цикле создаем таймеры, которые через 1 секунду делают
остальные элементы активными:
setTimeout(()=>
{
p.pointerEvents="auto";
}, 1000);
После чего запускается процесс уменьшения и обратного перемещения
просмотренного рисунка в точку, которая определена координатами, сохраненными в переменных w и h:
t.transition=
"width 1s, left 1s, top 1s, transform 1s";
t.width="300px";
t.left=w+"px";
t.top=h+"px";
t.transform="rotate(0deg)";
Движение в обратном направлении, на исходную позицию, тоже сопровождается вращением снимка:
t.transition="..., transform 1s";
...
t.transform="rotate(0deg)";
Только сейчас, как вы видите, картинка вращается в противоположную сторону:
rotate(0deg)
В последнюю очередь мы повторно регистрируем обработчик и меняем
свойство z-index. Оно возвращается к исходному значению:
setTimeout(()=>
{
— 173 —
t.zIndex=1;
addEventListener("click", rotate);
}, 1000);
Так как идентификатор в данный момент хранит единицу, сценарий готов
обработать следующий щелчок и увеличить любую другую картинку (или ту же
самую).
На этом описание работы программы завершено.
13.2. Оптимальная галерея
Второй сценарий данной главы демонстрирует совершенно иной подход
при создании галерей фотографий. В исходном состоянии вы увидите на странице несколько превью изображений. Но, увеличив одно из них, можно с помощью кнопок «Вперед» и «Назад» смотреть остальные увеличенные снимки,
не возвращаясь к миниатюрам.
Запустите в браузере файл chapter13/script13.2.1.html — и вы увидите
9 картинок (рис. 13.2.1). Кликните по любой из них. Изображение в натуральную величину возникнет на белом полупрозрачном фоне поверх страницы
(рис. 13.2.2). Рядом с фото появятся кнопки просмотра галереи. Она имеет
ограничения по перемотке вперед и назад. К примеру, нажимайте правую
стрелку до тех пор, пока не загрузится последний, девятый снимок. В этот момент правая стрелка исчезнет — останется только левая (рис. 13.2.3). Перемотка вперед остановится. Выполните перемотку галереи в обратную сторону. После первого же щелчка кнопка справа появится вновь. По достижении начала
галереи скроется левая кнопка, и перемотка назад остановится. Если теперь
щелкнуть по правой кнопке, левая появится вновь.
На «исчезновении» кнопок еще раз сделаем акцент. Итак, если кликнуть
первый кадр из набора миниатюр, то будет отсутствовать левая стрелка. Если
кликнуть последний рисунок-миниатюру, то отсутствовать будет правая стрелка.
Документ содержит 9 изображений:
Файл chapter13/script13.2.1.html
<img class="im" src="img1/im1.jpg" alt="Фото">
...
<img class="im" src="img1/im9.jpg" alt="Фото">
Также есть фоновый слой (белый и полупрозрачный)
#bas {...; background: #FFFFFF; opacity: 0.95;}
...
<div id="bas"></div>
а, кроме того, слой с кнопками
<div id="view">
<img id="nz" src="pict/nz.png" alt="Назад">
...
<img id="vp" src="pict/vp.png" alt="Вперед">
</div>
и с картинкой
— 174 —
<div id="view">
...
<img id="pict" src="pict/net.jpg" alt="Фото">
...
</div>
Рис. 13.2.1
Коллекция фотографий на странице
Рис. 13.2.2
Просмотр увеличенного фото с возможностью пролистать большие изображения
без возврата на базовую часть страницы
— 175 —
Рис. 13.2.3
Пролистывание галереи вправо завершено
При внимательном изучении приведенного выше фрагмента появляется
вопрос: а зачем нам файл pict/net.jpg? Все очень просто — это микроскопическая белая точка размером 1х1 пиксель. По стандартам HTML5 атрибуту src
всегда должен быть задан какой-то конкретный адрес. Так как в исходном состоянии ни один из снимков не загружен в слой view, мы, следуя правилам, загружаем вместо них нейтральное миниатюрное изображение, которое не видно
на белом фоне и быстро заменяется одним из девяти рисунков.
В сценарии надо зарегистрировать четыре обработчика для пяти событий.
Для обработки двух событий — скрытия большой картинки по щелчку на
ней или при прокрутке страницы — служит функция del:
addEventListener("scroll", del);
document.getElementById("pict").
addEventListener("click", del);
Нажатие кнопок-стрелок обрабатывают две анонимные функции:
document.getElementById("nz").
addEventListener("click", function()
{
...
});
и
document.getElementById("vp").
addEventListener("click", function()
{
...
});
Клик на любой миниатюре запускает еще одну анонимную функцию:
document.getElementById("basis").
addEventListener("click", function(ev)
— 176 —
{
...
});
Как уже было в аналогичных случаях из предыдущего раздела, создаем
переменные для сокращенного обращения к соответствующим элементам страницы:
let view=document.getElementById("view").style;
let bas=document.getElementById("bas").style;
let pict=document.getElementById("pict");
Затем создаем переменные для сокращенного обращения к свойствам
кнопок перемотки:
let nz=document.getElementById("nz").style;
let vp=document.getElementById("vp").style;
Кроме того, вводим еще две переменные. Для хранения данных о количестве снимков в документе
let n=document.querySelectorAll(".im").length;
а также идентификатор индекса выбранного фото (его начальное значение выбрано произвольно и никак не влияет на работу программы):
let i=1;
При щелчке по одной из миниатюр сначала определяется, что клик состоялся на изображении
if(ev.target.tagName=="IMG")
{
...
}
потом вычисляется прокрутка страницы
let sc=window.pageYOffset;
далее выполняется смещение фоновых слоев на эту величину
bas.top=sc+"px";
view.top=sc+"px";
изображению на слое с id="view" присваивается адрес выбранного снимка
pict.src=ev.target.src;
и фоновые слои делаются видимыми:
view.visibility="visible";
bas.visibility="visible";
Затем определяем числовой индекс «кликнутого» рисунка, разбивая его
имя по символам 1/im
let rig=ev.target.src.split("1/im");
и по первым двум символам расширения
let lef=rig[1].split(".j");
Полученный результат присваиваем переменной i:
i=lef[0];
— 177 —
Далее «включаем» демонстрацию кнопок:
vp.visibility="visible";
nz.visibility="visible";
Теперь выясняем, надо ли скрыть правую или левую кнопки. Если был
запущен просмотр первого изображения, то скрываем кнопку «Назад»:
if(i==1)
nz.visibility="hidden";
Если был запущен просмотр последнего изображения, то скрываем кнопку
«Вперед»:
if(i==n)
vp.visibility="hidden";
Предположим, что сейчас на фоновом слое видны обе кнопки. Посмотрим, как их нажатие будет приводить к перемотке элементов галереи.
Когда мы нажимаем кнопку «Назад», запускается соответствующая анонимная функция:
document.getElementById("nz").
addEventListener("click", function()
{
...
});
Первым делом включаем видимость кнопки «Вперед», если она была
скрыта:
vp.visibility="visible";
Уже знакомым нам способом узнаем номер увеличенного снимка:
let posi=document.getElementById("pict").src;
let rig=posi.split("1/im");
let lef=rig[1].split(".j");
i=lef[0];
Функция работает, пока значение идентификатора i больше единицы:
if(i>1)
{
...
}
Если условие истинно, уменьшаем значение идентификатора:
i--;
Проявляем кнопку «Вперед», если она была скрыта:
vp.visibility="visible";
Загружаем на фоновый слой рисунок, индекс которого меньше на единицу:
document.getElementById("pict").src=
"img1/im"+i+".jpg";
Если демонстрируемое теперь фото имеет индекс первого элемента, скрываем
кнопку «Назад»:
— 178 —
if(i==1)
nz.visibility="hidden";
Функция перемотки изображений вперед работает похожим образом.
document.getElementById("vp").
addEventListener("click", function()
{
...
});
Включаем видимость кнопки «Назад», если она была скрыта:
nz.visibility="visible";
Узнаем номер увеличенного снимка:
let posi=document.getElementById("pict").src;
let rig=posi.split("1/im");
let lef=rig[1].split(".j");
i=lef[0];
Инструкции функции выполняются, пока значение идентификатора i
меньше n:
if(i<n)
{
...
}
Если условие истинно, увеличиваем значение идентификатора:
i++;
Проявляем кнопку «Назад», если она была скрыта:
nz.visibility="visible";
Загружаем на фоновый слой рисунок, индекс которого больше на единицу:
document.getElementById("pict").src=
"img1/im"+i+".jpg";
Если демонстрируемое теперь фото имеет индекс последнего элемента, скрываем кнопку «Вперед»:
if(i==n)
vp.visibility="hidden";
Прокрутим страницу или щелкнем на увеличенном изображении. Будет
запущена функция del:
function del()
{
view.visibility="hidden";
bas.visibility="hidden";
pict.src="pict/net.jpg";
nz.visibility="hidden";
vp.visibility="hidden";
}
Ее задача — сделать слои и кнопки вновь невидимыми, а изображению верхнего слоя вернуть первоначальный адрес pict/net.jpg. В принципе, последнюю
— 179 —
операцию можно было не выполнять. Но в таком случае при низкой скорости
соединения с сервером вы столкнетесь с неприятным эффектом. Допустим, вы
посмотрели один снимок, закрыли фотографию и щелкнули на другой снимок.
Вновь запустится режим просмотра, но из-за медленной загрузки второго
снимка вы некоторое время будете видеть первый. А это, согласитесь, не очень
хорошо. Присваивая src значение pict/net.jpg, мы избавляемся от подобных неприятностей, ведь маленькая белая точка загружается очень быстро и совершенно не будет видна в момент медленной загрузки второго рисунка. То есть
мы просто будем видеть белый полупрозрачный фон до тех пор, пока не появится новое изображение.
13.3. Перемотка с растворением
В этом примере мы также станем пролистывать изображения с помощью
двух кнопок: «Вперед» и «Назад». Только принцип работы слайдера будет
иной.
Запустите файл chapter13/script13.3.1.html. Перед вами страница, показанная на рисунке 13.3.1. В центре окна браузера есть фотография, а под ней
две графические кнопки, показывающие направление перемотки галереи.
Кликнем правую кнопку. Начальный рисунок плавно начнет смещаться
вправо и одновременно слева станет выдвигаться следующая картинка
(рис. 13.3.2). При этом снимок, уходящий вправо, растворяется по ходу движения, а входящий слева, наоборот, проявляется сквозь белый фон страницы.
Перемотку можно выполнять в двух направлениях. Количество элементов
в галерее ограничено девятью штуками. Когда на экране появится первая или
последняя фотография, соответствующая кнопка исчезнет, показывая, что в
данном направлении уже нечего просматривать (рис. 13.3.3).
В этом и двух последующих сценариях тоже есть возможность для любителей экспериментировать. Она состоит в том, чтобы непрерывно нажимать
кнопки, не дожидаясь полной смены рисунков. Поэтому в программах из текущего и двух следующих разделов предусмотрено отключение обработчиков на
время движения картинок.
Начнем изучение программы с выяснения особенностей разметки.
На странице есть базовый контейнер
Файл chapter13/script13.3.1.html
<div id="basis">
...
</div>
вмещающий все элементы. Контейнер располагается по центру окна браузера:
#basis {position: relative; margin: auto;
width: 1000px;}
В документе 9 изображений:
<img class="im" id="i1" src="img1/im1.jpg" alt="...">
...
— 180 —
<img class="im" id="i9" src="img1/im9.jpg" alt="...">
Пятое располагается по центру страницы:
#i5 {left: 350px;}
Смещение в 350 пикселей внутри базового контейнера как раз и позволяет рисунку быть в центре.
Первые четыре фото смещены влево:
#i1, #i2, #i3, #i4 {left: 50px; ...;}
Последние четыре — вправо:
#i6, #i7, #i8, #i9 {left: 650px; ...;}
Рис. 13.3.1
Начальный вид страницы
Рис. 13.3.2
Смена изображений
— 181 —
Рис. 13.3.3
Перемотка вперед завершена
Важное дополнение есть в настройках свойств картинок 1–4 и 6–9. Они
сделаны полностью прозрачными:
#i1, #i2, #i3, #i4 {...; opacity: 0;}
#i6, #i7, #i8, #i9 {...; opacity: 0;}
Под галереей миниатюр две кнопки для перемотки изображений:
<img id="nz" src="pict/nz.png" alt="Назад">
<img id="vp" src="pict/vp.png" alt="Вперед">
Сценарий начинается с объявления двух переменных, ссылающихся на
свойства кнопок
let nz=document.getElementById("nz").style;
let vp=document.getElementById("vp").style;
и создания переменной, которая выполняет роль счетчика рисунков:
let i=5;
В качестве ее начального значения выбран числовой индекс картинки, которая
находится на экране в момент загрузки страницы — то есть 5.
Прокруткой кадров управляют две функции:
document.getElementById("nz").
addEventListener("click", fnz);
document.getElementById("vp").
addEventListener("click", fvp);
Изучение сценария начнем с разбора функции fnz, которая управляет
движением снимков влево.
Первым делом отменяем привязку обработчиков, чтобы предотвратить
нажатие кнопок во время движения снимков:
document.getElementById("nz").
removeEventListener("click", fnz);
document.getElementById("vp").
removeEventListener("click", fvp);
— 182 —
Следующая инструкция функции:
vp.visibility="visible";
Она необходима на случай, когда все кадры прокручены вправо и, соответственно, кнопка «Вперед» скрыта. Если теперь нажать кнопку «Назад», то влево уйдет одно изображение. А значит, с этого момента нужно проявить кнопку
«Вперед», так как в этом направлении уже можно просматривать первый элемент слайдера.
Дальше проверяем условие:
if(i<9)
{
...
}
То есть функция работает, пока значение счетчика не достигло верхнего предела.
В этой ситуации выполняются следующие инструкции — сначала запускается центральный снимок:
let ii=document.getElementById("i"+i).style;
ii.transition="left 2s, opacity 2s";
ii.left="50px";
ii.opacity=0;
Он смещается влево на 300 пикселей (с 350 до 50 пикселей) в течение 2 секунд:
ii.transition="left 2s, ...";
ii.left="50px";
...
В течение тех же 2 секунд смещающееся фото становится полностью прозрачным:
ii.transition="..., opacity 2s";
...
ii.opacity=0;
Затем создается переменная, значение которой на единицу больше значения счетчика:
let n=i+1;
Она нужна, чтобы отправить в центр следующий снимок.
Теперь можно в течение 2 секунд проявить и одновременно сместить
правую картинку влево — на место предыдущей:
let ni=document.getElementById("i"+n).style;
ni.transition="left 2s, opacity 2s";
ni.left="350px";
ni.opacity=1;
Хотя все процессы описаны поочередно, смещение, растворение прежнего и проявление нового изображений происходит одновременно. Мы уже видели это, когда экспериментировали со страницей.
Теперь увеличим значение счетчика
i++;
— 183 —
и проверим, не достигло ли оно максимально возможного уровня:
if(i==9)
Если «да», то скрываем кнопку «Назад»:
nz.visibility="hidden";
На последнем этапе по истечении 1.8 секунды вновь регистрируем обработчики:
setTimeout(()=>
{
document.getElementById("nz").
addEventListener("click", fnz);
document.getElementById("vp").
addEventListener("click", fvp);
}, 1800);
С этого момента слайдер готов к дальнейшей перемотке фото.
Функция перемотки вперед работает по схожему алгоритму.
Отменяем привязку обработчиков:
document.getElementById("nz").
removeEventListener("click", fnz);
document.getElementById("vp").
removeEventListener("click", fvp);
Проявляем кнопку «Назад», если она была скрыта
nz.visibility="visible";
Проверяем условие:
if(i>1)
{
...
}
То есть достигнут или нет нижний предел. Если «нет», находящийся в центре
снимок запускается вправо
let ii=document.getElementById("i"+i).style;
ii.transition="left 2s, ...";
ii.left="650px";
...
и одновременно растворяется:
ii.transition="..., opacity 2s";
...
ii.opacity=0;
Добавляем новую переменную n, значение которой на единицу меньше
значения переменной i:
let n=i-1;
Отправляем в центр и проявляем следующий снимок:
let ni=document.getElementById("i"+n).style;
ni.transition="left 2s, opacity 2s";
ni.left="350px";
ni.opacity=1;
— 184 —
Уменьшаем значение счетчика
i--;
и проверяем, не достигло ли оно минимального уровня:
if(i==1)
Если «да», то скрываем кнопку «Вперед»:
vp.visibility="hidden";
И опять на последнем этапе по истечении 1.8 секунды регистрируем обработчики:
setTimeout(()=>
{
document.getElementById("nz").
addEventListener("click", fnz);
document.getElementById("vp").
addEventListener("click", fvp);
}, 1800);
Что мы имеем в итоге? Так как процессы движения кадров синхронизированы, то вправо или влево снимки перемещаются одновременно, создавая впечатление непрерывной ленты, появляющейся из страницы и скрывающейся в ней.
13.4. Галерея-аккордеон
В этом разделе мы увидим довольно необычный слайдер. На взгляд автора, программа — одна из наиболее интересных с точки зрения визуального эффекта. Как меха аккордеона или гармони сжимаются и расправляться, так и фотографии в новой галерее будут вести себя похожим образом. При этом описанный результат будет достигнут максимально простым методом.
Загрузите в браузер файл chapter13/script13.4.1.html. Перед вами страница, показанная на рисунке 13.4.1. В центре окна есть фотография, а с левой
стороны — кнопка для перемотки галереи влево. Нажмем ее. Исходный снимок
начнет сжиматься по горизонтали влево, пока совсем не исчезнет. Одновременно с этим справа «из ничего» также по горизонтали начнет разворачиваться
следующее фото (рис. 13.4.2). В результате первый кадр совсем скроется, уступив место второму (рис. 13.4.3). При этом появится кнопка перемотки вправо.
Теперь галерею можно прокручивать в любом направлении.
В галерее 9 элементов. Кроме начального снимка
Файл chapter13/script13.4.1.html
<img class="im" id="i1" src="img1/im1.jpg" alt="...">
#i1 {width: 600px;}
в документе прописаны еще восемь:
<img class="im" id="i2" src="img1/im2.jpg" alt="...">
...
<img class="im" id="i9" src="img1/im9.jpg" alt="...">
— 185 —
Эти восемь снимков в исходном состоянии сжаты по ширине до 0 пикселей:
.im {... width: 0; height: 400px;}
По бокам от первого фото две уже привычные нам кнопки:
<img id="nz" src="pict/nz.png" alt="Назад">
<img id="vp" src="pict/vp.png" alt="Вперед">
Только кнопка «Вперед» сразу после загрузки страницы не видна:
#vp {...; visibility: hidden;}
Нажатие кнопки «Назад» запускает функцию fnz:
document.getElementById("nz").
addEventListener("click", fnz);
Рис. 13.4.1
Начальный вид страницы
Рис. 13.4.2
Момент смены фотографий
— 186 —
Рис. 13.4.3
На странице новый снимок, видны обе кнопки перемотки
Но обратим внимание, что перед этим создаются три переменные:
со ссылками на стили кнопок перемотки
let nz=document.getElementById("nz").style;
let vp=document.getElementById("vp").style;
и счетчик картинок
let i=1;
Значение счетчика соответствует числовому индексу первого рисунка.
На первом этапе выполнения сценария перемотки назад мы отменяем
привязку обработчиков:
document.getElementById("nz").
removeEventListener("click", fnz);
document.getElementById("vp").
removeEventListener("click", fvp);
Затем делаем видимой кнопку «вперед»:
vp.visibility="visible";
Перемотка назад выполняется, пока счетчик не достигнет верхнего предела:
if(i<9)
{
...
}
В основе функции — следующий алгоритм: первое фото сжимается до
ширины в 0 пикселей в течение 2 секунд
let ii=document.getElementById("i"+i).style;
ii.transition="width 2s";
ii.width=0;
— 187 —
оставаясь неподвижным относительно левой координаты
ii.left=0;
контейнера
<div id="bas">
...
</div>
вмещающего изображения.
Так как у нас все рисунки изначально позиционированы к правой границе
контейнера
.im {position: absolute; ...; right: 0; ...;}
чтобы первая картинка сжималась влево, необходимо изменить ее позиционирование. Что мы и сделали данной инструкцией:
ii.left=0;
У нас вышло, что сейчас снимок имеет два взаимоисключающих свойства позиционирования: значения и left, и right равны нулю. Но, согласно спецификации CSS, в такой ситуации left имеет преимущество. Следовательно, удалять
свойство right не обязательно. И это хорошо — оно нам еще пригодится.
Одновременно с процессами, описанными выше, разворачивается следующий рисунок
let n=i+1;
let ni=document.getElementById("i"+n).style;
ni.transition="width 1.95s";
ni.width="600px";
после чего значение счетчика увеличивается на единицу:
i++;
После этого заново регистрируем обработчики:
setTimeout(()=>
{
document.getElementById("nz").
addEventListener("click", fnz);
document.getElementById("vp").
addEventListener("click", fvp);
}, 1800);
Когда переменная i станет равна 9
if(i==9)
просмотр остановится, а кнопка «Назад» станет невидимой:
nz.visibility="hidden";
Алгоритм перемотки изображений вперед очень похож. Этим процессом
управляет функция fvp:
document.getElementById("vp").
addEventListener("click", fvp);
— 188 —
Ее первая операция — отмена привязки обработчиков:
document.getElementById("nz").
removeEventListener("click", fnz);
document.getElementById("vp").
removeEventListener("click", fvp);
Следующая инструкция делает видимой кнопку «Назад», если перед этим
перемотка влево дошла до последнего кадра:
nz.visibility="visible";
Функция движения фото вперед работает, пока в окне браузера не окажется первый снимок:
if(i>1)
{
...
}
Создаем переменную для обращения к свойствам текущего рисунка:
let ii=document.getElementById("i"+i).style;
Сейчас все картинки привязаны к левой границе контейнера. Чтобы последнее изображение сжималось вправо, удалим у него свойство left. А вот
свойство right у рисунка осталось. Тем самым мы вновь привязали фотографию
к правому краю контейнера:
ii.removeProperty("left");
Дальше плавно сожмем текущее изображение:
ii.transition="width 2s";
ii.width=0;
Чтобы следующий рисунок разжимался вправо, оставляем у него привязку к левому краю контейнера. Уменьшаем индекс
let n=i-1;
и разворачиваем картинку:
let ni=document.getElementById("i"+n).style;
ni.transition="width 1.95s";
ni.width="600px";
Уменьшаем значение счетчика на единицу:
i--;
Когда переменная i станет равна 1
if(i==1)
просмотр остановится, а кнопка «вперед» станет невидимой:
vp.visibility="hidden";
И последний «штрих» — вновь регистрируем обработчики:
setTimeout(()=>
{
document.getElementById("nz").
addEventListener("click", fnz);
— 189 —
document.getElementById("vp").
addEventListener("click", fvp);
}, 1800);
Перемещать изображения вперед-назад можно сколь угодно долго.
13.5. Креативный слайдер
Такие слайдеры автор неоднократно видел на самых разных сайтах. Делаются эти программы с обязательным использованием библиотеки jQuery. Но
в данном разделе нам предстоит убедиться, что подобные сценарии легко пишутся на «ванильном» JavaScript. При этом автор постарался, чтобы даже
внешне снимки из галереи фотографий выглядели похожими на те, что демонстрируются в скриптах на jQuery.
Запустим в браузере файл chapter13/script13.5.1.html. Мы увидим страницу, показанную на рисунке 13.5.1. Перед нами набор изображений, стилизованных под фотопленку с различными кадрами. Пленка видна лишь частично.
По бокам от нее расположены кнопки перемотки. Нажмем правую. Кадры
плавно сместятся вправо (рис. 13.5.2). Центральное положение займет рисунок,
который до этого был виден лишь на две трети (рис. 13.5.3). Продолжаем
нажимать правую кнопку. Когда в окне браузера появятся последние кадры,
кнопка движения вправо исчезнет (рис. 13.5.4). Перемотаем пленку в обратную
сторону. Когда мы дойдем до первого фото, скроется кнопка движения влево.
Хотите увеличить снимки, чтобы разглядеть их подробнее? Кликайте на
любой из видимых картинок. Даже на частично видимой. Страницу заполнит
белый полупрозрачный фон, на котором появится увеличенное изображение без
пленочного обрамления (рис. 13.5.5). Если теперь щелкнуть мышью по фотографии или фону, увеличенная картинка исчезнет.
Прежде чем писать сценарий, создадим вторую папку с рисунками —
img2, в которой находятся точно такие же фото, как в папке img1, только с
изображением фотопленки по краям снимка.
Начнем изучение программы с выяснения особенностей разметки.
На странице есть базовый контейнер
Файл chapter13/script13.5.1.html
<div id="basis">
...
</div>
вмещающий все элементы. Контейнер располагается по центру экрана:
#basis {position: relative; margin: auto;
width: 1000px;}
В документе 15 изображений:
<img class="im" id="i1" src="img2/im1.jpg" alt="...">
...
<img class="im" id="i15"
src="img2/im15.jpg" alt="...">
— 190 —
Восьмое располагается по центру страницы
#i8 {left: 400px;}
седьмое рядом слева
#i7 {left: 200px;}
девятое рядом справа
#i9 {left: 600px;}
Смещение в 400 пикселей внутри базового контейнера как раз и позволяет рисунку номер 8 быть в центре. Остальные картинки расположены по краям контейнера:
#i1, #i2, #i3, #i4, #i5, #i6 {left: 0;}
...
#i10, #i11, #i12, #i13, #i14, #i15 {left: 800px;}
Рис. 13.5.1
Кадры из слайдера после загрузки страницы
Рис. 13.5.2
Кадры плавно перемещаются вправо
— 191 —
Рис. 13.5.3
Центральное положение занял следующий снимок
Рис. 13.5.4
Перемотка вправо завершена
Рис. 13.5.5
Просмотр увеличенного снимка
— 192 —
Все снимки имеют одинаковый z-index:
.im {... z-index: 1;}
Кнопки перемотки, как вы уже видели, расположены по бокам от рабочей
области слайдера:
<img id="nz" src="pict/nz.png" alt="Назад">
<img id="vp" src="pict/vp.png" alt="Вперед">
Для показа увеличенных фотографий в теле документа есть два слоя, уже
знакомые нам по сценарию из раздела 13.2:
<div id="bas"></div>
<div id="view">
<img id="pict" src="pict/net.jpg" alt="Фото">
</div>
Чтобы рисунок выезжал из тела страницы, в ней есть два слоя, выполняющие роль «шторок» по бокам от границ видимой части слайдера:
<div id="fon1"></div>
<div id="fon2"></div>
Установки слоев:
#fon1, #fon2 {position: absolute; top: 0;
z-index: 2; width: 250px; height: 250px;
background: #FFFFFF;}
#fon1 {left: 0;}
#fon2 {right: 0;}
Как видите оба слоя имеют цвет фона — белый — и поэтому не видны.
Кнопки расположились поверх «шторок» за счет более высокого z-index:
#nz, #vp {... z-index: 3;}
Чтобы структура документа была понятней, можно посмотреть на рисунок 13.5.6. Слои изображены серым цветом, полупрозрачные фото показывают,
где располагаются остальные картинки в тот момент, когда мы их не видим.
Теперь выясним, как все работает.
Рис. 13.5.6
Схема расположения слоев и фотографий
— 193 —
Регистрируем головную функцию, внутри которой расположится код
сценария:
ddEventListener("load", function()
{
...
});
Объявляем, как мы уже неоднократно делали, несколько переменных для
сокращенного обращения к основным элементам страницы:
let view=document.getElementById("view");
let bas=document.getElementById("bas").style;
let pict=document.getElementById("pict");
let nz=document.getElementById("nz").style;
let vp=document.getElementById("vp").style;
Регистрируем обработчик закрытия окна просмотра для двух событий —
клика на картинке или фоне и прокрутки страницы:
addEventListener("scroll", del);
...
view.addEventListener("click", del);
Запуском окна просмотра и его скрытием управляют уже знакомые нам
по разделу 13.2 функции:
document.getElementById("basis").
addEventListener("click", function(ev)
{
if(ev.target.className=="im")
{
...
}
});
function del()
{
view.style.visibility="hidden";
bas.visibility="hidden";
pict.src="pict/net.jpg";
}
В новом сценарии функция del короче на две инструкции, так как сейчас
кнопки перемотки скрывать не надо — они отсутствуют на слое с увеличенным
рисунком. Анонимная функция, управляющая просмотром, теперь выясняет,
относится ли активированный элемент к классу im, к которому принадлежат
изображения галереи. Таким образом, кнопки перемотки не попадут в список
рисунков, требующих увеличения.
Далее мы создаем переменную-счетчик. Так как посередине страницы
находится восьмой снимок, то задаем переменной соответствующее значение:
let i=8;
Для перемотки слайдера в начало и в конец мы написали две функции:
document.getElementById("nz").
addEventListener("click", fnz);
document.getElementById("vp").
addEventListener("click", fvp);
— 194 —
Сначала разберем алгоритм движения назад (функция fnz).
На первом шаге отменяем привязку обработчиков:
document.getElementById("nz").
removeEventListener("click", fnz);
document.getElementById("vp").
removeEventListener("click", fvp);
На втором делаем видимой кнопку «Вперед», если в данный момент она
скрыта:
vp.visibility="visible";
Основные инструкции выполняются при условии, что индекс центральной фотографии как минимум на единицу меньше максимального индекса
(максимальный индекс — 15):
if(i<14)
{
...
}
Затем последовательно меняем положение четырех снимков. Стоящий
слева от центрального отправляем за левую «шторку»:
let k=i-1;
let ki=document.getElementById("i"+k).style;
ki.transition="left 2s";
ki.left=0;
Центральный кадр смещаем влево на 200 пикселей:
let ii=document.getElementById("i"+i).style;
ii.transition="left 2s";
ii.left="200px";
Теперь он будет виден лишь частично. Снимок, правый от центрального, перемещаем в центр:
let n=i+1;
let ni=document.getElementById("i"+n).style;
ni.transition="left 2s";
ni.left="400px";
Наконец, следующее по порядку фото, скрытое за правой шторкой, смещаем
влево на 200 пикселей (со значения 800 до значения 600):
let h=i+2;
let hi=document.getElementById("i"+h).style;
hi.transition="left 2s";
hi.left="600px";
Увеличиваем значение счетчика:
i++;
Если оно достигло установленного максимума, скрываем кнопку «Назад»:
if(i==14)
nz.visibility="hidden";
Как и в программах из предыдущих разделов, по истечении необходимого промежутка времени восстанавливаем привязку обработчиков:
— 195 —
setTimeout(()=>
{
document.getElementById("nz").
addEventListener("click", fnz);
document.getElementById("vp").
addEventListener("click", fvp);
}, 1800);
Теперь рассмотрим функцию fvp. В ней точно так же на первом этапе отменяется привязка обработчиков, а на последнем шаге обработчики регистрируются вновь.
Во время прокрутки вперед операции выполняются в обратном порядке.
Делаем видимой кнопку «назад», если она была скрыта:
nz.visibility="visible";
Условие прокрутки вперед — индекс центральной фотографии как минимум на единицу больше минимального индекса (минимальный индекс — 1):
if(i>2)
{
...
}
Смещаем вправо за «шторку» снимок, расположенный справа от центрального:
let k=i-1;
let ki=document.getElementById("i"+k).style;
ki.transition="left 2s";
ki.left="400px";
Смещаем вправо на 200 пикселей центральный кадр:
let ii=document.getElementById("i"+i).style;
ii.transition="left 2s";
ii.left="600px";
Смещает в центр кадр, который стоял слева от центрального и был виден только на две трети:
let n=i+1;
let ni=document.getElementById("i"+n).style;
ni.transition="left 2s";
ni.left="800px";
Последний этап — смещение влево очередного по номеру рисунка, который
скрывался за «шторкой»:
let h=i-2;
let hi=document.getElementById("i"+h).style;
hi.transition="left 2s";
hi.left="200px";
Уменьшаем значение счетчика
i--;
и проверяем, не достигло ли оно минимального значения. Если «да», то скрываем кнопку «Вперед»:
if(i==2)
— 196 —
vp.visibility="hidden";
Как видите, мы написали сценарий, который прекрасно обходится без
компонентов jQuery.
13.6. Автоматический слайдер
«Фишка» многих ресурсов — просмотр слайдеров в автоматическом режиме, когда одно фото сменяет другое по истечении небольшого промежутка
времени. Мы не станем отставать от лидеров web-индустрии и создадим свои
слайд-шоу. В этом разделе нам предстоит рассмотреть одну из таких программ.
Итак, запускаем файл chapter13/script13.6.1.html. На странице появится
уже привычное изображение горного массива (рис. 13.6.1). Нажимаем кнопку
«Старт» и видим, что вместо нее появилась кнопка «Стоп». Не спешим, ждем.
Спустя 2 секунды снимок «уедет» вверх (рис. 13.6.2), а вместо него останется
другой горный пейзаж (рис. 13.6.3). Примерно через 3.4 секунды поле начала
движения первого снимка вторая картинка плавно сместиться вверх за границы
экрана, и вы увидите фотографию цветущего луга на фоне горного пика. Ждем.
Этот рисунок тоже отправится за границы окна браузера и на его месте возникнет новый. И так далее. В этой серии последним будет шестой кадр. После него
вновь появится первая картинка, и просмотр возобновится.
Смену кадров можно остановить в любой момент, нажав кнопку «Стоп».
Вместо нее теперь опять возникнет кнопка «Старт». Кликнув ее, мы продолжим
просмотр с места остановки.
Для этого примера необходимо определенное расположение фотографий.
Если быть точным, они сложены «стопкой»:
Файл chapter13/script13.6.1.html
<img src="img1/im1.jpg" class="im" id="p1" alt="...">
...
<img src="img1/im6.jpg" class="im" id="p6" alt="...">
Это подтверждается их настройками:
.im
#p1
#p2
#p3
#p4
#p5
#p6
{position: absolute; left: 200px; top: 20px; ...;}
{z-index: 16;}
{z-index: 15;}
{z-index: 14;}
{z-index: 13;}
{z-index: 12;}
{z-index: 11;}
Кроме снимков в теле документа есть две кнопки:
<input id="but1" type="button" value="Старт">
<input id="but2" type="button" value="Стоп">
У них совершенно одинаковое положение:
#but1, #but2 {position: absolute;
top: 500px; left: 200px; ...;}
— 197 —
В процессе работы слайдера то одна, то другая кнопка делаются невидимыми.
В исходном состоянии скрыта кнопка «Стоп»:
#but2 {visibility: hidden;}
Рис. 13.6.1
Страница автоматического слайдера
Рис. 13.6.2
Начальное изображение плавно смещается вверх
— 198 —
Сценарий начинается объявлением шести переменных. Первая — идентификатор действующей кнопки:
let w=1;
Поскольку в начальном состоянии видна кнопка «Старт», идентификатору w
присвоено число 1. Ориентируясь на значение данной переменной, слайдер либо запускается, либо останавливается.
Переменная i — идентификатор начала процесса. Рассматривая работу
программы, мы первым делом объясним ее назначение:
let i=1;
Еще у нас есть счетчик смещения рисунка вверх
let k=20;
счетчик снимков
let s=1;
а также идентификатор таймера
let t="";
В ходе выполнения программы мы несколько раз будем обращаться к
свойствам первого фото. Поэтому вводим переменную — ссылку на стили этой
картинки:
let p1=document.getElementById("p1").style;
Рис. 13.6.3
Нажав кнопку «Стоп», можно зафиксировать на экране
любую картинку из галереи
— 199 —
Нажатие кнопки «Старт» запускает выполнение нескольких инструкций:
document.getElementById("but1").
addEventListener("click", function()
{
...
});
Первым делом меняем режимы видимости кнопок. «Старт» скрываем
document.getElementById("but1").
style.visibility="hidden";
а «Стоп» показываем:
document.getElementById("but2").
style.visibility="visible";
Сейчас мы запустили слайдер, значит идентификатору действующей кнопки
должно быть присвоено значение 1:
w=1;
Делаем это на случай, если ранее просмотр был остановлен кнопкой «Стоп»
(как вы увидите дальше, после нажатия «Стоп» значение w равно двум).
Последняя инструкция — запуск функции слайдера:
drive();
Нажатие кнопки «Стоп» вызывает обратные процессы:
document.getElementById("but2").
addEventListener("click", function()
{
...
});
Идентификатор получает значение 2:
w=2;
Кнопка «Старт» возвращается на исходную позицию
document.getElementById("but1").
style.visibility="visible";
а кнопка «Стоп» исчезает:
document.getElementById("but2").
style.visibility="hidden";
Теперь сама функция drive. Она работает только при условии, что значение переменной w равно единице:
function drive()
{
if(w==1)
{
let ph="p"+s;
...
}
}
В переменную ph поочередно
(ph="p"+s).
«загружается»
— 200 —
id каждого снимка
let ph="p"+s;
Первым выполняется блок
if(i==1)
{
i=0;
setTimeout(drive, 2000);
}
Если бы он отсутствовал, верхнее изображение начало бы движение сразу
после нажатия кнопки «Старт». При наличии такого блока мы даем посетителю
2 секунды на просмотр первой фотографии. Затем i становится равно нулю, и
функция вызывается заново. Теперь первый блок пропускается, так как условие
if(i==1) ложно, и начинают выполняться следующие инструкции. Таким образом, программа, «ориентируясь» на значение переменной i, «понимает», первый
это вызов или нет, надо задержать верхний снимок или нет.
Дальше все просто. Если значение счетчика рисунков меньше 7
if(s<7)
а значение смещения первой картинки меньше -1000
if(k>=-1000)
свойству top фотографии присваивается значение переменной k:
document.getElementById(ph).style.top=k+"px";
Сейчас у нас идет речь о верхнем снимке, поэтому id="p1", а значение top составляет 20px (начальное значение k=20 выбрано именно таким, поскольку в
исходном положении все фотографии внутри базового слоя относительно верхнего края окна браузера смещены на 20 пикселей). Происходит выполнение
оператора k-=3
k-=3;
и функция drive спустя 10 миллисекунд вызывается снова:
t=setTimeout(drive, 10);
Теперь значение top составляет 17px, и посетитель видит, как изображение
начинает смещаться вверх. Так повторяется до тех пор, пока выполняется условие k>=-1000. То есть снимок уходит вверх в общей сложности на 1020 пикселей от начального положения. Этого достаточно, чтобы скрыться с экрана монитора.
Когда верхняя фотография полностью исчезла, программа переходит к
следующему блоку инструкций:
k=20;
s++;
setTimeout(drive, 10);
Теперь переменная s увеличивается на единицу, и функция приступает к
работе с фотоснимком im2.jpg. Поскольку условие k>=-1000 снова истинно,
повторяется выполнение блока
document.getElementById(ph).style.top=k+"px";
k-=3;
t=setTimeout(drive, 10);
— 201 —
Затем исчезает фото im2.jpg, и программа «берется» за третий снимок.
И так далее. Таким образом, дело доходит до последнего — im6.jpg. В этот момент становится истинным условие if(s==6), и самое первое изображение возвращается на исходную позицию
p1.top="20px";
но при этом располагаясь ниже последнего, шестого рисунка:
p1.zIndex=10;
После исчезновения шестого кадра условие if(s<7) становится ложным, и выполнение программы возобновляется:
if(s==7)
{
p1.zIndex=16;
for(let n=2; n<7; n++)
document.getElementById("p"+n).style.top="20px";
s=1;
clearTimeout(t);
drive();
}
На этом этапе первой картинке возвращается ее исходный z-index:
p1.zIndex=16;
Затем в цикле остальные рисунки перемещаются в исходное положение:
for(let n=2; n<7; n++)
document.getElementById("p"+n).style.top="20px";
Счетчику снимков вновь присваивается значение 1:
s=1;
Таймер останавливается
clearTimeout(t);
и функция запускается по новой:
drive();
Начинается повторный просмотр снимков.
Если нажать кнопку «Стоп», переменная w получит новое значение 2,
условие
if(w==1)
станет ложным и выполнение функции drive прервется. Снимки зафиксируются в том положении, в каком их застал клик.
13.7. Растворяем фото
Во втором автоматическом слайдере смена изображений происходит методом «растворения».
— 202 —
Запустите в браузере файл chapter13/script13.7.1.html. Вы увидите страницу, аналогичную той, что наблюдали в разделе 13.6 (рис. 13.7.1). Только теперь картинки не уезжают вверх, а медленно проявляются сквозь друг друга
(рис. 13.7.2).
В основе страницы все те же шесть рисунков
Файл chapter13/script13.7.1.html
<img id="i1" class="im" src="img1/im1.jpg" alt="...">
...
<img id="i6" class="im" src="img1/im6.jpg" alt="...">
расположенные стопкой. Но теперь верхний снимок виден
#i1 {opacity: 1;}
а нижние — нет:
.im {...; opacity: 0;}
Также есть две кнопки — «Старт» и «Стоп», действующие почти аналогично кнопкам из предыдущего раздела.
Программа работает по следующему принципу.
Объявляем две переменные: идентификатор таймера
let w;
и счетчик снимков
let s=1;
Рис. 13.7.1
Начальное положение фото в новом слайдере точно такое же, как и в предыдущем
— 203 —
Нажатие кнопки «Старт» активирует три инструкции:
через 1 секунду запускается слайдер:
setTimeout(diss, 1000);
кнопка «Старт» исчезает:
document.getElementById("but1").
style.visibility="hidden";
кнопка «Стоп» появляется»:
document.getElementById("but2").
style.visibility="visible";
Нажатие «Стоп» вызывает обратные процессы. Выполнение функции diss
прерывается
clearTimeout(w);
кнопка «Старт» появляется
document.getElementById("but1").
style.visibility="visible";
кнопка «Стоп» скрывается
document.getElementById("but2").
style.visibility="hidden";
Рис. 13.7.2
Сквозь верхний снимок проявляется следующий
У функции diss две стадии исполнения. Первая — при условии, что количество продемонстрированных кадров меньше 6. Вторая — вслед за превышением этого показателя:
— 204 —
function diss()
{
if(s<6)
{
...
}
else
{
...
}
}
Сразу после запуска функции выполняется четыре операции. Вычисляется id текущего рисунка
let a="i"+s;
создается переменная для обращения к свойствам картинки
let t=document.getElementById(a).style;
задается время «растворения»
t.transition="opacity 2s";
и плавно снижается показатель непрозрачности текущего изображения
t.opacity=0;
Затем значение счетчика увеличивается на единицу:
s++;
и происходит частично схожий процесс: вычисляется id следующего рисунка
a="i"+s;
создается переменная для обращения к свойствам следующей картинки
t=document.getElementById(a).style;
задается время ее «проявления»
t.transition="opacity 2s";
и плавно увеличивается показатель ее непрозрачности
t.opacity=1;
В результате вторая картинка проявляется сквозь исчезающую первую.
Затем по прошествии 3 секунд процесс возобновляется:
setTimeout(diss, 3000);
Когда на экране оказывается последний, шестой снимок, запускается
процесс повторной демонстрации изображений. Счетчик проходов получает
начальное значение
s=1;
последний, шестой кадр растворяется
let t=document.getElementById("i6").style;
t.transition="opacity 2s";
t.opacity=0;
— 205 —
первый же, наоборот, проявляется
t=document.getElementById("i1").style;
t.transition="opacity 2s";
t.opacity=1;
и через 3 секунды процесс возобновляется
setTimeout(diss, 3000);
Так достигается замкнутый цикл автоматической демонстрации снимков.
— 206 —
14. ТЕСТИРОВАНИЕ СЦЕНАРИЕВ
Допустим, вы зашли на какой-то сайт. Он вам нравится — красиво
оформлен, интересные эффекты, все работает. Однако это только внешняя сторона. К своему удивлению, проведя несложный «аудит» этого сайта с применением специальных, но очень простых методов, вы можете обнаружить, что за
красивым фасадом скрываются многочисленные ошибки в разметке, стилях и
коде JavaScript. Порой количество ошибок и недочетов переваливает за 100 и
даже за 200 только на одной странице. А суммарно они иногда исчисляются
тысячами. Причем такие показатели обнаруживаются даже у корпоративных
ресурсов студий web-дизайна, которые рекламируют услуги по созданию сайтов и вроде бы должны гарантировать качество.
Страницы с ошибками в коде документов — это бракованный товар!
«Грязный» код ухудшает ранжирование сайтов в поисковых системах. А это —
потерянные посетители, клиенты, покупатели, заказчики.
14.1. Зачем тестировать
К сожалению, в огромном количестве случаев с тестированием разметки
и кода дело обстоит из рук вон плохо. Обычно студия дизайна берет какойнибудь популярный движок и делает на нем сайты, совершенно не заморачиваясь тем, что в этих движках множество ошибок и нарушений стандартов. В результате заказчики получают красивую обертку, под которой скрывается уйма
недочетов.
Есть два типа клиентов, которые заказывают разработку сайтов фрилансерам или компаниям. Первые — не очень продвинутые — не задаются вопросом о качестве полученного продукта. Дизайн нравится, страницы открываются, заказы отправляются — вот и ладно. Вторые смотрят на это под иным углом
зрения. Есть немало заказчиков, которые не примут готовый «продукт», пока
досконально не проверят, что именно вы им сделали. Автор неоднократно
наблюдал, как такие «привередливые» клиенты тщательно проверяли полученные сайты и выставляли исполнителям множество претензий, заставляя доводить ресурс «до ума».
Так что чем строже вы оцениваете свою работу, чем чище ваш код, тем
больше вероятность написать качественный сценарий с первого раза и не тратить время на исправления и переделки. В идеале код должен быть совершенно
чистым и не содержать ни единой ошибки или нарушения стандартов!
К сожалению, в современных браузерах все очень тщательно продумано
и предусмотрено. В результате, даже получив некачественный код, браузеры
понимают, чего хотел добиться программист, и демонстрируют правильную
web-страницу. А это провоцирует разработчиков небрежно относиться к своим
обязанностям. Представьте: web-обозреватели ежедневно успешно исправляют
миллионы ошибок, которыми кишат миллионы сайтов.
Очень придирчиво относитесь к выбору популярных платформ, движков
и CMS для создания сайтов. Да, такие системы значительно облегчают труд ди— 207 —
зайнера и верстальщика. Но почти любая из них содержит в своем «ядре» многочисленные ошибки, которые потом перекочевывают в ваши разработки. Если
вам необходимо выполнить крупный проект — смиритесь с неизбежным: без
платформы, движка или CMS, скорее всего, не обойтись. Если проект небольшой и не нуждается в системе управления контентом, попробуйте самостоятельно написать его «от корки до корки». Так вы можете создать ресурс с абсолютно чистым кодом. Есть еще один вариант действий: выберите CMS, которая
вам нравится больше всего, скачайте дистрибутив на свой компьютер и попробуйте исправить его исходный код. Работа большая и трудоемкая, зато у вас
появится исправленный экземпляр CMS, на копиях которого вы в дальнейшем
станете делать «чистые» сайты.
Надеюсь, вы не хотите оказаться в рядах горе-программистов? Тогда не
ленитесь и досконально проверяйте свою работу. Вот перечень самых необходимых действий:
проверка кода с помощью валидаторов и устранение ошибок;
проверка работоспособности сценариев в различных браузерах, в том
числе достижение идентичности результатов работы сценариев в этих браузерах;
устранение логических ошибок (если они обнаружены в процессе тестирования) в скриптах;
оптимизация кода.
Рассмотрим этот перечень подробнее.
14.2. Поиск ошибок в коде
Вы написали сценарий для какой-то из страниц вашего сайта. Начинаете
проверять, как работает получившаяся связка, и понимаете, что проект функционирует не так, как было задумано. В результате нескольких экспериментов
убеждаетесь, что в коде допущена какая-то ошибка и, может быть, не одна. Если скрипт большой, поиск огрехов превращается в непростую задачу. И вот тогда вам на помощь может прийти валидатор. Он сделает за вас всю работу —
вам останется только исправлять найденные им ошибки.
Валидатор — это специальная программа для проверки кода, написанного
на одном из языков, предназначенных для создания web-проектов. Задача такой
программы — найти ошибки, если они есть, и выдать их перечень разработчику, или сообщить, что код безупречный, если ошибок нет.
Можно сказать и по-другому: назначение того или иного валидатора —
проверять код на соответствие стандартам языка программирования.
Стандарты HTML и CSS разрабатывает Консорциум Всемирной Паутины — World Wide Web Consortium (W3C). Он же предоставляет и валидаторы
для проверки разметки страниц и написания таблиц стилей. Вот их адреса в
сети:
валидатор HTML5 — https://validator.w3.org/;
валидатор CSS3 — http://jigsaw.w3.org/css-validator/.
— 208 —
Данные валидаторы очень удобны. Они позволяют проверять разметку и
таблицы стилей:
страниц, уже загруженных в сеть;
файлов, загруженных в валидатор с вашего компьютера;
в коде, скопированном из вашего файла и помещенном в специальное
текстовое поле (по мнению автора, самый удобный способ).
Например, после обработки кода разметки все ошибки и предупреждения
с комментариями на английском языке появятся на странице HTML-валидатора
в ее нижней части. При этом ошибки будут сопровождаться надписью Error на
розовым фоне, а предупреждения — надписью Warning на желтом фоне.
Например, строка кода <script type="text/javascript"> получит предупреждение, так как по современным стандартам указывать тип скрипта не надо, но в то
же время данная ошибка не критична, так как не мешает нормальному отображению страницы. Зато следующая строка <img src="img/im1.jpg"> будет отмечена надписью Error, так как у рисунка пропущен обязательный согласно
стандартам HTML5 атрибут alt.
У HTML-валидатора есть еще одна полезная функция: по завершении
проверки он показывает, сколько времени было потрачено на загрузку вашей
страницы.
Если ошибок нет, валидаторы сообщат вам об этом. Любопытно заметить,
что о безупречности HTML-кода вы получите простое текстовое подтверждение. А вот качественную таблицу стилей Консорциум Всемирной Паутины
предлагает отметить специальным знаком — что-то вроде знака качества, который вам будет предложено разместить на странице сайта (по крайней мере, так
было на момент написания книги).
Естественно, что разметка и таблицы стилей всех файлов из книги в обязательном порядке проверялись в этих валидаторах. Результаты такой проверки
вы можете видеть на рисунках 14.2.1 и 14.2.2.
Рис. 14.2.1
Проверка кода одного из файлов в HTML валидаторе. Как видите, ошибок нет
— 209 —
Что касается валидатора JavaScript, то здесь ситуация несколько иная. Таких валидаторов много. Автор опробовал несколько и выбрал из них вариант, в
котором реализован самый строгий подход к оценке кода (рис. 14.2.3). Вот его
адрес:
валидатор JavaScript — https://beautifytools.com/javascript-validator.php.
Он позволяет вставить в специальное текстовое поле ваш код, после чего
сразу, без нажатия каких-либо кнопок, начинается проверка скрипта. Единственное неудобство этого валидатора, обнаруженное мною на момент написания книги — сообщения об ошибках, предупреждения и рекомендации он выдает в общем списке, не выделяя пункты этого списка по степени важности.
Рис. 14.2.2
Проверка таблицы стилей одного из файлов в CSS валидаторе.
Как видите, и здесь ошибок нет
Рис. 14.2.3
Проверка одного из сценариев. И опять ни одной ошибки
— 210 —
Особенность всех упомянутых валидаторов в том, что они находят все
ошибки в разметке, в таблицах стилей, в коде программ, в том числе пропущенные символы, опечатки (только не в обычном тексте), необъявленные переменные и т. д. Понятно, что задача хорошего программиста — исправить эти
ошибки и получить идеально чистый и правильный код.
14.3. Проверка в браузерах
Один из важнейших этапов тестирования файлов со сценариями — проверка их работоспособности в разных web-обозревателях. Написав программу и
убедившись, что она корректно выполняется в одном браузере, нельзя останавливаться на достигнутом. Ведь может оказаться, что в другом браузере сайт будет работать совсем не так, как вы ожидали.
Чем в большем количестве web-обозревателей проведены испытания, тем
лучше для вашего проекта. Это гарантирует, что все посетители сайта увидят на
его страницах именно то, что вы хотели продемонстрировать.
На мой взгляд обязательный набор разработчика должен включать минимум 5 web-обозревателей. Если вы решили серьезно заниматься web-программированием, советую установить на свой компьютер эти браузеры. Расскажу о
них по порядку.
1. Браузер Microsoft Edge. Он входит в состав операционной системы
Windows 10, поэтому не нуждается в скачивании и установке. Многие опытные
программисты не любят данный браузер и игнорируют его при проверке сценариев. Я считаю, что это серьезная ошибка. Дело в том, что Microsoft Edge является браузером по умолчанию в операционной системе Windows 10. Многие
пользователи, не такие продвинутые, как программисты, просто используют то,
что у них изначально установлено на ПК. Следовательно, большой процент посетителей Интернета прибегают к услугам Microsoft Edge.
2. Google Chrome. На мой субъективный взгляд — лучший браузер.
И, судя по некоторым опросам, наиболее популярный. Он моложе некоторых
своих конкурентов, но снабжен самыми передовыми решениями и очень быстро развивается. И это не удивительно — ведь за ним стоит такой гигант, как
компания Google. Скачать ПО можно здесь: https://www.google.ru/chrome/.
3. Яндекс.Браузер. После его создания компания Яндекс вложила заметные средства в рекламу и популяризацию своей разработки. Поэтому данный
браузер стоит у многих владельцев компьютеров с ОС Windows. Этих людей
обязательно надо учитывать. Скачать дистрибутив можно здесь: https://
browser.yandex.ru/.
4. Opera — один из старейших браузеров, существовавших еще во времена борьбы за лидерство между Netscape Navigator и Internet Explorer. В России у него много поклонников. Недаром среди российских пользователей Интернета показатель его популярности в несколько раз выше, чем на общемировом уровне. Кстати, браузер Opera был одним из первых, кто начал поддерживать таблицы стилей. Скачать программное обеспечение можно здесь: https://
www.opera.com/ru.
— 211 —
5. Mozilla Firefox. В этом браузере сценарии необходимо проверять в
обязательном порядке. У него достаточно высокий уровень популярности. При
этом есть ряд отличий в обработке кода по сравнению с четырьмя перечисленными выше браузерами. Что-то Mozilla Firefox обрабатывает аналогично
остальным браузерам, а что-то по-своему. Во всяком случае, я неоднократно
сталкивался с ситуациями, когда код, отлично работавший в других браузерах,
начинал «капризничать» в Mozilla Firefox. Скачать браузер можно здесь:
https://www.mozilla.org/ru/firefox/new/.
И уж совсем идеальной можно считать проверку, которую, кроме всего
прочего, удалось провести на устройствах Apple с операционной системой
macOS и браузером Safari.
Думаю, что цель таких масштабных испытаний с использованием группы
разных браузеров понятна: необходимо добиться совершенно одинаковой работы ваших программ во всех браузерах. Только получив этот результат, можно
считать свои программы вполне качественными.
14.4. Устранение логических ошибок
Главная тема этого раздела — логические ошибки в коде сценариев. Они
возникают, когда вы упускаете какие-то нюансы или неверно применяете математические алгоритмы. Казалось бы, проверка в валидаторах убеждает нас в
полном соответствии кода стандартам. Тем не менее, наличие такой ошибки
обнаруживается, когда ваш сценарий начинает работать не так, как вы ожидали.
При этом очень часто бывает, что вычислить логическую ошибку совсем непросто. Я расскажу вам о некоторых элементарных приемах поиска, обнаружения и исправления ошибок.
Конечно, для проверки кода лучше всего использовать специальные инструменты разработчика, встроенные во многие браузеры. Однако тема это достаточно большая и сложная, формат данной книги не позволяет досконально
рассказать обо всех возможностях таких инструментов. Поэтому мы рассмотрим более простые способы.
Способ первый. В те блоки программы, в которых логика работы нарушается, поочередно добавляйте в разные точки вызов метода alert(). Например, у
вас есть «сомнительный» фрагмент, в котором исходное значение переменной
меняется неоднократно:
...
let n=1;
инструкция 1
инструкция 2
if(...)
{
инструкция 3
}
else
{
инструкция 4
}
...
— 212 —
Вставьте в разные точки этого блока вызов alert(), например, так:
...
инструкция 1
alert(n);
...
Потом так:
...
инструкция 2
alert(n);
...
А потом вот так:
...
if(...)
{
инструкция 3
alert(n);
}
...
И еще вот так:
...
else
{
инструкция 4
alert(n);
}
...
Или сразу во все четыре точки. Таким образом, вы последовательно увидите, как меняются ваша переменная, и обнаружите, где происходит отклонение от заданной линии поведения.
Второй способ предпочтительнее в ситуации с быстро меняющимися параметрами, например, при перемещении указателя мыши по странице. Чтобы
отследить поток значений, внедрите в разметку страницы элемент визуального
отображения этих значений. Например, так:
<div id= "test "><div>
Найдите фрагмент, вызывающий сомнения, например:
...
let n=1;
function func()
{
инструкция 1
инструкция 2
}
...
Добавьте в код сценария команду выведения данных:
...
let n=1;
— 213 —
function func()
{
инструкция 1
инструкция 2
document.getElementById("test").innerHTML=n;
}
...
Анализируя показания координат, вы сразу увидите, какова последовательность изменения значений переменной и все ли идет «по графику».
Чтобы приблизить эти рассуждения к реалиям, рассмотрим возникновение ошибки и способ ее поиска на конкретном примере. Архив с программами
содержит три страницы, которые специально разработаны для иллюстрации
рассуждений данного раздела.
Итак, зайдем на первую из них: chapter14/script14.4.1.html. После загрузки документа мы видим на странице только рамку. Пока указатель мыши
не попадает внутрь рамки, ничего не происходит.
Теперь медленно проведите курсором внутри рамки слева направо. Вы
обнаружите, что на белом фоне плавно возникнет фотография старинного автомобиля (рис. 14.4.1). Проведите курсором в обратном направлении — справа
налево. Фотография также плавно растворится. Наилучший эффект достигается
именно при медленном движении мыши. Если быстро провести указателем по
рабочей области, фотография проявится или растворится только частично. То
же произойдет, если указатель начал двигаться не от боковых сторон рамок, а,
например, от середины изображения.
Не правда ли, интересный пример? Но вот беда: несмотря на кажущееся
благополучие, в сценарии допущена логическая ошибка (естественно, сделано
это умышленно). Причем ошибку можно обнаружить не сразу. В чем же она?
Давайте еще поэкспериментируем. Проведите указателем мыши по рамке
так, как это показано на рисунке 14.4.2. Сначала слева направо по периметру
рамки, а затем ниже рисунка — когда вы возвращаете указатель справа налево.
Проделайте так трижды, а на четвертый раз медленно возвращайте указатель
мыши теперь уже по рисунку. Вы думаете, фото автомобиля плавно растворится? А вот и нет. Снимок по-прежнему будет ярким и сочным. В чем же дело?
Рис. 14.4.1
Пример страницы с логической ошибкой в сценарии
— 214 —
Рис. 14.4.2
Чтобы обнаружить ошибку, проведите по странице указателем мыши так,
как показано на рисунке
Посмотрим код страницы:
Файл chapter14/script14.4.1.html
<!DOCTYPE html>
<html lang="ru">
<head>
...
<style>
...
#car {opacity: 0;}
</style>
<script>
addEventListener("load", function()
{
document.getElementById("car").
addEventListener("mousemove", opa);
});
let d=0;
let i=0;
function opa()
{
let h=event.pageX;
if(h-d>3)
{
i+=0.01;
document.getElementById("car").style.opacity=i;
d=h;
}
if(d-h>3)
{
i-=0.01;
document.getElementById("car").style.opacity=i;
d=h;
}
}
</script>
</head>
— 215 —
<body>
...
<div id="pic">
<img src="pict/oldcar.jpg" id="car" alt="Фото">
</div>
...
</body>
</html>
Как работает программа? В документе есть слой с фотографией автомобиля:
<div id="pic">
<img src="pict/oldcar.jpg" id="car" alt="Фото">
</div>
В исходном состоянии автомобиль не виден:
#car {opacity: 0;}
В теле сценария у нас объявлены две переменные:
let d=0;
let i=0;
Переменная d предназначена для хранения предыдущей координаты мыши —
то есть перед текущей координатой мыши по оси X. Переменная i — счетчик
уровня непрозрачности.
Как только событие mousemove произойдет над рисунком (то есть указатель мыши начнет движение над рисунком), будет выполнен запуск обработчика. Вот его код:
function opa()
{
let h=event.pageX;
if(h-d>3)
{
i+=0.01;
document.getElementById("car").style.opacity=i;
d=h;
}
if(d-h>3)
{
i-=0.01;
document.getElementById("car").style.opacity=i;
d=h;
}
}
Давайте считать, что сейчас курсор двигается слева направо. Первым делом проверяется условие if(h-d>3). В начальный момент d имеет значение 0,
поэтому разность между h и d удовлетворяет условию. Таким образом, при попадании указателя мыши на фото будут выполняться инструкции первого блока
if:
i+=0.01;
document.getElementById("car").style.opacity=i;
d=h;
— 216 —
В первой строке счетчик увеличивается на 0.01. Во второй строке его значение присваивается свойству непрозрачности изображения. В третьей выполняется операция присвоения текущей координаты мыши переменной d.
Смотрим, что произойдет дальше. Курсор мыши продолжает движение
вправо, а значит вторично запускается функция opa. Опять выполняется проверка условия if(h-d>3). Как видите, теперь сравнивается новая координата указателя со старой, полученной на предыдущем шаге и помещенной в переменную d. Если условие истинно, показатель непрозрачности опять увеличится.
Продолжаем вести мышь слева направо — значение переменной i все увеличивается и увеличивается. В какой-то момент уровень непрозрачности достигнет
1, и фотография станет видна полностью.
Теперь разберемся, что будет происходить во время движения указателя
мыши по картинке в обратном направлении. Как раз вторая часть функции и
предназначена для обработки таких действий:
if(d-h>3)
{
i-=0.01;
document.getElementById("car").style.opacity=i;
d=h;
}
В данном случае проверяется обратное условие:
if(d-h>3)
То есть если предыдущая координата мыши больше текущей, значит указатель
двигается в обратном направлении, и показатель непрозрачности уменьшается
на 0.01:
i-=0.01;
document.getElementById("car").style.opacity=i;
d=h;
По достижении переменной i нулевой «отметки» рисунок полностью исчезает. Вроде бы все правильно, как в первом эксперименте. Теперь вспомним
второй эксперимент, где мы многократно перемещали указатель мыши слева
направо по картинке и справа налево ниже картинки. Изображение проявлялось, но не растворялось. Этот результат опровергает нашу уверенность, что
все в порядке. В сценарии явно кроется какая-то ошибка, и найти ее с помощью
валидатора не удастся, ибо с синтаксисом у программы все в порядке.
Чтобы разобраться в проблеме, давайте используем метод поиска логических ошибок, о котором говорилось выше. Так как за показатель непрозрачности отвечает переменная i, проверим, что с ней происходит «за кадром». Лучше
всего внедрить в страницу элемент визуального отображения значений этой переменной. Например, вот так:
Файл chapter14/script14.4.2.html
...
<div id="pic">
<img src="pict/oldcar.jpg" id="car" alt="Фото">
</div>
— 217 —
<div id="ind">0</div>
...
Теперь у нас под фотографией есть слой, в котором будут показываться
значения переменной i. Станем выводить ее показатели следующим образом:
...
function opa()
{
let h=event.pageX;
document.getElementById("ind").innerHTML=i;
...
Для демонстрации работы новой версии программы есть другая страница:
chapter14/script14.4.2.html. Повторим теперь на ней ход второго эксперимента,
проводя указателем мыши по картинке слева направо и под картинкой — в обратном направлении. Совершенно неожиданно мы обнаружим, что, достигнув
значения 1, программа не останавливается, и переменная i продолжает расти
(рис. 14.4.3). У возможных значений этой переменной вообще нет ограничений.
Вот в чем ошибка! Проведя 4 раза указателем мыши слева направо по рисунку,
а обратно — под рисунком, мы подняли значение i выше предельного уровня
для показателя непрозрачности. В результате после того, как мы один раз провели мышью над рисунком справа налево, показатель непрозрачности попрежнему остался выше 1, и картинка не растворилась.
Рис. 14.4.3
Данная версия сценария позволила нам обнаружить ошибку — неконтролируемый рост
значений переменной i
То есть мы ошиблись с логикой работы программы, не учтя, что посетитель может повести себя на странице не так, как мы от него ожидаем. Как исправить недочет? Очень просто: надо ограничить рост и снижение значений переменной i. Ее показания при любом развитии событий не должны быть выше 1
и ниже 0.
На основе полученных данных внесем изменения в сценарий:
Файл chapter14/script14.4.3.html
...
function opa()
{
let h=event.pageX;
— 218 —
if(h-d>3)
{
if(i<=1)
{
i+=0.01;
document.getElementById("car").style.opacity=i;
d=h;
}
else
{
i=1;
}
}
if(d-h>3)
{
if(i>=0)
{
i-=0.01;
document.getElementById("car").style.opacity=i;
d=h;
}
else
{
i=0;
d=0;
}
}
}
...
Здесь мы ввели проверку двух дополнительных условий:
if(i<=1)
и
if(i>=0)
Если они нарушаются, то выполняется такой блок инструкций:
else
{
i=1;
}
Или такой:
else
{
i=0;
d=0;
}
Теперь программа будет работать как надо. Ее правильную версию вы
можете увидеть в действии на странице chapter14/script14.4.3.html.
Надеюсь, рассмотрение этого случая полезно — как пример алгоритма
поиска логических ошибок в программах.
Наконец, еще один прием, который может вам пригодиться при отладке
программ. Если какой-то фрагмент сценария или разметки вызывает у вас сомнения, можно временно «отключить» его функционирование.
— 219 —
Допустим, на странице необходимо «изъять» некий элемент. Это можно
сделать, применив дескриптор комментария HTML:
<!-- ... -->
Вот конкретный пример временного удаления фотографии из разметки:
<!-- <img src="im1.png" alt=”Фото”> -->
Отключить часть сценария можно, поставив перед необходимым фрагментом символы комментариев, о которых мы говорили в разделе 4.16. Вот так:
// document.getElementById("im1").
addEventListener("click", func);
Или так:
/* document.getElementById("im1").
addEventListener("click", func); */
Естественно, первый вариант комментирования строки в сценарии более
рационален, так как символы // добавляются и удаляются быстрее, чем во втором варианте.
Если временно требуется отключить несколько элементов разметки, то
вновь используйте дескриптор комментария HTML:
<!-- <img src="im1.png" id="im1">
<img src="im2.png" id="im2">
<img src="im3.png" id="im3"> -->
Отключить несколько строк в сценарии можно теми же способами, что и
в случае с одной строкой. Так
// document.getElementById("im1").
addEventListener("click", func);
// document.getElementById("im2").
addEventListener("click", func);
// document.getElementById("im3").
addEventListener("click", func);
и так
/* document.getElementById("im1").
addEventListener("click", func);
document.getElementById("im2").
addEventListener("click", func);
document.getElementById("im3").
addEventListener("click", func); */
В этом случае более рационален второй вариант комментирования строк
в сценарии, так как символы /* и */ добавляются и удаляются быстрее, чем в
первом варианте.
14.5. Оптимизация кода
Оптимальной можно считать программу, которая:
максимально лаконична;
занимает минимально возможное дисковое пространство;
максимально быстро работает.
— 220 —
Современные посетители сайтов любят, чтобы все делалось как можно
быстрее, поэтому оптимизация кода играет очень важную роль. Долго грузится
страница? Самые нетерпеливые разочарованно покинут ваш ресурс. И наоборот, быстрая загрузка уже на начальном этапе создаст у клиента приятное впечатление.
На чем можно сэкономить при написании кода? На очень многом. Покажу вам это на ряде примеров.
Допустим, у вас есть переменная a и вам надо увеличить ее значение на
единицу. Можно записать данную операцию так:
a=a+1;
А можно иначе:
a++;
Понятно, что вторая запись более лаконична — по сравнению с первой
мы сэкономили 2 символа.
Теперь представим, что у вас есть цикл со счетчиком. Его можно записать
так:
let i;
for(i=0; i<5; i++)
{
...
}
То есть сначала объявляем переменную i, а потом в описании условий цикла
присваиваем ей начальное значение.
Ясно, что следующий вариант данного цикла более краток:
for(let i=0; i<5; i++)
{
...
}
И опять экономия составила 2 символа.
Кстати, обратите внимание, что правильный выбор оператора цикла между следующими вариантами — for, while, do while — также может сократить
ваш код.
Используйте досрочный выход из цикла посредством оператора break,
если вы уже получили требуемый результат, но предельное условие для цикла
еще не достигнуто.
Пойдем дальше. Помните, в главе 11 у нас были примеры одновременной
регистрации обработчиков событий? Вернемся к этим примерам. Допустим,
вам надо зарегистрировать три обработчика для одного события:
document.getElementById("im").
addEventListener("click", func);
document.getElementById("im").
addEventListener("click", doc);
document.getElementById("im").
addEventListener("click", res);
— 221 —
Это слишком «тяжелая» запись. Ее можно сделать более «легкой»:
const v=document.getElementById("im");
v.addEventListener("click", func);
v.addEventListener("click", doc);
v.addEventListener("click", res);
А в некоторых случаях можно сделать еще проще:
document.getElementById("im").
addEventListener("click", function()
{
func();
doc();
res();
});
Раз уж мы упомянули обработчики, вот еще пример сокращения кода:
при регистрации обработчиков событий уровня окна можно не указывать объект window. Вместо
window.addEventListener("load", func);
window.addEventListener("scroll", scro);
можно писать
addEventListener("load", func);
addEventListener("scroll", scro);
Вспомним про еще один способ оптимизации — путем уменьшения количества переменных. У нас в главе 4 был пример с функцией, которая содержала два цикла. Вместо двух переменных, являющихся счетчиками — каждая
своего цикла, — мы использовали только одну переменную, тем самым экономя на памяти:
function func()
{
...
let d;
for(d=0; d<5; d++)
{
...
}
...
for(d=0; d<3; d++)
{
...
}
...
}
Здесь у нас после завершения первого цикла переменная d обнуляется,
после чего приступает к работе во втором цикле.
Уменьшить объем кода можно, используя сокращенные формы условных
выражений. Так, если у вас после проверки истинности или ложности условия
— 222 —
должна выполняться только одна инструкция, это лучше оформить следующим
образом. Вместо
if(a<5)
{
b=0;
}
написать
if(a<5)
b=0;
Вместо
if(a<5)
{
b=0;
}
else
{
b=1;
}
использовать укороченную запись:
if(a<5)
b=0;
else
b=1;
Или вообще для проверки таких условий применять тернарный оператор:
let b=a<5?0:1;
Как вы помните, сократить можно и оформление оператора цикла, если
внутри него выполняется только одна инструкция. Вместо
for(let c=0; c<2; c++)
{
alert(c);
}
можно написать
for(let c=0; c<2; c++)
alert(c);
Но тут заострю ваше внимание на том, что в некоторых случаях отсутствие фигурных скобок может привести к ошибке. В главе 4 у нас были примеры с демонстрацией различий между операторами let и var. Продублируем
один из этих примеров:
Файл chapter14/script14.5.1.html
<script>
let i=1;
if(true)
{
let i=2;
}
alert(i);
</script>
— 223 —
Запустив данную страницу, вы увидите число 1 в диалоговом окне.
Чуть-чуть изменим программу, убрав фигурные скобки и заменив имя переменной в блоке условия:
Файл chapter14/script14.5.2.html
<script>
let i=1;
if(true)
let a=2;
alert(i);
</script>
Открыв этот документ в браузере, вы увидите чистую страницу, хотя
ожидали появления диалогового окна с цифрой 1. Почему? Вроде бы по выполняемым операциям оба сценария идентичны. Если же мы заглянем в консоль
(рис. 14.5.1), то обнаружим, что у нас возникла ошибка: объявлять переменную
let в блоке условия или цикла без фигурных скобок нельзя! А вот var — можно.
Таким образом, у нас появился еще один пункт в перечне различий между let и
var.
Рис. 14.5.1
Сообщение об ошибке в консоли браузера
Продолжим.
Экономить удастся даже на способах «внедрения» результатов работы
сценария на HTML-страницы. Конкретный пример. Допустим, у нас есть страница, на которую сразу после загрузки выводятся квадраты чисел от 1 до 5.
Напишем ее:
Файл chapter14/script14.5.3.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Квадраты чисел</title>
— 224 —
<script>
addEventListener("load", function()
{
let t="<table><tr><td>Число</td>
<td>Его квадрат</td></tr>";
for(let i=1; i<6; i++)
t+="<tr><td>"+i+"</td><td>"+i**2+"</td></tr>";
t+="</table>";
document.getElementById("di").
insertAdjacentHTML("beforeend", t);
});
</script>
</head>
<body>
<div id="di">Квадраты чисел</div>
</body>
</html>
И сравним с более «легкой» версией этой же программы:
Файл chapter14/script14.5.4.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Квадраты чисел</title>
<script>
addEventListener("load", function()
{
let t="<table><tr><td>Число</td>
<td>Его квадрат</td></tr>";
for(let i=1; i<6; i++)
t+="<tr><td>"+i+"</td><td>"+i**2+"</td></tr>";
document.getElementById("di").innerHTML=
"Квадраты чисел"+t+"</table>";
});
</script>
</head>
<body>
<div id="di"></div>
</body>
</html>
В чем разница между версиями? В первом случае текст «Квадраты чисел»
уже размещен на странице. Поэтому добавлять результаты расчетов нам приходится в самый конец контейнера div методом insertAdjacentHTML, запись которого выглядит несколько громоздко. К тому же мы сначала добавляли к переменной t завершающий тег таблицы
t+="</table>";
и только потом выводили на страницу готовый фрагмент разметки. В результате у нас получился файл размером 501 байт.
Во втором случае контейнер div мы изначально оставили пустым, что
позволило использовать для вывода результатов более компактную запись
— 225 —
свойства innerHTML. Кроме того, мы окончательно сформировали необходимый фрагмент разметки прямо во время его вывода на страницу:
document.getElementById("di").innerHTML=
"Квадраты чисел"+t+"</table>";
В результате второй вариант оказался легче первого на 25 байт.
В ряде случаев для «облегчения» кода и ускорения работы программы
можно использовать функции-стрелки, с которыми мы познакомились в главе 6.
Ускоряет «запуск» сайта в браузере пользователя и такая вещь, как загрузка контента по частям. Если у вас «длинная» страница с большим объемом
текста и рисунков, разбейте данные на части, которые будут подгружаться по
мере прокрутки документа вниз. Для этого удобно воспользоваться технологией Ajax.
— 226 —
15. ЗАКЛЮЧЕНИЕ
Итак, настало время подвести итоги. Мы изучили в теории и ознакомились на практике со множеством приемов креативного программирования на
JavaScript. Такие приемы нечасто применяют в разработке и еще реже описывают в учебниках. Более того: про многие из подходов вы не прочитаете ни в
одной книге. Между тем, как мы убедились, эти методы существуют и дают
очень неплохие результаты.
Но, конечно, автор охватил не все возможные приемы из разряда креативных. Например, иногда в их перечне могут оказаться совершенно заурядные
операции, по каким-то причинам не попавшие в самоучители. Вот реальный
пример с оператором возведения в степень **. Как вы знаете, помимо обычной
записи операций существуют их сокращенные версии (еще они называются
операторами комбинированного присваивания). Обычная запись возведения в
степень:
x=x**y;
А вот ее комбинированный вариант:
x**=y;
С большой долей вероятности можно сказать, что вы не найдете в литературе описание сокращенной записи возведения в степень. Во всяком случае, автор не обнаружил упоминания такой записи ни в одной из прочитанных им
книг по программированию на JavaScript.
Между тем, оператор комбинированного присваивания ** существует —
он появился в версии ES7 в 2016 г. Мы его упоминали в таблице 4.8.2 из главы 4. Оператор прекрасно работает, в чем легко убедиться на данном примере:
Файл chapter15/script15.1.html
<script>
let x=5;
let y=3;
x**=y;
alert(x);
</script>
Так что же в итоге? Перефразируя известные строки Уильяма Шекспира,
можно сказать: «Есть многое, читатель мой, на свете, о чем не сообщают в книгах нам». А это значит, что перед вами открываются широкие просторы в поиске новых креативных подходов к программированию на JavaScript.
Мне остается только пожелать вам удачи.
— 227 —
СПИСОК ЛИТЕРАТУРЫ
Книги
1. Янцев, В. В. JavaScript. Картинки, галереи, слайдеры. — СПб. : Лань,
2022.
2. Чиннатхамби, К. JavaScript с нуля. — СПб. : Питер, 2022.
3. Симпсон, К. Вы не знаете JS. Асинхронная обработка & оптимизация. — СПб. : Питер, 2022.
4. Янцев, В. В. JavaScript. Как писать программы. — СПб. : Лань, 2021.
5. Янцев, В. В. JavaScript. Обработка событий на примерах. — СПб. :
Лань, 2021.
6. Янцев, В. В. JavaScript. Готовые программы. — СПб. : Лань, 2021.
7. Диков, А. В. Web-программирование на JavaScript. — СПб. : Лань, 2021.
8. Диков, А. В. Клиентские технологии веб-программирования: JavaScript
и DOM. — СПб. : Лань, 2020.
9. Диков, А. В. Клиентские технологии веб-дизайна. HTML5 и CSS. —
СПб. : Лань, 2019.
10. Мейер, Э. А. CSS. Карманный справочник. — М. : Диалектика, 2019.
11. Роббинс, Д. HTML5. Карманный справочник. — М. : Вильямс, 2017.
Интернет-ресурсы
12. ECMA-262 // ECMA International. — URL: https://www.ecmainternational.org/publications-and-standards/standards/ecma-262/.
13. Web APIs // mdn_. — URL: https://developer.mozilla.org/en-US/docs/
Web/API.
— 228 —
ОГЛАВЛЕНИЕ
1. Введение............................................................................................................................................3
1.1. О чем эта книга...........................................................................................................................3
1.2. Особенности изложения материала ........................................................................................4
1.3. Оформление сценариев ............................................................................................................5
2. Размещение сценариев...................................................................................................................7
2.1. Ошибочный метод......................................................................................................................7
2.2. Сценарий в теле документа ................................................................................................... 10
2.3. Сценарий в заголовочной части ............................................................................................ 12
2.4. Самое правильное место ....................................................................................................... 13
2.5. Взаимодействие сценариев ................................................................................................... 15
3. Комментарии обычные и не очень ........................................................................................... 18
3.1. Традиционные комментарии .................................................................................................. 18
3.2. И такое возможно .................................................................................................................... 21
4. Переменные и их классификации ............................................................................................. 24
4.1. Правильные имена переменных ........................................................................................... 24
4.2. Типы данных в переменных ................................................................................................... 24
4.3. Переменные: var, let................................................................................................................ 26
4.4. Константы const ....................................................................................................................... 30
4.5. Глобальные и локальные переменные ................................................................................. 32
4.6. Меньше или больше переменных? ....................................................................................... 34
4.7. Многострочный код в переменных ........................................................................................ 35
4.8. Операторы для переменных .................................................................................................. 37
5. Массивы такие и этакие .............................................................................................................. 43
5.1. Простые массивы .................................................................................................................... 43
5.2. Варианты применения ............................................................................................................ 54
5.3. Многомерные массивы ........................................................................................................... 58
6. Функции на все случаи жизни .................................................................................................... 64
6.1. Разнообразие функций ........................................................................................................... 64
6.2. Стрелочные функции .............................................................................................................. 71
6.3. Массив как аргумент функции ................................................................................................ 74
6.4. Функция как аргумент функции .............................................................................................. 76
6.5. Обратный вызов ...................................................................................................................... 82
6.6. Обещанного три года ждут ..................................................................................................... 84
7. Проверяем условия ...................................................................................................................... 89
7.1. Операторы сравнения и логические операторы .................................................................. 89
7.2. Проверка условий ................................................................................................................... 91
7.3. Нужны ли фигурные скобки?.................................................................................................. 96
7.4. Функция как условие в условии ............................................................................................. 97
8. Запуск циклов .............................................................................................................................. 100
8.1. Простые циклы ...................................................................................................................... 100
8.2. Вновь про фигурные скобки ................................................................................................. 107
8.3. Функция как условие в цикле ............................................................................................... 108
8.4. Реверсивный запуск цикла ................................................................................................... 110
— 229 —
9. Управление таймерами .............................................................................................................. 113
9.1. Запускаем таймер ................................................................................................................. 113
9.2. Останавливаем таймер ........................................................................................................ 119
9.3. Функции в таймере ................................................................................................................ 121
10. Манипулируем строками ......................................................................................................... 123
10.1. Про кавычки ......................................................................................................................... 123
10.2. Методы обработки строк .................................................................................................... 125
10.3. Интерполяция и шаблонные литералы ............................................................................ 133
11. Обработчики событий ............................................................................................................. 136
11.1. События на HTML-страницах ............................................................................................. 136
11.2. Регистрация обработчиков................................................................................................. 137
11.3. Циклы в помощь .................................................................................................................. 142
11.4. Остановка, отмена, перезапуск обработчиков ................................................................. 144
11.5. Передача аргументов в обработчик .................................................................................. 149
11.6. Отмена обработки событий по умолчанию ...................................................................... 152
12. Добавляем элементы............................................................................................................... 155
12.1. Создание элементов ........................................................................................................... 155
12.2. Настройка атрибутов и свойств ......................................................................................... 161
12.3. Удаление атрибутов и свойств .......................................................................................... 164
12.4. Ликвидация элементов ....................................................................................................... 165
13. «Ванильный» JavaScript.......................................................................................................... 168
13.1. Вращаем изображения ....................................................................................................... 168
13.2. Оптимальная галерея ......................................................................................................... 174
13.3. Перемотка с растворением ................................................................................................ 180
13.4. Галерея-аккордеон .............................................................................................................. 185
13.5. Креативный слайдер ........................................................................................................... 190
13.6. Автоматический слайдер.................................................................................................... 197
13.7. Растворяем фото ................................................................................................................ 202
14. Тестирование сценариев......................................................................................................... 207
14.1. Зачем тестировать .............................................................................................................. 207
14.2. Поиск ошибок в коде ........................................................................................................... 208
14.3. Проверка в браузерах ......................................................................................................... 211
14.4. Устранение логических ошибок ......................................................................................... 212
14.5. Оптимизация кода ............................................................................................................... 220
15. Заключение ................................................................................................................................ 227
Список литературы ........................................................................................................................ 228
— 230 —
Валерий Викторович ЯНЦЕВ
JAVASCRIPT.
КРЕАТИВНОЕ ПРОГРАММИРОВАНИЕ
УЧЕБНОЕ ПОСОБИЕ
Издание второе, стереотипное
Зав. редакцией литературы
по информационным технологиям
и системам связи О. Е. Гайнутдинова
ЛР № 065466 от 21.10.97
Гигиенический сертификат 78.01.10.953.П.1028 от 14.04.2016 г., выдан ЦГСЭН в СПб
Издательство «ЛАНЬ»
lan@lanbook.ru; www.lanbook.com
196105, Санкт-Петербург, пр. Юрия Гагарина, д.1, лит. А.
Тел.: (812) 336-25-09, 412-92-72.
Бесплатный звонок по России: 8-800-700-40-71
Подписано в печать 16.01.24.
Бумага офсетная. Гарнитура Школьная. Формат 70×100 1/16.
Печать офсетная/цифровая. Усл. п. л. 18,85. Тираж 30 экз.
Заказ № 086-24.
Отпечатано в полном соответствии
с качеством предоставленного оригинал-макета в АО «Т8 Издательские технологии»
109316, г. Москва, Волгоградский пр., д. 42, к. 5.