Академический Документы
Профессиональный Документы
Культура Документы
«Замечательная ясность, юмор и изрядная доля интеллекта заставят даже непрограмиста положительно
взглянуть на методику решения задач».
— Кори Доктороу, второй редактор Boing Boing, писатель-фантаст
«Это одна из немногих книг по программированию, которую я считаю незаменимой (а я к этой категории
причисляю книг десять, не более)».
— Дэвид Гелернтер, профессор по компьютерным технологиям,
Йельский университет
«Очевидно, Эрик и Элизабет знают свое дело. Интернет-технологии становятся все более сложными,
и творческое создание веб-страниц начинает играть все более важную роль. Элегантная архитектура
занимает центральное место в каждой главе, каждая концепция передается с равной дозой прагматизма
и остроумия».
— Кен Голдстейн, бывший директор Shop.com и автор книги
This is Rage: A Novel of Silicon Valley and Other Madness
«Книга Изучаем HTML, XHTML и CSS представляет собой тщательно проработанное современное
руководство по дальновидным практикам в области разметки и представления веб-страниц. Авторы
предвидят, какие моменты могут вызвать у читателя замешательство, и своевременно разъясняют их.
Использованный подход, в основе которого лежат обилие наглядных примеров и последовательность
изложения, является оптимальным для читателя: он будет вносить небольшие изменения и наблю-
дать итоговый эффект в браузере, что позволит разобраться в назначении каждого нового элемента».
— Дэнни Гудмен, автор книги Динамический HTML: подробное руководство»
(Dynamic HTML: The Definitive Guide)
«Книга Изучаем HTML, XHTML и CSS с самого начала создает у читателя ощущение, что весь процесс
обучения окажется простым и увлекательным. Освоение HTML при правильном объяснении не слож-
нее изучения основ родного языка, при этом авторы проделали отличную работу и приводят наглядные
примеры по каждой концепции».
— Майк Дэвидсон, президент и исполнительный директор Newsvine, Inc
«Вместо изложения материала в стиле традиционных учебников Программируем для iPhone и iPad пред-
лагает читателю живую, увлекательную и даже приятную методику обучения программированию для
iOS. Материал подобран умело и качественно: в книге рассматриваются многие ключевые технологии,
включая Core Data, и даже такие важные аспекты, как проектирование интерфейса. И где еще можно
прочитать, как UIWebView и UITextField беседуют у камина?»
— Шон Мерфи, проектировщик и разработчик приложений для iOS
«Книга Программируем для iPhone и iPad объясняет принципы разработки приложений iOS с самого на-
чала. Основные изменения по сравнению с первым изданием относятся к iOS 4, Xcode 4 и написанию
приложений для iPad. Благодаря пошаговым описаниям с визуальным стилем изложения материала эта
книга становится отличным средством изучения программирования для iPhone и iPad во всех аспектах,
от простейших до нетривиальных».
— Рич Розен, программист и соавтор книги Mac OS X for Unix Geeks
ББК 32.988-02-018.1
УДК 004.43
Ф88
ISBN 978-5-496-01257-7
Вы готовы сделать шаг вперед в веб-программировании и перейти от верстки в HTML и CSS к созданию
полноценных динамических страниц? Тогда пришло время познакомиться с самым «горячим» языком про-
граммирования — JavaScript!
С помощью этой книги вы узнаете все о языке JavaScript: от переменных до циклов. Вы поймете, почему
разные браузеры по-разному реагируют на код и как написать универсальный код, поддерживаемый всеми
браузерами. Вам станет ясно, почему с кодом JavaScript никогда не придется беспокоиться о перегружен-
ности страниц и ошибках передачи данных. Не пугайтесь, даже если ранее вы не написали ни одной строчки
кода, — благодаря уникальному формату подачи материала эта книга с легкостью проведет вас по всему пути
обучения: от написания простейшего скрипта до создания сложных веб-проектов, которые будут работать во
всех современных браузерах.
Особенностью данного издания является уникальный способ подачи материала, выделяющий серию «Head
First» издательства O’Reilly в ряду множества скучных книг, посвященных программированию.
ББК 32.988-02-018.1
УДК 004.43
Права на издание получены по соглашению с O’Reilly. Все права защищены. Никакая часть данной книги не может быть
воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
ISBN 978-1449340131 англ. © Authorized Russian translation of the English edition of Head First JavaScript
Programming, 1st Edition (ISBN 9781449340131)
© 2014 Eric Freeman, Elisabeth Robson. This translation is published and sold by
permission of O’Reilly Media, Inc., which owns or controls all rights to publish
and sell the same
ISBN 978-5-496-01257-7 © Перевод на русский язык ООО Издательство «Питер», 2015
© Издание на русском языке, оформление ООО Издательство «Питер», 2015
Посвящается JavaScript — ты не родился в благополучной семье,
но превзошел все языки, которые пытались конкурировать
с тобой в браузерах.
авторы
ен
Эрик Фрим
Эрик, по словам одного из создателей серии Head Элизабет — программист, писатель и преподаватель.
First — «одна из редких личностей, хорошо разбира- Она влюблена в свою работу еще со времени учебы
ющихся в языке, практике и культуре самых разных в Йельском университете, где она получила степень
областей — технохипстер, вице-президент, инженер, магистра в области компьютерных технологий.
аналитик». Элизабет уже давно занимается Интернетом; она
Почти десять лет Эрик отработал на руководящей участвовала в создании популярного сайта Ada
должности — технического директора Disney Online Project — одного из первых сайтов, помогающих жен-
& Disney.com в компании The Walt Disney Company. щинам найти информацию о работе и образовании
Сейчас Эрик отдает свое время WickedlySmart — мо- в области компьютерных технологий.
лодой компании, которую он создал совместно с Эли- Она стала одним из учредителей WickedlySmart —
забет. фирмы, работающей в области интернет-образова-
Эрик — специалист по компьютерным технологиям, ния на базе веб-технологий. Здесь она создает книги,
он занимался исследованиями вместе с Дэвидом статьи, видеокурсы и т. д. На должности директора
Гелернтером в Йельском университете. Его диссер- по специальным проектам в O’Reilly Элизабет разра-
тация стала фундаментальным трудом в области батывала семинары и курсы дистанционного обуче-
интерфейсов, реализующих метафору рабочего ния. Так проявилась ее страсть к созданию учебных
стола, а также первой реализацией потоков актив- курсов, помогающих людям разобраться в новых
ности — концепции, разработанной им совместно технологиях. До прихода в O’Reilly Элизабет ра-
с Гелернтером. ботала в The Walt Disney Company, где руководила
В свободное время Эрик серьезно занимается музы- исследованиями и разработками в сфере цифровых
кой; последний проект Эрика — Immersion Station, — мультимедийных технологий.
созданный вместе со Стивом Роучем, можно найти Когда Элизабет не сидит за компьютером, она ходит
в iPhone App Store. в походы, занимается велоспортом и греблей или
Эрик живет с женой и дочерью на острове Бейн- готовит вегетарианские блюда.
бридж. Его дочь часто заглядывает в музыкальную Вы можете написать Элизабет по адресу beth@
студию Эрика, чтобы поиграть с синтезаторами wickedlysmart.com или посетить ее блог http://
и генераторами аудиоэффектов. elisabethrobson.com.
Пишите Эрику по адресу eric@wickedlysmart.com
или посетите его сайт http://ericfreeman.com.
8
оглавление
Содержание (сводка)
Введение 25
1 Первое знакомство с JavaScript. В незнакомых водах 37
2 Настоящий код. Следующий шаг 79
3 Знакомство с функциями. Функции для всех 113
4 Наведение порядка в данных. Массивы 157
5 Знакомьтесь: объекты. Поездка в Объектвиль 203
6 Взаимодействие с веб-страницей. Модель DOM 257
7 Типы, равенство, преобразования и все такое.
Серьезные типы 291
8 Все вместе. Построение приложения 341
9 Асинхронное программирование. Обработка событий 403
10 Первоклассные функции. Функции без ограничений 449
11 Анонимные функции, область действия и замыкания.
Серьезные функции 495
12 Нетривиальное создание объектов. Создание объектов 539
13 Использование прототипов. Сильные объекты 579
Содержание (настоящее)
Введение
Ваш мозг и JavaScript. Вы учитесь — готовитесь к экзамену. Или пытаетесь
освоить сложную техническую тему. Ваш мозг пытается оказать вам услугу.
Он старается сделать так, чтобы на эту очевидно несущественную информа-
цию не тратились драгоценные ресурсы. Их лучше потратить на что-нибудь
важное. Так как же заставить его изучить JavaScript?
9
оглавление
1 В незнакомых водах
JavaScript открывает фантастические возможности. JavaScript,
основной язык программирования Всемирной паутины, позволяет определять
расширенное поведение в веб-страницах. Забудьте о сухих, скучных, статичных
страницах, которые просто занимают место на экране, — с JavaScript вы будете
взаимодействовать с пользователями, реагировать на события, получать и ис-
пользовать данные из Интернета, выводить графику... и многое, многое другое.
При хорошем знании JavaScript вы сможете даже программировать совершенно
новое поведение на своих страницах.
И не сомневайтесь — ваши знания будут востребованы. Сейчас JavaScript
не только является одним из самых популярных языков программирования,
но и поддерживается всеми современными (и многими несовременными) брау-
зерами; более того, появились встроенные реализации JavaScript, существую-
щие отдельно от браузеров. А впрочем, хватит разговоров. Пора браться за дело!
JS
Привлекайте пользователя к взаимодействию со страницей 61
Близкое знакомство с console.log 63
Как открыть консоль 64
Пишем серьезное приложение на JavaScript 65
Как добавить код в страницу? (считаем способы) 68
Разметка и код: пути расходятся 69
10
оглавление
2
настоящий код
Следующий шаг
Вы уже знаете, что такое переменные, типы, выраже-
ния... и так далее. Вы уже кое-что знаете о JavaScript. Более
того, знаний достаточно для того, чтобы начать писать настоящие
программы, которые делают что-то интересное, которыми кто-то бу-
дет пользоваться. Правда, вам не хватает практического опыта на-
писания кода, и мы прямо сейчас начнем решать эту проблему. Как?
А просто возьмемся за написание несложной игры, полностью реали-
зованной на JavaScript. Задача масштабная, но мы будем двигаться
к цели постепенно, шаг за шагом. Итак, беремся за дело, а если вам
Начало
вдруг захочется использовать нашу разработку в своих проектах —
мы не против, распоряжайтесь кодом, как считаете нужным.
11
оглавление
знакомство с функциями
12
оглавление
4
0 1 2 3 4 5 6 7 8 9
Массивы
60 50 60 58 54 54 58
50 52 54
13
оглавление
знакомьтесь: объекты
5 Поездка в Объектвиль
До настоящего момента мы использовали примитивы
и массивы. И при этом применялась методология процедурного про-
граммирования с простыми командами, условиями, циклами for/while
и функциями. Такой подход был далек от принципов объектно-ориентиро-
ванного программирования. Собственно, он вообще не имел ничего об-
щего с объектно-ориентированным программированием. Мы использова-
ли объекты время от времени (причем вы об этом даже не знали), но еще
не написали ни одного собственного объекта. Пришло время покинуть
скучный процедурный город и заняться созданием собственных объектов.
В этой главе вы узнаете, почему объекты сильно улучшают нашу жизнь —
во всяком случае в области программирования. Так и знайте: привыкнув
к объектам, вы уже не захотите возвращаться обратно. Да, и не забудьте
прислать открытку, когда обживетесь.
14
оглавление
взаимодействие с веб-страницей
6 Модель DOM
Вы значительно продвинулись в изучении JavaScript. Ф
актически
вы из новичка в области сценарного программирования превратились в... програм-
миста. Впрочем, кое-чего не хватает: для полноценного использования ваших на-
выков JavaScript необходимо уметь взаимодействовать с веб-страницей, в которой
располагается ваш код. Только в этом случае вы сможете писать динамические
страницы, которые реагируют на действия пользователя и обновляются после за-
грузки. Как взаимодействовать со страницей? Через объектную модель документа
DOM (Document Object Model). В этой главе мы рассмотрим DOM и общие принци-
пы работы с этой моделью из JavaScript для расширения возможностей страницы.
html
head body
15
оглавление
7 Серьезные типы
Настало время серьезно поговорить о типах.Одна из замечательных
особенностей JavaScript заключается в том, что начинающий может достаточно
далеко продвинуться, не углубляясь в подробности языка. Но чтобы действитель-
но овладеть языком, получить повышение по работе и заняться тем, чем дей-
ствительно стоит заниматься, нужно хорошо разбираться в типах. Помните, что
мы говорили о JavaScript, — что у этого языка не было такой роскоши, как акаде-
мическое определение, прошедшее экспертную оценку? Да, это правда, но отсут-
ствие академической основы не помешало Стиву Джобсу и Биллу Гейтсу; не по-
мешало оно и JavaScript. Это означает, что система типов JavaScript... ну, скажем
так — не самая продуманная, и в ней найдется немало странностей. Но не бес-
покойтесь, в этой главе мы все разберем, и вскоре вы научитесь благополучно
обходить все эти неприятные моменты с типами.
16
оглавление
все вместе
8 Построение приложения
Подготовьте свой инструментарий к работе. Да, ваш инстру-
ментарий — ваши новые навыки программирования, ваше знание DOM
и даже некоторое знание HTML и CSS. В этой главе мы объединим все это
для создания своего первого полноценного веб-приложения. Довольно
примитивных игр с одним кораблем, который размещается в одной стро-
ке. В этой главе мы построим полную версию: большое игровое поле, не-
сколько кораблей, ввод данных пользователем прямо на веб-странице. Мы
создадим структуру страницы игры в разметке HTML, применим визуаль-
ное оформление средствами CSS и напишем код JavaScript, определяю-
щий поведение игры. Приготовьтесь: в этой главе мы займемся полноцен-
ным, серьезным программированием и напишем вполне серьезный код.
A
Как мы будем представлять данные кораблей 362
Реализация объекта модели 365
Корабль 1
C
Подготовка метода fire 366
D Корабль 2 Реализация контроллера 373
E Обработка выстрела 374
F Планирование кода... 375
G Корабль 3 HIT Реализация метода parseGuess 376
0 1 2 3 4 5 6 Подсчет и обработка выстрелов 379
Как связать обработчик событий с кнопкой Fire 383
Передача данных контроллеру 384
Как размещать корабли 388
Метод generateShip 389
Генерирование начальной позиции нового корабля 390
Завершение метода generateShip 391
17
оглавление
асинхронное программирование
9 Обработка событий
В этой главе вам предстоит подняться на принципиаль-
но новый уровень. До настоящего момента мы писали код, который
обычно выполняется сверху вниз. Конечно, в нем использовались функ-
ции, объекты и методы, но выполнение шло по заранее намеченной колее.
Жаль, что нам приходится сообщать такие новости во второй половине кни-
ги, но такая структура кода не характерна для JavaScript. Большая часть
кода JavaScript пишется для обработки событий. Каких событий? Да лю-
бых. Пользователь щелкает на странице, данные поступают из сети, в брау
зере срабатывает таймер, в DOM происходят изменения... Это далеко не
полный список. Более того, в браузере постоянно происходят события, ко-
торые в основном остаются незамеченными. В этой главе мы пересмотрим
свой подход к программированию и узнаем, для чего же нужно писать код,
реагирующий на события.
18
оглавление
первоклассные функции
19
оглавление
11 Серьезные функции
Мы узнали много нового о функциях, но это далеко не всё.В этой главе
мы пойдем дальше и разберемся в темах, которыми обычно занимаются профессиона-
лы. Вы научитесь действительно эффективно работать с функциями. Глава будет не
слишком длинной, но с довольно интенсивным изложением материала, так что к кон-
цу главы выразительность вашего кода JavaScript превзойдет все ожидания. Вы также
будете готовы к тому, чтобы взяться за анализ кода коллеги или заняться изучением
библиотеки JavaScript с открытым кодом, потому что мы заодно изучим некоторые рас-
пространенные идиомы и соглашения, связанные с использованием функций. А если
вы никогда не слышали об анонимных функциях и замыканиях, то это самое подхо-
дящее место для знакомства!
20
оглавление
12 Создание объектов
До настоящего момента мы создавали объекты вручную.Для
каждого объекта использовался объектный литерал, который задавал все
без исключения свойства. Для небольших программ это допустимо, но для
серьезного кода потребуется что-то получше, а именно конструкторы объ-
ектов. Конструкторы упрощают создание объектов, причем вы можете созда-
вать объекты по единому шаблону — иначе говоря, конструкторы позволяют
создавать серии объектов, обладающих одинаковыми свойствами и содержа-
щих одинаковые методы. Код, написанный с использованием конструкторов,
получается гораздо более компактным и снижает риск ошибок при создании
большого количества объектов. Уверяем, что после изучения этой главы вы
будете пользоваться конструкторами так, словно занимались этим всю созна-
тельную жизнь.
Создание объектов с использованием объектных литералов 540
О сходстве и различии между объектами 541
Конструкторы 543
Как создать конструктор 544
Как использовать конструктор 545
Как работают конструкторы 546
В конструкторы также можно добавить методы 548
Опасная зона 551
Техника безопасности 551
Даешь массовое производство! 554
Тест-драйв на новых машинах 556
Не спешите расставаться с объектными литералами 557
Преобразование аргументов в объектный литерал 558
Преобразование конструктора Car 559
Экземпляры 561
Даже сконструированные объекты могут содержать
независимые свойства 564
Конструкторы в реальном мире 566
Объект Array 567
Другие встроенные объекты 569
21
оглавление
13
использование прототипов
Сильные объекты
Научиться создавать объекты — только начало.Пришло время «на-
качать мышцы» — изучить расширенные средства определения отношений между
объектами и организовать совместное использование кода. Кроме того, нам пона-
добятся механизмы расширения существующих объектов. Иначе говоря, нам нужно
расширить свой инструментарий работы с объектами. В этой главе вы увидите, что
в JavaScript реализована достаточно мощная объектная модель, но она немно-
го отличается от модели традиционных объектно-ориентированных языков. Вместо
типичных объектно-ориентированных систем на базе классов JavaScript использует
модель прототипов — объектов, способных наследовать и расширять поведение
других объектов. Какая в этом польза для вас? Вскоре узнаете. Итак, за дело...
Object
Представление объектов на диаграммах 581
Снова о конструкторах: код используется повторно,
toString()
hasOwnProperty() но насколько эффективно? 582
// and more
Дублирование методов действительно создает проблемы? 584
Что такое «прототип»? 585
Наследование через прототип 586
Как работает наследование 587
Dog Prototype
species: "Canine" Переопределение прототипа 589
bark() Как получить прототип 591
run()
wag()
Как создать прототип 592
Переопределение унаследованного метода 594
О динамических прототипах 598
Более интересная реализация метода sit 600
ShowDog Prototype Еще раз: как работает свойство sitting 601
league: “Webville” С чего начать проектирование объектов 605
stack()
bait() Создание цепочки прототипов 607
gait() Как работает наследование в цепочке прототипов 608
groom()
Анализ результатов 617
Наводим чистоту 618
Еще немного усилий 619
ShowDog
name: “Scotty”
Вызов Dog.call шаг за шагом 620
breed: “Scottish Terrier” Применяем наследование с пользой... расширяя встроенный объект 626
weight: 15
handler: “Cookie” Теория великого объединения Всего JavaScript 628
Объекты для лучшей жизни 628
Собираем все вместе 629
22
как пользоваться этой книгой
Введение
Не могу поверить,
что они включили
такое в книгу
по программированию
на JavaScript!
ный
на насущ
ел е м ы ответим А К ОЕ
В этом р
азд чили Т
п о ч ем у они вклю v a S cript?»
вопрос: «
Так
и р о ва н и ю на Ja
м
о програм
в книгу п
как работать с этой книгой
24 введение
введение
дальше� 25
как работать с этой книгой
ь воспр ия
<head>
<title>
шает эффективност
My Playlist
</title>
<head>
<body>
и значительно повы
<h1>Kick’n Tunes
</h1>
иа л
<p>BT — Satellite: nice downbeat tune.
тер
</p>
о, ма
<p>
е тог
...
Кром
ым исследований). “Код найден, получите”
Веб-сервер
26 введение
введение
дальше� 27
как работать с этой книгой
Мы использовали рисунки, потому что мозг лучше воспринимает графику, чем species: "Canine"
bark()
run()
текст. С точки зрения мозга рисунок стоит 1024 слова. А когда текст комбинируется wag()
с графикой, мы внедряем текст прямо в рисунки, так мозг работает эффективнее. ShowDog Prototype
league: “Webville”
28 введение
введение
дальше� 29
как работать с этой книгой
Примите к сведению
Это учебник, а не справочник. Мы намеренно убрали из книги все, что могло бы помешать изуче-
нию материала, над которым вы работаете. И при первом чтении книги начинать следует с самого
начала, потому что книга предполагает наличие у читателя определенных знаний и опыта.
30 введение
введение
Упражнения ОБЯЗАТЕЛЬНЫ.
Упражнения являются частью основного материала книги. Одни упражнения способству-
ют запоминанию материала, другие помогают лучше понять его, третьи ориентированы
на его практическое применение. Не пропускайте упражнения.
дальше� 31
как работать с этой книгой
</script>
</head>
<body>
Все содержимое веб-страницы размещается здесь.
</body>
</html>
32 введение
введение
Исмаэль Мартин
Bing
ро Демиддел
ф Ст
Джеф
Фрэнк Д. Мур
Брюс Форкуш
Хавь е Руэдас
Альфред Дж. Спеллер
дальше� 33
группа рецензирования
К издательству O’Reilly:
Огромное, гигантское
спасибо нашему редак-
тору Меган Блан-
шетт, которая рас-
чистила путь для этой
книги, устранила все Пусть эта улыбка не вводит вас
препятствия, терпе- в заблуждение, этот человек —
ливо ждала и жерт настоящий профессионал.
вовала семейным
досугом ради ее завершения.
Кроме того, она помогает нам сохранить рассудок
в наших отношениях с O’Reilly (а O’Reilly — в их
отношениях с нами). Мы обожаем тебя и не до-
ждемся следующего совместного проекта!
Меган Бланшетт
Еще один дружеский привет нашему главному редактору Майку
Хендриксону, который активно продвигал эту книгу с само-
го начала. Спасибо, Майк; ни одна наша книга не вышла бы без
твоего участия. Ты был нашим предводителем больше десяти
лет, и мы любим тебя!
34 введение
введение
Участникам из O’Reilly:
Мы искренне благодарим всю группу O’Reilly: Мелани Ярбро, Боба Пфалера и Дэна Фоксмита,
которые придали форму этой книге; Эду Стивенсону, Хьюджетт Бэрриер и Лесли Крэнделлу,
которые руководили маркетингом, — мы оценили их нестандартный подход к делу. Спасибо Элли
Волкхакузен, Рэнди Камеру и Карен Монтгомери за стильный дизайн обложки, который про-
должает служить нам. Как обычно, спасибо Рэчел Монахан за бескомпромиссное редактирование
(и за то, как она нас подбадривала), а Берту Бейтсу — за исключительно полезную обратную связь.
дальше� 35
1 первое знакомство с javascript
В незнакомых водах
Залезай, водичка просто
класс! Мы в общих чертах
познакомимся с JavaScript,
напишем первый код, запустим его
и посмотрим, как он выполняется
в браузере!
HTML CSS
38 глава 1
первое знакомство с javascript
CSS
<html>
<head>
<title>Icecream</title>
<script>
var x = 49;
</script>
<body>
<h1>Icecream Flavors</h1>
<h2><em>49 flavors</em></h2>
<p>All your favorite
flavors!</p>
</body>
Браузер Браузер
</html>
запомните этот em
факт, мы еще к не
му вернемся...
дальше 4 39
включение javascript в страницу
Тест-драйв
Введите код страницы и сохраните его в файле с именем
“behavior.html”. Теперь загрузите страницу в браузере (пере-
тащите файл в окно браузера или воспользуйтесь командой
Файл > Открыть). Что делает наш код?
Подсказка: чтобы это понять, нужно подождать пять секунд.
40 глава 1
первое знакомство с javascript
часто
Задаваемые
вопросы
В: Я слышал, что JavaScript называют «игрушечным языком». В: Мой друг работает с JavaScript в Photoshop... по крайней
Это правда? мере он так говорит. Это возможно?
О:
О:
В традиционных языках программирования — C, C++ или
Только по названию. Язык JavaScript создавался на пике Java — код компилируется перед выполнением. В процессе компи-
популярности Java, и разработчики JavaScript удачно воспользо- ляции код преобразуется в представление, понятное для машины
вались этим обстоятельством. Оба языка заимствуют некоторые (и обычно оптимизированное по скорости выполнения). Сценарные
элементы синтаксиса из языков семейства C, но в остальном они языки интерпретируются, то есть браузер выполняет каждую строку
имеют мало общего. JavaScript сразу же, как только встретит ее. Для сценарных языков
производительность во время выполнения не так важна; они в
В: Значит, JavaScript — лучший способ создания динамиче-
большей степени ориентированы на построение прототипов, инте-
рактивное программирование с максимальной гибкостью. С ранними
ских страниц? А как насчет решений на базе Flash? версиями JavaScript так и было, по этой причине еще много лет
дальше 4 41
история javascript
42 глава 1
первое знакомство с javascript
var total =
price - (price * (discount / 100));
freeShipping();
juggle();
count = count - 1;
alert("WOOF WOOF");
} else {
alert("woof woof");
var circleArea =
дальше 4 43
решение упражнения
}
Создать переменную circleRadius и присвоить ей значе-
ние 20.
var circleRadius = 20;
Создать переменную с именем circleArea...
var circleArea =
...и присвоить ей результат выражения
Math.PI * (circleRadius * circleRadius); (1256.6370614359173)
44 глава 1
первое знакомство с javascript
Если не хочешь
ограничиваться обычными
статическими веб-
страницами, без JavaScript
тебе не обойтись.
И это правда.
Страницы, построенные на базе HTML и CSS, могут хорошо выглядеть. Но изучив
JavaScript, вы сможете строить принципиально новые разновидности страниц.
Более того, их будет правильнее рассматривать не как обычные страницы, а как А заодно
приложения! и подзара-
ботать!
Что вы говорите? «Конечно, я это отлично знаю, иначе зачем бы мне читать эту
книгу?» Вообще-то мы хотели воспользоваться возможностью и немного погово-
рить об изучении JavaScript. Если у вас уже имеется опыт работы на каком-нибудь
языке программирования или языке сценариев, то вы примерно представляете,
что вас ждет. Если до сегодняшнего дня вы ограничивались HTML и CSS, знайте:
при изучении языка программирования вас ждет нечто принципиально новое.
В HTML и CSS в основном выполняются декларативные операции. Допустим, вы
объявляете, что некоторый текст абзаца или все элементы класса «sale» должны
быть окрашены в красный цвет. JavaScript добавляет в страницу новое поведение,
а для этого необходимо описать вычисления. Вам понадобятся средства для опи-
сания разнообразных операций: «вычислить счет игрока, сложив количество бал-
лов», или «повторить следующее действие десять раз», или «когда пользователь
нажмет эту кнопку, воспроизвести такой-то звуковой сигнал», или даже «зайти
в Твиттер, получить последнее сообщение и разместить его на этой странице».
Язык, необходимый для решения таких задач, сильно отличается от HTML и CSS.
Давайте посмотрим, чем именно…
дальше 4 45
команды javascript
p {
...А абзацы выводятся шрифтом
font-family: sans-serif; без засечек.
}
А если нет — приветствуем
пользователя по имени (впрочем,
в нашем примере Оуэну 25 лет,
так что сообщение не выводится).
46 глава 1
первое знакомство с javascript
Переменные и значения
Вероятно, вы заметили, что в программах JavaScript обычно используются пере-
менные. Переменные предназначены для хранения значений. Каких? Рассмотрим
несколько примеров:
нную
2
Команда объявляет переме
и при сва ива ет
с именем winners
e”
var winners = 2; ей чис лов ое зна чен ие 2.
uk
“D
winners
Переменной присваивается
последовательность символов
var name = "Duke"; (такая последовательность
называется строкой). name
se
fal
Переменной isEligible присва
ивается
значение false. Переменные,
var isEligible = false; мающие два значен при ни-
ия, true и false
(истина/ложь), называются
логиче- isEligible
скими (или булевскими).
дальше 4 47
ключевые слова javascript
Да, и еще один момент: не стоит путать JavaScript и использовать в качестве имен
переменных встроенные ключевые слова, такие как var, function или false. Эти имена
тоже выпадают из списка. В этой книге мы еще рассмотрим некоторые ключевые
слова и разберемся, что они означают, а пока ограничимся кратким списком:
часто
Задаваемые
вопросы
В: Что такое «ключевое слово»? В: А если ключевое слово входит В: В JavaScript учитывается регистр
О:
рованных слов языка JavaScript. JavaScript (то есть переменную, в имени которой
использует такие слова для собственных присутствует ключевое слово if)? Если вы работали с разметкой HTML,
целей. Если вы начнете использовать их как
имена своих переменных, то только собьете В: Конечно, можете. Запрещены только
то, скорее всего, привыкли к тому, что ре-
гистр символов не учитывается; в конце
с толку свой браузер. точные совпадения. Желательно, чтобы концов, для браузера тег <head> ничем
ваш код был простым и понятным, поэто- не отличается от <HEAD>. Но в JavaScript
му использовать имена вида elze тоже регистр символов учитывается в именах
нежелательно — их легко спутать с else. переменных, ключевых словах, именах
функций... Короче, практически везде. Так
что будьте внимательны с использованием
верхнего и нижнего регистра!
48 глава 1
первое знакомство с javascript
WEBVILLE
T I M E S
Как избежать дос
адных ошибок с
Выбор имен пере
именами
широк, по этому менных чрезвычайно двугла во
ре коме нд ац ий , мы дадим не сколько Как? Пр го ог неды ша ще го др акон а. ли
от многих неприяко то ры е сп ас ут ва с бу кв ы каосто начинайте с прописной ав отек JavaScript.
тностей: жд ое сл ов о, кр
ом то ры ис поль зу ютИ хотя некоторые
Выбирайте осмы го : tw oH ea de dD е пе рв о- ных, начи им ен а пе ре ме н-
сленные имена. баты й ре ги ст р» rag on With Fi re. «Гор- мендуем нающие ся с _, мы не реко-
оч ен ь по пуля ре н и та к по ст
Может, имена _m обладает до ст у вас нет для это упать, если только
начают для вас, но, $, r и foo что-то оз- создания именаточной гибкостью для чин (ко го очень ве ск
поймут. Имена an ок ру жа ющие вас не не обхо и пе ре менной с любой гда бу дут, тогда сами поих при-
gle , cu ди мо й то чн ймете).
и passedExam не rrentPressure ву ют и др уг ие сх ос ть ю. Су ще ст - Будьте осторожны.
со вр ем ен ем , но только не забудут ся но «г
ем ы за пи си им ен
ор баты й ре ги ст , Будьте ос
намного более по и сд ел ают ва ш код распро ст раненным р» ст ал са мы м то рожн
пе ре ме нн ых . По ы с вы бо ром им ен
нятным. (в том числе и за зд не е мы пр ивед
Ис поль зу йт е «г пр ед елами JavaScript) ещ е ем
. не ск ол ько пр
в именах из не скор баты й ре ги ст р» Используйт выбора имен, а по ав ил ос то рожн ого
ольких слов. е имена, начина те: выби ка пр ос то запомни-
с _ и $, только в ющие
До пу ст им , в ка
ко й- то мо ме нт особых случаях. ся жите сь райте понятные имена, дер-
нужно выбрать им ва м подальше от ключ
я переменной, дл Им ен а пе ре ме нн всегда ук евых слов и
я с $, обычно ре зе ых , начи на ющ ие ся переменназывайте var при объявлении
рвируются для би ой.
б
О синтаксисе
Однострочный комментарий начинается с двух Логические значения true и false записываются без
косых черт (//). Комментарии только содержат ин- кавычек.
формацию о коде для вас и других разработчиков. rockin = true;
В программе они не выполняются.
Переменным не обязательно присваивать значение
// Это комментарий
при объявлении:
Лишние пробелы разрешены (почти везде). var width;
x = 2233;
Язык JavaScript, в отличие от разметки HTML,
Строки должны заключаться в двойные кавычки учитывает регистр символов. Другими словами,
(или одиночные, но выберите что-то одно — будьте Counter и counter — разные переменные.
последовательны).
дальше 4 49
синтаксические упражнения
СТАНЬ браузером
Ниже приведен код JavaScript, содержащий
ошибки. Представьте себя на месте браузера
и попробуйте отыскать ошибки в коде.
А когда это будет сделано, загляните
в ответы в конце главы
и посмотрите, не упустили
ли вы чего-нибудь.
A
// Test for jokes
var joke = "JavaScript walked into a bar....';
var toldJoke = "false"; Пока не задумывайтесь над тем,
что делает этот фрагмент
var $punchline =
JavaScript; просто постарайтесь
"Better watch out for those semi-colons." отыскать ошибки в переменных
var %entage = 20; и синтаксисе.
var result
if (toldJoke = = true) {
Alert($punchline);
} else
alert(joke);
}
B
\\ Movie Night
var zip code = 98104;
var joe'sFavoriteMovie = Forbidden Planet;
var movieTicket$ = 9;
if (movieTicket$ >= 9) {
alert("Too much!");
} else {
alert("We're going to see " + joe'sFavoriteMovie);
}
50 глава 1
первое знакомство с javascript
Поаккуратнее с выражениями!
Чтобы выразить намерения на языке JavaScript, вам понадобятся выражения.
Каждое выражение вычисляется, а результатом является значение. Мы уже
встречались с выражениями в примерах кода. Давайте рассмотрим выражение
в следующей команде:
ум-
Команда JavaScript присваивает вычисленный Знак * используется для
для дел ени я.
результат переменной total. ножения, а / —
Результат выражения может быть значением другого типа; мы еще вернемся к этой
теме. А пока важно знать, что при вычислении любого выражения будет получен некий
результат — число, строка или логическое выражение. Что же нам это дает?
дальше 4 51
упражнения с выражениями
Возьмите карандаш. Для каждого из приведенных выражений вычислите его значение и запишите в выделенном
месте. Именно ЗАПИШИТЕ… забудьте слова мамы о том, что нельзя писать в книгах, и запишите ответ на странице!
Свериться с ответами можно в конце главы.
Оператор != проверяет,
что два значения НЕ равны. Для любознательных
Вы заметили, что в присваивании
Вопрос на повы- используется оператор =, а при
шенную оценку! проверке ==? Когда вы присваива-
ете значение переменной, ставьте
1000 + "108" Существует несколько ответов. Из них пра- один знак равенства =, а когда
вилен только один. Какой вы выберете?
проверяете, совпадают ли два
______________________
значения, — два знака ==. Начина
-
ющие программисты часто путают
эти операторы.
52 глава 1
первое знакомство с javascript
while (juggling) {
keepBallsInAir();
}
дальше 4 53
циклы javascript
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
Значение scoops
var scoops = 5; больше нуля?
while (scoops > 0) { Несомненно!
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
54 глава 1
первое знакомство с javascript
1 шарик съели,
Следующая команда уменьшает текущее значение scoops на 1 4 осталось!
и присваивает результат (4) все той же переменной scoops.
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
Еще много
var scoops = 5; осталось!
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
дальше 4 55
цикл while в javascript
2 шарика ушло,
3 осталось!
Следующая команда уменьшает текущее значение scoops
на 1 и присваивает результат (3) той же переменной scoops.
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
56 глава 1
первое знакомство с javascript
3 шарика съели,
2 осталось!
Все продолжается снова и снова: при каждом выполнении
цикла scoops уменьшается на 1, в браузер выводится очередная
строка, а программа заходит на очередной круг.
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
5 шариков съели,
До последнего раза... но теперь кое-что изменилось. Переменная осталось 0!
scoops равна нулю, а условие оказывается ложным. На этом все
кончается; цикл больше выполняться не должен. На этот раз про-
грамма обходит блок и выполняет команду, следующую за ним.
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
var scoops = 5;
while (scoops > 0) {
document.write("Another scoop!<br>");
scoops = scoops - 1;
}
document.write("Life without ice cream isn't the same");
дальше 4 57
условные команды javascript
if (cashInWallet > 5) {
order = “Кутить так кутить: чизбургер, картошку и колу”;
} else {
order = “А я возьму стакан воды”;
}
58 глава 1
первое знакомство с javascript
В: Я видел код, в котором условие состоит из одной перемен- В: Откуда взялось название «булевские значения» (другое
ной, и эта переменная содержит даже не логическое значение, название логических значений)?
О:
а строку. Как он работает?
дальше 4 59
развлечения с магнитами
Развлечения с магнитами
Магниты с фрагментами программы JavaScript перепутались. Сможете ли вы расставить
их по порядку, чтобы получить работоспособную программу JavaScritp, которая будет
выдавать приведенный ниже результат? Прежде чем продолжать чтение, сверьтесь
с ответами в конце главы.
document.write("Happy Birthday to
you.<br>");
var i = 0;
var name = "Joe";
i = i + 1; }
Восстановленная программа
должна выдавать этот
document.write("Happy Birthday to you.<br>"); результат.
while (i < 2) {
Расставьте
магниты в этой
области.
60 глава 1
первое знакомство с javascript
Создание сигнала
Браузер поддерживает простейший механизм оповещения пользо-
вателей при помощи функции alert. Вызовите alert со строкой,
содержащей сообщение, и браузер выведет ее в симпатичном
диалоговом окне. Честно говоря, мы немного злоупотребляли этой
функцией, она очень проста и удобна; но ее следует применять только
тогда, когда вы действительно хотите, чтобы пользователь отложил
все дела и немедленно ознакомился с вашим сообщением.
Эти три способа встре-
чаются в данной главе.
Прямая запись в документ
Веб-страницу можно рассматривать как документ (именно так ее называет
браузер). Функция document.write позволяет вывести произвольную
разметку HTML и контент в произвольной точке страницы. Так поступать не
рекомендуется, хотя время от времени этот способ все же применяется. Мы
воспользовались, потому что он достаточно прост для изучения JavaScript.
Вывод на консоль
В каждой среде JavaScript существует консоль, на которую можно вы- Консоль — удобный инстру-
водить сообщения из программного кода. Чтобы вывести сообщение мент для поиска ошибок
на консоль, вызовите функцию console.log и передайте ей строку в коде! Если вы ошиблись
(вскоре мы рассмотрим работу с консолью более подробно). Функция при вводе (например, пропу-
стили кавычку), JavaScript
console.log — отличный инструмент диагностики и отладки. Впро- обычно выводит на консоль
чем, в нормальной программе вывод на консоль спрятан от пользова- описание ошибки, которое
теля, так что данный механизм вряд ли можно назвать эффективным. упростит ее поиск.
дальше 4 61
сравнение средств вывода javascript
Все средства взаимодействия явились на маскарад. Удастся ли вам узнать их под масками?
Соедините описания справа с именами в левом столбце. Мы провели одну линию за вас.
62 глава 1
первое знакомство с javascript
часто
Задаваемые
вопросы
В: Я понимаю, что console.log может использоваться для В: А консоль умеет делать что-нибудь еще, кроме обычного
вывода строк, но что это вообще такое? Почему “console” вывода?
О:
и “log” разделены точкой?
В:
синтаксис “console.log” и передаем выводимое значение в скобках.
Помните об этом; объекты будут намного подробнее рассматри- Все это, конечно, хорошо, но как найти консоль? Я использую
ваться в этой книге. А пока достаточно просто уметь использовать ее в своем коде, но не вижу выводимых данных!
console.log.
О: Во многих браузерах окно консоли открывается отдельной
командой. За подробностями переходите к следующей странице.
дальше 4 63
использование консоли javascript
ается
Консоль открыв
жн ей ча ст и окна
в ни
браузера..
64 глава 1
первое знакомство с javascript
count = count - 1;
if (count > 0) {
} else {
Мозговой
штурм
У этого кода есть небольшой недостаток. Он ра-
ботает правильно, но вывод нельзя назвать
абсолютно идеальным. Удастся ли вам найти
этот недостаток и исправить его?
дальше 4 65
тег script
66 глава 1
первое знакомство с javascript
1 Просмотрите на разметку HTML; в нее будет вставлен код JavaScript. Наберите HTML-код
и включите фрагмент JavaScript между тегами <script>, приведенный пару страниц назад.
Для этого подойдет Блокнот (Windows) или TextEdit (Mac), работающие в режиме просто-
го текста. Или, если у вас имеется любимый редактор HTML — Dreamweaver, Coda или
WebStorm, вы можете работать в нем.
<head>
<meta charset="utf-8">
<title>My First JavaScript</title>
</head>
<body>
<script> Теги <script>. Вы уже знаете, что в них
размещается код JavaScript.
</script>
</body>
</html>
дальше 4 67
как добавить код в страницу
68 глава 1
первое знакомство с javascript
1 Откройте index.html и выделите весь код (то есть весь текст между
тегами <script>). Выделение должно выглядеть примерно так:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My First JavaScript</title>
</head>
<body> Выделите только код без тегов <script>;
во внешнем файле они не понадобятся...
<script>
var word = "bottles";
var count = 99;
while (count > 0) {
console.log(count + " " + word + " of beer on the wall");
console.log(count + " " + word + " of beer,");
console.log("Take one down, pass it around,");
count = count - 1;
if (count > 0) {
console.log(count + " " + word + " of beer on the wall.");
} else {
console.log("No more " + word + " of beer on the wall.");
}
}
</script>
</body>
</html>
code.js
дальше 4 69
внешний javascript
3 Теперь нужно включить ссылку на файл “code.js” в файл “index.html”, чтобы файл
загружался при загрузке страницы. Для этого удалите код JavaScript из “index.html”,
но оставьте теги <script>. Затем добавьте в открывающий тег <script> атрибут src
со ссылкой на “code.js”.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My First JavaScript</title>
</head>
АТрибут src элемента <script> содержит
<body> ссылку на файл JavaScript.
<script src="code.js">
70 глава 1
первое знакомство с javascript
КА
<script src="goodies.js">
var = "quick hack";
ИБ
</script> ОШ
дальше 4 71
интервью с javascript
откровенно о javascript
Интервью недели:
Знакомьтесь — JavaScript
Head First: Добро пожаловать, JavaScript. Вы посто- JavaScript: Честно говоря, это одна из моих
янно заняты, работаете в множестве веб-страниц... сильных сторон. Вы запускаете браузер, вводите
Спасибо, что нашли время пообщаться с нами. несколько строк JavaScript, и программа работает.
JavaScript: Нет проблем. У меня действительно Кроме того, я отлично подхожу для новичков. Го-
хватает дел; JavaScript используется едва ли не на ка- ворят, для начинающего программиста нет более
ждой современной веб-странице для самых разных подходящего языка, чем JavaScript.
целей, от простых эффектов меню до полноценных Head First: Но за простоту приходится платить?
игр. Ни единой свободной минуты! JavaScript: Да, я прост в том смысле, что со мной
Head First: Удивительно. Всего несколько лет назад можно сходу взяться за работу. Но при этом я доста-
говорили, что вы «недоделанный, игрушечный язык точно глубок, и во мне реализованы все современ-
сценариев», а сегодня вы повсюду. ные программные конструкции.
JavaScript: Не напоминайте мне о тех временах. Head First: Например?
Утекло много воды, и многие выдающиеся умы JavaScript: Что вы скажете о динамических типах,
трудились над тем, чтобы меня улучшить. полноценных функциях и замыканиях?
Head First: В чем улучшить? Основные возможно- Head First: Откровенно говоря, ничего. Я не знаю,
сти почти не изменились… что это такое.
JavaScript: Ну, в нескольких отношениях. Во-пер- JavaScript: Понятно… Ничего страшного. Продол-
вых, сейчас я работаю с молниеносной быстротой. жайте читать, и вы все узнаете.
Хотя меня по-прежнему считают языком сценари- Head First: Ну хотя бы в общих чертах?
ев, теперь по быстродействию я почти не уступаю
JavaScript: JavaScript проектировался для ра-
традиционным компилируемым языкам.
боты в динамичной веб-среде, где пользователь
Head First: А во-вторых? взаимодействует со страницами, данные меня-
JavaScript: Мои умения выполнять разные опера- ются, происходят события, а язык отражает
ции сильно расширились. Библиотеки JavaScript, данный стиль программирования. Мы вернемся
доступные в современных браузерах, определяют к этой теме позднее, когда вы больше узнаете
географическое местоположение, воспроизводят о JavaScript.
аудио- и видеоролики, выводят графику на веб- Head First: Послушать вас, так вы идеальный язык.
странице и делают многое другое. Но чтобы все Это правда?
это было возможно, необходимо знать JavaScript.
JavaScript стискивает зубы…
Head First: Вернемся к критике. Мы слышали и не
JavaScript: Знаете, я вырос не в стенах академиче-
столь любезные отзывы... Кажется, вас называли
ских учреждений, как большинство языков. Я ро-
«языком для поделок».
дился в реальном мире, и очень быстро оказался
JavaScript: Ну и что? Я один из самых распростра- перед выбором — тонуть или выплывать. Я не идеа-
ненных языков... А может, и самый распространен- лен; безусловно, у меня есть свои «плохие стороны».
ный. Я сражался с конкурентами и победил. Помни-
Head First с улыбкой ведущего: Сегодня мы уви-
те Java в браузере? Жалкое зрелище. VBScript? Ха-ха.
дели вас с новой стороны. У нас есть тема для буду-
JScript? Flash?! Silverlight? Я мог бы продолжать.
щего разговора. Что-нибудь скажете на прощание?
Разве я выжил бы, если бы был плохим?
JavaScript: Не судите меня по тому, что несовер-
Head First: Вас критиковали за некоторую... «упро-
шенно. Изучайте лучшее и используйте в работе!
щенность».
72 глава 1
первое знакомство с javascript
КЛЮЧЕВЫЕ
МОМЕНТЫ
дальше 4 73
решение упражнения
if (toldJoke == true) {
Alert($punchline); Должно быть alert, а не Alert. Язык
JavaScript учитывает регистр символов.
} else
alert(joke); Пропущена
открывающая
} скобка.
74 глава 1
первое знакомство с javascript
(9 / 5) * temp + 32 50
Что получится, если переменная temp равна 10? __________
Логическое выражение. Опе-
ратор == проверяет, равны Истинным или ложным будет это выражение, если переменная
ли два значения. false
color содержит значение “pink”? __________
true
А если значение “orange”? __________
color == "orange"
дальше 4 75
решение упражнения
Магниты расставлены
по местам!
var i = 0;
while (i < 2) {
i = i + 1;
document.write("Happy Birthday to
you.<br>");
Восстановленная
программа должна
выдавать этот
результат.
76 глава 1
первое знакомство с javascript
дальше 4 77
2 настоящий код
80 глава 2
настоящий код
Начнем с проектирования
Нам понадобятся переменные, а еще числа и строки, команды а-
ок озн
if, условные проверки и циклы... но где и сколько? И как все Круж ачало
н
это собрать воедино? Чтобы ответить на эти вопросы, мы чает ец.
н
или ко
должны лучше представлять себе, что должно происходить
в игре.
Начало Прямо й блок
Сначала нужно определить общую структуру игры. Общая 1 о л ь н ы
уг
р а жает
схема выглядит так: изоб
вие.
дейст
3 Игра закончена
Вывести оценку, которая
3 Вывод счета/
определяется по количеству оценки
попыток.
Итак, мы в общих чертах представляем, что
должна делать программа. Теперь нужно более Игра
закончена
подробно проработать отдельные шаги.
Смотрите, настоящая
блок-схема!
дальше 4 81
проектирование игры
Чуть подробнее...
По высокоуровневому описанию и блок- Игра запускается и создает один
схеме вполне профессионального вида 1 корабль, занимающий три клетки
мы достаточно хорошо представляем, как в одной строке из семи клеток.
должна работать игра. Но прежде чем пе-
Позиции клеток обозначаются целыми
реходить к написанию кода, необходимо
числами; например, на этом рисунке
разобраться с некоторыми подробностями. корабль занимает клетки 1,2,3:
Представление кораблей
Для начала нужно определиться с тем, как
будут представляться корабли на игровом 0 1 2 3 4 5 6
поле. Виртуальная сетка игрового поля
не зря называется «виртуальной». Иначе
говоря, в программе она не существует. 2 Начинаем! Запрашиваем координаты выстрела:
Если и игра, и пользователь знают, что
корабль располагается в трех смежных
клетках из семи (начиная с нуля), то заво- A
дить представление для строки таблицы
не обязательно. Возможно, вам хочется
определить структуру данных, в которой
хранится содержимое всех семи ячеек,
и попытаться разместить корабль в ней. B Проверяем, попал ли пользователь в одну
Но в этом просто нет необходимости. До- из трех клеток корабля. Количество
статочно знать, в каких ячейках находится попаданий хранится в переменной.
корабль (предположим, в ячейках 1, 2 и 3).
Игра заканчивается, когда пользователь попал
3
Получение данных от пользователя по всем трем клеткам и количество попаданий
в переменной hits достигает трех. Программа
Для получения данных от пользователя сообщает, за сколько выстрелов был потоплен
можно использовать функцию prompt. корабль.
Когда пользователь должен ввести новую
позицию, программа вызывает функцию Простое взаимодействие с игрой
prompt, выводящую сообщение, и получает
данные — число в диапазоне от 0 до 6.
Вывод результатов
Как организовать вывод? Пока мы про-
должим использовать функцию alert.
Решение топорное, но оно работает.
(В полноценной игре мы будем напрямую
обновлять веб-страницу, но до этого еще
нужно добраться.)
82 глава 2
настоящий код
Разбираем псевдокод
К планированию и написанию кода следует подходить методично. Мы начнем с написания
псевдокода. Псевдокод представляет собой промежуточную стадию между кодом JavaScript
и описанием программы на разговорном языке. Как вы вскоре увидите, он помогает понять
логику работы программы еще до того, как вы займетесь разработкой настоящего кода.
В псевдокоде упрощенного варианта «Морского боя» имеется раздел с описанием переменных,
и раздел с описанием логики программы. Раздел переменных сообщает, какие данные необ-
ходимо хранить в коде, а раздел логики — какой код нужно будет написать для создания игры.
ОБЪЯВИТЬ три переменные для хранения позиции каждой клетки корабля. Присвоить Переменные,
им имена location1, location2 и location3. которые нам
понадобятся.
ОБЪЯВИТЬ переменную для номера текущей попытки. Присвоить ей имя guess.
ОБЪЯВИТЬ переменную для количества попаданий. Присвоить ей имя hits
и инициализировать значением 0.
ОБЪЯВИТЬ переменную для количества попыток. Присвоить ей имя guesses
и инициализировать значением 0.
ОБЪЯВИТЬ переменную для хранения информации о том, потоплен корабль или нет.
Присвоить ей имя isSunk и инициализировать значением false.
дальше 4 83
морской бой — упражнение
0 1 2 3 4 5 6
location1 = 3;
location2 = 4;
location3 = 5;
1, 4, 2, 3, 5
3 4 5 0 0 false
3 4 5 1 1 0 false
84 глава 2
настоящий код
<title>Battleship</title>
<meta charset="utf-8">
</head>
<body>
<h1>Play battleship!</h1>
<script src="battleship.js"></script>
</body>
</html>
Код JavaScript включается
в конце <body>, чтобы вся
страница была загружена
к тому моменту, когда
браузер начнет выполнять
код battleship.js. Именно это вы увидите при
загрузке страницы. Чтобы
игра заработала, придется
написать дополнительный код!
Мозговой
штурм
Напрягите извилины!
Возможно, мы немного забегаем вперед, но какой код вы бы написали для гене-
рации случайной позиции корабля при каждой загрузке страницы? Какие факторы
необходимо учесть для правильного размещения корабля? Запишите свои мысли
по этому поводу.
дальше 4 85
программирование игры
86 глава 2
настоящий код
дальше 4 87
цикл ввода
88 глава 2
настоящий код
Результат вызова
тся
prompt присваивае
переме нн ой gu ess .
"5"
ция
Получив данные от пользователя, функ
дан-
prompt возвращает их программе. В
ки)
ном случае входные данные (в форме стро
присваиваются переменной guess.
й код...
Наверное, вам хочется опробовать свежеиспеченны
й цикл: он будет
...но лучше не надо. Ваш браузер войдет в бесконечны
Будьте запрашивать координаты выстрела, потом запра шиват ь снова... и так
дальше 4 89
проверка ввода
// Объявления пeременных
90 глава 2
настоящий код
О:
результат только в случае истинности Логические операторы объединяют
В этой ситуации JavaScript автома- обоих условий.
два булевских выражения и создают
тически преобразует строку введенных
данных в число, чтобы сравнить guess В: Что такое «бесконечный цикл»?
один результат true или false. Два
важнейших логических оператора:
О:
< 0 и guess > 6. Если вы ввели только
число (допустим, 4), JavaScript сможет Резонный вопрос. Бесконечный || ИЛИ. Результат будет истинным,
преобразовать строку «4» в число 4. цикл — одна из многочисленных про- если истинно хотя бы одно
Позднее мы поговорим о преобразо- блем, преследующих неосторожных из двух выражений.
ваниях типов. программистов. Как вам известно,
&&
дальше 4 91
проверка попаданий
92 глава 2
настоящий код
// Объявления переменных
дальше 4 93
оповещение пользователя
// Объявления переменных
94 глава 2
настоящий код
Упражнение Помните, мы говорили о том, что псевдокод часто бывает неидеальным? В исходном псевдокоде
мы забыли об одной мелочи: мы не сообщаем пользователю, попал он в корабль (HIT) или про-
махнулся (MISS). Сможете ли вы расставить эти фрагменты кода в правильные места?
else {
alert("HIT!"); }
alert("MISS");
// Объявления переменных
дальше 4 95
а теперь все вместе
var location1 = 3;
var location2 = 4;
var location3 = 5;
var guess;
var hits = 0;
var guesses = 0;
var isSunk = false;
96 глава 2
настоящий код
Сначала мы ввели
некорректное число 9.
Потом ввели 0,
чтобы получить
промах.
На третьем (и послед-
нем) попадании корабль
был потоплен.
Мы видим, что для
победы понадобилось
4 выстрела с общей
точностью 0.75.
дальше 4 97
использование булевских операторов
98 глава 2
настоящий код
дальше 4 99
булевские операторы — упражнение
Упражнение
Боб
100 глава 2
настоящий код
А нельзя ли покороче...
Даже не знаем, как бы это сказать, но... вы излишне многословны в опре-
делении своих условий. Что мы имеем в виду? Для примера возьмем
хотя бы это условие:
В условиях if булевские переменные
часто проверяются на истинность
или ложность.
if (inStock == true) {
...
} Переменная inStock может содержать
одно из двух значений: true или false.
Как выясняется, такое сравнение немного избыточно. Вся суть условия заключается в том, что его ре-
зультатом является значение true или false, но наша булевская переменная inStock уже содержит одно из
этих значений. Следовательно, сравнивать ее ни с чем не нужно; достаточно просто указать ее в условии
саму по себе. Иначе говоря, можно использовать запись следующего вида:
if (inStock) { Если просто указать логическую переменную саму
по себе, то в случае истинности этой переменной
... условие тоже будет истинным, а блок будет выполнен.
} А если переменная inStock лож
на, то и условие
ложно, и программный блок
пропускается.
Возможно, кто-то скажет, что исходная, длинная версия более четко выражала намерения
программиста, но на практике чаще применяется более компактная запись. Кроме того,
компактная запись еще и проще читается.
Внизу приведены две команды, использующие переменные onSale и inStock в условиях для вычисления
Упражнение переменной buyIt. Рассмотрите все возможные комбинации значений inStock и onSale в обеих командах.
С какой версией buyIt будет чаще принимать истинное значение?
дальше 4 101
фиксированные значения
С Я
И Т
УЧ
0 1 2 3 4 5 6 0 1 2 3 4 5 6
102 глава 2
настоящий код
Используем случайную
var location1 = randomLoc;
позицию с двумя сле-
var location2 = location1 + 1; дующими смежными
клетками.
var location3 = location2 + 1;
дальше 4 103
использование случайных чисел
Умножая случайное число на 5,
мы получаем число в диапазоне
от 0 до 5, не включая 5, — та-
кие числа, как 0.13983, 4.231,
2.3451 или, допустим, 4.999.
часто
Задаваемые
вопросы
В: Если мы хотим сгенерировать число В: Выходит, если мне понадобится В: Моя игра не работает. На веб-страни-
от 0 до 4, то почему в программе встре- случайное число от 0 до 100 (включая це не выводится ничего, кроме заголовка
чается число 5: 100), я должен написать «Play battleship». Как узнать, что я сделал
Math.floor(Math.random() * 5)? Math.floor(Math.random() * 101)? не так?
104 глава 2
настоящий код
Для за
мет
Возвращаемся к контролю качества ок
Все необходимое готово. Давайте соберем код воедино (мы уже сделали
это ниже) и заменим существующий фрагмент размещения корабля. Когда
все будет сделано, проведите несколько пробных запусков и посмотрите,
как быстро вам удастся потопить врага.
var randomLoc = Math.floor(Math.random() * 5);
var location1 = randomLoc;
var location2 = location1 + 1;
var location3 = location2 + 1;
Замените объявления переменных
var guess; location этими новыми командами.
var hits = 0;
var guesses = 0;
var isSunk = false;
Попадание
с первого
выстрела.
Второй
выстрел
е
оказывается мене
успешным .
А потом два
попадания
подряд.
Последнее по
падание, корабль
потоплен!
дальше 4 105
упражнение поиск ошибки
Наши выстрелы...
Промах
на первом
выстреле.
Второй выстрел
попадает в одну
я.
из клеток корабл
Потом мы про-
должаем вводить
ту же клетку,
и программа за-
считывает новые
попадания!
На третьем
попадании корабль
тонет! Но ведь это
неправильно. Он не
Мы ввели 0, 1, 1, 1, должен тонуть от
а корабль находится того, что мы три-
Для за
в клетках 1, 2, 3. жды выстрелили
в одну клетку.
меток
Обнар
ужена
ошибка
При по !
вторн
ых вы-
стрел
ах в по
ражен
ную кл -
етку к
орабль
Чем же это кончится? тонет
, хотя
этого
Удастся ли нам найти ошибку? быть
не долж
Удастся ли нам исправить ошибку? но.
106 глава 2
настоящий код
КЛЮЧЕВЫЕ
МОМЕНТЫ
Для описания логики программы JavaScript можно воспользо- Логические операторы сравнивают логические значения.
ваться блок-схемой, на которой представлены точки принятия Например, true || false дает true, а true && false дает false.
решений и выполняемые действия.
Функция Math.random генерирует случайное число в диапа-
Прежде чем переходить к написанию кода, бывает полезно зоне от 0 до 1 (не включая 1).
создать эскиз программы на псевдокоде.
Функция Math.floor округляет число в сторону уменьшения
Псевдокод представляет собой приближенное представле- до ближайшего целого.
ние того, что должен делать ваш код. При использовании функций Math.random и Math.floor имена
Булевские операторы делятся на две категории: операторы должны начинаться с прописной буквы M.
сравнения и логические операторы. При использовании в вы- Функция JavaScript prompt выводит диалоговое окно с со-
ражении булевские операторы дают результат true или false. общением и полем, в котором пользователь вводит запра-
Операторы сравнения сравнивают две величины и воз- шиваемое значение.
вращают true или false. Например, оператор сравнения В этой главе метод prompt используется для получения дан-
< («меньше») может использоваться в выражении 3 < 6 ных у пользователя, а метод alert — для вывода результатов
(результат — true). игры в браузере.
дальше 4 107
решение упражнения
0 1 2 3 4 5 6
location1 = 3;
location2 = 4;
location3 = 5;
1, 4, 2, 3, 5
3 4 5 0 0 false
3 4 5 1 1 0 false
3 4 5 4 2 1 false
3 4 5 2 3 1 false
3 4 5 3 4 2 false
3 4 5 5 5 3 true
108 глава 2
настоящий код
дальше 4 109
решение упражнения
Бухгалтеры Боб и Билл работают над приложением для веб-сайта своей компании (приложение прове-
ряет цену и выдает рекомендацию для покупки). Оба они написали команды if/else с использованием
булевских выражений. Каждый уверен, что его код правилен. Так кто же из них прав? А кому лучше
заниматься бухгалтерией, а не программированием? Ниже приведено наше решение.
Билл
ер).
Из Боба получился лучший программист (а может, и бухгалт
Боба работа ет, а решени е Билла — нет. Чтобы понять ,
Решени е
почему, проверим условия Боба и Билла с тремя разным и зна-
чениями цены (слишком низкое, слишком высокое и правильное),
и посмотрим, какие результаты будут получены:
110 глава 2
настоящий код
Помните, мы говорили о том, что псевдокод часто бывает неидеальным? В исходном псевдоко-
де мы забыли об одной мелочи: мы не сообщаем пользователю, попал он в корабль (HIT) или
промахнулся (MISS). Сможете ли вы расставить эти фрагменты кода в правильные места? Ниже
приведено наше решение:
// Объявления переменных
alert("MISS");
}
}
}
var stats = "You took " + guesses + " guesses to sink the battleship, " +
"which means your shooting accuracy was " + (3/guesses);
alert(stats);
дальше 4 111
решение упражнения
112 глава 2
3 знакомство с функциями
114 глава 3
знакомство с функциями
потребуется изменить, то коррекция происходит в нескольких console.log(dogName + " says woof woof");
}
местах. И чем больше программа, тем больше будет изменений,
а следовательно, и возможностей для ошибок. Значит, нужно
как-то выделить повторяющийся код и разместить его в одном
месте, где его можно будет легко использовать повторно при
необходимости.
Мозговой
штурм
Как улучшить этот код? Потратьте несколько минут
на размышления — удастся ли вам что-нибудь
предложить? А может, в JavaScript есть что-то
такое, что пригодится в таких ситуациях?
дальше 4 115
Ах, если бы только код можно было
повторно использовать везде, где потребуется...
Я бы просто вызывала его вместо того,
чтобы набирать заново. И чтобы ему можно
было присвоить понятное имя, которое легко
запоминается. И чтобы изменения вносились
только в одном месте, а не во многих...
Как жаль, что это только мечты...
116 глава 3
знакомство с функциями
дальше 4 117
как работает функция
Ого, кода стало гораздо меньше. Так что ваш коллега, которому потребуется
разобраться в программе и внести небольшое изменение, легко справится
с задачей. Кроме того, вся логика сосредоточена в одном месте.
Хорошо, но как разные фрагменты кода связываются друг с другом и как
работает механизм вызова? Давайте подробно рассмотрим его, шаг за шагом.
118 глава 3
знакомство с функциями
После вызова вся основная работа После того как значения аргументов будут
выполняется в теле функции. связаны с именами параметров, начинает
ся
выполнение команд в теле функции.
Когда значения всех параметров известны
(например, параметр name содержит строку
«rover», а параметр weight — число 23), можно function bark(name, weight) {
переходить к выполнению тела функции.
if (weight
23 > 20) {
Команды в теле функции выполняются console.log(dogName
"rover" + " says WOOF WOOF");
сверху вниз, как и команды любого другого } else {
написанного вами кода. Единственное отли-
"rover" + " says woof woof");
console.log(dogName
чие заключается в том, что с именами name
и weight связываются значения аргументов, }
передаваемые при вызове функции. }
Параметры работают в теле
функции как переменные, которым
присваиваются переданные значе-
ния аргументов.
дальше 4 119
функции упражнение
Для просмотра результатов
выполнения bark используйте
средства разработчика своего
браузера.
А когда все будет сделано... Выполняется
логика тела функции (в этом примере для имени
Rover и веса 23 выводится сообщение “WOOF
WOOF”), и работа функции на этом завершается.
После этого управление возвращается команде,
следующей за вызовом bark.
bark("bruno", 21);
120 глава 3
знакомство с функциями
Развлечения с магнитами ,
,
Магниты с фрагментами программы JavaScript ,
перепутались. Удастся ли вам расставить магниты
по местам и восстановить работоспособную про- }
грамму, чтобы она выдавала приведенный далее }
результат? Возможно, какие-то магниты окажутся } (
лишними. Сверьтесь с ответами в конце главы.
{
function )
whatShallIWear(80);
else {
r t-shirt");
console.log("Wea
}
whatShallIWear
temperature
temp whatShallIWear(60);
whatShallIWear(5
0);
Консоль JavaScript
Wear a jacket
Wear a t-shirt Так должна выглядеть консоль.
Wear a sweater
дальше 4 121
интервью с функциями
откровенно о функциях
Интервью недели:
С функциями на дружеской ноге
Head First: Добрый вечер, Функция! Мы надеемся Head First: Хорошо, с alert и prompt все понятно,
на откровенный разговор, очень хочется узнать но возьмем Math.random — это и на функцию-то
о вас побольше. не похоже.
Функция: Спасибо за приглашение. Функция: Math.random — это функция, но она
присоединена к другой полезной штуке, которой
Head First: Многие новички JavaScript о вас забыва-
неопытные разработчики тоже пренебрегают:
ют. Они просто берутся за дело и пишут код — строку
к объекту.
за строкой, сверху вниз, обходясь без функций. А от
вас вообще есть польза? Head First: Ах да, объекты. Наши читатели еще
познакомятся с ними в одной из последующих глав.
Функция: Зря они так. Им же хуже, потому что
от меня очень много пользы. Можно посмотреть Функция: Вот и ладно. Тогда и вернемся к этому
на это так: я предоставляю возможность написать разговору
код один раз, а потом использовать его повторно
Head First: А что там с аргументами/параметрами?
снова и снова.
Все это выглядит довольно запутанно.
Head First: Простите за бестактность, но вы всего
Функция: Считайте, что каждый параметр — это
лишь позволяете делать одно и то же снова и снова...
своего рода переменная в теле функции. При
Скучновато звучит, вы не находите?
вызове функции каждое передаваемое значение
Функция: Вовсе нет, вызовы функций параметри- присваивается соответствующему параметру.
зуются: при каждом использовании функции вы
Head First: Тогда что такое аргументы?
передаете аргументы для выполнения конкретной
операции, которая вам нужна. Функция: Просто другое название значений, пере-
даваемых функции при вызове.
Head First: Ммм... Например?
Head First: Честно говоря, я не понимаю, из-за
Функция: Допустим, вы хотите сообщить поль-
чего столько разговоров. Да, с вашей помощью
зователю суммарную стоимость товаров, лежа-
можно повторно использовать код и передавать
щих в корзине; вы пишете для этого функцию
значения в параметрах. И это все? Вроде все просто
computeShoppingCartTotal. Этой функции можно
и понятно.
передавать корзины разных пользователей, и каж-
дый раз она будет вычислять стоимость конкретной Функция: О, это далеко не все. Я еще многое умею:
корзины. возвращать значения, скрывать ваш код от посто-
ронних глаз, проделывать одну классную штуку под
Head First: Если вы так хороши, почему неопытные
названием «замыкание», а еще я нахожусь в очень
разработчики не хотят вас использовать?
близких отношениях с объектами.
Функция: Ничего подобного, они постоянно
Head First: Oооо... ДА НЕУЖЕЛИ?! И мы сможем
используют меня: alert, prompt, Math.random,
провести эксклюзивное интервью на эту тему?
document.write. Трудно написать осмысленную
программу без функций. Дело не в том, что неопыт- Функция: Посмотрим…
ные пользователи не используют функции, просто
они не определяют свои собственные функции.
122 глава 3
знакомство с функциями
дальше 4 123
параметры и аргументы
Мозговой
штурм Какой результат выведет эта программа? Уверены?
function doIt(param) {
param = 2;
}
var test = 1;
doIt(test);
console.log(test);
124 глава 3
знакомство с функциями
Переменные
function dogYears(dogName, age) {
var years = age * 7;
console.log(dogName + " is " + years + " years old");
}
var myDog = "Fido";
dogYears(myDog, 4);
Функции
function makeTea(cups, tea) {
console.log("Brewing " + cups + " cups of " + tea);
}
var guests = 3;
makeTea(guests, "Earl Grey");
Встроенные функции
function secret() {
console.log("The secret of life is 42");
}
secret();
function speak(kind) {
var defaultSound = "";
Аргументы
if (kind = = "dog") {
alert("Woof");
} else if (kind = = "cat") {
alert("Meow");
} else {
alert(defaultSound); Параметры
}
}
var pet = prompt("Enter a type of pet: ");
speak(pet);
дальше 4 125
передача по значению
function addOne(x) {
x = x + 1; x
}
7 7
addOne(age);
age Это x
КОПИЯ
age.
function addOne(x) { 7 8
x увели-
x = x + 1; age чивается x
Увеличивается в функции
значение x. } addOne.
Переменная age сохра-
няет прежнее значение,
хотя x изменяется.
126 глава 3
знакомство с функциями
Мозговой
штурм Помните это упражнение? Что вы скажете
Второй заход теперь, когда вам известно о передаче по зна-
чению? А может, вы дали правильный ответ
с первого раза?
function doIt(param) {
param = 2;
}
var test = 1;
doIt(test);
console.log(test);
дальше 4 127
подробнее об аргументах функций
Эксперименты с функциями
До настоящего момента мы говорили о «нормальном» использовании функций.
Но что произойдет, если передать функции слишком много аргументов? Или
слишком мало? Интуиция подсказывает, что ничего хорошего. Проверим?
128 глава 3
знакомство с функциями
дальше 4 129
команда return
Вывод!
7 Программа вычисляет площадь круга,
используя значение 5.2 параметра r. Консоль JavaScript
The area is: 84.94866535306801
8 Функция возвращает вычисленное зна-
чение. Выполнение функции на этом
прекращается.
130 глава 3
знакомство с функциями
Анатомия функции
Мы рассмотрели смысл основных компонентов определения
и вызова функций; новые знания нужно закрепить в памяти,
Даже если функция
чтобы они не забылись. Основные части определения функции: не имеет параметров,
пара круглых скобок ()
За ключевым словом
Определение функции function следует имя Затем в круглых все равно необходима.
всегда начинается скобках перечисля-
функции.
с ключевого слова ются параметры,
function. разделенные запяты-
ми (нуль и более).
Тело функции
function addScore ( level , score ) { находится между
Переменные, фигурными скоб-
используемые ками и содержит
внутри функции, var bonus = level * score * .1;
набор команд
объявляются (точно таких же,
в теле функции. return score + bonus; которые исполь-
зуются в обычном
} коде).
часто
Задаваемые
вопросы
В: Что произойдет, если я перепутаю О: По сути верно. Но функция сама выпол- В: Что произойдет, если переменная-
порядок аргументов и параметрам будут няет работу по созданию переменной, так аргумент использует то же имя, что и
присвоены аргументы, предназначенные что вам не нужно ставить ключевое слово параметр? Допустим, в обоих случаях
для других параметров? var перед именами параметров. используется имя x?
О: Все, что угодно! Почти наверняка дело В: Каким правилам должны подчинять- О: Даже если имена аргумента и пара-
кончится ошибкой во время выполнения или ся имена функций? метра совпадают, параметр x содержит
О:
некорректным кодом. Всегда внимательно копию аргумента x, поэтому они считаются
следите за параметрами функций и знайте, Имена функций должны подчиняться разными переменными. Изменение зна-
какие аргументы должна получать функция. тем же правилам, что и имена переменных. чения параметра x не изменяет значения
В:
не ставится var? Ведь параметр — это цию и дают представление о том, что делает Что вернет функция, не содержащая
просто новая переменная, верно? функция. В именах функций (как и в именах команды return?
О:
переменных) рекомендуется использовать
«горбатый регистр» (например, camelCase). Функция без команды return вернет
undefined.
дальше 4 131
объявления локальных переменных
Я смотрю, мы начали
размещать объявления переменных
прямо внутри функций. Они
работают точно так же, как обычные
переменные?
132 глава 3
знакомство с функциями
Мы должны
поговорить о том, как вы
пользуетесь переменными...
дальше 4 133
имена переменных
Верно подмечено.
Существует давняя традиция использовать
переменную с именем i для управления
циклом. Эта традиция идет от тех времен,
когда объем программы был ограничен
(а код пробивался на перфокартах) и у ко-
ротких имен были реальные преимущества.
Теперь это всего лишь традиция, понятная
любому программисту. Для этой же цели ча-
сто используются переменные j, k, а иногда
даже x и y. Впрочем, это всего лишь исключе-
ние из общего полезного правила о выборе
осмысленных имен переменных.
134 глава 3
знакомство с функциями
дальше 4 135
область действия
136 глава 3
знакомство с функциями
дальше 4 137
замещение переменных
Локальная и глобальна
я
переменные никак не
вли-
яют друг на друга;
если
изменить одну, это
изме-
нение никак не отра
зится
на другой. Эти пере
менные
существуют незави
симо.
138 глава 3
знакомство с функциями
var centerX = 0;
var centerY = 0;
var width = 600;
var height = 400;
Параметры
function setup(width, height) {
centerX = width/2;
centerY = height/2;
}
function circleArea(r) {
var area = Math.PI * r * r;
Глобальные переменные
return area;
}
setup(width, height);
var area = circleArea(radius);
var distance = computeDistance(x, y, centerX, centerY);
alert("Area: " + area);
alert("Distance: " + distance);
дальше 4 139
беседа у камина
Беседа у камина
Сегодня в студии: глобальная
и локальная переменные спорят о том,
кто из них важнее в программе.
140 глава 3
знакомство с функциями
Хорошо, что ты
не видишь мои локальные
переменные.
Другая функция
Функция
дальше 4 141
вопросы о переменных
часто
Задаваемые
вопросы
В: Следить за локальными и глобальными переменными В: Если перезагрузить страницу в браузере, все глобальные
хлопотно, почему не ограничиться одними глобальными? переменные будут инициализированы заново?
Я всегда так делаю.
О:
О: Если вы пишете сколько-нибудь сложный код (или код, ко-
Да. Перезагрузка страницы фактически начинает ее сущест-
вование заново (в том, что касается переменных). А если во время
перезагрузки выполнялся какой-то код, то все локальные перемен-
торый будет сопровождаться в течение некоторого времени),
вам неизбежно придется заняться управлением переменными. ные тоже исчезнут.
При злоупотреблении глобальными переменными становится
трудно следить за тем, где используются переменные (и где В: Должны ли локальные переменные всегда объявляться
изменяются их значения), а это может привести к ошибкам в начале функции?
в коде. Это обстоятельство начинает играть еще более важную
роль при совместной работе над кодом или при использова- О: Как и в случае с глобальными переменными, локальные
нии сторонних библиотек (хотя хорошо написанная библиотека переменные можно объявлять непосредственно перед их первым
имеет структуру, при которой такие проблемы не возникают). использованием в функции. Тем не менее у программистов принято
объявлять переменные в начале функции, чтобы каждый, кто будет
Итак, используйте глобальные переменные там, где это оправданно,
читать ваш код, мог легко найти эти объявления и сразу получить
но будьте умеренны — и везде, где это возможно, используйте представление обо всех локальных переменных, используемых в
локальные переменные. По мере накопления опыта JavaScript вы функции. Кроме того, если вы отложите объявление переменной,
освоите дополнительные способы структурирования кода, упро- а потом случайно используете переменную в теле функции раньше,
щающие его сопровождение. чем предполагалось, это может привести к неожиданным послед-
В:
бальными переменными в JavaScript. Отчасти это объясняется
тем, что с этим языком легко взяться за работу и начать програм-
Если имя параметра совпадает с именем глобальной пе-
мировать, — и это хорошо, потому что JavaScript не заставляет
ременной, значит ли это, что параметр «замещает» глобальную
программиста обязательно использовать жесткую структуру кода.
переменную?
С другой стороны, когда на JavaScript так пишется серьезный код,
О: Да — точно так же, как происходит при объявлении в функции
который должен сопровождаться в течение значительного времени
(а это относится практически ко всем веб-страницам), возникают
новой локальной переменной с таким же именем, как у глобальной. проблемы. Впрочем, JavaScript — мощный язык, в котором пред-
Замещение глобального имени вполне допустимо, если вы уверены, усмотрена поддержка таких конструкций, как объекты, обеспечи-
что глобальная переменная не должна использоваться внутри вающие модульное строение кода. На эту тему написано много
функции. Впрочем, лучше включить в программу комментарий, книг, а ваше первое знакомство с объектами состоится в главе 5
чтобы не создавать проблем при последующем чтении вашего кода. (остались какие-то две главы).
142 глава 3
знакомство с функциями
Мы много говорили
о локальных и глобальных
переменных, о том, где их следует
объявлять, но ничего не сказали о том,
где должны объявляться функции. Их тоже
следует размещать в самом начале файлов
JavaScript?
дальше 4 143
упражнение
function thingamajig(size) {
var facky = 1;
clunkCounter = 0;
if (size = = 0) {
display("clank");
} else if (size = = 1) {
display("thunk");
} else {
while (size > 1) {
facky = facky * size;
size = size - 1;
}
clunk(facky);
}
}
function display(output) {
console.log(output);
clunkCounter = clunkCounter + 1;
}
var clunkCounter = 0;
thingamajig(5);
console.log(clunkCounter);
Попробуйте передать Умной Машине числа 0,
1, 2, 3, 4, 5 и т.д. Сможете ли вы понять,
как она работает?
144 глава 3
знакомство с функциями
Ру ководс тво
е кода
по гигиен
Бесплатно
дальше 4 145
кто я упражнение
т о я ?
К Несколько понятий JavaScript, облачившись в маскарадные костюмы, развлекаются
игрой «Кто я?». Они дают подсказки, а вы должны угадать их по тому, что они говорят
о себе. Предполагается, что участники всегда говорят о себе правду. Запишите рядом
с каждым высказыванием имя соответствующего участника.
Сегодняшние участники:
function, аргумент, return, область действия, локальная переменная, гло-
бальная переменная, передача по значению, параметр, вызов функции,
Math.random, встроенные функции, повторное использование кода.
Я повсюду.
146 глава 3
знакомство с функциями
Почему Шерлок решил, что дело не стоит расследовать? Как он узнал, что
преступник не сможет украсть деньги, просто взглянув на код? Может, в коде
кроется какая-то проблема? И даже не одна?
дальше 4 147
ключевые моменты
КЛЮЧЕВЫЕ
МОМЕНТЫ
При вызове функции указывается ее имя и аргу- Функции также помогают сократить дублирование
менты, которые становятся значениями параме- кода или полностью устранить его.
тров (если они есть).
Вы также можете использовать многочисленные
Функция может возвращать значение командой встроенные функции JavaScript’s — такие, как
return. alert, prompt и Math.random, — для выполнения
полезной работы в своих программах.
Функция создает локальную область действия
параметров и локальных переменных, исполь- Вызывая встроенные функции, вы используете
зуемых функцией. существующий код, который вам не нужно писать
самостоятельно.
Переменные имеют либо глобальную об-
ласть действия (то есть видимы в любой Программный код желательно организовать та-
точке программы), либо локальную об- ким образом, чтобы объявления функций и гло-
ласть действия (видимы только в границах бальных переменных располагались вместе
функции, в которой они объявлены). в начале файла JavaScript.
148 глава 3
знакомство с функциями
дальше 4 149
решение упражнения
вес собаки
Функция bark не проверяет, что
т вызов
положителен. Следовательно, это
ому что -1 меньше 20.
будет работать, пот
150 глава 3
знакомство с функциями
function whatShallIWear
( temp ) { Лишние магниты.
}
else if (temp < 70) { }
console.log("Wear a sweater");
} temperature
else {
r t-shirt");
console.log("Wea
}
whatShallIWear(5
0);
whatShallIWear(80);
whatShallIWear(60);
Консоль JavaScript
Wear a jacket
Wear a t-shirt
Wear a sweater
дальше 4 151
решение упражнения
Переменные
function dogYears(dogName, age) {
myDog, guests, pet,
var years = age * 7;
years, defaultSound
console.log(dogName + " is " + years + " years old");
}
var myDog = "Fido";
dogYears(myDog, 4); Функции
dogYears, makeTea,
function makeTea(cups, tea) {
secret, speak,
console.log("Brewing " + cups + " cups of " + tea);
}
var guests = 3;
makeTea(guests, "Earl Grey"); Встроенные функции
alert, console.log,
function secret() {
prompt
console.log("The secret of life is 42");
}
secret();
Аргументы
function speak(kind) {
myDog, 4, guests,
var defaultSound = "";
“Earl Grey”, pet,
if (kind = = "dog") {
plus all the string
alert("Woof"); arguments to alert
} else if (kind = = "cat") { and console.log
alert("Meow");
} else {
alert(defaultSound); Параметры
}
} dogName, age,
var pet = prompt("Enter a type of pet: "); cups, tea, kind
speak(pet);
152 глава 3
знакомство с функциями
Ниже приведен фрагмент кода JavaScript с переменными, определениями функций и вызовами функ-
ций. Ваша задача — найти переменные, используемые во всех аргументах, параметрах, локальных
и глобальных переменных. Запишите имена переменных в соответствующих прямоугольниках справа,
затем обведите кружком «замещенные» переменные. Ниже приведено наше решение.
Параметры
function setup(width, height) {
centerX = width/2;
width, height,
centerY = height/2; x1, y1, x2,
} y2, r
дальше 4 153
решение упражнения
Умная Машина
Умная Машина — потрясающее изобретение. Она пыхтит, стучит и бренчит, но что же она
делает?.. Лучше спросите кого-нибудь другого. Например, программиста — они утверждают, что
знают, как работает этот агрегат. Удастся ли вам разобраться в коде и найти, что же с ним не так?
thunk
Результат для 1
1 JavaScript console
(2) clunk
Результат для 2
2
JavaScript console
Результат для 3
(6) clunk
6 JavaScript console Результат для 4
(24) clunk
Результат для 5
24 JavaScript console
(120) clunk
120
л один любопытный
И что же это значит? Говорят, Умную Машину изобре
в словах . Знаете — берем
тип, который развлекался перестановкой букв
: GOD, OGD, DGO, GDO
слово DOG и генерируем все возможные перестановки
что из трех букв
и ODG. Таким образом, Умная Машина подсчитывает,
ных комбин аций. А если взять слово
можно составить всего шесть возмож
ого! В общем , такие ходили
из пяти букв, то комбинаций будет уже 120 —
яет факториал!
слухи. А тут вдруг выясняется, что Умная Машина вычисл
Кто бы мог подумать?!
Что такое факториал? Посмотрите в Википедии!
154 глава 3
знакомство с функциями
Код под Почему Шерлок решил, что дело не стоит расследовать? Как он узнал,
увеличительным что преступник не сможет украсть деньги, просто взглянув на код?
стеклом Может, в коде кроется какая-то проблема? И даже не одна? Ниже
приведено наше решение.
дальше 4 155
решение упражнения
о я ?
Кт
Несколько понятий JavaScript, облачившись в маскарадные костюмы, развлека-
ются игрой «Кто я?». Они дают подсказки, а вы должны угадать их по тому, что
они говорят о себе. Предполагается, что участники всегда говорят о себе прав-
ду. Запишите рядом с каждым высказыванием имя соответствующего участника.
Ниже приведено наше решение.
н ие
Сегодняшние участники:
аргумент
Меня передают функции.
параметр
Мне передаются значения аргументов.
передача по значению
По сути я означаю «создание копии».
вызов функции
Синоним «передачи управления функции».
156 глава 3
4 наведение порядка в данных
Массивы
И часто ты сюда
попадаешь? Только когда ставят.
Вы нам поможете?
Знакомьтесь: компания Bubbles-R-Us специализируется на мыльных
пузырях. Ее непрекращающиеся исследования направлены на то,
чтобы пользователи по всему миру выдували только самые красивые
и крупные мыльные пузыри. Сегодня компания занимается тестиро-
ванием нескольких версий нового изобретения — «пузырь-фактора»,
а проще говоря, проверяет, сколько пузырей дает каждая из новых раз-
новидностей раствора. Вот как выглядят экспериментальная установка:
60 50 60 58 54 54 58 50 52 54
158 глава 4
наведение порядка в данных
var scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54];
Образец №2
определенно будет
самым лучшим.
Один из экспертов
фирмы Bubbles-R-Us.
дальше 4 159
знакомство с массивами
В массиве все
значения собраны " "
вместе. t ch " ugh
" co r" te do
lla ers nde ola ie
i t e c k
van but lav cho coo
" " " " "
0 1 2 3 4
160 глава 4
наведение порядка в данных
r"
de
" "
ch gh
en " cot r" te" dou
av la rs de la e
l il te en co ki
" "van
"but
" lav
"cho
"coo
flavorOfTheDay
0 1 2 3 4
flavors
flavors 0 1 2 3 4
Значение элемента
с индексом 3 изменилось.
дальше 4 161
получение длины массива
0 1 2 3 4
flavors
к как индексы
Обратите внимание: та
ся с 0, длина массива
массива начинают
него инд екса.
всегда на 1 меньше послед
var products = ["Choo Choo Chocolate", "Icy Mint", "Cake Batter", "Bubblegum"];
var last = ________________;
var recent = products[last];
162 глава 4
наведение порядка в данных
<!doctype html>
<html lang="en"> Вам показалось, что наше
<head> Серьезное Бизнес-Приложение
<title>Phrase-o-matic</title> из главы 1 было недостаточно
серьезным? Ладно. Попробуйте
<meta charset="utf-8"> это, если вам нужно показать
<script> что-то начальнику.
function makePhrases() {
var words1 = ["24/7", "multi-tier", "30,000 foot", "B-to-B", "win-win"];
var words2 = ["empowered", "value-added", "oriented", "focused", "aligned"];
var words3 = ["process", "solution", "tipping-point", "strategy", "vision"];
дальше 4 163
код генератора красивых фраз
Мы определяем функцию
makePhrases, которая будет
function makePhrases() { вызываться позднее.
164 глава 4
наведение порядка в данных
5 Почти готово; фраза построена, осталось вывести ее. Для этого мы,
как обычно, воспользуемся функцией alert.
alert(phrase);
6 Введите последнюю строку кода, еще раз посмотрите на плоды своих трудов,
прежде чем загружать их в браузере, — здесь есть чем гордиться. Запустите
программу и подумайте, как получше применить сгенерированные фразы.
Так выглядит
результат!
дальше 4 165
вопросы о массивах
часто
Задаваемые
вопросы
В: Порядок элементов в массиве ва- В: Может ли массив быть пустым? В: Что произойдет при попытке обра-
О:
жен? щения к массиву по слишком большому
О: Да, может. Более того, вскоре вы уви- или малому индексу (допустим, мень-
Чаще всего важен, но бывают и исклю- дите пример использования пустого масси- ше 0)?
чения. В массиве из примера с пузырями
порядок очень важен, потому что индекс
ва. Такой массив создается командой
О: Предположим, вы объявили массив:
массива определяет номер образца — рас- var emptyArray = [ ];
твор 0 дает результат 60, и это число со- var a = [1, 2, 3];
храняется в элементе с индексом 0. Если Позднее вы cможете добавить в пустой
данные в массиве будут перепутаны, весь массив элементы. после чего пытаетесь обратиться к элемен-
эксперимент пойдет насмарку! Однако в ту a[10] или a[-1]. В обоих случаях будет
некоторых случаях порядок элементов роли
не играет. Например, если в массиве хра-
В: Мы видели массивы, в которых
получен результат undefined. Так что нужно
либо следить за тем, чтобы в обращениях
нятся случайно выбранные слова, вполне хранятся строки и числа; можно ли раз-
использовались только действительные
возможно, что вас интересует только само мещать в массивах другие данные?
индексы, либо проверять результат и убе-
присутствие слова в массиве. Но если позд-
нее вы решите, что слова должны быть О: Да, можно. Более того, в массивах мо-
ждаться, что он отличен от undefined.
166 глава 4
наведение порядка в данных
Что нужно
построить. .
Новые результаты
Директор
Bubbles-R-Us.
орая будет
ож ет е на пи са т ь программу, кот
Вы см очень нужно
отчет? Мне это
выводить такой ои зводство!
растворов в пр
для запуска новых
Bubbles-R-Us
les-R-Us
— Директор Bubb
60
Bubble solution #0 score:
50
Bubble solution #1 score:
60
Bubble solution #2 score:
остальные результаты...
Bubbles tests: 36
Highest bubble score: 69
score: #11, #18
Solutions with highest
дальше 4 167
структура отчета
Мозговой
штурм
Подумайте, как бы вы подошли к созданию этого отчета. Рассмотрите каждую часть отчета
и подумайте, как сгенерировать правильные выходные данные. Запишите свои мысли.
168 глава 4
наведение порядка в данных
Разговор в офисе
Посмотрим, что за
отчет. Надо понять, как
его программировать...
дальше 4 169
перебор массива
var scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69,
34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61,
46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44];
console.log(output);
Затем мы создаем выходную
i = i + 1; строку с номером раствора
(просто индекс массива)
} Строка выводится и результатом.
вызовом console.log.
170 глава 4
наведение порядка в данных
Развлечения с магнитами
Мы написали программу, проверяющую, в каких
ngth)
сортах мороженого есть кусочки жевательной резинки. while (i < hasBubbleGum.le
Весь код был аккуратно разложен, но магниты упали
на пол. Ваша задача — вернуть их на место. Будьте
внимательны: кажется, пока магниты собирали с пола, i = i + 2;
среди них оказались несколько лишних. Прежде чем {
двигаться дальше, сверьтесь с ответами в конце } i = i + 1;
главы.
}
{ {
var i = 0;
i])
if (hasBubbleGum[
ate",
Choo Choo Chocol
var products = [" "Cake Batter",
"Icy Mint",
"Bubblegum"];
console.log(products[i] +
" contains bubble gum");
Результат, который мы
хотим получить.
Консоль JavaScript
дальше 4 171
перебор в цикле for
A B C
for (var i = 0; i < scores.length; i = i + 1) {
output = "Bubble solution #" + i + " score: " + scores[i];
console.log(output);
Здесь размещается
а-
} ТЕЛО цикла. Все ост
ь без изм ене ний , есл и
лос
пер ем ещ ени я
не считать
уве лич ени я i в заг оло вок
команды.
172 глава 4
наведение порядка в данных
var i = 0;
ngth) {
while (i < hasBubbleGum.le
i]) {
if (hasBubbleGum[
console.log(products[i] +
" contains bubble gum");
}
дальше 4 173
все вместе
веб-страницы.
Стандартная разметка
мн ого не тр ебу ется —
Нам от нее ь сценарий.
соз дат
ровно столько, чтобы
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bubble Factory Test Lab</title>
<script> Массив результатов.
var scores = [60, 50, 60, 58, 54, 54,
58, 50, 52, 54, 48, 69,
34, 55, 51, 52, 44, 51,
69, 64, 66, 55, 52, 61,
46, 31, 57, 52, 44, 18,
41, 53, 55, 61, 51, 44];
} Строка выводится на
кон- (Обратите внимание на разрыв длинной
соль. Вот и все! Приш
ло строки. Так можно делать, если при этом
</script> время запустить от
чет. не разрывается текст, заключенный в ка-
</head> вычки. Здесь разбивка выполняется после
оператора конкатенации (+), и поэтому
<body></body> проблем не возникает. Будьте вниматель-
</html> ны и наберите текст точно так, как он
приведен здесь.)
174 глава 4
наведение порядка в данных
Тест-драйв
Сохраните файл с именем bubbles.html и загру- Консоль JavaScript
зите его в браузере. Убедитесь, что окно консоли Bubble solution #0 score: 60
открыто (возможно, вам придется перезагрузить
Bubble solution #1 score: 50
страницу, если консоль была активизирована после
загрузки страницы). Просмотрите отчет, сгенери- Bubble solution #2 score: 60
рованный по данным Bubbles-R-Us. Bubble solution #3 score: 58
Bubble solution #4 score: 54
Bubble solution #5 score: 54
То, что хотел директор. Bubble solution #6 score: 58
Bubble solution #7 score: 50
Bubble solution #8 score: 52
Bubble solution #9 score: 54
Bubble solution #10 score: 48
Отчет со всеми резуль- Bubble solution #11 score: 69
татами выглядит хорошо,
можно переходить к сле- Bubble solution #12 score: 34
дующему пункту — опре- Bubble solution #13 score: 55
делению максимальных Bubble solution #14 score: 51
результатов. Но с их
поиском все еще остаются Bubble solution #15 score: 52
проблемы. Чтобы немно- Bubble solution #16 score: 44
го упростить обнаруже- Bubble solution #17 score: 51
ние «победителей», нужно
поработать с остальными Bubble solution #18 score: 69
требованиями. Bubble solution #19 score: 64
Bubble solution #20 score: 66
Bubble solution #21 score: 55
Bubble solution #22 score: 52
Bubble solution #23 score: 61
Bubble solution #24 score: 46
Bubble solution #25 score: 31
Bubble solution #26 score: 57
Bubble solution #27 score: 52
Bubble solution #28 score: 44
Bubble solution #29 score: 18
Bubble solution #30 score: 41
Bubble solution #31 score: 53
Bubble solution #32 score: 55
Bubble solution #33 score: 61
Bubble solution #34 score: 51
Bubble solution #35 score: 44
дальше 4 175
беседа у камина между for и while
176 глава 4
наведение порядка в данных
Пожалуйста:
for (;answer != "forty-two";)
Прекрасно работает.
Неуклюжие выкрутасы.
дальше 4 177
постфиксное увеличение
Что, опять?..
Нельзя ли покороче?
В своих программах мы неоднократно использовали
команды следующего вида:
Допустим,
Берем переменную и увеличи-
myImpor tantCounter
ваем ее на единицу.
содержит число —
например, 0.
myImportantCounter = myImportantCounter + 1;
Эта команда встречается так часто, что в JavaScript для нее предусмотрена сокращенная
запись. Она называется оператором постфиксного увеличения (инкремента), и несмотря
на название, работает очень просто. С оператором постфиксного увеличения предыдущую
строку кода можно заменить следующей:
Просто добавьте «++»
за именем переменной.
myImportantCounter++;
myImportantCounter--;
После выполнения этой команды
значение myImportantCounter
становится на 1 меньше.
Почему мы сейчас рассказываем об этом? Потому что этот оператор часто используется
в циклах for. Сейчас мы немного «подчистим» код при помощи оператора постфиксного
увеличения…
178 глава 4
наведение порядка в данных
Короткий тест-драйв
Пробный запуск поможет убедиться в том, что переход
на постфиксное увеличение не нарушил работу программы.
Сохраните файл bubbles.html и перезагрузите программу.
На экране должен появиться тот же отчет, который вы
видели ранее.
Консоль JavaScript
Bubble solution #0 score: 60
Bubble solution #1 score: 50
Bubble solution #2 score: 60
...
Отчет выглядит Bubble solution #34 score: 51
точно так же. Bubble solution #35 score: 44
дальше 4 179
планирование остатка отчета
Результаты по всем
Разговор в офисе образцам выводятся, теперь
продолжается... нужно сгенерировать остав-
шуюся часть отчета.
Вы сможете
написать пр
которая буде ограмму,
Джуди: Начинать нужно т выводить
отчет? Мне примерно т
это очень ну акой
с определения общего ко- новых раство жно для запу
Bubbles-R-Us
ров в произво ска
дство!
личества образцов. Здесь — Директор
Bubbles-R-Us
все просто — это длина мас- Bubble so
lution #0
score: 60
Bubble so
сива scores. Bubble so
lution #1
lution #2
score: 50
score: 60
180 глава 4
наведение порядка в данных
Консоль JavaScript
Bubble solution #0 score: 60
Bubble solution #1 score: 50
Bubble solution #2 score: 60
...
... а затем запишите
в пустых полях данные, Bubble solution #34 score: 51
которые будут выведены Bubble solution #35 score: 44
на консоль.
Bubbles tests: ________
Highest bubble score: _______
дальше 4 181
добавление максимального результата
остальные результаты
...
Bubbles tests: 36
Highest bubble score: 69
Solutions with highest score: 11, 18
182 глава 4
наведение порядка в данных
При добавлении новых элементов необходимо следить за тем, по какому индексу разме-
щается добавляемое значение. В противном случае вы создадите разреженный массив,
то есть массив с «дырами» (например, массив со значениями в позициях 0 и 2, но без
значения в позиции 1). Разреженный массив — это не обязательно плохо, но он требует
особого внимания. А пока следует сказать, что существует другой способ добавления
новых элементов, с которым не нужно беспокоиться об индексе. Это метод push, и вот
как он работает:
Создает новый элемент по следующему
доступному индексу (который равен 0)
var genres = []; и присваивает ему значение “Rockabilly".
genres.push("Rockabilly");
genres.push("Ambient"); Создает еще один элемент в следующей
свободной позиции (1 в данном случае)
var size = genres.length; и присваивает ему значение “Ambient".
дальше 4 183
вопросы об итерациях и массивах
часто
Задаваемые
вопросы
В: Команда for содержит объявление
и не содержит значений в других позициях.
Создать разреженный массив несложно:
пользовать undefined приведет к ошибке,
или по крайней мере к неожиданному
переменной и ее инициализацию. Вы var sparseArray = [ ]; поведению программы. Чтобы проверить
говорили, что объявления переменных sparseArray[0] = true; значение на undefined, используйте запись:
следует размещать в начале файла. Как spraseArray[100] = true; if (myarray[i] = = undefined) {
это понимать? В этом примере sparseArray содержит всего ...
Функция push получает аргумент и добавля- быть разреженным или содержит хотя
ет его как новое значение в конец массива бы одно значение undefined, то перед
genres. использованием элемента массива вам
стоит убедиться в том, что его значение
В: Можно ли чуть подробнее о том, что
отлично от undefined. Если вы ограничи-
ваетесь выводом значений на консоль,
такое «разреженный массив»? проблем не будет, но скорее всего, зна-
184 глава 4
наведение порядка в данных
дальше 4 185
решение упражнения
Мозговой
штурм
Взгляните на код из приведенного выше упражнения. А что, если функция push внезапно про-
падет? Смогли бы вы переписать этот код без использования push? Запишите свою версию:
186 глава 4
наведение порядка в данных
var highScore = 0;
var output;
for (var i = 0; i < scores.length; i++) {
output = "Bubble solution #" + i + " score: " + scores[i];
console.log(output);
if (scores[i] > highScore) {
highScore = scores[i];
}
}
console.log("Bubbles tests: " + scores.length);
console.log("Highest bubble score: " + highScore);
Консоль JavaScript
А вот и наши победители...
Bubble solution #0 score: 60
Образцы 11 и 18 показали наивысший результат: 69! Они заслу- Bubble solution #1 score: 50
женно признаются победителями в этой группе образцов. ...
Bubble solution #34 score: 51
Bubbles tests: 36
Highest bubble score: 69
Solutions with the highest score: 11,18
дальше 4 187
думаем о коде и функциях
В последней главе
мы очень много говорили
о функциях. Тогда почему мы
не используем их?
188 глава 4
наведение порядка в данных
Код Bubbles-R-Us.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
н объявляться
Массив scores не долже
<title>Bubble Factory Test Lab</title>
аю щи х с результатами,
в функциях, работ
<script> различаться при
потому что они будут
var scores = [60, 50, 60, 58, 54, 54, и фу нкции. Вместо
каждом использовани
вать scores в ар-
58, 50, 52, 54, 48, 69,
этого мы будем переда могли исполь-
нкц ии
34, 55, 51, 52, 44, 51, гументе, чтобы фу
для генерирования
69, 64, 66, 55, 52, 61, зовать массив scores
46, 31, 57, 52, 44, 18, результатов.
41, 53, 55, 61, 51, 44];
дальше 4 189
рефакторинг с созданием функций
function printAndGetHighScore(scores) {
var highScore = 0;
var output;
for (var i = 0; i < scores.length; i++) {
output = "Bubble solution #" + i + " score: " + scores[i];
console.log(output);
if (scores[i] > highScore) {
Этот код выглядит точно так же.
highScore = scores[i];
Точнее, он ВЫГЛЯДИТ точно так
} же, но теперь использует пара-
метр scores вместо глобальной
}
переменной scores.
return highScore;
}
Здесь добавлена одна строка,
возвращающая highScore в точку
вызова функции.
190 глава 4
наведение порядка в данных
<!doctype html>
<html lang="en">
<head>
<title>Bubble Factory Test Lab</title>
<meta charset="utf-8">
<script>
var scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69,
34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61,
46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44];
function printAndGetHighScore(scores) {
var highScore = 0;
var output;
Наша новая
for (var i = 0; i < scores.length; i++) {
функция готова
output = "Bubble solution #" + i + " score: " + scores[i]; к использованию.
console.log(output);
if (scores[i] > highScore) {
highScore = scores[i];
}
}
return highScore;
}
дальше 4 191
упражнение по рефакторингу
закончим
Работа уже началась, но
мы ее вме ст е с вам и!
192 глава 4
наведение порядка в данных
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bubble Factory Test Lab</title>
<script>
var scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69,
34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61,
46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44];
function printAndGetHighScore(scores) {
var highScore = 0;
var output;
for (var i = 0; i < scores.length; i++) {
output = "Bubble solution #" + i + " score: " + scores[i];
console.log(output);
if (scores[i] > highScore) {
highScore = scores[i];
}
}
return highScore;
}
</script>
</head>
<body> </body>
</html>
дальше 4 193
снова пузыри
ами
Вот вам массив с затрат
азца
на производство каждого обр
из м асс ива scor es.
Итак, что же нужно сделать? Нужно взять образцы с максимальным результатом и выбрать
из них образец с минимальными затратами. К счастью, у нас имеется массив costs, повторя-
ющий структуру массива scores, то есть затраты для элемента scores с индексом 0 хранятся в
элементе массива costs с индексом 0 (.25), затраты для элемента scores с индексом 1 хранятся
в элементе массива costs с индексом 1 (.27), и так далее. То есть затраты на производство
каждого образца хранятся в элементе с тем же индексом, что и результат этого образца. Такие
массивы называются параллельными:
Scores и costs — параллельные
каждо-
массивы, потому что для
ется
го результата из scores име
вую щее зна чен ие затрат
соответст
в costs с тем же индексом.
var costs = [.25, .27, .25, .25, .25, .25, .33, .31, .25, .29, .27, .22, ..., .29];
var scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, ..., 44];
194 глава 4
наведение порядка в данных
дальше 4 195
преобразование псевдокода
Переведите
псевдокод
на JavaScript.
}
var mostCostEffective = getMostCostEffectiveSolution(scores, costs, highScore);
console.log("Bubble Solution #" + mostCostEffective + " is the most cost effective");
196 глава 4
наведение порядка в данных
#11
ОБРАЗЕЦ
ды
мытья посу
ан а ср ед ства для
2/3 стак
воды
3,5 литра ется
ина (прода
ов ы е ло жки глицер
2–3 стол
)
в аптеках
ой
ы в больш
ингредиент
СТ РУ КЦ ИИ : Смешать
ИН !
звлекаться
миске и ра
дальше 4 197
ключевые моменты
КЛЮЧЕВЫЕ
МОМЕНТЫ
Массивы представляют собой структуры Для создания новых массивов обычно исполь-
данных для работы с упорядоченными зуются литералы массивов.
данными.
Пустой массив можно создать командой
Массив содержит набор элементов, каждый var myArray = [ ];
из которых обладает индексом.
Для перебора элементов массива обычно
Индексы массивов начинаются с нуля, соот- используется цикл for.
ветственно индекс первого элемента равен
Заголовок цикла for объединяет инициализа-
нулю.
цию переменной, проверку условия и измене-
Все массивы имеют свойство length, в ко- ние переменной.
тором хранится количество элементов в мас-
Цикл while чаще всего используется, когда ко-
сиве.
личество итераций неизвестно, и цикл должен
К любому элементу можно обратиться по ин- выполняться, пока условие остается истин-
дексу. Например, запись myArray[1] использу- ным. При известном количестве выполнений
ется для обращения к элементу с индексом 1 обычно применяется цикл for.
(второй элемент массива).
Если в середине массива существуют неопре-
Если элемент не существует, при попытке деленные элементы, такой массив называет-
обратиться к нему будет получено значение ся разреженным.
undefined.
Значение переменной можно увеличить опе-
Присваивание существующему элементу при- ратором постфиксного увеличения
ведет к изменению его текущего значения. ++.
Присваивание элементу, не существующе- Значение переменной можно уменьшить
му в массиве, приведет к созданию нового оператором постфиксного умень-
элемента. шения --.
В элементах массивов могут храниться зна- Для добавления новых значений в массив
чения любого типа. можно использовать метод push.
Значения элементов массива не обязаны
относиться к одному типу.
198 глава 4
наведение порядка в данных
var products = ["Choo Choo Chocolate", "Icy Mint", "Cake Batter", "Bubblegum"];
var last = products.length - 1; Для получения индекса последнего
var recent = products[last]; элемента можно использовать
длину массива, уменьшенную на 1.
При длине 4 индекс последнего эле-
мента равен 3 (так как индексы
начинаются с нуля).
ate",
Choo Choo Chocol
var products = [" nt", "Cake Batter",
"Icy Mi
"Bubblegum"];
i]) {
if (hasBubbleGum[
Результат, который должна
console.log(products[i] + вывести программа.
" contains bubble gum");
} Консоль JavaScript
i = i + 1; Bubblegum contains bubble gum!
дальше 4 199
решение упражнения
var i = 0;
ngth) {
while (i < hasBubbleGum.le
i]) {
if (hasBubbleGum[
console.log(products[i] +
" contains bubble gum");
i = i + 1;
} Запишите
свой код.
200 глава 4
наведение порядка в данных
Консоль JavaScript
Bubble solution #0 score: 60
Bubble solution #1 score: 50
Bubble solution #2 score: 60
...
Bubble solution #34 score: 51
... а затем запишите в пустых
полях данные, которые будут Bubble solution 36
#35 score: 44
выведены на консоль. Bubbles tests: ________ 69
Highest bubble score: _______
дальше 4 201
решение упражнения
Функция getMostCostEffectiveSolution
получает массив результатов, массив
затрат и максимальный результат. Сначала cost присваи-
вается большое число,
function getMostCostEffectiveSolution(scores, costs, highscore) { которое уменьшается
а
var cost = 100; Решение с минимальными затратами при нахождении образц
с меньш им и зат рат а-
отслеживается в переменной cost...
var index; ... а индекс образца с минимальными ми (и максимальным
затратами — в переменной index. результатом).
res
Перебираем массив sco
for (var i = 0; i < scores.length; i++) { так же , как пр еж де. ..
ым.
if (scores[i] = = highscore) { ... и проверяем, является ли результат максимальн
if (cost > costs[i]) { Если результат максимален, мы проверяем затраты.
index = i; Если текущие затраты больше сохраненных, значит,
мы нашли образец с меньшими затратами; его пози-
cost = costs[i]; ция (индекс в массиве) сохраняется в переменной index,
а затраты — в cost (хранящей минимальные затра-
} ты по всем просмотренным элементам).
}
}
После завершения цикла в переменной index хра-
.
return index; нится индекс образца с минимальными затратами
Он возвра щает ся коду, вызвав шему функц ию.
}
var mostCostEffective = getMostCostEffectiveSolution(scores, costs, highScore);
console.log("Bubble Solution #" + mostCostEffective + " is the most cost effective");
Индекс (номер образца)
выводится на консоль.
202 глава 4
5 знакомьтесь: объекты
Поездка в Объектвиль
Мы уезжаем в Объектвиль
и навеки покидаем этот
скучный процедурный город!
Ждите, мы непременно
пришлем открытку!
У машины
есть марка,
например,
«Шевроле».
У машины есть цвет.
204 глава 5
знакомьтесь: объекты
Подробнее о свойствах...
Конечно, характеристики настоящего автомобиля не ограничи-
ваются несколькими свойствами, но в своей программе мы хотим
отразить только эти свойства. Давайте рассмотрим их в контексте
типов данных JavaScript:
.
Машина обладает набором свойств
Машина, представ-
ленная в программе ение.
объектом Car. У каждого свойства есть имя и знач
year: 1957
Свойства year, passengers
и mileage содержат числа.
color: “red”
passengers: 2
convertible: false
Свойство convertible
mileage: 1021 содержит булевское
значение.
Объект Car
Мозговой
штурм
Какие еще свойства вы включили бы в объект Car? Подумайте
и запишите их внизу. Помните: в программе нужны далеко не все
характеристики, существующие в реальном объекте.
текла
анные с
Тониров ктно,
т р я т ся эффе
см о вклю-
о ли их
но нужн авление
предст
чать в
а?
объ ект
ь
Запишите здес Здесь запишите
имена свойств. соответствующие
значения.
{
make
__________ «Chevy»
: ______________,
model
__________ : ______________,
year
__________ : ______________,
color
__________ : ______________,
passengers : ______________,
__________
Запишите здесь свои convertible : ______________,
__________
ответы. При желании mileage : ______________,
__________
дополните список соб-
ственными свойствами. __________ : ______________,
__________ : ______________
}; Обратите внимание на
элементы синтаксиса,
мы разместили вокруг которые
свойств и значений. Воз
эта информация вам ещ можно,
е пригодится... Предупре
на всякий случай. ждаем
Мозговой
штурм
А если машина — это такси? Какие свойства и значения будут у нее общими с Шевроле
1957 года? Какие могут отличаться? Какие дополнительные свойства может иметь (или
не иметь) такси?
206 глава 5
знакомьтесь: объекты
Никто не заставляет вас ограничиваться всего одним объектом. Настоящая сила объектов (как вы
Упражнение вскоре увидите) проявляется в ситуациях, в которых вы создаете множество объектов и пишете код,
способный работать с любым полученным объектом. Попробуйте создать с нуля новый объект — пред-
ставляющий еще одну машину. Итак, напишите код для создания второго объекта.
var cadi = {
Разместите
здесь свойства
объ екта Cadillac.
};
Верх не откидывается,
машина вмещает
до пяти пассажиров (сзади
большое откидное сиденье).
208 глава 5
ТА Н Ц ИЯ
АФ НАЯ ии В е
КВИ бвиль
Ш Т Р ол и ц
нием п
Выда н а отделе
е н
¹ 10 ости мы
ие скор ектов.
ре в ы ш объ
фа за п данию
; вм е с то штра жения» по соз
сь ви
тделали ла уличного д обки:
аз в ы легко о
ь « пр а ви в ф и г у рные ск
р т кта
На этот м вам повтори ие объе
предло
жи е о п р еделен
йт
аключа
ельно з
Обязат
t = {
var ca "fluff
y"
name:
}; м:
еточие
з н аче ния дво
т
йства о
йте имя сво
Отделя
{
anet =
var pl eter: 49528
diam
о в них
о обычн
}; рокой, н
ьной ст я
как им
л
произво
ет быть ных: л ь з у емая е е
а мо ж сп о лы ,
йств
Имя сво тся имена пе
ремен ока, и пробе ычки.
ю Е с л и стр одержит в к ав
испол ь зу
ва, с ч ить
св о й с т к л ю
{ за
dget = 4, димо
var wi 3.1 необхо
cost$: ": true
le
"on sa :
менами
}; д и н а к о выми и
со
ойства
ь два св ет.
не буд
е рж а т
о ж ет сод а т ь
не м бот
Объект О! Ра
ca st = { Н Е ВЕРН
re
var fo Temp: 82,
high
mp: 56
highTe я тыми:
тся зап
}; зделяю
ойств ра
че ние» св
имя/зна
Пары «
{
dget = l",
var ga " anv i
name: true
y:
isHeav
ится:
}; не став
пятая
о й ства за на!
не нуж
него с в
послед я здес
ь
ачения пята
После зн За
o = {
perher an",
var su "B atm er"
name: Crusad
i as: "Caped
al
};
210 глава 5
знакомьтесь: объекты
Самая маленькая
виле!
машина в Объект var fiat = {
make: "Fiat",
model: "500",
year: 1957,
color: "Medium Blue",
passengers: 2,
convertible: false,
mileage: 88000
};
чка,
Самая обычная то
ничег о ос об ен но го.
я
Сначала указываетс ...затем точка... ...затем имя
имя объект а.. . свойства.
fiat.mileage
Точечная запись.
var miles = fiat.mileage; ●● Точечная запись (.)
if (miles < 2000) { открывает доступ
к свойствам объекта.
buyIt(); Сначала указываетс
я пере-
хр анится ●● Например, fiat.color —
} менная, в которой и имя
те м то чк а свойство объекта
объект, за fiat с именем color
свойства.
и значением «Medium
Blue».
fiat.mileage = 10000;
212 глава 5
знакомьтесь: объекты
Развлечения с магнитами
Потренируйтесь в создании объектов и использовании точечной записи,
расставьте магниты по местам. Будьте внимательны, в набор могли зате-
саться посторонние магниты!
var dog = {
name: _________
_________: 20.2
age: ________
________: "mixed",
activity: ____________ Фидо надеется,
о
что вы правильн
}; за да ди т е ег о
var bark; свойства.
if (_____________ > 20) {
bark = "WOOF WOOF";
} else {
bark = "woof woof";
}
var speak = __________ + " says " + ________ + " when he wants to " + ____________;
console.log(speak);
fido.dogYears = 35;
214 глава 5
знакомьтесь: объекты
часто
Задаваемые
вопросы
В: Сколько свойств может иметь объект? В: Что произойдет при попытке добавить новое свойство
О:
if (fiat.make) { ... }
Например, можно создать пустой объект, а потом добавлять но объект fiat не содержит свойства make?
свойства динамически в зависимости от логики кода. Такой способ
создания объекта станет понятнее в будущем, когда у вас будет
больше опыта использования объектов.
О: Результат выражения fiat.make будет равен undefined, если
fiat не содержит свойства с именем make.
var lookMaNoProps = { };
lookMaNoProps.age = 10;
В: Что произойдет, если поставить запятую после последнего
свойства?
if (lookMaNoProps.age > 5) {
lookMaNoProps.school = "Elementary"; О: В большинстве браузеров это не приведет к ошибке. Однако
} в старых версиях некоторых браузеров выполнение кода JavaScript
может быть прервано. Итак, если вы хотите, чтобы ваш код работал
В: Чем объект лучше простого набора переменных? В конце
как можно в большем количестве браузеров, старайтесь избегать
лишних запятых.
концов, ничто не мешает нам представить каждое из свойств
объекта fiat отдельной переменной, верно? В: Можно ли воспользоваться функцией console.log для
О: Объекты скрывают сложность данных, чтобы разработчик
вывода объекта на консоль?
216 глава 5
знакомьтесь: объекты
”
ello ue Мы не знаем, как
3 “h tr
интерпретатор JavaScript
число строка булевское ссылка
представляет ссылки
значение
на объекты (да в общем-то
-
Это примитивные пере- Это ссылочная пере
менные. Каждая переменная менн ая ; в не й хр ан ит ся это для нас и не важно).
содержит то значение, ссылка на об ъе кт .
которое в нее было помещено.
В переменной сохраняется
ссылка на объект Car.
Объект
myCar Ссылочное
Сам объект Car значение.
в переменной не хранится!
218 глава 5
знакомьтесь: объекты
Предварительная проверка
Довольно разговоров про объекты! Давайте создадим объект и проверим его, передав функции prequal.
Откройте простейшую страницу HTML (prequal.html), включите в нее приведенный ниже код, загрузите
страницу и посмотрите, пройдет ли проверку объект taxi:
var taxi = {
make: "Webville Motors",
model: "Taxi",
year: 1955,
color: "yellow",
passengers: 4,
convertible: false,
mileage: 281341
};
function prequal(car) {
if (car.mileage > 10000) {
return false;
} else if (car.year > 1960) {
return false;
}
return true;
}
if (worthALook) {
console.log("You gotta check out this " + taxi.make + " " + taxi.model);
} else {
console.log("You should really pass on the " + taxi.make + " " + taxi.model);
}
И что получилось?
Консоль JavaScript
А вот что получилось... Давайте
быстро пройдемся по коду на сле- You should really pass on the Webville Motors Taxi
дующей странице и посмотрим,
почему же объект taxi был от-
вергнут проверочной функцией...
function prequal(car) {
year: 1955
color: "yellow"
passengers: 4
}
car указывает на тот
же объект, что и taxi!
return false;
} else if (car.year > 1960) { В данном случае пробег больше
10 000 миль, поэтому prequal
return false; возвращает false. Жаль, прикольная
} была бы машинка.
4 К сожалению, такси уже набрало основательный пробег, поэтому первая проверка car.mileage > 10 000
дает результат true. Функция возвращает false, и переменной worthALook также присваивается false.
На консоль выводится сообщение о том, что машина не прошла проверку.
if (worthALook) {
console.log("You gotta check out this " + taxi.make + " " + taxi.model);
} else {
console.log("You should really pass on the " + taxi.make + " " + taxi.model);
}
220 глава 5
знакомьтесь: объекты
prequal(cadi); prequal(fiat);
Здесь
запишите
значение
prequal.
var chevy = {
make: "Chevy",
model: "Bel Air",
year: 1957,
color: "red",
passengers: 2,
convertible: false,
mileage: 1021
};
prequal(chevy);
name: “Fido”
fido
weight: 40
breed: “Mixed”
Когда объект присваивается
переменной, в переменную loves: “walks”
заносится ссылка на этот
объект. Сам объект в пере-
менной не хранится.
Dog
Итак, при вызове функции и передаче ей объекта вы передаете не сам
объект, а ссылку на него. Получается, что при использовании семантики
передачи по значению в параметре передается копия ссылки, которая
также указывает на исходный объект.
Что же все это означает для нас? Одно из самых важных последствий заключается в том, что при
изменении свойства объекта в функции изменяется свойство исходного объекта. Следовательно,
все изменения, вносимые в объект внутри функции, продолжат действовать и после завершения
функции. Рассмотрим пример...
222 глава 5
знакомьтесь: объекты
loseWeight(fido, 10);
Dog
Передавая объект fido
функции, мы на самом
деле передаем ссылку
на объект.
dog
var superSecretFile = {
level: "classified",
opened: 0,
password: 2,
contents: "Dr. Evel's next meeting is in Detroit."
};
var secret = getSecret(_______________, _____);
console.log(secret);
224 глава 5
знакомьтесь: объекты
Я вернулся, и у меня
есть новая программа.
Эта крошка будет штамповать
новые машины с утра
до вечера.
<!doctype html>
<html lang="en">
<head>
<title>Object-o-matic</title>
<meta charset="utf-8">
<script>
function makeCar() {
var makes = ["Chevy", "GM", "Fiat", "Webville Motors", "Tucker"];
var models = ["Cadillac", "500", "Bel-Air", "Taxi", "Torpedo"];
var years = [1955, 1957, 1948, 1954, 1961];
var colors = ["red", "blue", "tan", "yellow", "white"];
var convertible = [true, false];
function displayCar(car) {
console.log("Your new car is a " + car.year + " " + car.make + " " + car.model);
}
</script>
</head>
<body></body>
</html>
Генератор Машин
Генератор Машин (написанный тем же автором, что и программа Генератор Красивых
Фраз) с утра до вечера производит машины со случайным набором свойств. Иначе гово-
ря, вместо маркетинговых лозунгов эта программа генерирует случайные комбинации
марки, модели, года выпуска и всех остальных свойств объекта car. В сущности, это ваш
личный автомобильный завод, только воплощенный в программном коде. Давайте поближе
познакомимся с тем, как он работает.
1 Прежде всего у нас имеется функция makeCar, которая вызывается для создания
новой машины. Программа определяет четыре массива с маркой, моделью, го-
дом выпуска и цветом машины, а также массив с булевским признаком, который
указывает, имеет ли машина откидной верх. Мы генерируем пять случайных
чисел для выбора комбинации этих признаков из всех пяти массивов. Также
генерируется еще одно случайное число, которое будет обозначать количество
пассажиров.
В этих четырех массивах содер-
жатся варианты марки, модели,
года выпуска и цвета...
226 глава 5
знакомьтесь: объекты
var fiat = {
make: "Fiat",
model: "500", но добавить
year: 1957, Функцию мож —
ъе кт
прямо в об
так.
color: "Medium Blue", это делается
passengers: 2,
Просто присвойте определение функции
convertible: false, свойству. Да, свойства тоже могут
mileage: 88000, быть функциями!
drive: function() {
Обратите внимание: мы не указываем
alert.log("Zoom zoom!"); имя функции, а просто ставим ключе-
} вое слово function, за которым следует
}; тело. Имя функции совпадает с именем
свойства.
При вызове функции drive (ах, простите — метода drive) тоже используется
точечная запись, но на этот раз с именем объекта fiat и именем свойства
drive, только за именем свойства следуют круглые скобки (как при вызове
любой другой функции).
fiat.drive();
228 глава 5
знакомьтесь: объекты
var fiat = {
make: "Fiat",
model: "500",
year: 1957,
color: "Medium Blue",
passengers: 2, Свойство для хранения текущего
convertible: false, состояния двигателя (true, если
двигатель запущен; false, если
mileage: 88000,
остановлен).
started: false,
Метод для запуска двигателя. Пока
start: function() { этот метод делает совсем немного —
started = true; он задает свойству started значение true.
},
drive: function() {
if (started) {
alert("Zoom zoom!");
} else {
alert("You need to start the engine first.");
}
}
}; А здесь начинается самое интересное: если двигатель
запущен, то при вызове drive выводится сообщение
“Zoom zoom!", а если нет — предупреждение о том,
что сначала нужно запустить двигатель.
Интересно: вместо
того, чтобы просто изменить
свойство started напрямую, мы
зачем-то пишем для него метод. Зачем?
Разве простое изменение свойства
не уменьшает объем кода?
простым вызовом:
fiat.started = true;
230 глава 5
знакомьтесь: объекты
fiat.drive();
Сначала мы попытаемся вести машину с неработающим двига-
fiat.start(); телем; программа должна вывести сообщение с напоминанием.
fiat.drive(); Затем двигатель будет запущен, и мы поведем машину. Наконец,
fiat.stop(); когда пробная поездка будет закончена, машина остановится.
Поиск всегда
начинается с моих
локальных переменных.
drive: function() { Нет, здесь нет.
if (started) {
...
} А может,
} started — параметр
функции? Нет, здесь тоже
не видно.
Хмм, глобальная переменная?
Снова нет. Ладно, сдаюсь, здесь
какая-то ошибка.
start: function() {
this.started = true; Используйте this в точечной
}, записи перед каждым обра-
щением к свойству started.
Тем самым вы сообщаете
stop: function() { интерпретатору JavaScript,
this.started = false; что имеете в виду свойство
ЭТОГО конкретного объек-
},
та (чтобы интерпретатор
JavaScript не думал, что вы
drive: function() { ссылаетесь на переменную).
if (this.started) {
alert("Zoom zoom!");
} else {
alert("You need to start the engine first.");
}
}
};
232 глава 5
знакомьтесь: объекты
СТАНЬ браузером
Ниже приведен код JavaScript, содержащий ошибки. Представьте себя на месте
браузера и попробуйте найти ошибки в коде. Выполнив упражнение, сверьтесь
с ответами в конце главы и посмотрите, удалось ли вам найти
все ошибки до единой.
var song = {
name: "Walk This Way",
artist: "Run-D.M.C.",
minutes: 4,
seconds: 3,
genre: "80s",
playing: false,
play: function() {
if (!playing) {
Не стесняйтесь, делайте this = true;
пометки прямо здесь...
console.log("Playing "
+ name + " by " + artist);
}
},
pause: function() {
if (playing) {
this.playing = false;
}
}
};
this.play();
this.pause();
make: "Fiat"
model: "500"
... more properties here...
Объект fiat со всеми именами
started: false
и значениями свойств, включая
start: function() { метод start.
this.started = true;
}
fiat stop: function() { ... }
drive: function() {
... }
() {
start: function
true;
this.started =
}
fiat.start();
234 глава 5
знакомьтесь: объекты
}
drive: function() {
... }
chevy.start();
make: "Webville..."
В методе start объекта taxi ключевое слово this model: "Taxi"
... more properties here ...
указывает на объект taxi. started: false
start: function() {
start: function() {
taxi.start();
this.started = true;
}
this.started = true; stop: function() { ... }
О: называем эту функцию методом только ключевое слово this по-прежнему будет
Метод — это функция, присвоенная потому, что она находится внутри объекта. работать?
О:
имени свойства в объекте. Функции вызы- Метод делает все, что делает функция,
ваются по именам, а методы — точечной именно потому, что он и есть функция. Да. Вспомните: указатель на объект,
записью, включающей имя объекта и имя
свойства. Ключевое слово this в теле ме- В: Значит, методы могут возвращать
метод которого был вызван, присваивается
в момент вызова.
тода указывает на объект, для которого этот значения?
В:
О:
метод был вызван. Когда this присваивается ссылка на
О:
ключевого слова function в объекте мы Как насчет передачи аргументов
не указываем имя функции. Что с ним методам? Это тоже возможно? Значение this присваивается при
случилось?
О: вызове метода. Таким образом, при вызове
Мозговой
штурм
Допустим, вы скопировали методы start, stop и drive в созданные ранее объекты chevy и cadi.
Что необходимо изменить в коде, чтобы эти методы правильно работали?
236 глава 5
знакомьтесь: объекты
Пора уже привести весь автопарк в движение. Добавьте метод drive в каждый объект. Когда
это будет сделано, добавьте код методов запуска двигателя, ведения машины и остановки.
Упражнение Сверьтесь с ответами в конце главы.
var taxi = {
make: "Webville Motors", Включите этот код
model: "Taxi", после определений cadi.start();
А вы наблюдательны.
Да, копирование методов start, stop и drive в каждый
объект машины определенно приводит у дублированию
кода. В отличие от других свойств, которые могут за-
висеть от конкретного объекта, эти методы остаются
неизменными для всех объектов.
Что вы говорите? «Отлично, повторное использование
кода в действии»? Нет, не торопитесь. Конечно, код ис-
пользуется повторно, но для этого он копируется, и не
однократно, а много раз! Что произойдет, если мы вдруг
решим, что на работу нужно ехать по особым правилам?
Код придется перерабатывать в каждом объекте машины.
Нехорошо. Исправления во многих местах не только
неэффективны, но и создают риск ошибок.
Однако вы выявили проблему более серьезную, чем про-
стое копирование: предполагается, что присутствие оди-
наковых свойств во всех наших объектах делает их всех
частными случаями объекта машины. Что если свойство
mileage будет случайно опущено в одном из объектов —
можно ли будет считать его машиной?
Все эти проблемы вполне реальны. Тем не менее мы
займемся ими в следующей главе, посвященной нетри-
виальному использованию объектов, когда речь пойдет
о правильной организации повторного использования
кода объектов.
238 глава 5
знакомьтесь: объекты
JavaScript console
И тут возникает другая тема: существует другой способ make: Chevy
обращения к свойствам. Вы заметили альтернативный синтаксис model: Bel Air
обращения к свойствам chevy? Оказывается, в JavaScript есть два способа year: 1957
обращения к свойствам объектов. Точечная запись вам уже известна: color: red
chevy.color Имя объекта, за которым следует passengers: 2
точка и имя свойства. convertible: false
mileage: 1021
Но существует и другой способ: запись с квадратными скобками:
За именем объекта ставятся
chevy["color"] квадратные скобки, в которые заклю- Немного похоже на обращение
чается имя свойства в кавычках. к элементам массивов.
Эти две формы эквивалентны и делают одно и то же. Единственное, чем отличается запись с квадрат-
ными скобками — она обладает чуть большей гибкостью и может использоваться в следующей форме:
240 глава 5
знакомьтесь: объекты
Приготовиться к тест-драйву
Обновите код и опробуйте его в деле! Вот что получилось
у нас со следующим тестовым кодом:
Работа по интеграции свойства fuel в объект машины еще не завершена. Например, можно ли запустить
Упражнение двигатель при отсутствии бензина? Проверим метод start:
start: function() {
this.started = true;
}
Здесь
запишите
свой код.
Мозговой
штурм
Еще раз взгляните на код объекта fiat. В каких еще местах свойство fuel может
влиять на поведение машины (или, наоборот, стоит добавить поведение, изме-
няющее свойство fuel)? Запишите свои предложения здесь.
242 глава 5
знакомьтесь: объекты
fiat.addFuel(2);
fiat.start();
fiat.drive();
fiat.stop();
Насколько проще понять, что происходит в этом коде — ведь он описывает мир при
помощи объектов, обладающих состоянием и поведением.
Впрочем, это только начало! Мы можем пойти еще дальше и непременно это сделаем.
Теперь, когда вы так много узнали об объектах, мы поднимем ваши навыки на новый
уровень и начнем писать полноценный объектно-ориентированный код, используя
другие возможности JavaScript и многие передовые методы разработки (чрезвычайно
важные при работе с объектами).
Но есть еще кое-что, что вы должны знать до того, как мы перейдем к следующей главе…
Date Math
С помощью объекта JSON
вы сможете передавать
объекты objects в другие
Этот объ ект приложения.
позволяет
искать текст RegExp JSON
в строках
по заданному
шаблону.
Все эти объекты пре- Эти объекты предоставляет
доставляет JavaScript. браузер. Они играют ключевую
роль при написании приложений
для браузера!
Document Window
Window предст
авля-
ет свойства, от
но
Метод log объ екта сящиеся к браузе -
ру,
console использовался и методы, котор
ые
для вывода сообщений могут использо
-
на консоль. ваться в вашем
коде.
Console
244 глава 5
знакомьтесь: объекты
откровенно об объекте
Интервью недели:
Объект рассказывает о себе
Head First: Добро пожаловать, Объект, это была потряса- Head First: Ведь мы это уже делали? Все эти машины?
юще интересная глава. Переход на объектные представ-
ления — это же полный переворот в сознании. Объект: Они похожи только потому, что написанный
нами код создает их похожими. Другими словами, все эти
Объект: Что вы, все только начинается. объекты обладают одинаковыми свойствами и методами.
Head First: Как это? Head First: Точно. И мы, кстати, говорили о дублирова-
Объект: Объект — это набор свойств, верно? Одни нии кода в объектах, которое может иметь нежелатель-
свойства используются для хранения состояния объекта, ные последствия для сопровождения.
а другие представляют собой функции (а вернее, методы), Объект: Следующий шаг — научиться создавать объекты,
определяющие поведение объекта. которые будут похожими и будут использовать код, нахо-
Head First: Да, я понимаю. Вообще-то мы не думали дящийся в одном месте. Это подводит к проектированию
о методах как о свойствах, но в конце концов, метод — объектно-ориентированного кода. Теперь, после знаком-
всего лишь пара «имя/значение»... если, конечно, можно ства с основами, вы уже готовы к этой теме.
назвать функцию значением.
Head First: Безусловно, наши читатели рады это услы-
Объект: Еще как можно! Более того, эта мысль — насто- шать!
ящее озарение, сознаете вы это или нет. Запомните ее.
Объект: Но здесь есть один важный момент, о котором
Head First: Мы немного отклонились от темы... необходимо знать.
Объект: Итак, мы рассматривали объекты с их наборами Head First: Вот как?
свойств и создавали множество разных объектов — как,
например, множество разных типов машин. Объект: Рядом с вами находится множество объектов,
которые вы можете использовать в своем коде.
Head First: Верно.
Head First: Неужели? Мы и не заметили, где?
Объект: Но все эти объекты были слишком привязаны
к конкретному применению. Чтобы возможности объ- Объект: Как насчет вызова console.log? Что такое, по-
ектов проявились в полной мере, необходимо создать вашему, console?
некий шаблон, на основе которого «штампуются» похо- Head First: Раз уж мы говорим об объектах, наверное,
жие объекты. это объект?
Head First: Вы имеете в виду объекты, относящиеся
Объект: В ТОЧКУ. А log?
к одному типу?
Head First: Свойство... эээ, метод?
Объект: В определенном смысле. Вы увидите, что
в JavaScript концепция типа весьма любопытна. Однако Объект: Снова В ТОЧКУ. А как насчет alert?
вы на верном пути. Вы увидите, какие возможности от-
крываются при написании кода для работы с объектами Head First: Понятия не имею.
одного типа. Допустим, вы можете написать код, который Объект: Это тоже связано с объектами, но отложим эту
работает для любого вида транспорта, будь то велосипед, тему на будущее.
машина или автобус. В этом вся соль.
Head First: Что ж, вы рассказали нам много полезного
Head First: Безусловно, это выглядит интересно. Что еще об объектах. Надеемся, эта беседа еще будет продолжена.
для этого необходимо?
Объект: Конечно, мы еще встретимся.
Объект: Немножко лучше разбираться в объектах
и иметь механизм создания похожих объектов. Head First: Прекрасно! Тогда до следующего раза.
ess =
var acc "code9"
);
tElem entById(
t.ge
documen Какой пароль
erHTML;
ess.inn будет выведен
a r cod e = acc
v t"; при вызове alert?
midnigh
de = code + "
co Запишите ответ
ode);
о секр
aScr ipt.
т о к од Jav
Э
А это разметка
HTML.
шенн
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dr. Evel's Secret Code Page</title>
</head>
<body>
<p id="code1">The eagle is in the</p>
<p id="code2">The fox is in the</p>
<p id="code3">snuck into the garden last night.</p>
совер
246 глава 5
знакомьтесь: объекты
дущую страницу,
Если вы пропустили преды
Looks like this
code is using a
document object.
ретно
= ;
var access "code9")
е испытание.
entById(
.getElem
document nnerHTML
; What pass code
var code
= access.i
";
will you see in
енно сек
the
Here’s
cript.
JavaS
Here’s the
HTML.
<!doctype html>
<html>
в главе 6!
<head>
<meta charset="utf-8">
Code Page</title>
<title>Dr. Evel's Secret
соверш
Это будет жизненно важно
</head>
<body>
in the</p>
<p id="code1">The eagle is
in the</p>
<p id="code2">The fox is
the garden last night.</p>
<p id="code3">snuck into
would rain</p>
<p id="code4">They said it
robin crow at</p>
<p id="code5">Does the red
find Mr.</p>
<p id="code6">Where can I
boys to bring tea and</p>
<p id="code7">I told the
dough? The cake won't</p>
<p id="code8">Where's my
at</p>
<p id="code9">My watch stopped
fly without umbrella.</p>
<p id="code10">barking, can't
canary flies at</p>
<p id="code11">The green
owns a fine</p>
<p id="code12">The oyster The above JavaScript
<script src="code.js"></script>
</body> code is being
</html> included here.
КЛЮЧЕВЫЕ
МОМЕНТЫ
Объект представляет собой набор свойств. Методы могут получать аргументы (как и обычные
функции).
Для обращения к свойствам обычно используется
точечная запись: имя переменной, содержа- При вызове метода объекта ключевое слово this
щей объект, затем точка и имя свойства. указывает на объект, метод которого вызывается.
Свойства можно добавить в объект в любой мо- Чтобы обратиться к свойствам объекта в методе,
мент. Для этого следует присвоить значение с ука- необходимо использовать точечную запись с ука-
занием имени нового свойства. занием this вместо имени объекта.
Свойства можно удалять из объектов оператором В объектно-ориентированном программировании
delete. разработчик мыслит понятиями объектов, а не
В отличие от переменных, содержащих примитив- процедур.
ные значения (строки, числа и булевские вели- Объект обладает состоянием и поведением.
чины), переменная не может содержать объект. Состояние может влиять на поведение, а поведе-
Вместо этого в ней хранится ссылка на объ- ние может влиять на состояние.
ект. Такие переменные называются ссылочными.
Объекты инкапсулируют, или скрывают
При передаче объекта функция получает копию от пользователя, сложность своего состояния
ссылки на объект, а не копию объекта. Таким обра- и поведения.
зом, изменение свойств объекта в функции приво-
дит к изменениям в исходном объекте. Хорошо спроектированный объект содержит ме-
тоды, абстрагирующие подробности выполнения
Свойства объекта могут содержать функции. Функ- операций с объектом, так что вам не приходится
ция, определенная в объекте, называется методом.
беспокоиться о них.
Методы можно вызывать в точечной записи:
Наряду с объектами, которые вы создаете,
имя объекта, точка и имя свойства метода, за ко-
JavaScript содержит много встроенных объектов,
торым следует пара круглых скобок.
которые вы можете использовать в программах.
Метод во всем аналогичен функции, кроме того, Многие из этих встроенных объектов используются
что он существует в объекте. в книге.
ь
Запишите здес Здесь запишите
имена свойств. соответствующие
значения.
{
Мы используем строки,
make
__________ “Chevy”
: ______________, булевские значения
и числа в зависимости
model
__________ : ______________,
“Bel Air” от свойства.
year
__________ 1957
: ______________,
__________
color : ______________,
“red”
passengers : ______________,
__________ 2
convertible false
__________ : ______________,
Запишите здесь
свои ответы. При mileage
__________ : ______________,
1021
желании дополните accessories
список собствен-
__________ : “Fuzzy
______________,
Dice”
ными свойствами. whitewalls
__________ true
: ______________
};
248 глава 5
знакомьтесь: объекты
Никто не заставляет вас ограничиваться всего одним объектом. Настоящая сила объектов (как вы
вскоре увидите) проявляется в ситуациях, в которых вы создаете множество объектов и пишете
код, способный работать с любым полученным объектом. Попробуйте создать с нуля новый
объект — представляющий еще одну машину. Итак, напишите код для создания второго объекта.
Ниже приведено наше решение.
var cadi = {
make: "GM",
model: "Cadillac",
year: 1955,
Разместите
color: "tan", здесь свойства
объ екта Cadillac.
passengers: 5,
convertible: false,
mileage: 12892
};
Верх не откидывается,
машина вмещает
до пяти пассажиров (сзади
большое откидное сиденье).
Счетчик показывает
12 892 мили.
name: "Fido"
weight: 20.2
age: 4
breed: "mixed"
, 20.2 activity: "fetch balls"
Fido
bark age Объект dog.
гниты.
Оставшиеся ма
var dog = {
"Fido"
name: _________ ,
weight
_________: 20.2 ,
4
age: ________ ,
что
________:
breed "mixed", Фидо надеется,
ав ил ьн о за да-
"fetch balls"
activity: ____________ вы пр
ой ст ва.
дите его св
};
var bark;
dog.weight > 20) {
if (_____________
bark = "WOOF WOOF";
} else {
bark = "woof woof";
}
var speak = __________
dog.name + " says " + ________ dog.activity
dog.bark + " when he wants to " + ______________;
console.log(speak);
250 глава 5
знакомьтесь: объекты
prequal(cadi); prequal(fiat);
false
false
Здесь
запишите
значение
prequal.
var chevy = {
make: "Chevy",
model: "Bel Air",
year: 1957,
color: "red",
passengers: 2,
convertible: false,
mileage: 1021
};
prequal(chevy);
true
var superSecretFile = {
level: "classified",
opened: 0,
password: 2,
contents: "Dr. Evel's next meeting is in Detroit."
};
Объ ект supserSecretFile
superSecretFile
var secret = getSecret(_______________, 2
_____); может передаваться функ-
console.log(secret); циям getSecret и setSecret.
superSecretFile
setSecret(_________________, 2
_____, "Dr. Evel's next meeting is in Philadelphia.");
superSecretFile
secret = getSecret(_______________, 2
_____);
console.log(secret);
252 глава 5
знакомьтесь: объекты
pause: function() {
И здесь для обращения к свойству playing
if (this.playing) { должно использоваться ключевое слово this.
this.playing = false;
}
}
};
ется
Ключевое слово this не использу
что бы вызв ать
this song.play(); за пределами метода; ука-
для конк рет ного объе кта , мы
метод .
нной это го объе кта
this song.pause(); зываем имя переме
Пора уже привести весь автопарк в движение. Добавьте метод drive в каждый объект. Когда это будет
сделано, добавьте код методов запуска двигателя, ведения машины и остановки. Ниже приведено
наше решение.
254 глава 5
знакомьтесь: объекты
Работа по интеграции свойства fuel в объект машины еще не завершена. Например, можно ли
запустить двигатель при отсутствии бензина? Помогите нам интегрировать свойство fuel в код.
Для этого проверьте состояние fuel перед запуском двигателя. Если метод start вызывается при
пустом бензобаке, выведите какое-нибудь осмысленное сообщение для водителя, например:
«The car is on empty, fill up before starting!» Перепишите метод start,
добавьте его в код и протестируйте. Ниже приведено наше решение.
var fiat = {
make: "Fiat",
model: "500",
year: 1957,
color: "Medium Blue",
passengers: 2,
convertible: false,
mileage: 88000,
fuel: 0,
started: false,
start: function() {
if (this.fuel == 0) {
alert("The car is on empty, fill up before starting!");
} else {
this.started = true;
}
},
stop: function() {
this.started = false;
},
drive: function() {
if (this.started) {
if (this.fuel > 0) {
alert(this.make + " " +
this.model + " goes zoom zoom!");
this.fuel = this.fuel - 1;
} else {
alert("Uh oh, out of fuel.");
this.stop();
}
} else {
alert("You need to start the engine first.");
}
},
addFuel: function(amount) {
this.fuel = this.fuel + amount;
}
};
Модель DOM
Погоди, ковбой. Если хочешь
познакомиться со мной
поближе, сначала научись
обходиться с моей объектной
моделью документа...
Это HTML.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dr. Evel's Secret Code Page</title>
</head> абзацу
Обратите внимание: каждому
<body> сваи вает ся иден тиф икат ор (id).
при
<p id="code1">The eagle is in the</p>
<p id="code2">The fox is in the</p>
<p id="code3">snuck into the garden last night.</p>
<p id="code4">They said it would rain</p>
<p id="code5">Does the red robin crow at</p>
<p id="code6">Where can I find Mr.</p>
<p id="code7">I told the boys to bring tea and</p>
<p id="code8">Where's my dough? The cake won't</p>
<p id="code9">My watch stopped at</p>
<p id="code10">barking, can't fly without umbrella.</p>
<p id="code11">The green canary flies at</p>
<p id="code12">The oyster owns a fine</p>
<script src="code.js"></script>
</body> Включение кода
JavaScript....
</html>
258 глава 6
взаимодействие с веб-страницей
Вы узнаете
Что же делает этот код? об объектах
document и element
Давайте разберем эту программу и выясним, как доктор Зло генерирует пароли. А когда в этой главе.
каждый шаг будет очевиден, вы начнете понимать, как работает код в целом:
var access =
Элемент с идентификатором
document.getElementById("code9"); “code9” является элементом
var code = access.innerHTML; абзаца, а содержимое элемен-
а
та (его «внутренняя разметк
code = code + " midnight"; HTML») представляет собой
alert(code); текст «My watch stopped at».
var access =
document.getElementById("code9");
var code = access.innerHTML;
code = code + " midnight";
alert(code);
t”
Итак, мы добавляем “ midnigh
к строке «My wat ch stop ped at»,
получаем строку «My watch
stopped at midnight» и выводим
ее в диалоговом окне.
дальше 4 259
как работает код
В двух словах
Еще раз — что же произошло? Наш код JavaScript обратился к странице
(также называемой документом), получил элемент (с идентификатором
"code9"), взял содержимое этого элемента (строка "My watch stopped at"),
добавил к ней строку " midnight" и вывел результат на экран.
the
The eagle is in
the
The fox is in ht.
garden last nig
snuck into the
ld rain
They said it wou crow at
in
Does the red rob
d Mr.
Where can I fin and
s to bring tea
I told the boy 't
gh? The cake won
Where's my dou
d at
My watch stoppe rella.
fly without umb
barking, can't
flies at
The green canary
s a fine
The oyster own
Браузер
260 глава 6
взаимодействие с веб-страницей
head body
title script h1 h2 p
em
дальше 4 261
построение dom
document
html
3. Д
ля каждого элемента, вложенного в текущий
элемент, добавьте этот элемент как дочерний
по отношению к текущему элементу в модели
DOM. Мы уже приготовили
эту модель DOM за вас.
document Завершенная модель DOM
представлена на следующей
html странице.
head body
4. В
ернитесь к пункту 3 для каждого добавлен-
ного элемента. Повторяйте до тех пор, пока
все элементы не будут обработаны.
262 глава 6
взаимодействие с веб-страницей
ument всегда
Объ ект doc верхнем уровне.
на
находится а,
часть дерев
Это особая ет исполь-
ож
которая м для document также играет
JavaScript
зоваться в уп а ко в сей document роль корня перевернутого
ст
получения до дерева.
.
модели DOM
html
Эти ветви напоминают
ветки дерева.
head body
My blog My blog
h2 p p
А эти узлы напоминают
листья (они не содержат
другие элементы, из кото- Great Today I took a
рых «растут ветки», — day bird I saw couple of
только текст). watching three... photos...
мое страницы
Модель DOM включает содержи
како е-т о текстовое
и элементы. (Возможно,
буде т скры то при ото бражении
содержимое ствует
е оно при сут
DOM, но по крайней мер
в модели).
дальше 4 263
упражнение на построение dom
<!doctype html>
СТАНЬ браузером <html lang="en">
<head>
Представьте себя на месте
<meta charset="utf-8">
браузера. Ваша задача —
<title>Movies</title>
разобрать разметку HTML </head>
и построить для нее модель <body>
DOM. Используйте разметку, <h1>Movie Showtimes</h1>
приведенную справа, <h2 id="movie1">Plan 9 from Outer Space</h2>
и нарисуйте свою модель <p>Playing at 3:00pm, 7:00pm.
<span>
DOM внизу. Мы уже начали
Special showing tonight at <em>midnight</em>!
рисовать ее за вас.
</span>
</p>
Сравните свой ответ <h2 id="movie2">Forbidden Planet</h2>
с нашим решением, <p>Playing at 5:00pm, 9:00pm.</p>
приведенным в конце
главы. </body>
</html>
Здесь нарисуйте
document
свою модель DOM.
html
264 глава 6
взаимодействие с веб-страницей
дальше 4 265
использование getelementbyid для получения элемента
Начнем с модели DOM. Ниже изображена простая модель DOM; она состоит из нескольких абзацев
HTML, каждый из которых снабжен идентификатором id. Каждый абзац содержит некоторый текст. Конечно,
элемент <head> тоже присутствует, но мы опустили лишние подробности, чтобы не усложнять ситуацию.
document
html
head body
А теперь используем JavaScript для того, чтобы сделать разметку более интересной. Допу-
стим, текст с идентификатором greenplanet (“All is well”) нужно заменить на “Red Alert: hit by phaser fire!”,
например, из-за действий пользователя или на основании данных, полученных от веб-службы. К этой
теме мы еще вернемся, а пока просто обновим текст greenplanet. Для этого нам понадобится элемент
с идентификатором “greenplanet”. Следующий код выполняет обновление:
яет
Объ ект document представл
ани цу в бра узе ре и сод ер-
всю стр
ель DO M. Эт ому
жит полную мод
рос
объ екту можно передать зап Здесь мы приказываем
ска эле мен та
на выполнение пои объекту document найти
м.
с заданным идентификаторо элемент с заданным иден-
тификатором.
document.getElementById("greenplanet");
p
...после чего
код JavaScript
t”)
getElementById(“greenplane может проде-
возвращает элемент абзаца, лывать с ним
-
соответствующий идентифи разные инте-
катору “greenplanet”... ресные штуки.
266 глава 6
взаимодействие с веб-страницей
Вызов getElementById
Элемент присваивается ищет элемент greenplanet
переменной planet. и возвращает его.
Свойство innerHTM
L Содержимое элемента greenplanet
элемента planet мо
- заменяется новым текстом...
жет использоваться
для изменения соде Модель DOM (и страница) обновля-
р- ются, и новый текст появляется
жимого элемента
. в браузере.
Вскоре мы поговорим
о свойствах элементов document
подробнее...
html
head body
дальше 4 267
как работает getelementbyid
Выполните шаги 1, 2, 3.
document
html
head body
p
Просто скажите, что я должен
сделать.
3
268 глава 6
взаимодействие с веб-страницей
Операции, которые
могут выполняться
em
Получение содержимого (текст или HTML). с объектом element.
Изменение содержимого.
body
p
Чтение атрибута.
Добавление атрибута.
Удаление атрибута.
Операция, которую мы хотим выполнить со своим элементом (напомним, что это элемент
<p> с идентификатором "greenplanet") — замена содержимого «All is well» на «Red Alert: hit
by phaser fire!». Объект element присвоен переменной planet в нашем коде; воспользуемся
этой переменной для изменения одного из свойств объекта, а именно innerHTML:
дальше 4 269
использование innerhtml
вляет
innerHTML предста
Значение свойства я мо же т бы ть выве-
ра
собой строку, кото рока.
к любая другая ст
дена на консоль ка
Соответственно, при
выводе значения свойства
innerHTML на консоль мы
видим новое значение.
И веб-страница
тут же изменяется!
270 глава 6
взаимодействие с веб-страницей
Освежаем воспоминания
Пожалуй, стоит ненадолго отвлечься от основной темы. Возможно, вы говорите себе:
«Погоди, что-то такое было с идентификаторами и классами, хотя я и не помню подробно-
стей... Кажется, они имели какое-то отношение к CSS?» Нет проблем — давайте немного
передохнем, освежим в памяти кое-какую полезную информацию — и вы в два счета будете
готовы продолжить путь...
После того как элемент будет определен, его можно выбрать в CSS для при-
менения стилевого оформления. Это делается так:
дальше 4 271
изменение dom
Веб-страница, которую
До... вы видите, и базовая модель
DOM до изменения содержи-
мого через innerHTML...
document
html
head body
Элемент, содержимое
которого требуется
изменить...
...и после.
Веб-страница, которую
вы видите, и модель DOM
после изменения содержи-
мого через innerHTML.
document
html
head body
272 глава 6
взаимодействие с веб-страницей
часто
Задаваемые
вопросы
В: А что произойдет, если при вызове document.
вы включаете в свой файл HTML, например <p>some
text</p>. В процессе загрузки и разбора файла HTML
getElementById передается несуществующий иден- браузер создает объект element для каждого элемента
тификатор? страницы и добавляет все эти объекты в модель DOM.
В:
элемента. Значение null более подробно рассматри-
вается в следующей главе. Логично было бы включить в объект element
В: Можно ли использовать document.get
свойство с именем “content” или хотя бы “html”.
Почему вместо этого используется имя innerHTML?
ElementById для выбора элементов по классу —
скажем, если я хочу получить группу элементов
класса “planets”?
О: Мы согласны, имя выбрано странное. Свойство
innerHTML представляет все содержимое элемента,
О: Нет, но вы мыслите в правильном направлении.
включая вложенные элементы (например, абзац по-
мимо текста абзаца может включать элементы <em>
Метод getElementById позволяет выбирать элемен-
и <img>). Существует ли парное свойство outerHTML?
ты по идентификатору. Однако существует другой
Да! Оно возвращает всю разметку HTML внутри эле-
метод DOM — getElementsByClassName, который
мента, а также сам элемент. На практике outerHTML
может использоваться для получения элементов по
используется довольно редко, тогда как свойство
имени класса. С этим методом вы получаете набор
innerHTML сплошь и рядом применяется для обнов-
элементов, принадлежащих классу (так как один класс
ления содержимого элементов.
может содержать множество элементов). Кроме этого
существует метод, возвращающий набор элемен-
тов — getElementsByTagName. Он возвращает все В: Итак, присваивая что-то innerHTML, я могу
элементы с заданным именем тега. Мы рассмотрим заменить содержимое любого элемента новым
метод getElementsByTagName немного позднее. Тогда содержимым. А что будет, если я, допустим, за-
же вы увидите, как обрабатывается возвращаемая им меню содержимое элемента <body>?
группа элементов.
дальше 4 273
Возьми в руку карандаш
li id=”e19”
one at a time!
ul
Перед вами модель DOM, в которой спрятано
секретное сообщение. Выполните приведенный
li id=”e18”
ниже код, чтобы раскрыть секрет! Ответ приведен
pages
внизу.
a lot
strong id=”e14”
document.getElementById("e7")
li id=”e17”
document.getElementById("e8")
the
laugh
document.getElementById("e16")
document.getElementById("e9")
li id=”e16”
document.getElementById("e18")
div id=”e11”
turn
document.getElementById("e13")
I do
strong id=”e13”
document.getElementById("e12")
li id=”e15”
document.getElementById("e2")
but
Please
ент, выбираемый
Запишите элем
кода, а также
каждой строкой
ог о элемента. Так
содержимое эт ! funny
етное сообщение
вы найдете секр
em id=”e12”
body
not
p id=”e10”
span id=”e8”
I’m
it up!
can
p id=”e9”
document
div id=”e4”
back
script
span id=”e7”
html
you
h2 id=”e3”
p
Secret Message
It ain’t
p id=”e5”
head
has come
span id=”e6”
braggin’
title
if
p
time id=”e2”
meta
time
h1 id=”e1”
274 глава 6
взаимодействие с веб-страницей
Планетарный тест-драйв
Теперь вы знаете, как использовать метод document.getElementById
для обращения к элементам, а свойство innerHTML — для изменения
содержимого элемента. Давайте применим новые знания на практике.
Ниже приведена разметка HTML нашего примера с планетами. В сек-
цию <head> включен элемент <script> с кодом, а в теле документа
определяются три абзаца. Если вы не сделали этого ранее, введите
разметку HTML и код JavaScript для обновления DOM:
<!doctype html>
<html lang="en">
<head> Элемент script
<meta charset="utf-8"> с кодом.
<title>Planets</title>
<script> Как было показано ранее,
var planet = document.getElementById("greenplanet"); мы получаем элемент
planet.innerHTML = "Red Alert: hit by phaser fire!"; <p> с идентификатором
“greenplanet” и изменяем
</script>
его содержимое.
</head>
<body>
<h1>Green Planet</h1> Элемент <p>, который
<p id="greenplanet">All is well</p> будет изменяться из кода
<h1>Red Planet</h1> JavaScript.
<p id="redplanet">Nothing to report</p>
<h1>Blue Planet</h1>
<p id="blueplanet">All systems A-OK</p>
</body>
</html>
дальше 4 275
почему не работает код
Я трижды проверил
и перепроверил разметку
и код — все равно не работает.
Никакие изменения на странице
не отражаются.
Uncaught TypeError:
Cannot set property
'innerHTML' of null
276 глава 6
взаимодействие с веб-страницей
Пробуем снова...
Перезагрузите страницу с новой функцией init и свойством
onload. На этот раз браузер полностью загружает страницу,
строит полную модель DOМ и только после этого вызывает
вашу функцию init.
дальше 4 277
браузер и обработчик событий
А ты
не жди. Просто дай
мне функцию обратного
вызова, а я вызову ее после
завершения загрузки.
Легко...
вот тебе функция
с именем init.
278 глава 6
взаимодействие с веб-страницей
Получил!
Я сохраняю init
в свойстве onload, чтобы
не забыть имя функции...
Уф, пришлось
изрядно потрудиться,
но страница все-таки загружена.
Давайте посмотрим... Мне нужно
вызвать функцию, на которую ссылается
свойство onload... Так, это функция init.
Вызываю init!
дальше 4 279
размышления о функциях и обработчиках событий
280 глава 6
взаимодействие с веб-страницей
дальше 4 281
изменение стиля страницы из javascript
Нет ли желания поработать другой частью мозга? Нам понадобится стиль CSS для
класса “redtext”, который назначает тексту абзаца красный цвет (“red”). Если вы справ-
ляетесь с такими задачами с закрытыми глазами — что ж, прекрасно. Если с тех пор,
как вы писали CSS, прошло много времени, не огорчайтесь; все равно попробуйте
это сделать. В любом случае, ответ приведен в конце главы.
282 глава 6
взаимодействие с веб-страницей
До...
document
html
Элемент до вызова
метода setAttribute. head body
Обратите внимание:
элемент уже содержит p id =”greenplanet” p id =”redplanet” p id =”blueplanet”
один атрибут, id.
All is Nothing to All systems
well report A-OK
...и после.
html
дальше 4 283
получение значения атрибута
ь null!
И не забывайте, что getElementById тоже может вернут
стоит убедиться в том,
Каждый раз, когда вы запрашиваете некоторое значение,
что вы получили то, что просили...
ом
если элемент с заданным идентификатор
Вызов getElementById может вернуть null, е полу чени я элем енто в стои т
правилам посл
не существует в DOM. По общепринятым юдат ь это прав ило, но тогд а
могл и бы собл
проверить полученную ссылку на null. Мы
книга стала бы на 1000 страниц длиннее.
284 глава 6
взаимодействие с веб-страницей
<!doctype html>
, CSS
<html lang="en"> Вся разметка HTML
Ja va Sc rip t на ше й
и код
<head>
программы.
<meta charset="utf-8">
<title>Planets</title>
Здесь включается класс redtext, чтобы
<style>
при добавлении “redtext” как значения
.redtext { color: red; } атрибута class в нашем коде текст
</style> выводился красным цветом.
<script>
function init() { Напоминаем: мы получа-
var planet = document.getElementById("greenplanet"); ем элемент "greenplanet"
и сохраняем значение
planet.innerHTML = "Red Alert: hit by phaser fire!"; в переменной planet.
planet.setAttribute("class", "redtext"); Затем мы изменяем
} содержимое элемента,
window.onload = init; и, наконец, добавляем
атрибут class, который
</script> окрашивает текст эле-
Функция init вызывается только
</head> после того, как страница будет мента в красный цвет.
<body> полностью загружена!
<h1>Green Planet</h1>
<p id="greenplanet">All is well</p>
<h1>Red Planet</h1>
<p id="redplanet">Nothing to report</p>
<h1>Blue Planet</h1>
<p id="blueplanet">All systems A-OK</p>
</body>
</html>
Последний тест-драйв...
Загрузите страницу в браузере. В абзаце с иденти-
фикатором «greenplanet» сообщение выводится
ярко-красным шрифтом, так что пользователь
наверняка обратит на него внимание!
дальше 4 285
что еще можно делать с dom
Удаление
существующих Удаление элементов из DOM.
элементов. ul Также из DOM можно удалять любые элементы:
вы берете родительский элемент и удаляете лю-
бые из его потомков. И снова удаленный элемент
li li исчезает из окна браузера сразу же, как только он
будет удален из DOM.
...или получение
Получение родительского Перебор элементов в DOM.
всех дочерних элемента.
элементов... ul id=”list” Получив доступ к элементу, вы сможете найти
все его дочерние элементы, получить «соседей»,
то есть одноранговые элементы, а также узнать
li li родителя. По своей структуре DOM сильно напо-
минает генеалогическое дерево.
...получение одноранговых элементов...
286 глава 6
взаимодействие с веб-страницей
КЛЮЧЕВЫЕ
МОМЕНТЫ
Объектная модель документа, или DOM, — При изменении элемента (посредством изменения его
внутреннее представлением веб-страницы в браузере. свойства innerHTML) изменения немедленно отража-
ются на веб-странице.
Браузер создает модель DOM страницы в процессе
загрузки и разбора разметки HTML. Метод getAttribute позволяет получать значения
атрибутов элементов.
Для получения доступа к DOM в коде JavaScript исполь-
зуется объект document. Метод setAttribute позволяет задавать значения
атрибутов элементов.
Объект document обладает свойствами и методами,
которые могут использоваться для получения доступа Если ваш код размещается в элементе <script> в сек-
и модификации DOM. ции <head> страницы, проследите за тем, чтобы он не
пытался изменять DOM до того, как страница будет
Метод document.getElementById получает эле-
полностью загружена.
мент из DOM по идентификатору.
Свойство onload объекта window может использо-
Метод document.getElementById method возвращает
ваться для назначения обработчика события
объект element, представляющий элемент стра-
(или функции обратного вызова) для загрузки страницы.
ницы.
Обработчик события свойства onload объекта window
Объект element обладает свойствами и методами, при
вызывается сразу же после того, как страница будет
помощи которых вы сможете прочитать содержимое
полностью загружена.
элемента и изменить его.
Существует много разных событий, которые могут
Свойство innerHTML содержит текстовое содержимое
обрабатываться из кода JavaScript при помощи функ-
элемента, а также всю его вложенную разметку HTML.
ций-обработчиков.
Чтобы изменить содержимое элемента, вы изменяете
значение его свойства innerHTML.
дальше 4 287
решение упражнения
<!doctype html>
Стань браузером <html lang="en">
<head>
Решение
<meta charset="utf-8">
Представьте себя на месте <title>Movies</title>
браузера. Ваша задача — </head>
разобрать разметку HTML <body>
и построить для нее модель <h1>Movie Showtimes</h1>
DOM. Используйте разметку, <h2 id="movie1" >Plan 9 from Outer Space</h2>
<p>Playing at 3:00pm, 7:00pm.
приведенную справа,
<span>
и нарисуйте свою модель
Special showing tonight at <em>midnight</em>!
DOM внизу. Мы уже начали </span>
рисовать ее за вас. </p>
<h2 id="movie2">Forbidden Planet</h2>
<p>Playing at 5:00pm, 9:00pm.</p>
</body>
</html>
html
head body
span
em
288 глава 6
взаимодействие с веб-страницей
song1
________.innerHTML = "Blue Suede Strings, by Elvis Pagely";
song2.innerHTML
__________________ = "Great Objects on Fire, by Jerry JSON Lewis";
innerHTML = "I Code the Line, by Johnny JavaScript";
song3.____________
}
onload
window.___________ addSongs
= ____________;
</script>
</head>
<body>
<h1>My awesome playlist</h1>
<ul id="playlist"> Пустой список песен. При-
веденный выше код должен
<li id="song1"></li> добавлять текст в каждый
из элементов <li> списка.
<li id="song2"></li>
<li id="song3"></li>
</ul>
Так должна выгляд еть
</body> веб-страница после
загрузки, когда код
</html> JavaScript заработает.
дальше 4 289
решение упражнения
290 глава 6
7 типы, равенство, преобразования и все такое
Серьезные типы
Song
292 глава 7
типы, равенство, преобразования и все такое
т о я ?
К
Группа значений JavaScript и незваных гостей, облачившись в маска-
радные костюмы, развлекаются игрой «Кто я?». Они дают подсказки,
а вы должны угадать их по тому, что они говорят о себе. Предполага-
ется, что участники всегда говорят о себе правду. Соедините линией
каждое высказывание с именем соответствующего участника. Мы уже
провели одну линию за вас. Прежде чем двигаться дальше, сверьтесь
с ответами в конце главы.
Если упражнение покажется вам слишком сложным — не отчаивайтесь
и подсмотрите ответ.
Сегодняшние
участники:
undefined
Я — значение элемента, не существующего
в разреженном массиве.
NaN
[]
О:
массива) на неопределенность?
О: Это зависит от структуры кода. Если ваш код написан так, что
Да, есть — значение undefined относится к типу Undefined.
Почему? Вероятно, логика выглядит примерно так: это не объект,
свойство или переменная может не иметь значения при выполнении не число, не строка и не булевское значение... И вообще ничто
некоторого блока, то проверка на undefined даст вам возможность определенное. Так почему бы не назначить этой неопределенности
обработать эту ситуацию вместо того, чтобы выполнять вычисления «неопределенный» тип? Это одна из тех странностей JavaScript,
с неопределенными значениями. с которыми нужно просто смириться.
294 глава 7
типы, равенство, преобразования и все такое
В ЛАБОРАТОРИИ
В своей лаборатории мы любим разбирать, заглядывать «под капот»,
экспериментировать, подключать диагностические инструменты
и выяснять, что же происходит на самом деле. Сегодня, в ходе ис-
следования системы типов JavaScript, мы обнаружили маленький
диагностический инструмент для анализа переменных — typeof.
Наденьте лабораторный халат и защитные очки, заходите и присо-
единяйтесь к нам.
Оператор typeof встроен в JavaScript. Он используется для проверки
типа операнда (того, к чему применяется оператор). Пример:
console.log(typeof test1);
console.log(typeof test2);
console.log(typeof test3);
console.log(typeof test4);
console.log(typeof test5); Запишите здесь
console.log(typeof test6); результаты. Какие-
console.log(typeof test7); нибудь сюрпризы?
console.log(typeof test8);
console.log(typeof test9);
Снова в лабораторию
Стоп, мы забыли включить null в свои тестовые данные. Недостающий тест:
Консоль JavaScript
var test10 = null;
296 глава 7
типы, равенство, преобразования и все такое
Следует учитывать, что получение null не всегда означает, что что-то пошло
не так. Это может означать, что объект еще не существует и его нужно создать
или его нужно пропустить при обработке. Пользователь может открыть или
закрыть погодный виджет на сайте. Если виджет открыт, то в DOM существует
элемент <div> с идентификатором “weatherDiv”, а если закрыт — такого элемента
нет. Так польза от null становится очевидной:
исло
Ч рое не является числом
кото Хотите верьте, хотите нет, но существуют
числовые значения, которые невозможно
представить в JavaScript! В JavaScript эти
значения не выражаются, поэтому для них
используется специальное значение:
NaN
ript,
Легко написать команду JavaSc В JavaScript используется значе-
яетс я не-
результатом которой явл ние NaN (сокращение от “Not a Number”,
чис лов ое знач ени е.
определенное то есть «не число») для представления
числовых результатов... не имеющих
Несколько примеров: представления. Для примера возьмем
var a = 0/0; 0/0. Результат 0/0 не имеет собственного
представления на компьютере, поэтому
В математике это в JavaScript он представляется специ-
выражение не имеет альным значением NaN.
-
однозначного результа
та — так откуда его ВОЗМОЖНО, NaN —
возьмет JavaScript? САМОЕ СТРАННОЕ
ЗНАЧЕНИЕ В МИРЕ.
Оно не только пред-
var b = "food" * 1000; ставляет все числовые
значения, не имеющие
Мы не знаем, как собственного представления; это единст-
должен выгляд еть венное значение в JavaScript, не равное
результат, но это самому себе!
безусловно не число!
Да, вы поняли правильно. Если срав-
нить NaN с NaN, они не будут равны!
var c = Math.sqrt(-9);
NaN != NaN
Квадратный корень
из отрицательного
числа — это комплекс-
t
ное число, а в JavaScrip
та кие чис ла не им ею т
представления.
298 глава 7
типы, равенство, преобразования и все такое
Работа с NaN
Может показаться, что вам очень редко придется иметь дело со значени-
ем NaN, но если ваш код обрабатывает числовые данные, вы удивитесь,
как часто оно будет встречаться на вашем пути. Вероятно, самой частой
А!
операцией будет проверка числа на NaN. С учетом того, что вы узнали
о JavaScript, решение может показаться очевидным:
К
ИБ
if (myNum = = NaN) { Врод е бы долж
Но не работае
но работать... ОШ
myNum = 0; т.
}
Что вообще творится? NaN имеет числовой тип? Как то, что не является
числом, может иметь числовой тип? Спокойно, дышите глубже. Считай- Если у вас голова
те, что имя NaN просто выбрано неудачно. Вероятно, вместо «не число» не идет кругом,
стоило использовать что-то вроде «число, не имеющее представления» вероятно, вы чего-то
(хотя, конечно, красивого сокращения уже не получится). Лучше всего не поняли из этой книги.
рассматривать NaN именно так — как число, которое невозможно пред-
ставить (по крайней мере на компьютере).
В общем, ваш список странностей JavaScript расширяется.
Упражнение В этой главе мы рассматривали некоторые... ммм... интересные значения. А теперь рассмотрим
интересное поведение. Попробуйте добавить приведенный ниже код под элементом <script>
обычной веб-страницы и посмотрите, что будет выведено на консоль при загрузке страницы.
Причины вам пока неизвестны, но попробуйте хотя бы предположить, что при этом происходит.
Консоль JavaScript
if (99 = = "99") {
console.log("A number equals a string!"); > false
} else {
console.log("No way a number equals a string");
}
Здесь запишите
результат.
300 глава 7
типы, равенство, преобразования и все такое
if (testMe = = 99) {
// Происходит что-то хорошее
}
Тривиально, верно? Еще бы, что может быть проще? Однако воз-
можна и другая ситуация — более того, мы уже проделали нечто
подобное минимум один раз, хотя вы, возможно, и не заметили.
var x = 99;
if (testMe = = 99) { На этот раз строка = — это оператор присваивания.
// Происходит что-то сравнивается с числом. Он используется для изменения
хорошее значения переменной.
}
x = = 99
Что же произойдет при сравнении строки с числом? Все- = = — это оператор проверки
ленский хаос? Цепная реакция в компьютере? Беспорядки равенства. Он сравнивает одно
на улицах? значение с другим и выясняет,
равны ли они между собой.
Нет, JavaScript хватает ума понять, что в практическом кон-
тексте 99 и “99” почти не отличаются друг от друга. Но что
именно происходит «за кулисами», чтобы такое сравнение
работало? Давайте посмотрим.
302 глава 7
типы, равенство, преобразования и все такое
м.
1 = = true Сравниваем число с булевским значение
о 1.
Значение true преобразуется в числ
true
ением.
"1" = = true Строка сравнивается с булевским знач
true прео браз ует ся в числ о 1.
undefined = = null
true
К сожалению, 1 и 0 не равны.
false Получаем false.
304 глава 7
типы, равенство, преобразования и все такое
Я к своим
Как проверить строгое равенство сравнениям отношусь
гораздо строже.
== ===
"42" = = 42
true "42" = == 42
"0" = = 0 "0" = = = 0
306 глава 7
типы, равенство, преобразования и все такое
часто
Задаваемые
вопросы
В: Что произойдет, если сравнить число (например, 99) В: Существует ли оператор !==?
О:
со строкой, которая не преобразуется в число (“ninety-nine”)?
В: Как JavaScript преобразует строки в числа? В: Будут ли те же правила использоваться при сравнении,
О:
А если я попробую выполнить проверку “true” == true?
== ===
Смотрите, кто пришел — мистер Бескомпромисс-
ность. Учти, что, по мнению некоторых ведущих экс-
пертов JavaScript, разработчикам стоит исполь-
зовать меня, и только меня. Они считают, что
тебя лучше вообще убрать из языка.
А ты сравни, как часто встречаются == и === в коде
по всему миру. Боюсь, ты сильно отстаешь в по
пулярности. Даже сравнивать смешно.
Возможно, ты прав, но люди постепенно начи-
нают вникать в суть дела, и цифры меняются.
Вряд ли. Я предоставляю ценные услуги. Кому из
нас не приходится, скажем, сравнивать строку,
введенную пользователем, с числом? Но к полезной функциональности прилагаются
все правила, которые необходимо помнить про-
сто для использования ==. Не усложняйте код и
жизнь; используйте ===. А если вам потребуется
преобразовать ввод пользователя в число, для
этого существуют свои методы.
Ага, а еще ты каждый день ходил в начальную
школу пешком, по снегу и в гору — притом в обе
стороны... Сам подумай, зачем усложнять себе
Очень смешно. Нет ничего плохого в четкой
жизнь на ровном месте?
и строгой семантике сравнений. Иначе при-
ходится постоянно держать в голове правила,
и если вдруг забудешь о них — происходят самые
неожиданные и неприятные вещи.
Короче, я не только выполняю те же преобразова-
ния, что и ты, но еще и добавляю к ним полезные
преобразования типов. Каждый раз, когда я смотрю на твои правила,
меня начинает подташнивать. Чтобы сравнить
булевское значение с чем угодно, необходимо
сначала преобразовать его в число? Где логика?
А ты что будешь делать? Сразу сдашься, вернешь
false и отправишься домой?
308 глава 7
типы, равенство, преобразования и все такое
== ===
Нет, но в твоих сложных правилах легко за-
путаться.
Пока все работает. Посмотри, сколько кода уже
написано — причем часто его пишут програм-
мисты, как бы это сказать... рядового уровня.
Это хорошо, но страницы становятся все бо-
лее сложными и запутанными. Не пора ли по-
ступать так, как рекомендуют профессионалы?
В смысле — принять душ после одной из бесед
с тобой?
Нет, ограничиться использованием to ===. Код
становится более понятным, исчезает риск
неожиданных граничных ситуаций.
Хмм... А откупиться не думал? Я охотно прове-
ду свою жизнь на пляже, нежась на солнышке
с «Маргаритой» в руке.
Отлично! Если
кому-то не хватало
проблем — пожалуйста, два
оператора равенства. Какой из них
использовать?
Разработчики т
акже
называют опер
атор ===
(строгое равенс
тво)
оператором «т
ождест-
венности».
310 глава 7
типы, равенство, преобразования и все такое
=
что пытается выполнить преобразова-
ние типов, чтобы узнать, действительно
ли значения равны.
азуется
С вычитанием “10” преобр
var mini = "10" — 5; етс я 10 минус 5,
в число 10; получа
то ест ь 5.
312 глава 7
типы, равенство, преобразования и все такое
часто
Задаваемые
вопросы
В: Оператор + всегда интерпретируется как конкатенация, В: Это все? Или бывают другие преобразования?
если один из операндов является строкой?
В этом случае получается строка “3 pizzas”, а в этом: В результате переменной num присваивается значение 7. Функция
Number получает аргумент, и если возможно, преобразует его
var order = 1 + (2 + " pizzas");
в число. Если преобразовать аргумент в число не удается, Number
результатом будет строка “12 pizzas”. возвращает... вы не поверите... NaN.
Infinity - "1"
"42" + 42 “4242”
2 + "1 1"
99 + 101
"1" - "1"
Но вы еще ни слова
не сказали о том, как равенство
работает для объектов. И вообще, какие
два объекта можно считать равными?
314 глава 7
типы, равенство, преобразования и все такое
var1 var2
Не важно, что хранится
в этих объектах. Если
ссылки не одинаковые,
то и объекты не равны.
Две ссылки равны только в том случае, если они
ссылаются на один объект
Проверка равенства двух переменных, содержащих
ссылки на объекты, возвращает true только в том
случае, если две ссылки указывают на один объект.
if (var1 = = = var3) {
// Да это же один объект!
}
Эта ссылка...
Наконец-то —
две ссылки
на объ екты
равны.
var3 var1 var2
...равна этой ссылке. Они относятся
к одному объекту, а следовательно, равны!
function findCarInLot(car) {
for (var i = 0; i < lot.length; i++) {
if (car = = = lot[i]) {
return i;
}
}
return -1;
}
var chevy = {
make: "Chevy",
model: "Bel Air"
};
var taxi = {
make: "Webville Motors",
model: "Taxi"
};
var fiat1 = {
make: "Fiat",
model: "500"
};
var fiat2 = {
make: "Fiat",
model: "500" Эрл, владелец
}; стоянки.
Ваши ответы.
var lot = [chevy, taxi, fiat1, fiat2];
316 глава 7
типы, равенство, преобразования и все такое
if (0) {
// ... Проверяем 0?
}
Выполняем условную
проверку
для пустой строки. Чт
о будет?
Рискнете сделать ст
if ("") { авку?
// Этот код когда-нибудь будет выполнен? Принимаем ставки.
}
Секунду, мы используем NaN в булевском
выражении? И как это значение будет
if (NaN) { интерпретировано?
// Хм, а что NaN делает в логическом условии?
}
318 глава 7
типы, равенство, преобразования и все такое
var stolenDiamond = { };
if (stolenDiamond) {
console.log("You stole the diamond");
lies++;
}
var car = {
keysInPocket: null
};
if (car.keysInPocket) {
console.log("Uh oh, guess you stole the car!");
lies++;
}
if (car.emptyGasTank) {
console.log("You drove the car after you stole it!");
lies++;
}
var foundYouAtTheCrimeScene = [ ];
if (foundYouAtTheCrimeScene) {
console.log("A sure sign of guilt");
lies++;
}
if (foundYouAtTheCrimeScene[0]) {
console.log("Caught with a stolen item!");
lies++;
}
одного
var yourName = " "; Строка из
пробела.
if (yourName) {
console.log("Guess you lied about your name");
lies++;
}
return lies;
}
var numberOfLies = lieDetectorTest();
console.log("You told " + numberOfLies + " lies!");
if (numberOfLies >= 3) {
console.log("Guilty as charged");
}
Мозговой
штурм
Как вы думаете, что делает этот код? Не видите ли вы
в нем каких-то странностей — особенно с учетом того,
что вам известно о примитивных типах?
320 глава 7
типы, равенство, преобразования и все такое
Мы создаем три
примитивных строки
и присваиваем их переменным.
var name = "Jenny";
var phone = "867-5309";
var fact = "This is a prime number";
А здесь строки объединяются
посредством конкатенации
для создания еще одной прими-
var songName = phone + "/" + name; тивной строки.
ОБЪЕКТ СО СВЕРХВОЗМОЖНОСТЯМИ
Просто голова
идет кругом. Значит, строка
преобразуется туда-сюда между
примитивом и объектом? И как мне
уследить за всем этим?
часто
Задаваемые
вопросы
В: На всякий случай уточню: мне когда-
строковые операции, тогда в вашем рас-
поряжении мгновенно появляется объект
ством полезных свойств, как строки, поэтому
для них эта функциональность используется
нибудь придется следить за тем, где моя строки. намного реже, чем для строк. И помните: все
строка представляет собой примитив,
а где — объект? В: Имеется произвольная строка. Как
это происходит «за кулисами», так что вам
не придется активно следить за происхо-
322 глава 7
типы, равенство, преобразования и все такое
свойство length
символов в строке. Его удобно
Свойство length содержит количество
ки.
использовать при переборе символов стро Свойство length использу-
ется для последовательного
перебора символов строки.
";
var input = "jenny@wickedlysmart.com
i++) {
for(var i = 0; i < input.length;
if (input.charAt(i) = = = "@") {
index " + i);
console.log("There's an @ sign at
}
} Консоль JavaScript
А метод charAt получает There's an @ sign at index 5
символ с конкретным
индексом в строке.
метод charAt
до длины строки (минус 1) и возвращает
Метод charAt получает целое число от 0
в заданной позиции. Строку можно рассма-
строку с одним символом, находящимся
являются отдельные символы, а индексы
тривать как массив, элементами которого
). Если переданный индекс больше либо
начинаются с 0 (как и в обычных массивах
ая строка.
равен длине строки, возвращаетс я пуст
a b c de f
vaScript не суще-
Учтите, что в Ja у
ого типа, поэтом
ствует символьн ви де но вы х charAt(0)
charAt(5)
ются в
символы возвраща всего один символ. содержит “a
”. содержит “f”.
к, со де рж ащ их
стро
метод indexOf
о сим вола первого
ент и возвращает индекс первог
Метод получает строку-аргум
строке.
вхождения арг умента в своей
я которой
Строка, дл ться метод
ва
будет вызы
the hat"; indexOf.
var phrase = "the cat in
Мы хотим найти позицию первого
вхождения “cat" в исходной строке.
Of("cat");
var index = phrase.index
ex);
sitting at index " + ind
console.log("there's a cat
Консоль JavaScript
Возвращается индекс первого There's a cat sitting at index 4
вхождения “cat”.
редать второй
Также можно пе кс начальной
ин де
аргумент —
и по ис ка .
позици
e", 5);
index = phrase.indexOf("th
ex);
sitting at index " + ind
console.log("there's a the
Консоль JavaScript
g");
index = phrase.indexOf("do
ex);
sitting at index " + ind
console.log("there's a dog
Консоль JavaScript
Если строку найти не удалось, There's a dog sitting at index -1
метод возвращает -1.
324 глава 7
типы, равенство, преобразования и все такое
метод substring
Метод substring получает два инд
екса, после чего изв лекает и возв
заключенную меж ду ними строку. ращ ает
орой будет
Строка, для кот
tring.
вызываться subs
var data = "name|phone|address"
;
var val = data.substring(5, 10); Метод возвращает строку,
начинающуюся с индекса 5
console.log("Substring is " +
val); и заканчивающуюся индек-
сом 10 (не включая его).
Мы получаем новую строку,
Консоль JavaScript
состоящую из символов
с индексами от 5 до 10. Substring is phone
val = data.substring(5);
console.log("Substring is now Консоль JavaScript
" + val);
Substring is now phone|
address
метод split
ти по позиции
ани читель и разбивает строку на час
Метод split получает символ-огр
ограничителя.
ет
t использу
Метод spli ь для разбиения
; ел
var data = "name|phone|address" ограничит роки на части,
сх од н ой ст в массиве.
var vals = data.split("|");
и
ы е во зв р ащаются
котор
", vals);
console.log("Split array is
Строковый суп
toLowerCase
Возвращает строку,
ки
в которой все символы Находит подстро
верхнего регистра преобразу- ме ня ет их др уг ой
ются к нижнему регистру. replace и за
строкой.
lastIndexOf
Возвращает нову
ю строку, Соединяет строки
из которой удален dexOf, .
исходной строки.
а часть Аналогичен in
ди т по следнее
но нахо
м ес то at
slice
вхождение (в
первог о) . conc
326 глава 7
типы, равенство, преобразования и все такое
- 4 5 6 7 ”
“123
Брэд, ветеран сценарного программирования, и Ларри,
недавний выпускник, решили, что задача не так уж сложна. ет
й буд
д , к оторы о. Что-
Ларри, сидя за компьютером, подумал: «Что должен делать е ко ть ег
и ш ит верга жен
этот код? Проверить, что строка имеет достаточную длину, и нап ать или от ят, он дол де-
м и н
и
прин фон был п р 9, р з
а
что в середине стоит дефис и что все остальные символы
те л е и ц и фр 0–
бы сем
являются цифрами. Я могу воспользоваться свойством length ять из
и умею обращаться к символам строки методом charAt». состо дефисом.
х
ленны
Брэд откинулся на спинку кресла в кафе и принялся размыш-
лять над тем же вопросом: «Что должен делать этот код?»
Сначала он подумал: «Строка — это объект. Существует мно-
жество методов, которые я могу использовать для проверки
введенного телефона. Я воспользуюсь ими, и все сработает.
В конце концов, объект есть объект». Читайте дальше, и вы
узнаете, как Брэд и Ларри строили программы, а заодно по-
лучите ответ на главный вопрос: кому досталось кресло?
Кресло
В комнате Ларри
Ларри хочет написать код с использованием методов строк:
В комнате Брэда
Брэд написал код для проверки двух чисел и дефиса:
Брэд начинает так же, как Ларри...
«Формально Ларри был первым, потому что Брэду пришлось искать Мозговой
описания всех этих методов, — сказал Руководитель Проекта, —
но в спецификации кое-что изменилось. Конечно, это не создаст штурм
проблем для таких опытных программистов, как вы».
А вы не видите каких-нибудь
«Если бы мне давали монетку каждый раз, когда я это слышу», — ошибок, которые может выз-
подумал Ларри, хорошо знавший, что изменений спецификации без вать использование isNaN
проблем не бывает. «Но Брэд выглядит подозрительно спокойным. в программе Брэда?
Что бы это значило?» Тем не менее Ларри все еще полагает, что
способ Брэда — обычный выпендреж. И что в следующем раунде
он снова первым напишет код и победит.
иде:
лефо на в в
мер те
ите но
Получ
4 5 6 7 ” .
“12 3- , к от
будет
орый . Что-
Изменения в спецификации.
е код ергать его ен
и ш ит в
и нап ать или от н долж
р и н и м л п р инят, о которые
п б ы ,
ефон фр 0–9
бы тел из семи ци ефисом.
ть д
состоя азделяться
р
могут
328 глава 7
типы, равенство, преобразования и все такое
function validate(phoneNumber) {
if (phoneNumber.length > 8 ||
phoneNumber.length < 7) {
return false;
}
for (var i = 0; i < phoneNumber.length; i++) {
if (i = = = 3) {
if (phoneNumber.length = = = 8 &&
Ларри внес несколько
phoneNumber.charAt(i) != = '-') { изменений в логику. Кода
return false; немного, но разобраться
} else if (phoneNumber.length = = = 7 && в нем стало посложнее.
isNaN(phoneNumber.charAt(i))) {
return false;
}
} else if (isNaN(phoneNumber.charAt(i))) {
return false;
}
}
return true;
}
function validate(phoneNumber) {
return phoneNumber.match(/^\d{3}-?\d{4}$/);
Код Эми.
}
330 глава 7
типы, равенство, преобразования и все такое
Снова в лаборатории
Ученые из лаборатории продолжают исследовать JavaScript с помощью
оператора typeof, и им удалось обнаружить нечто интересное. В про-
цессе исследований они открыли новый оператор instanceof, с которым
наконец-то состоится научный прорыв. Наденьте лабораторный халат
и защитные очки и посмотрите, удастся ли вам расшифровать этот код
JavaScript и результаты. Предупреждение: это определенно самый
странный код из всего, что вы видели ранее.
Консоль JavaScript
Порядок, и мы видим
экземпляр...
результат
сверьте свой о что это все
Обязательно е гл ав ы. Н
в конц -
с ответами
т на эт от вопрос вы полу
ит ? О т ве ес ли вы ещ е
знач ка,
ру глав. А по
чите через па вы постепенно станови-
не заметили
— JavaScript.
ут ым пр ог раммистом
тесь кр а!
рь езная тем
Это очень се
КЛЮЧЕВЫЕ
МОМЕНТЫ
В JavaScript существуют две категории типов: Если два операнда имеют разные типы, оператор
примитивы и объекты. Любое значение, кото- строгого равенства (===) возвращает false.
рое не относится к примитивному типу, является
объектом. Используйте ===, если хотите избежать проверки
типов. Однако помните, что преобразования типа
Примитивы: числа, строки, булевские значения, == иногда бывают полезны.
null и undefined. Все остальное — объекты.
Преобразование типа также используется с дру-
undefined означает, что переменная (или гими операторами, например арифметическими
свойство, или элемент массива) еще не была и оператором конкатенации строк.
инициализирована значением.
В JavaScript существуют пять псевдоложных
null означает «нет объекта». значений: undefined, null, 0, “” (пустая строка)
NaN означает «не число», хотя правильнее рас- и false. Все остальные значения являются псев-
сматривать NaN как число, которое не может доистинными.
быть представлено в JavaScript. NaN относится Строки иногда ведут себя как объекты. Если ис-
к числовому типу. пользовать свойство или метод для примитивной
Значение NaN никогда не равно любому другому строки, JavaScript временно преобразует строку
значению, даже самому себе, поэтому для про- в объект, использует свойство, а затем преобра-
верки на NaN следует использовать функцию зует результат обратно в примитивную строку.
isNaN. Это происходит автоматически, так что вам даже
не придется задумываться о преобразовании.
Два значения проверяются на равенство опера-
торами == или ===. Объекты строк поддерживают много методов,
полезных для операций со строками.
Если два операнда имеют разные типы, оператор
проверки равенства (==) пытается преобразовать Два объекта равны только в том случае, если
один из операндов к другому типу перед выпол- переменные, в которых хранятся ссылки, ука-
нением проверки. зывают на один объект.
332 глава 7
типы, равенство, преобразования и все такое
т о я ?
К
Группа значений JavaScript и незваных гостей, облачившись в маска-
радные костюмы, развлекаются игрой «Кто я?». Они дают подсказки,
а вы должны угадать их по тому, что они говорят о себе. Предполага-
ется, что участники всегда говорят о себе правду. Соедините линией
каждое высказывание с именем соответствующего участника. Мы уже
провели одну линию за вас.
Ниже приведено наше решение.
Сегодняшние
участники:
undefined
Я — значение элемента, не существующего
в разреженном массиве.
NaN
[]
ни е
В ЛАБОРАТОРИИ ше
ре
В своей лаборатории мы любим разбирать, заглядывать «под ка-
пот», экспериментировать, подключать диагностические инстру-
менты и выяснять, что же происходит на самом деле. Сегодня
в ходе исследования системы типов JavaScript мы обнаружили
маленький диагностический инструмент для анализа перемен-
ных — typeof. Наденьте лабораторный халат и защитные очки,
заходите и присоединяйтесь к нам.
Оператор typeof встроен в JavaScript. Он используется для про-
верки типа операнда. Пример:
Оператор typeof
var subject = "Just a string";
получает операнд
и определяет его тип.
var probe = typeof subject;
Консоль JavaScript
console.log(probe);
string
В данном случае выводится тип “string”. Обра-
тите внимание: для представления типов typeof
использует строки вида “string”, “boolean”,
“number”, “object”, “undefined” и т. д.
334 глава 7
типы, равенство, преобразования и все такое
е
снова в лаборатории ени
реш
Стоп, мы забыли включить null в тестовые данные.
Недостающий тестовый пример:
Консоль JavaScript
var test10 = null;
object
console.log(typeof test10); Наш результат.
В этой главе мы рассматривали некоторые... ммм... интересные значения. А теперь рассмотрим интересное
поведение. Попробуйте добавить приведенный ниже код под элементом <script> обычной веб-страницы
и посмотрите, что будет выведено на консоль при загрузке страницы. Причины вам пока неизвестны,
но попробуйте хотя бы предположить, что при этом происходит.
Консоль JavaScript
if (99 = = "99") {
console.log("A number equals a string!"); A number equals a str
ing!
} else {
console.log("No way a number equals a string");
}
== ===
"42" = = 42 true false "42" = == 42
=
выполнить преобразование типов, чтобы узнать,
действительно ли значения равны.
Оба оператора
работают при
== Сравнивает значения и проверяет их на равен-
ство. При этом значения, относящиеся к разным
сравнении двух
типам, попросту не рассматриваются.
объектов!
== =
Присваивает значение переменной.
Такого операт
т ву
ора
ет .
== == Сравнивает ссылки на объекты и возвращает true,
не су щ ес
если они равны, и false в противном случае.
336 глава 7
типы, равенство, преобразования и все такое
ся в1,
“1” преобразует
— 1 да ет
Infinity - "1" Infinity а Infinity
In fin ity .
"42" + 42 “4242”
"1" - "1" 0
Сначала выполняется
console.log("Result: " + 10/2) “Result: 5” деление 10/2, затем
результат присо-
единяется к строке
“Result: ”
3 + " bananas " + 2 + " apples" “3 bananas 2 apples”
338 глава 7
типы, равенство, преобразования и все такое
function findCarInLot(car) {
for (var i = 0; i < lot.length; i++) {
if (car = = = lot[i]) {
return i;
}
}
return -1;
}
var chevy = {
make: "Chevy",
model: "Bel Air"
};
var taxi = {
make: "Webville Motors",
model: "Taxi"
};
var fiat1 = {
make: "Fiat",
model: "500"
};
var fiat2 = {
make: "Fiat",
model: "500"
}; Эрл,
Наши ответы. владелец
стоянки.
var lot = [chevy, taxi, fiat1, fiat2];
ни е
Снова в лабораториире ш е
Ученые из лаборатории продолжают исследовать JavaScript с помо-
щью оператора typeof, и им удалось обнаружить нечто интересное.
В процессе исследований они открыли новый оператор instanceof,
с которым наконец-то состоится научный прорыв. Наденьте лабо-
раторный халат и защитные очки и посмотрите, удастся ли вам рас-
шифровать этот код JavaScript и результаты. Предупреждение: это
определенно самый странный код из всего, что вы видели ранее.
Консоль JavaScript
Порядок, и мы видим
экземпляр... quack quack toy работает как
т на этот объект... мы можем
значит? Отве
И что же все па ру глав. вызвать метод.
чите через
вопрос вы полу еще не заметили —
вы
А пока, если становитесь
крутым
ос т еп ен но о очень object И выводится
вы п Sc ri pt . Эт
ом Java тип object.
программист
а!
серь езная тем Но программа сообща-
true
ет, что объект «яв-
ляется экземпляром»
Duck... Что бы это
значило? Хм.
Наши результаты.
340 глава 7
8 все вместе
Построение приложения
Меня восхищает, как
из нескольких разнородных Эй! Ты не туда
компонентов тебе удается создать смотришь! Шарлотка
нечто по-настоящему аппетитное. выше.
Данные о коли-
честве прома-
хов и попаданий
выводятся над
игровым полем.
Эффектное игро-
вое поле рисуется
на веб-странице
вместе с сеткой
и фоном.
Теперь от ваших
выстрелов скры-
ваются целых
три корабля.
Координаты выстрела
вводятся прямо
на странице.
Мозговой
штурм Забудьте на минуту о JavaScript... Взгляните на прототип. Если
сосредоточиться на структуре и визуальном представлении стра-
ницы, как бы вы создали ее с использованием HTML и CSS?
342 глава 8
все вместе
чать по
В ячейках таблицы будут
о ди м о е можно ска om/hfjs
размещаться корабли Все необх lysmart.c
p://wicked
и маркеры промахов MISS. адресу htt
344 глава 8
все вместе
Тест-драйв Введите приведенный выше код (или скачайте весь код к книге по адресу http://wickedlysmart.com/hfjs)
в файл battleship.html, загрузите его в браузере. Наш результат показан ниже.
Каждая клетка
Каждый идентифи- в сетке соответст-
катор описывает вует элементу <td>
положение клетки в таблице.
в сетке. Например,
левой верхней ячейке
присваивается иден- бук-
тификатор “00”, В игре строки обозначаются
B, C и т. д.), но в наших
а правой нижней — вам и (A,
ет
идентификатор “66”. идентификаторах буква буд
еня тьс я соо тве тст вую щей
зам
вая
цифрой (0, 1, 2 и т. д.). Пер
стр ока та бли цы обо зна чае т-
Этой ячейке вует
ся индексом 0 (соответст
присваивается ), а пос лед няя — 6 (соо т-
бук ве A
идентификатор “06”. Ячейка с идентифи- ветствует бук ве G).
катором “66”.
Разметка HTML-таблицы. Добавьте ее между тегами <div>:
346 глава 8
все вместе
Мы будем опо-
вещать игроков
о потопленных
кораблях, выводя
сообщения в левом
верхнем углу.
А здесь игроки
будут вводить
координаты
выстрелов.
body {
Элемент <div> “board”
background-color: black; использует относи-
} тельное позициониро-
div#board { вание, а все вложен-
ные элементы будут
position: relative; позиционироваться
width: 1024px; относительно этого
элемента <div>.
height: 863px;
margin: auto;
background: url("board.jpg") no-repeat;
} КЛЮЧЕВЫЕ
div#messageArea { МОМЕНТЫ
Область сооб-
position: absolute;
щений разме-
top: 0px; щается в левом
верхнем углу Свойство “position: relative” позициониру-
left: 0px;
игрового поля. ет элемент относительно его нормаль-
color: rgb(83, 175, 19);
ной позиции в потоке страницы.
}
Свойство “position: absolute” позициони-
рует элемент относительно позиции его
Элемент <div> области сообщений вложен
в элемент <div> игрового поля, поэтому ближайшего родителя.
его позиция задается относительно по-
Свойства top и left задают величину
следнего. Итак, он будет смещен на 0px
по вертикали и на 0px по горизонтали смещения элемента (в пикселах) от-
относительно левого верхнего угла эле- носительно его позиции по умолчанию.
мента <div> игрового поля.
348 глава 8
все вместе
Мы также можем разместить таблицу и форму в элементе <div> “board”, снова используя
абсолютное позиционирование для размещения этих элементов в точности там, где
они должны находиться. Остаток кода CSS выглядит так:
body {
background-color: black;
}
div#board {
position: relative;
width: 1024px;
height: 863px;
margin: auto;
background: url("board.jpg") no-repeat;
}
div#messageArea {
position: absolute;
top: 0px;
left: 0px;
color: rgb(83, 175, 19);
ела
} на 173 пикс к-
т <t ab le > смещается и на 9 8 пи
table { Элемен го края
али от лево игро-
по горизонт икали от верхнего края
position: absolute; ве рт ра вн ив ается
селов по ом у т аблица вы
эт
left: 173px; вого поля, по ражения.
онового изоб
top: 98px; по сетке ф
border-spacing: 0px;
ет четко
} Каждый элемент <td> име
еде лен ную шир ину и выс оту, так
опр
td { бли цы выр авн иваются
что ячейки та
width: 94px; по кле тк ам сет ки.
height: 94px;
ображаться в правом
} Элемент <form> будет от
я. Он немного скры-
form { нижнем углу игрового пол
вни зу спр ава , но э то не страшно
вает числа
position: absolute; ). Так же элементу <form>
(вы все равно их знаете
bottom: 0px; при ятн ый зел ены й цвет, соответ-
назначается жения.
го изо бра
right: 0px; ствующий цвету фоново
padding: 15px;
background-color: rgb(83, 175, 19);
}
form input { Наконец, к двум элементам
<input> применяются стили,
background-color: rgb(152, 207, 113);
чтобы они соответствовали
border-color: rgb(83, 175, 19); оформлению игрового поля, —
font-size: 1em; и работа закончена!
}
Тест-драйв Мы приближаемся к очередной контрольной точке. Соберите в файле HTML всю разметку
HTML и CSS, после чего перезагрузите страницу в браузере. Вот что вы должны увидеть:
350 глава 8
все вместе
Прежде чем писать код размещения попаданий и промахов на игровом поле, давайте немного потренируемся
в работе с CSS. Добавьте в свою разметку классы “hit” и “miss”, соответствующие приведенным ниже действиям
игрока. Обязательно проверьте свой ответ!
о буквы
Напоминаем, чт ать
аз ов
нужно преобр
Корабль 1: A6, B6, C6 ... G = 6.
в цифры: A = 0,
Корабль 2: C4, D4, E4
Корабль 3: B0, B1, B2
352 глава 8
все вместе
Представ-
ление
Модель
Здесь выводятся
сообщения типа
«ПОПАЛ!», «Промах-
нулся», «Ты потопил
мой корабль!»
Маркер MISS
на сетке.
Изображение корабля
на сетке.
Запишите здесь
свои методы!
};
354 глава 8
все вместе
var view = {
displayMessage: function(msg) {
Начнем отсюда.
},
displayHit: function(location) {
},
displayMiss: function(location) {
}
};
Реализация displayMessage
Возвращаемся к написанию кода displayMessage. Напомним, что этот код должен:
Использовать DOM для получения элемента с иден-
тификатором “messageArea”.
Задавать свойству innerHTML элемента сообщение,
переданное методу displayMessage.
356 глава 8
все вместе
Следующим шагом станет добавление класса “hit” к элементу ячейки. Для этого
мы можем воспользоваться методом setAttribute:
displayHit: function(location) {
var cell = document.getElementById(location);
cell.setAttribute("class", "hit"); Элементу назначается класс “hit”.
При этом в ячейке таблицы немед-
}, ленно появляется изображение корабля.
Добавим этот код в объект представления, а также напишем аналогичный метод displayMiss:
var view = {
displayMessage: function(msg) {
var messageArea = document.getElementById("messageArea");
messageArea.innerHTML = msg;
Идентификатор, созданный
},
по введенным пользователем
координатам, используется для
а.
получения обновляемого элемент
displayHit: function(location) {
var cell = document.getElementById(location);
cell.setAttribute("class", "hit");
}, Этому элементу назначается
класс “hit”.
displayMiss: function(location) {
var cell = document.getElementById(location);
cell.setAttribute("class", "miss");
} То же самое делается в методе displayMiss,
только элементу назначается класс “miss”,
};
отвечающий за отображение промаха
на игровом поле.
Не забудьте добавить код методов displayHit и displayMiss в файл battleship.js.
358 глава 8
все вместе
Следующий тест-драйв...
Давайте проверим, как работает этот код, прежде чем двигать-
ся дальше... Мы возьмем выстрелы из упражнения «Учебные
стрельбы» и реализуем их в программном коде. Вот как выглядит
последовательность, которую мы хотим реализовать:
“A0”
it
view.displayMiss("00"); Напомним, что методы displayH
“D4” yMi ss пол учаю т коор дина ты
view.displayHit("34"); и displa
е из ком-
view.displayMiss("55"); выстрела, уже преобразованны
“F5” “бук ва+ц ифр а” в ст року из двух
бинации
view.displayHit("12"); ующ ую иден тиф и-
“B2” цифр, соответств
view.displayMiss("25"); одно й из я чеек таб лицы .
катору
“C5”
view.displayHit("26");
“C6”
Модель
Разобравшись с объектом представления, перейдем к модели. В объекте модели хранится текущее состояние
игры. Модель также часто содержит логику, связанную с изменениями состояния. В нашем случае состояние
включает позиции кораблей, координаты попаданий и счетчик потопленных кораблей. Пока вся необхо-
димая логика ограничится проверкой того, попал ли выстрел игрока в корабль, и пометкой попадания.
Объект модели должен выглядеть примерно так:
view.displayHit(“10”)
Модель Представ-
Модель оповещает представ- ление
ление об изменении состоян
ия,
чтобы представление могло
обновить игровое поле.
360 глава 8
все вместе
Другой корабль E
Необходимо как-то
стоит на клет- отслеживать попада-
ках D2–D4. F ния. Корабли состо-
ят из трех клеток,
G Корабль 3 HIT поэтому для каждого
судна нужно хранить
0 1 2 3 4 5 6 до трех попаданий.
Третий корабль G3-G5.
Использовать девять переменных (по ана- Использовать три разных массива, по одному
логии с тем, как мы представляли позиции для каждого корабля. Каждый массив содер-
кораблей в главе 2). жит данные трех клеток.
Использовать массив с элементами, представ- Использовать объект с именем ship и тремя
ляющими клетки игрового поля (49 элемен- свойствами (для каждой из трех клеток). Объ-
тов). В элементе каждой клетки, содержащей екты ship хранятся в массиве с именем ships.
часть корабля, хранится номер корабля.
______________________________________
Использовать массив для хранения всех девя- ______________________________________
ти позиций. В элементах 0–2 хранятся данные ______________________________________
первого корабля, в элементах 3–5 — данные
второго, и т. д. Или запишите
собственный вариант.
var ship1 = { locations: ["10", "20", "30"], hits: ["", "", ""] };
var ship2 = { locations: ["32", "33", "34"], hits: ["", "", ""] };
var ship3 = { locations: ["63", "64", "65"], hits: ["", "", "hit"] };
362 глава 8
все вместе
Развлечения с магнитами
Расставьте на игровом поле магниты с маркерами попаданий и промахов для
следующей последовательности ходов и структуры данных кораблей. Удалось
ли игроку потопить все корабли? Мы сделали первый ход за вас.
Координаты выстрелов:
Реализуйте эти ходы
A6, B3, C4, D1, B0, D4, F0, A1, C6, B1, B2, E4, B6 на игровом поле.
Структура
var ships = [{ locations: ["06", "16", "26"], hits: ["hit", "", ""] }, данных. После
серии выстрелов
{ locations: ["24", "34", "44"], hits: ["", "", ""] }, могут появиться
{ locations: ["10", "11", "12"], hits: ["", "", ""] }]; новые маркеры
попаданий.
var ships = [{ locations: ["31", "41", "51"], hits: ["", "", ""] },
{ locations: ["14", "24", "34"], hits: ["", "hit", ""] },
{ locations: ["00", "01", "02"], hits: ["hit", "", ""] }];
Игрок стреляет по клетке "D4", попадет ли он в корабль?______ Если да, то в какой? __________
Игрок стреляет по клетке "B3", попадет ли он в корабль?______ Если да, то в какой? __________
364 глава 8
все вместе
Метод fire преобразует выстрел игрока в промах или попадание. numShips: количество
Мы знаем, что за маркеры промахов и попаданий отвечает объект кораблей в игре.
представления view, но fire должен предоставить игровую логи- ships: позиции кораб
ку определения результата выстрела (промах или попадание). лей и координаты
попаданий.
Определить, попал ли выстрел в корабль, несложно: shipsSunk: коли-
Модель чество потоплен-
Проверить каждый корабль и узнать, занимает ли он ных кораблей.
указанную клетку. shipLength: длина
Если клетка присутствует в списке позиций, значит, вы- каждого корабля
(в клетках).
стрел попал в цель. Программа помечает соответствующий
элемент массива hits (и сообщает представлению о попада- fire: метод для выполне-
нии). Метод возвращает true — признак попадания. ния выстрела и проверки
результата (промах или
Если указанная клетка не занята кораблем, значит, игрок промахнулся. попадание).
Мы сообщаем об этом представлению и возвращаем false.
Метод fire должен определять, не был ли корабль потоплен, но этим мы займемся чуть позже.
366 глава 8
все вместе
Проверка попаданий
Итак, при каждой итерации цикла необходимо проверить, присутствует ли
указанная клетка в массиве locations объекта ship:
Последовательно перебираем корабли.
for (var i = 0; i < this.numShips; i++) {
var ship = this.ships[i]; Получаем массив клеток, занимаемых
кораблем. Стоит напомнить, что
locations = ship.locations;
это свойство корабля, в котором
хранится массив.
Здесь должен находиться код, который
} проверяет, присутствует ли указанна
я
клетка в массиве locations корабля.
Ситуация такова: имеется строка guess с координатами клетки, которая ищется в массиве
locations. Если значение guess встречается в массиве, значит, выстрел попал в корабль:
Мы должны определить, совпадает ли
guess = "16"; значение в guess с одним из значений
locations = ["06", "16", "26"]; из массива locations объекта корабля.
В принципе, можно было бы написать еще один цикл, который перебирает все элементы
массива locations и сравнивает каждый элемент с guess; если значения совпадают, значит,
выстрел попал в цель.
Но существует и другое, более простое решение:
сиве указанное
var index = locations.indexOf(guess); Метод indexOf ищет в мас
ие и возв ращ ает его инд екс (или -1,
значен
сут ств ует в массиве).
если значение от
fire: function(guess) {
for (var i = 0; i < this.numShips; i++) { Для каждого корабля...
var ship = this.ships[i];
Если координаты клетки присут-
var locations = ship.locations;
ствуют в массиве locations, значит,
var index = locations.indexOf(guess); выстрел попал в цель.
Это отличное начало для работы над объектом модели. Осталось сделать еще
пару вещей: определить, был ли корабль потоплен, и сообщить представлению
об изменении модели, чтобы оно могло обновить изображение. Посмотрим,
как это сделать…
368 глава 8
все вместе
Пожалуй, этот код получился слишком длинным. Почему? Некоторые ссылки можно сократить
посредством сцепления — ссылки на объекты объединяются в цепочки, позволяющие избежать
создания временных переменных (таких, как переменная locations в приведенном коде).
Почему переменная locations названа временной? Потому что она используется только для
промежуточного хранения массива ship.locations, чтобы мы могли вызвать метод indexOf
для получения индекса guess. Ни для чего больше переменная locations в этом методе не
используется. Сцепление позволяет нам избавиться от временной переменной locations:
ship.locations.indexOf(guess)
1 Получаем 2 Объект обладает свой- 3 И поддерживает
объ ект ством locations, кото- метод indexOf.
ship. рое является массивом.
fire: function(guess) {
for (var i = 0; i < this.numShips; i++) {
var ship = this.ships[i];
var index = ship.locations.indexOf(guess);
if (index >= 0) {
ship.hits[index] = "hit";
Мы добавим проверку здесь,
if (this.isSunk(ship)) { после того как будем точ-
но знать, что выстрел попал
this.shipsSunk++; в корабль. Если корабль по-
} топлен, то мы увеличиваем
счетчик потопленных кораблей
return true; в свойстве shipsSunk модели.
}
}
return false;
Новый метод isSunk добавляется сразу же после
}, fire. И не забудьте, что все свойства и методы
isSunk: function(ship) { ... } модели должны разделяться запятыми!
370 глава 8
все вместе
Взаимодействие с представлением
Вот, собственно, и все, что нужно сказать об объекте модели. Модель содержит состо-
яние игры и логику проверки попаданий и промахов. Не хватает только кода, который
бы оповещал представление о попаданиях или промахах в модели. Давайте напишем его:
ый код
Мы приводим полн
var model = { ли , чт обы вы
объекта моде
boardSize: 7, ок ин ут ь взг ля дом всю
могли
numShips: 3, картину.
shipsSunk: 0,
shipLength: 3,
ships: [ { locations: ["06", "16", "26"], hits: ["", "", ""] },
{ locations: ["24", "34", "44"], hits: ["", "", ""] },
{ locations: ["10", "11", "12"], hits: ["", "", ""] } ],
fire: function(guess) {
for (var i = 0; i < this.numShips; i++) {
var ship = this.ships[i];
var index = ship.locations.indexOf(guess);
вление о том,
if (index >= 0) { Оповещаем предста едует выве-
gu ess сл
ship.hits[index] = "hit"; что в клетке
ния.
сти маркер попада
view.displayHit(guess);
view.displayMessage("HIT!"); И приказываем представлению
вывести сообщение “HIT!”.
if (this.isSunk(ship)) {
view.displayMessage("You sank my battleship!");
this.shipsSunk++;
Сообщаем игроку, что
} он потопил корабль!
return true;
}
влению, что
} Сообщаем предста
еду ет вывести
в клетке guess сл
view.displayMiss(guess); пр ом ах а.
маркер
view.displayMessage("You missed.");
И приказываем представлению
return false; вывести сообщение о промахе.
},
isSunk: function(ship) {
Методы объекта представления
for (var i = 0; i < this.shipLength; i++) { добавляют класс “hit” или “miss”
if (ship.hits[i] != = "hit") { к элементу с идентификатором,
return false; содержащимся в guess. Таким
образом, представление преобра-
}
зует «попадания» из массива hits
} в разметку HTML. Не забывайте,
return true; что HTML-«попадания» нужны
} для отображения информации,
а «попадания» в модели представ-
}; ляют фактическое состояние.
Тест-драйв Добавьте весь код модели в файл battleship.js. Протестируйте его методом fire модели, переда-
вая строку и столбец из guess. Позиции кораблей до сих пор жестко фиксированы, поэтому вам
будет легко их подбить. Добавьте собственные выстрелы (несколько дополнительных промахов).
(Чтобы увидеть нашу версию кода, загрузите файл battleship_tester.js.)
model.fire("53");
Чтобы получить
такие же резуль- model.fire("06");
таты, вам при- model.fire("16");
дется удалить или model.fire("26");
закомментиро-
вать предыдущий
код тестирования. model.fire("34");
В файле battleship_ model.fire("24");
tester.js показано,
model.fire("44");
как это делается.
model.fire("12");
model.fire("11");
model.fire("10");
часто
Задаваемые
вопросы
В: Объединение команд посредством сцепления лучше серии О: В общем-то на любую. Конечно, на практике слишком боль-
раздельных команд? шая глубина вложения маловероятна (если в вашей программе
О:
строкам, это нормально. А если вы применяете сцепление, сле-
дите за тем, чтобы цепочки не были слишком длинными; их будет Свойство boardSize и другие свойства модели будут использо-
труднее читать и понять. ваться в будущем коде. Модель отвечает за управление состоянием
372 глава 8
все вместе
Реализация контроллера
После завершения работы над представлением и моделью можно переходить к реализации
контроллера. На верхнем уровне контроллер связывает все остальные компоненты, получая
координаты выстрела guess, обрабатывая их и передавая модели. Кроме того, он отслежи-
вает некоторые административные подробности — скажем, текущее количество выстрелов
и текущие достижения игрока. В этом контроллер зависит от модели, поддерживающей
состояние игры, и от представления, обеспечивающего отображение информации.
Если говорить конкретнее, контроллеру поручаются следующие обязанности:
Обработка выстрела
Это очень полезный
Контроллер должен получить координаты выстрела, введенные пользо- прием. Сосредоточь-
вателем, проверить их и передать объекту модели. Но откуда он получит тесь на требованиях
данные? Не беспокойтесь, скоро мы ответим и на этот вопрос. А пока будем к конкретному коду.
Попытки охватить
предполагать, что в коде в какой-то момент вызывается метод processGuess
всю задачу сразу
контроллера, которому передается строка в следующем формате: часто оказываются
менее эффективными.
"A3" Формат выстрелов «Морского боя»
вам хорошо известен: буква,
за которой следует цифра.
Конечно, созна-
Теперь после получения координат в этом формате (алфавитно-цифровая тельный игрок
комбинация вида «A3») данные необходимо преобразовать в формат, понят- никогда не вве-
дет некоррект-
ный модели (комбинация из двух цифр, например «03»). Высокоуровневая ные данные, верно?
схема преобразования входных данных в цифровой формат выглядит так: Если бы! Лучше
явно проверить
Допустим, мы получаем полученные данные
строку в формате на правильность.
“буква+цифра”:
"A3"
374 глава 8
все вместе
Планирование кода...
Вместо того чтобы размещать весь код обработки дан-
ных в методе processGuess, мы напишем небольшую
вспомогательную функцию (которую, если потребуется,
можно будет использовать повторно). Эта функция будет
называться parseGuess.
Прежде чем переходить к написанию кода, давайте по-
следовательно разберем, как должна работать функция:
Получить
1 данные
от игрока
Игрок вводит координаты в класси-
1 ческом стиле «Морского боя»: буква,
за которой следует цифра. нет
Данные
2 действи
тельны?
Данные проверяются на действитель-
2 ность (не null, не слишком короткие да
и не слишком длинные).
3 Преобразовать
букву в цифру
да
Проверка второй цифры (также
5 в диапазоне от 0 до 6).
Вторая
нет цифра
5 действи
Если хотя бы одна проверка завер-
6 шилась неудачей, вернуть null. В про-
тельна?
Вернуть Создать
null и вернуть
6 строку
376 глава 8
все вместе
function parseGuess(guess) {
var alphabet = ["A", "B", "C", "D", "E", "F", "G"];
Мозговой
штурм
Вместо того чтобы жестко фиксировать 6 как максимальное значение
строки или столбца игрового поля, мы используем свойство boardSize
модели. Как вы думаете, какие преимущества это решение принесет
в долгосрочной перспективе?
function parseGuess(guess) {
var alphabet = ["A", "B", "C", "D", "E", "F", "G"];
console.log(parseGuess("A0"));
console.log(parseGuess("B6")); Консоль JavaScript
console.log(parseGuess("G3")); 00
console.log(parseGuess("H0"));
16
console.log(parseGuess("A7"));
63
Перезагрузите battleship.html и проследите за тем, чтобы окно консоли null
было открыто. На консоли должны появиться результаты вызовов
parseGuess и, возможно, пара оповещений. null
378 глава 8
все вместе
var controller = {
guesses: 0,
Метод parseGuess будет
использоваться для про-
processGuess: function(guess) { верки введенных данных.
var location = parseGuess(guess);
if (location) {
Если метод не возвращает null, Помните: null явля-
значит, был получен действи- ется псевдоистинным
} тельный объект location. значением.
} Здесь размещается остальной
код контроллера.
};
var controller = {
guesses: 0, esses++
Команда this.gu
ув ел ич ив ает
просто
ач ен ие св ой ст ва
processGuess: function(guess) { зн
на рабо-
Если пользователь guesses на 1. О
ак же,
var location = parseGuess(guess); ввел правильные ко- тает точно т
ординаты, счетчик в ци кл ах for.
if (location) { как i++
выстрелов увеличива-
this.guesses++; ется на 1.
var hit = model.fire(location); Также заметьте, что
} при вводе недействи-
Затем комбинация строки и столбца тельных координат мы
} передается методу fire. Напомним, не наказываем игрока
}; что метод fire возвращает true при и не включаем эту
попадании в корабль. попытку в подсчет.
Игра окончена?
Осталось лишь определить, когда игра закончится. Как это сделать? Мы знаем, что для
этого должны быть потоплены все три корабля. Каждый раз, когда выстрел игрока попадает
в цель, мы будем проверять, потоплены ли три корабля, по свойству model.shipsSunk.
Мы немного обобщим эту проверку, и вместо того чтобы сравнивать с числом 3, будем
использовать для сравнения свойство numShips модели. Возможно, позднее вы захотите
перейти на другое количество кораблей (скажем, 2 или 4), и тогда вам не придется пере-
делывать код, чтобы он правильно работал.
var controller = {
guesses: 0,
Если выстрел попал в цель,
а количество потопленных
processGuess: function(guess) { кораблей равно количеству
var location = parseGuess(guess); кораблей в игре, выводится
сообщение о том, что все
if (location) { корабли потоплены.
this.guesses++;
var hit = model.fire(location);
if (hit && model.shipsSunk = = = model.numShips) {
view.displayMessage("You sank all my battleships, in " +
this.guesses + " guesses");
}
Выводим общее количество
выстре-
} лов, которое потребовалос
ь игроку
} для того, чтобы потопить
корабли.
Свойство guesses является
}; свойством
объ екта this, то есть контр
оллера.
380 глава 8
все вместе
Тест-драйв Включите весь код контроллера в файл battleship.js и добавьте приведенные вызовы
функций для тестирования контроллера. Перезагрузите battleship.html и обратите внима-
ние на маркеры промахов и попаданий. Они в правильных местах? (Загрузите страницу
battleship_tester.js, чтобы увидеть нашу версию.)
Вам придется удалить или закомме
нтировать предыдущий
тестовый код, чтобы получить пока
занные результаты.
В файле battleship_tester.js показано,
как это делается.
controller.processGuess("A0");
controller.processGuess("A6");
controller.processGuess("B6");
controller.processGuess("C6");
controller.processGuess("C4");
controller.processGuess("D4");
controller.processGuess("E4");
controller.processGuess("B0");
controller.processGuess("B1");
controller.processGuess("B2");
Мозговой
штурм
Мы сообщаем игроку о завершении игры в области сообщений,
когда он потопит все три корабля. Однако после этого игрок все
равно может вводить координаты. Как сделать так, чтобы игрок
не мог продолжать стрельбу после того, как все корабли будут
потоплены?
1
A0
”
2
“
3
function handleFireButton() {
// Получение координат от формы
// и передача их контроллеру.
}
382 глава 8
все вместе
function handleFireButton() {
var guessInput = document.getElementById("guessInput");
var guess = guessInput.value; Координаты выстрела передаются
controller.processGuess(guess); контроллеру, а дальше все должно
заработать как по волшебству!
Это не обычный тест-драйв — наконец-то все готово для полноценной игры! Убедитесь
Тест-драйв в том, что весь код добавлен в файл battleship.js, и перезагрузите battleship.html в бра-
узере. Так как позиции кораблей жестко фиксированы, победить будет нетрудно. Ниже
приведены координаты выигрышных выстрелов, но вам следует провести более полное
тестирование кода. Дополните серию промахами и недействительными комбинациями.
A6
384 глава 8
все вместе
Для любознательных
Не нравится щелкать на кнопке Fire? Да, этот способ работает, но он мед-
ленный и неудобный. Гораздо проще нажимать клавишу Enter (Ввод),
не правда ли? Простой фрагмент кода для обработки нажатия Enter:
function init() {
var fireButton = document.getElementById("fireButton");
fireButton.onclick = handleFireButton;
var guessInput = document.getElementById("guessInput");
guessInput.onkeypress = handleKeyPress;
}
Добавляем новый обработчик —
для обработки событий нажатия
клавиш в поле ввода HTML.
Обработчик нажатий клавиш;
вызывается при каждом на- Браузер передает объект события
жатии клавиши в поле input обработчику. Объект содержит
страницы. информацию о том, какая клавиша
была нажата.
function handleKeyPress(e) {
var fireButton = document.getElementById("fireButton");
if (e.keyCode = = = 13) {
fireButton.click(); Если нажата клавиша Enter,
return false; то свойство keyCode события рав-
но 13. В таком случае кнопка Fire!
} должна сработать так, словно
} игрок щелкнул на ней. Для этого
можно вызвать метод click кнопки
И мы возвращаем false, чтобы fireButton (фактически этот вызов
форма не делала ничего лишнего имитирует нажатие кнопки).
(например, не пыталась пере-
дать данные).
A
Клетки
кораблей B
не должны
Корабль
Корабль
перекры-
ваться. C
D Корабль
Корабли могут
располагаться
вертикально E
или горизон-
тально.
F
G Корабль
0 1 2 3 4 5 6
386 глава 8
все вместе
Развлечения с магнитами
Блоки алгоритма, генерирующего позиции кораблей, перепутались.
Сможете ли вы разместить магниты в правильной последовательности,
чтобы создать работоспособный алгоритм? Прежде чем двигаться
дальше, сверьтесь с ответами в конце главы.
Сгенерировать случайное
направление (вертикальное
или
горизонтальное) для нового
корабля.
Функция generateShipLocations
Начнем с метода generateShipLocations. Этот метод в цикле создает корабли, пока массив ships
модели не будет заполнен достаточным количеством кораблей. Каждый раз, когда метод генери-
рует новый корабль (что делается методом generateShip), он использует метод collision для
проверки возможных перекрытий. Если корабль перекрывается с другими кораблями, метод
отказывается от него и делает следующую попытку.
В этом коде следует обратить внимание на новую разновидность циклов — do while. Цикл do while
работает почти так же, как while, за одним исключением: он сначала выполняет команды в теле
цикла, а потом проверяет условие. Некоторые логические условия (хотя это бывает не так часто)
лучше подходят для циклов do while, чем для циклов while.
388 глава 8
все вместе
ь может
Горизонтальный корабл
пол ага ть ся в лю бой строке...
рас
в строке 0–4,
ь должен начинаться
Вертикальный корабл
о еще для двух клеток...
чтобы осталось мест
390 глава 8
все вместе
generateShip: function() {
var direction = Math.floor(Math.random() * 2);
Этот код генерирует начальну
var row, col; ю
позицию корабля на игровом поле
.
if (direction = = = 1) {
row = Math.floor(Math.random() * this.boardSize);
col = Math.floor(Math.random() * (this.boardSize - this.shipLength));
} else {
row = Math.floor(Math.random() * (this.boardSize - this.shipLength));
col = Math.floor(Math.random() * this.boardSize);
раницы)
} ие 3 (с пр едыдущей ст обобщения
Значен h для
this.shipLengt использо-
заменено на о можно было рабля.
чт об ы ег
кода, иной ко
var newShipLocations = []; звольной дл
вать с прои
for (var i = 0; i < this.shipLength; i++) {
if (direction = = = 1) { Круглые скобки гарантируют,
к col
Код для горизонтального корабля. что значение i будет прибавлено
вани я резу льт ата в строку.
Разберем его на составляющие... до преобразо
Мозговой
штурм
В этом коде задействованы два цикла: внешний цикл перебирает все
корабли в модели, а внутренний цикл перебирает все позиции, прове-
ряемые на перекрытие. Во внешнем цикле используется управляющая
переменная i, а во внутреннем — переменная j. Почему мы используем
два разных имени переменных?
392 глава 8
все вместе
var model = {
boardSize: 7,
numShips: 3,
shipLength: 3, Удалите фиксированные
позиции кораблей...
shipsSunk: 0,
ships: [ { locations: ["06", "16", "26"], hits: ["", "", ""] },
{ locations: ["24", "34", "44"], hits: ["", "", ""] },
{ locations: ["10", "11", "12"], hits: ["", "", ""] } ],
ships: [ { locations: [0, 0, 0], hits: ["", "", ""] },
...и замените их мас-
{ locations: [0, 0, 0], hits: ["", "", ""] }, сивами, инициализиро-
{ locations: [0, 0, 0], hits: ["", "", ""] } ], ванными нулями.
fire: function(guess) { ... },
isSunk: function(ship) { ... },
generateShipLocations: function() { ... },
generateShip: function() { ... },
collision: function(locations) { ... }
};
function init() {
var fireButton = document.getElementById("fireButton");
fireButton.onclick = handleFireButton;
var guessInput = document.getElementById("guessInput");
guessInput.onkeypress = handleKeyPress;
A0
394 глава 8
все вместе
и
Заметк Если
н а ш л а ошибку.
Кажетс
я, я ь снова
а н и я в корабл
п а д игра
после по е к о о р д инаты,
е ж нии.
ввести т е т о попада
о б щ а и!
снова со и у б е д и тесь сам
йте
Попробу
ожет,
т о о ш и бка? А м
Итак, э ? И если
и д о л ж но быть вить?
так к а к ее испра
ошибка,
т о б этом
з д е с ь , что вы о
е
Запишит
е :
думает
396 глава 8
все вместе
КЛЮЧЕВЫЕ
МОМЕНТЫ
Прежде чем писать код размещения попаданий и промахов на игровом поле, давайте
немного потренируемся в работе с CSS. Добавьте в разметку классы “hit” и “miss”,
соответствующие приведенным ниже действиям игрока. Мы подготовили два класса
CSS для ваших экспериментов. Добавьте эти два правила в CSS, а затем представьте,
что на игровом поле расставлены корабли в следующих позициях: Не забудьте загр
узить
все необходимое,
Корабль 1: A6, B6, C6 вклю-
чая два графичес
ких
Корабль 2: C4, D4, E4 файла, которые
по-
требуются для эт
Корабль 3: B0, B1, B2 ого
упражнения.
А это выстрелы игрока:
A0, D4, F5, B2, C5, C6
Добавьте один из двух приведенных классов к ячейкам игрового поля (элементам
<td> таблицы), чтобы на сетке в правильных местах выводились маркеры промахов
и попаданий.
.hit {
background: transparent url("ship.png") no-repeat center center;
}
.miss {
background: transparent url("miss.png") no-repeat center center;
}
После добавления
там
классов к элемен
но
игровое поле долж
выглядет ь т ак .
398 глава 8
все вместе
Изображение корабля
на сетке.
displayMiss: function(location) {
// Код будет приведен позднее
}
};
Использовать девять переменных (по ана- Использовать три разных массива, по одному
логии с тем, как мы представляли позиции для каждого корабля. Каждый массив содер-
кораблей в главе 2). жит данные трех клеток.
Использовать массив с элементами, пред- Использовать объект с именем ship и тремя
ставляющими клетки игрового поля (49 эле- свойствами (для каждой из трех клеток). Объ-
ментов). В элементе каждой клетки, содержа- екты ship хранятся в массиве с именем ships.
щей часть корабля, хранится номер корабля.
______________________________________
Использовать массив для хранения всех девя- ______________________________________
ти позиций. В элементах 0–2 хранятся данные ______________________________________
первого корабля, в элементах 3–5 — данные
второго, и т. д. Все эти решения работают!
Или запишите (Вообще-то мы опробовали их, когда
собственный вариант. готовили материал для этой главы.)
Этот вариант использовался в книге.
Сгенерировать случайное
направление (вертикальное
или
горизонтальное) для нового
корабля.
Сгенерировать случайную
позицию для нового корабля.
Проверить, не перекрываютс
я ли
клетки нового корабля
с клетками каких-либо
существующих кораблей.
400 глава 8
все вместе
var ships = [{ locations: ["06", "16", "26"], hits: ["hit", "hit", "hit"] },
{ locations: ["24", "34", "44"], hits: ["hit", "hit", "hit"] },
{ locations: ["10", "11", "12"], hits: ["hit", "hit", "hit"] }];
Лишние магниты.
var ships = [{ locations: ["31", "41", "51"], hits: ["", "", ""] },
{ locations: ["14", "24", "34"], hits: ["", "hit", ""] },
{ locations: ["00", "01", "02"], hits: ["hit", "", ""] }];
C4, A0
Ships 2 и 3 В каких позициях? ______________
Какие корабли уже были “подстрелены”?____________
1
var ship2 = ships[____];
var locations = ship2.locations;
console.log("Location is " + locations[____]);
1
Допишите следующий код, чтобы он определял, было ли попадание в первой клетке третьего корабля:
2
var ship3 = ships[____];
hits
var hits = ship3._____;
hits[0] = = = "hit") {
if (_______
console.log("Ouch, hit on third ship at location one");
}
Допишите следующий код, чтобы он записывал попадание в третью клетку первого корабля:
ship1 = ships[0];
var ______
hits
var hits = ship1._______;
hits[____]
2 “hit”
= ________;
402 глава 8
9 асинхронное программирование
Обработка событий
Мозговой
штурм
Вы знаете, как работает браузер, правда? Он загружает страницу и все ее содержимое,
а затем отображает ее на экране. Но этим роль браузера вовсе не ограничивается. Что еще
он делает? Выберите из следующего списка операции, которые, по вашему мнению, браузер
может выполнять незаметно для пользователя. Если не уверены — попробуйте угадать.
404 глава 9
асинхронное программирование
Пользователь
немного переместил
Я просто хочу сказать, мышь. Если интересно,
что загрузка страницы я могу сообщить,
завершилась. насколько именно.
Браузер
Воспроизведение Сообщаю — прошла
видеоролика на странице минута.
приостановилось.
Конечно-конечно, как
только я завершу загрузку, ваш
обработчик будет готов к работе.
И не сомневайтесь, я его
непременно выполню.
Браузер.
Обработчик — код,
который должен
быть выполнен
позднее, после
завершения загрузки
страницы.
406 глава 9
асинхронное программирование
ика
В случае события load имя обработч
window.onload = pageLoadedHandler; назначается свойству onload объекта
window.
Тест-драйв
Создайте новый файл event.html и добавьте код тестирования нового обработчика
события загрузки. Загрузите страницу в браузере и убедитесь в том, что сообщение
появляется на экране.
Мозговой
штурм
Могли бы мы использовать обработчики событий
без функций?
408 глава 9
асинхронное программирование
Если ты
надеешься когда-нибудь
стать настоящим разработчиком
Javascript, тебе придется освоить
обработку событий.
Размытая версия
изображения.
Хмм, что бы
это могло быть?
дума-
А когда вы гада-
чт о до
ете,
ите
лись, щелкн ии,
зо бр а ж ен
на и
тано-
чтобы восс сход-
ь ег о в и
вит
ном виде.
410 глава 9
асинхронное программирование
Реализация игры
Загрузите эту разметку в браузере; вы увидите размытое изображение.
Чтобы реализовать игру, следует организовать обработку щелчков на
изображении (по щелчку должна появляться исходная, неразмытая
версия изображения).
К счастью, при каждом щелчке на элементе HTML на странице (или
касании на мобильных устройствах) генерируется соответствующее
событие. Ваша задача — написать обработчик для этого события
и включить в него код отображения исходной версиии изображения.
Решение этой задачи будет состоять из двух шагов:
window.onload = init;
function init() {
var image = document.getElementById("zero");
image.onclick = showAnswer; Обработчик назначает
ся свойству
} onclick объ екта изобра
жения, полу-
ченного из DOM.
Тест-драйв
Давайте опробуем нашу простую игру. Убедитесь в том,
что весь код HTML, CSS и JavaScript сохранен в файле
image.html, а загруженные с сайта http://wickedlysmart.
com/hfjs изображения хранятся в той же папке. Когда это
будет сделано, откройте файл в браузере и проверьте, как
он работает!
бра-
Щелкните в любой точке изо
про гра мм а выз вал а
жения, чтобы
да это
обработчик showAnswer. Ког
мента
происходит, свойство src эле
на экране
изображения изменяется и
бражение.
появляется нормальное изо
412 глава 9
асинхронное программирование
часто
Задаваемые
вопросы
В: Назначение свойства src изображения — то же, что назна- В: Выходит, обработчик вызывается внутри другого обра-
чение атрибута src вызовом setAttribute? ботчика?
О: В данном случае — да. При получении элемента HTML из DOM О: Не совсем. Обработчик onload содержит код, вызываемый
вызовом getElementById вы получаете объект element с набором при завершении загрузки страницы. При его вызове мы назначаем
методов и свойств. У всех объектов element имеется свойство id, обработчик свойству onclick элемента изображения, но вызван он
содержащее идентификатор элемента HTML (если он был назначен будет только тогда, когда пользователь щелкнет на изображе-
в HTML). Объект element изображения также содержит свойство src, нии — теоретически это может произойти значительно позднее
которому назначается графический файл, указанный в атрибуте завершения загрузки страницы. Получается, что два обработчика
src элемента <img>. вызываются в разное время.
Однако не все атрибуты имеют соответствующие свойства объекта,
и для их получения приходится использовать методы setAttribute
и getAttribute. В случае src и id можно использовать свойства и/или
методы getAttribute и setAttribute; результат от этого не изменится.
СТАНЬ браузером
Перед вами код игры. Представьте себя на месте браузера и попробуйте
определить, что должно происходить после каждого события. Когда
упражнение будет выполнено, загляните в конец главы и про-
верьте свои ответы. Первую строку мы заполнили за вас.
window.onload = init;
function init() {
var image = document.getElementById("zero");
image.onclick = showAnswer;
}
function showAnswer() {
var image = document.getElementById("zero");
image.src = "zero.jpg";
}
Код, который нужно выполнить...
Во время загрузки
страницы... Сначала определяются функции init и showAnswer
Когда происходит
событие заверше-
ния загрузки стра-
ницы...
Когда происходит
событие щелчка
на изображении...
414 глава 9
асинхронное программирование
Мозговой
штурм
А если бы у вас была целая страница с размытыми изображениями, которые можно было
бы по отдельности восстанавливать щелчком? Как бы вы спланировали код для решения
этой задачи? Какой способ можно было бы назвать примитивным? Можно ли реализовать
новую постановку задачи с минимальными изменениями в уже написанном коде?
416 глава 9
асинхронное программирование
window.onload = init;
function init() {
var image0 = document.getElementById("zero"); Поочередно получаем каждый
image0.onclick = showImageZero; элемент изображения от страницы
var image1 = document.getElementById("one"); и назначаем каждому элементу от-
дельный обработчик. И так шесть
image1.onclick = showImageOne; раз... Здесь показано только два.
... Здесь назначаются еще
} четыре обработчика.
function showImageZero() {
var image = document.getElementById("zero");
ся
image.src = "zero.jpg"; И нам потребуют т-
ст ь ра зн ых об рабо
} ше
для
чиков, по одному
function showImageOne() { жд ог о из об ра же ния.
ка
var image = document.getElementById("one");
image.src = "one.jpg";
}
... ...Еще четыре функции-обработчика.
Мозговой
штурм Какими недостатками обладает решение с отдельным обработчи-
ком для каждого изображения? Пометьте все подходящие ответы:
И все это хотелось бы сделать обобщенным способом, который будет работать даже при добавлении
новых изображений на страницу. Другими словами, если код будет правильно написан, добавление
изображений на страницу (или их удаление со страницы) не должно приводить к изменениям в коде.
Давайте разберемся, как это сделать.
418 глава 9
асинхронное программирование
часто
Задаваемые
вопросы
В: Вы сказали, что метод
работе с объектом NodeList необходимо
действовать осторожно. Обычно это все, что В: Функции-обработчики получают
getElementsByTagName возвращает необходимо знать о NodeList, если только аргументы?
список. Имеется в виду массив? вы не собираетесь добавлять и удалять
О:
О: Метод возвращает объект, с которым
элементы из DOM.
В:
Хороший вопрос... и очень своевремен-
ный. Да, получают, и мы как раз собираемся
рассмотреть объект события, передавае-
можно работать как с массивом, но на са- Значит, обработчик события щелчка
мом деле это объект типа NodeList. Объект можно назначить любому элементу? мый некоторым обработчикам.
NodeList представляет коллекцию узлов
(технический термин для объектов элемен- О: Почти. Получите любой элемент стра-
В: Элементы поддерживают другие
тов в дереве DOM). Чтобы выполнить пере- ницы и назначьте функцию его свойству типы событий, кроме щелчков?
бор коллекции, следует получить ее длину
из свойства length, а затем последовательно
обратиться к каждому элементу NodeList
onclick. Готово. Как вы уже видели, обра-
ботчик может обслуживать только этот О: Да, есть и другие события. Одно из них
конкретный элемент или же целую группу уже встречалось нам в коде игры «Морской
по индексу в квадратных скобках (так же, элементов. Конечно, элементы, не имеющие бой» — это событие нажатия клавиши. Тог-
как это делается с массивами). Впрочем, визуального представления на странице да функция-обработчик вызывалась при на-
на этом сходство между NodeList и масси- (например, эелменты <script> и <head>), жатии клавиши Enter. Мы также рассмотрим
вом завершается, поэтому в остальном при не поддерживают события щелчка. другие виды событий в этой главе.
420 глава 9
асинхронное программирование
Вы щелкаете на изображении...
“
объект со бы т ия ...
Событие
...который передается
обработчику события.
function showAnswer(eventObj) {
...
}
В обработчике можно использовать этот объект для получения
информации о событии: тип события, породивший его элемент и т. д.
Итак, что же собой представляет объект события? Как было сказано ранее, он содержит как общую,
так и специализированную информацию о событии. Специализированная информация зависит от
типа события, мы еще вернемся к этой теме. Общая информаия включает свойство target, в котором
хранится ссылка на объект, сгенерировавший событие. Итак, если пользователь щелкнул на элементе
страницы (например, на изображении), этот элемент является источником события и к нему можно
обратиться следующим образом:
Свойство target сообщает,
function showAnswer(eventObj) { какой элемент сгенерировал
var image = eventObj.target; данное событие.
}
Вы уже знаете, что объект события (для событий DOM) содержит свойства, которые предостав-
ляют расширенную информацию о произошедшем событии. Ниже перечислены другие свойства,
которые могут присутствовать в объекте события. Соедините каждое свойство объекта события
с его кратким описанием.
422 глава 9
асинхронное программирование
ъекта события
Свойство object об элемент,
function showAnswer(eventObj) { сс ыл ку на
содержит
сд елан щелчок.
var image = eventObj.target; на котором был
часто
Задаваемые
вопросы
Мозговой
В: Обработчику события onload тоже передается штурм
объект события?
424 глава 9
асинхронное программирование
откровенно о событиях
Интервью недели:
Беседа с браузером о событиях
Head First: Привет, Браузер. Спасибо, что загляну- Head First: Что это означает для наших читателей?
ли. Мы знаем, как у вас много работы. Браузер: Допустим, вы пишете обработчик, тре-
Браузер: Пожалуйста. И вы правы, едва успеваю бующий большого объема вычислений, — то есть
управляться со всеми этими событиями. обработчик, на выполнение которого потребуется
Head First: И как вы управляетесь с ними? Расска- много времени. Пока ваш обработчик усердно тру-
жите, как это волшебство выглядит, так сказать, дится, я сижу и ожидаю, когда он завершится. Только
из-за кулис. тогда я смогу продолжить обработку очереди.
Браузер: Как вам известно, события происходят Head First: Надо же! И часто вам приходится ждать
постоянно. Пользователь перемещает мышь, делает завершения медленного кода?
жесты на мобильном устройстве, поступают новые Браузер: Бывает... Но веб-программист быстро
данные, срабатывают таймеры... Суета, как в час понимает, что страница или приложение не реаги-
пик. В общем, хлопот выше крыши. рует из-за медленного обработчика. В общем, если
Head First: Но ведь вам ничего не нужно делать, веб-программист знает, как работают очереди сооб-
если для события не определен обработчик? щений, эта проблема встречается нечасто.
Браузер: Даже если обработчика нет, работы все Head First: Теперь наши читатели тоже это знают!
равно хватает. Кто-то должен обнаружить событие, Но вернемся к событиям. Их много видов?
интерпретировать его и проверить, назначен ли Браузер: Много. Есть события, относящиеся к пе-
для него обработчик. Если обработчик есть, нужно редаче данных, события DOM, связанные со стра-
передать ему управление. ницей, и много других. События DOM, например,
Head First: И как же вы следите за всеми событиями? генерируют объекты событий, содержащие более
Что если произойдет несколько событий одновре- подробную информацию о событии. В событии
менно? Вы же не можете делать несколько дел сразу? щелчка мышью есть информация о том, в какой
точке он был сделан, а в событии нажатия клави-
Браузер: Да, за короткий промежуток времени ши — о нажатой клавише и т. д.
может случиться множество событий. Иногда собы-
тия происходят слишком быстро, чтобы работать Head First: Итак, вы тратите значительное время
в реальном времени. Поэтому я ставлю их в очередь на обработку событий. Стоит ли оно того? В конце
по мере поступления, а затем прохожу по очереди концов, вам еще приходится заниматься загрузкой,
и запускаю обработчики там, где это необходимо. разбором, отображением страниц и т. п.
Head First: Прямо как в юности, когда я подрабаты- Браузер: О, это очень важно. В наши дни пишется
вал в закусочной! много кода, который делает страницы интерактив-
ными и увлекательными, а для этого нужны события.
Браузер: Наверное... Если заказы поступали каждую
миллисекунду или около того! Head First: Конечно, времена простых статических
страниц прошли.
Head First: И вам приходится просматривать содер-
жимое очереди, событие за событием? Браузер: Вот именно. Ой, у меня сейчас очередь
переполнится... Надо бежать!
Браузер: Конечно, и это очень важная особенность
JavaScript: существует всего одна очередь и один Head First: Хорошо… До следующего раза!
«программный поток выполнения». Значит, только
один «экземпляр меня» перебирает события.
Очереди и события
Вы уже знаете, что браузер поддерживает очередь событий.
И он незаметно для пользователя постоянно извлекает события из
очереди и обрабатывает их, передавая подходящему обработчику,
если он имеется.
Будьте внимательны!
Я обрабатываю события
последовательно, а не одновременно.
Для каждого события я вызываю его
Браузер. обработчик, если он существует.
Очередь событий
Страница загрузилась
Пользователь щелкнул
на странице
Сработал таймер Внимание, поступает новое
событие. Пользователь
Отправлены данные формы только что щелкнул
на очередном элементе.
Пользователь снова щелкнул
ет
перебира И еще один щелчок
Браузер о ч е р е ди
в
события х к новым, И еще один щелчок
ы
от стар ая каждое Сработал другой таймер
б р а б а т ыв
о
.
событие Отправлены данные формы
426 глава 9
асинхронное программирование
Эй, салага! У нас тут есть карта сокровищ, но ты должен помочь нам
определить координаты клада. Для этого нужно написать фрагмент кода, Упражнение
который выводит координаты при наведении указателя мыши на точку
карты. Начало кода приведено на следующей странице, но тебе придется
закончить его.
Когда код буд ет напи
сан,
наведите указатель
Карта. Клад помечен на крестик — и вы
мыши
крестиком! узнаете
координаты клада.
P. S. Мы настоятель
но
рекомендуем выпол-
Ваш код должен нить это упражне-
выводить коорди- ние — пираты будут
наты под картой. очень недовольны, есл
и
не получат коорди-
наты... Кстати, для
завершения кода вам
кое-что понадобит
ся:
Разрази меня гром! Код приведен ниже. Пока он включает страницу Упражнение
с картой и создает элемент абзаца для вывода координат. Вы должны
поработать над кодом и сделать так, чтобы он заработал. Желаем удачи!
А не то отправитесь на корм рыбам, а у нас еще несколько глав впереди...
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Pirates Booty</title>
<script>
window.onload = init;
function init() {
var map = document.getElementById("map");
_________________________________________
тия.
} обработчик собы
Здесь назначается
function showCoords(eventObj) {
var map = document.getElementById("coords");
________________________
________________________
map.innerHTML = "Map coordinates: "
+ x + ", " + y;
}
Здесь определяются
</script>
координаты.
</head>
<body>
<img id="map" src="map.jpg">
<p id="coords">Move mouse to find coordinates...</p>
</body>
</html>
ьной
работает в реал
Когда ваш код за ег о и за пи ши те
ит е
странице, загруз
а.
координаты клад
____________________________________
428 глава 9
асинхронное программирование
function timerHandler() {
alert("Hey what are you doing just sitting there staring at a blank screen?");
}
В этом обработчике мы просто выводим сообщение.
imeout, которая
А здесь вызывается функция setT
мен та: обра бот чик события
setTimeout(timerHandler, 5000); получает два аргу
кунд ах).
и интервал времени (в миллисе
function timerHandler() {
alert("Hey what are you doing just sitting there staring at a blank screen?");
}
Я что-то не понял
этот фокус с setTimeout:
мы передали функцию другой
функции?
setTimeout(timerHandler, 5000);
часто
Задаваемые
вопросы
В: Можно ли остановить отсчет setInterval? В: С onload я назначаю один обработчик для события. Но по-
432 глава 9
асинхронное программирование
window.onload = function() {
Код, который мы написали
var images = document.getElementsByTagName("img");
ранее. Ничего не изменилось...
for (var i = 0; i < images.length; i++) {
images[i].onclick = showAnswer;
}
};
function showAnswer(eventObj) {
воде нормального
var image = eventObj.target; Но теперь при вы
также вызываем
изображения мы ,
var name = image.id; значения события
setTimeout для на дв е се ку нды.
т через
name = name + ".jpg"; которое сработае
image.src = name;
В качестве обработчика указывается
метод reblur (см. ниже), которому
setTimeout(reblur, 2000, image); передается значение времени 2000 мил-
} лисекунд (две секунды) и аргумент —
изображение, которое необходимо снова
вывести в размытой версии.
function reblur(image) {
var name = image.id;
name = name + "blur.jpg"; Теперь при вызове обработчик
будет получать изображение.
image.src = name;
}
setTimeout не поддер-
жив ает доп олн ите ль-
Обработчик получает изо- Будьте ные арг уме нты в IE8
бражение, определяет его
идентификатор и строит осторожны! и ранее.
имя размытого изображения. Да, вы правильно поняли.
Присваивая атрибуту src Этот код не будет ра-
изображения построенное ботать в IE8 и более ранних верс
имя, мы заменяем нормаль- иях. Но вам
в любом случае не стоило брать IE8
ное изображение размытым. для этой
книги! Впрочем, позднее будет дан
о другое
реш ени е, которо е спр ави тся с
про блемой
совместимости IE8 (и более ранних
версий).
434 глава 9
асинхронное программирование
Тест-драйв таймеров
Нового кода не так уж много, но он влияет на работу приложения. Теперь,
если щелкнуть на изображении, браузер (через события таймера) следит
за тем, когда нужно вызвать обработчик reblur. Этот обработчик снова Теперь изображение,
размывает открытое изображение. Обратите внимание на асинхронность на котором щелкнул
пользователь, выво-
действий — вы управляете тем, на каких изображениях будут сделаны щел- дится в нормальном
чки, а служебный код активизируется по событиям щелчков и таймеров. Нет виде, но через две
никакого супералгоритма, который бы контролировал все происходящее, секунды снова размы-
определяя, что и когда должно вызываться. Программа строится из неболь- вается.
ших фрагментов кода, которые реагируют на события.
Протестируйте программу,
быстро щелкая на разных
изображениях. Всегда ли она
работает? Вернитесь к коду
и подумайте, как браузер
отслеживает все изображения,
которые необходимо размыть.
часто
Задаваемые
вопросы
В: Я могу передать всего один аргумент В: Функция showAnswer — обработчик, В: Итак, существуют события DOM,
обработчику setTimeout? но она создает новый обработчик reblur события таймеров... Какие еще?
О: О:
прямо в коде. Это нормально?
Нет, можно передать сколько угодно
аргументов: нуль, один и более. О: Вполне. Более того, это часто проис-
Большинство событий относятся к ка-
тегории событий DOM (например, щелчок
myElement.onmouseover = myHandler;
436 глава 9
асинхронное программирование
Упражнение
Когда игра с картинками была готова, Джуди написала код для обсуждения
на еженедельной встрече группы. Она даже объявила маленький конкурс —
первый, кто расскажет, что делает этот код, награждается бесплатным
обедом. Кто победит? Джим, Джо, Фрэнк? А может, вы?
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Don't resize me, I'm ticklish!</title>
<script>
function resize() {
var element = document.getElementById("display");
element.innerHTML = element.innerHTML + " that tickles!";
}
</script>
</head>
<body>
<p id="display">
Whatever you do, don't resize this window! I'm warning you!
</p>
<script>
window.onresize = resize;
</script>
</body>
</html>
ЛАБОРАТОРИЯ КОДА
Мы нашли крайне подозрительный код, и нам понадобится ваша помощь при тестировании. Мы уже
провели предварительный анализ, и хотя на первый взгляд мы имеем дело с абсолютно стандартным
JavaScript, что-то кажется странным. Ниже приведены два образца. В каждом случае необходимо
указать, что же в коде кажется подозрительным, протестировать код на работоспособность, а потом
попытаться проанализировать, что именно делает код. Запишите свои комментарии на странице.
Наш анализ приведен на следующей странице.
Образец #1
var addOne
= function(x
){
return x + 1
;
};
var six = ad
dOne(5);
2
Образец #
on() {
nloa d = functi
window.o
ded!");
r t( "T h e page is loa
ale
438 глава 9
асинхронное программирование
Образец #2
window.onload = function() { Здесь происходит нечто
похожее. Вместо того чтобы
alert("The page is loaded!"); определять функцию и при-
сваивать ее имя свойству
} window.onload, мы назначаем
функцию непосредственно
этому свойству. И снова
в определении функции ее имя
не указывается.
КЛЮЧЕВЫЕ
МОМЕНТЫ
Большая часть кода JavaScript пишется для обработки Объект события содержит свойства с дополнительной
событий. информацией о событии, включая тип (например, “click” или
Существует много разных видов событий, на которые может “load”) и источник (объект, с которым произошло событие).
реагировать ваш код. В старых версиях IE (IE 8 и ранее) используется модель
Чтобы отреагировать на событие, следует написать функ- события, отличающаяся от других браузеров.
цию — обработчик события и зарегистрировать ее. Многие события могут происходить с большой частотой. Ког-
Например, чтобы зарегистрировать обработчик для события
да происходит слишком много событий, которые браузер не
щелчка, следует присвоить функцию-обработчик свойству
успевает обрабатывать, события сохраняются в очереди
onclick элемента.
событий (в порядке возникновения), чтобы браузер мог
Вы не обязаны обрабатывать какие-либо события в своих последовательно выполнять обработчики всех событий.
программах. Вы сами выбираете события, на которые будете
реагировать. Если обработка события требует значительного объема
вычислений, это замедлит обработку событий в очереди,
Функции используются для назначения обработчиков,
потому что в любой момент времени может выполняться
поскольку в них удобно упаковывать код для выполнения
только один обработчик.
в будущем (когда произойдет событие).
Код, написанный для обработки событий, отличается от кода, Функции setTimeout и setInterval используются для
который выполняется от начала до конца, а потом завер- генерирования событий таймера по истечении заданного
шается. Обработчики событий могут выполняться в любое интервала времени.
время и в любом порядке: они выполняются асинхронно. Метод getElementsByTagName возвращает нуль, один
События, происходящие с элементами DOM (события DOM), или несколько объектов элементов, объединенных в объект
передаются в обработчик событий. NodeList (аналог массива с возможностью перебора).
440 глава 9
асинхронное программирование
Суп с событиями
load keypress
сходит mousemove
Событие прои Событие
click г е н е
тся
рируе енте при заверш ен ии Событие генериру- генерируется
ие лем ницы ется при перемеще-
Событелчке на э рикос- загрузки стра при каждом
и щ и п браузером . нии указателя мыши нажатии
пр (и л
ницы над элементом. клавиши.
стра и). unload
и
новен
Событие генерируе mouseover mouseout
тс
при закрытии окна я При наведении Событие генериру-
браузера или уходе указателя мыши ется при выходе
с веб-страницы. на элемент указателя мыши
resiz e генерируется за границы элемента.
Событие генериру- это событие.
art
dragst енерируется
ется при каждом touchst
изменении размера art
ие г и
окна браузера. Событ етаскивани це. Событи
е
при п е р
ст р а н и при при генерируется
та на ко
э л е м е н ментам сновении к эле-
н
play жит
сод ер Вы бу-
с сенсор а устройствах
ными эк
н и ц а > ? ранами.
Стра т <video то собы-
эл е м е н
ат э и кнопки
ь drop ется
получ и генериру
дете ри нажат Событие кании пере- touchend
п . ус
т и е
оизве
де н и я
pause при отп ого элемента.
м
воспр таскивае Событие генерируе
тся
нажатии
А это — при при завершении пр
и-
ст ановки.
кнопки прио косновения.
442 глава 9
асинхронное программирование
СТАНЬ браузером. Решение
Перед вами код игры. Представьте себя на месте браузера и попробуйте
определить, что должно происходить после каждого события. Когда
упражнение будет выполнено, загляните в конец главы и про-
верьте свои ответы. Ниже приведено наше решение.
window.onload = init;
function init() {
var image = document.getElementById("zero");
image.onclick = showAnswer;
}
function showAnswer() {
var image = document.getElementById("zero");
image.src = "zero.jpg";
}
444 глава 9
асинхронное программирование
Эй, салага! У нас тут есть карта сокровищ, но ты должен помочь нам опреде-
лить координаты клада. Для этого нужно написать фрагмент кода, который
выводит координаты при наведении указателя мыши на точку карты.
Разрази меня гром! Код приведен ниже. Пока он включает страницу с картой
и создает элемент абзаца для вывода координат. Вы должны поработать над
кодом и сделать так, чтобы он заработал. Желаем удачи! А не то отправитесь
на корм рыбам, а у нас еще несколько глав впереди... Вот и наше решение.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Pirates Booty</title>
<script>
window.onload = init;
function init() {
var map = document.getElementById("map");
map.onmousemove = showCoords;
_________________________________________
}
function showCoords(eventObj) {
var map = document.getElementById("coords");
var x = eventObj.clientX;
________________________
var y = eventObj.clientY;
________________________
map.innerHTML = "Map coordinates: "
+ x + ", " + y;
}
</script>
</head>
<body>
<img id="map" src="map.jpg">
<p id="coords">Move mouse to find coordinates...</p>
</body>
</html>
Остается навести указатель
мыши на X и получить координаты: 200, 190
Это код.
ер, сохраните
Чтобы остановить тайм
rval в переменной...
var t = setInterval(ticker, 1000); результат вызова setInte
446 глава 9
асинхронное программирование
window.onload = function() {
var images = document.getElementsByTagName("img");
for (var i = 0; i < images.length; i++) { Сначала удаляем назначение
обработчика свойству onclick.
images[i].onclick = showAnswer;
Затем назначаем обработчик
images[i].onmouseover = showAnswer; showAnswer свойству onmouseover
изображения...
images[i].onmouseout = reblur;
} А теперь функция reblur
назначается обработчиком
}; события mouseout (вместо
function showAnswer(eventObj) { события таймера). Для этого
она назначается свойству
var image = eventObj.target; onmouseout изображения.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Don't resize me, I'm ticklish!</title>
<script>
function resize() {
var element = document.getElementById("display");
element.innerHTML = element.innerHTML + " that tickles!";
}
Обработчик события называется resize.
</script> При вызове он просто добавляет текст
</head> в абзац с идентификатором “display”.
<body>
<p id="display">
Whatever you do, don’t resize this window! I'm warning you!
</p>
<script> Нас интересует событие изменения размера,
window.onresize = resize; поэтому мы создаем функцию-обработчик
(с именем resize) и назначаем ее свойству
</script> onresize объекта window.
</body>
</html>
448 глава 9
10 первоклассные функции
Стандартное объявление
function quack(num) { функции с ключевым словом
for (var i = 0; i < num; i++) { function, именем, парамет
ром и блоком кода.
console.log("Quack!");
}
}
Функция вызывается по имени, за которым
следует пара круглых скобок со списком
quack(3);
необходимых аргументов:
450 глава 10
первоклассные функции
Объявление переменной —
Браузер. Первым делом я всегда это не объявление функции.
просматриваю код и ищу Игнорируем и двигаемся
дальше.
в нем объявления функций.
function quack(num) {
Ага, вот и объявление for (var i = 0; i < num; i++
) {
функции. Его необходимо
console.log("Quack!");
обработать. Мы сделаем
это на следующей стра- }
нице.... }
if (migrating) {
После обработки объявления quack(4);
остальной код уже не пред-
ставляет интереса, потому fly(4);
что в нем нет объявлений }
функций.
нием кода), он ищет объявления функций. Обнаружив такое объявление, var fly = fu
nction(num)
{
браузер создает функцию и присваивает полученную ссылку переменной, for (var i
= 0; i < nu
m; i++) {
console.log(
имя которой совпадает с именем функции. Это выглядит примерно так: }
"Flying!");
};
Объявление функции
в коде. Посмотрим,
function qu
ack(num) {
Так, я нашел объявление
что с ним сделает
for (var i
= 0; i < nu
m; i++) {
функции — обработаем его, браузер...
console.log(
"Quack!");
}
прежде чем делать что-то }
еще...
if (migrati
ng) {
quack(4);
fly(4);
}
function quack(num) {
function quack(num) { for (var i = 0; i < num; i++) {
console.log("Quack!"); }
}
}
}
452 глава 10
первоклассные функции
ue
стадии выполнения:
tr Начинаем с простого
присваивания. Вы уже var migrating = true;
неоднократно видели,
migrating как выполняются такие var fly = function(num) {
операции. for (var i = 0; i < num; i++) {
Похоже, мне нужно console.log("Flying!");
создать переменную }
с именем migrating, };
с именем fly.
fly if (migrating) {
quack(4);
fly(4);
}
Теперь мы берем
ссылку на функ-
Теперь эта функция fly цию и присваиваем
присваивается ее переменной fly.
переменной с именем fly.
Были здесь,
console.log("Fl
yinvar
g!");migrating = true;
}
Разобравшись с переменной fly, браузер двигает- сделали это, };
var fly = functi
двигаемся
on(num) {
ся дальше. Следующая команда содержит объяв- for (i = 0; i
дальше...
< num; i++) {
function quack(
num) {
ление функции quack, которое было обработано for (i = 0; i
< num; i++}) {
console.log("Fl
ying!");
по объяв-
Функция, созданная
ю фу нк ци и qu ac k.
лени
quack
function quack(num) {
for (var i = 0; i < num; i++) {
В вызове функции console.log("Quack!");
}
присутствует аргумент, его }
нужно передать функции...
454 глава 10
первоклассные функции
var migrating
= true;
Функция,
на которую fly
ссылается
function(num) {
for (vari = 0; i < num; i++) {
переменная fly. }
console.log("Flying!");
В вызове функции
присутствует аргумент, его
нужно передать функции...
4
При вызове функ
ци
копия значения ар и
гу-
мента передает
ся
в параметре...
function(num) {
for (var i = 0; i < num; i++) {
console.log("Flying!"); ...после чего выполня-
} ется тело функции.
}
часто
Задаваемые
В: Мы видели выражения вида 3+4
вопросы О: Вовсе нет. Функциональное выражение
и Math.random() * 6, но как функция может
быть выражением? В: Какая польза от переменной, кото-
может находиться в разных местах, как
и другие выражения. Впрочем, это очень
О:
правильный вопрос, и мы вскоре вернемся
к нему.
вычислении дает некоторое значение. 3+4
дает результат 7, Math.random() * 6 дает
случайное число, а функциональное выра-
Прежде всего, вы можете воспользо-
ваться ею для вызова функции: В: Хорошо, в переменной может хра-
myFunctionReference(); ниться ссылка на функцию. Но на что
жение дает ссылку на функцию. конкретно эта ссылка указывает? На ка-
ся выражением? Впрочем, не будем забегать вперед — мы О: Это хорошая точка зрения на ссылки,
О: Нет, объявление функции является
вернемся к этому вопросу через несколько
страниц.
но лучше думать о них как об указателях
на «замороженный» код, который можно
командой. Считайте, что здесь выполняется
скрытое присваивание, которое связывает
ссылку на функцию с переменной за вас.
В: Функциональные выражения мо-
в любой момент извлечь и пустить в дело.
Позднее вы увидите, что эти «заморожен-
гут находиться только в правой части ные» функции содержат нечто большее, чем
Функциональное выражение не присваивает команды присваивания? просто код из своего тела.
ссылку на функцию ничему; вам приходится
делать это самостоятельно.
456 глава 10
первоклассные функции
СТАНЬ браузером
Ниже приведен код JavaScript. Представьте себя на месте браузера, который
обрабатывает этот код. Справа запишите каждую функцию при ее
создании. Не забудьте, что код должен обрабатываться в два прохода:
на первом обрабатываются объявления, а на втором — выражения.
а-
Запишите имена функций в последов
тельности их создания. Если функция
ем,
создается функциональным выражени
оно
var midi = true; укажите имя переменной, которой
var type = "piano"; присваивается. Первую функцию мы
уже записали за вас.
var midiInterface;
function createMidi() {
// ...
}
if (midi) {
midiInterface = function(type) {
// ...
};
}
458 глава 10
первоклассные функции
function(num) {
...
fly
}
Переменная может
хранить ссылку Сама
на функцию. функция.
a();
b();
// Проверяем удачу в игре “наперстки”
c = a;
a = b; Выполните этот
b = c; код (вручную!)
и определите,
c = a; выиграли вы
или проиграли.
a = c;
a = b;
b = c;
a();
откровенно о функциях
Интервью недели:
Как работают функции
Head First: Мы так рады, что вы снова в нашей сту- Функция: В этом-то профессионалы с высокими
дии. Вас окружают сплошные тайны, и читателям зарплатами отличаются от «серой массы». Интер-
не терпится узнать побольше. претация функции как любого другого значения
делает возможным применение многих интересных
Функция: Это правда, меня так сразу не поймешь.
программных конструкций.
Head First: Начнем с того, что вас можно создавать
Head First: Можете привести хотя бы один пример?
при помощи как объявлений, так и выражений.
Зачем два способа? Разве одного недостаточно? Функция: Конечно. Допустим, вы хотите написать
функцию, которая может сортировать что угодно.
Функция: Не забывайте, что эти способы опреде-
Нет проблем, вам понадобится функция, которая
ления функций работают по-разному.
получает два аргумента: коллекцию данных, кото-
Head First: Но результат ведь один: функция, так? рые нужно отсортировать, и другую функцию, ко-
торая умеет сравнивать два элемента в коллекции.
Функция: Да, но приглядитесь: объявление функ- На JavaScript такой код написать несложно. Вы
ции выполняет больше работы за вас. Оно создает пишете одну функцию сортировки для всех разно-
функцию, а затем переменную для хранения ссылки видностей коллекций, а потом сообщаете ей, как
на функцию. Функциональное выражение создает сравнивать данные, передавая функцию, которая
функцию, а с создаваемой ссылкой вы должны что- умеет выполнять сравнение.
то делать сами.
Head First: Эээ…
Head First: Но разве ссылка, полученная от функ-
ционального выражения, не всегда сохраняется Функция: Как я и говорю, в этой области проявля-
в переменной? ется отличие настоящих профессионалов от рядо-
вых программистов. Еще раз: мы передаем функцию,
Функция: Разумеется, нет. Более того, чаще этого которая умеет выполнять сравнение, другой функции.
не происходит. Помните: функция — это значение. Иначе говоря, мы рассматриваем функцию как зна-
Представьте, сколько всего можно делать с другими чение, передавая ее другой функции как значение.
видами значений, например ссылками на объекты.
Вот и с функциями можно сделать то же самое. Head First: И что нам это дает, кроме путаницы?
Head First: Но как такое возможно? Я объявляю Функция: Меньше кода, меньше черной работы,
функцию, я вызываю ее. Практически все, что по- больше надежности, больше гибкости, больше удоб-
зволяет любой язык, правильно? ства сопровождения... И более высокие зарплаты.
Функция: Неправильно. Думайте о функциях как Head First: Все это хорошо, конечно, но я плохо
о значениях, по аналогии с объектами или прими- представляю, как разобраться в этой теме.
тивными типами. Располагая ссылкой на функцию,
Функция: Придется немного поработать. Безуслов-
вы можете делать с ней все, что угодно. Но между
но, это одна из тех областей, для которой вам при-
функциями и другими видами значений существует
дется напрячь свои мыслительные способности.
различие, в котором проявляется моя особая роль:
функцию можно вызвать для выполнения кода, Head First: Я и так напрягаюсь, голова сейчас взор-
содержащегося в ее теле. вется. Пожалуй, мне надо прилечь.
Head First: Звучит впечатляюще, но я пытаюсь Функция: Как скажете. Спасибо за беседу!
представить, что можно еще делать с функциями,
кроме как определять и вызывать их?
И знаете что? Все это можно делать и с функциями. Более того, с функциями
можно делать все, что можно делать с другими значениями JavaScript. Итак,
первоклассные значения функций в JavaScript следует рассматривать наряду
с другими значениями уже известных вам типов: чисел, строк, булевских
значений и объектов.
Более формальное определение «первоклассности» выглядит так:
рым в языке
Первоклассное значение: значение, с кото
лнят ься те же опер ации, что
программирования могут выпо
ми, вклю чая возм ожно сть при
и с любыми другими значения
в аргум енте и возвраще ния
сваивания переменной, передачи
из функции.
462 глава 10
первоклассные функции
нных,
Структура да хранятся А это данные четырех пас
предст ав ля ю щ ая Все пассажиры (если хотите, дополнит
сажиров
па сс аж ир ов : в массиве. е список
своими друзьями и знаком
ыми).
Мозговой
штурм
Наша цель: написать код, который обращается к списку
пассажиров и проверяет выполнение некоторых условий, Подумайте, как бы вы написали код для
необходимых для вылета, например убеждается в том, что решения этих задач (проверка по «чер-
на борту нет пассажиров, занесенных в «черный список». ному списку», получение списка пасса-
Или что все пассажиры оплатили свой полет... Или, на- жиров, оплативших перелет, и получе-
конец, просто составляет список всех пассажиров рейса. ние общего списка пассажиров)?
function checkPaid(passengers) {
for (var i = 0; i < passengers.length; i++) {
if (!passengers[i].paid) {
return false;
}
} function checkNoFly(passengers) {
return true; for (var i = 0; i < passengers.length; i++) {
} if (onNoFlyList(passengers[i].name)) {
return false;
}
}
Отличается function printPassengers(passengers) {
return true;
только критерий for (var i = 0; i < passengers.length; i++) {
проверки: оплата }
console.log(passengers[i].name);
или присутствие
return false;
в «черном списке».
}
return true;
А функция вывода списка отличается }
только тем, что в ней вообще нет про-
верки (вместо этого данные пассажира
передаются console.log) и возвращаемое
значение игнорируется. При этом функция
все равно перебирает список пассажиров.
464 глава 10
первоклассные функции
function checkNoFlyList(passenger) {
function checkNotPaid(passenger) {
function sayIt(translator) {
var phrase = translator("Hello");
alert(phrase);
}
function hawaiianTranslator(word) {
if (word = = = "Hello") return "Aloha";
if (word = = = "Goodbye") return "Aloha";
}
sayIt(hawaiianTranslator);
Перебор пассажиров
Нам нужна функция, которая получает список пассажиров, и другая функция, которая умеет
проверять отдельного пассажира по заданному условию (например, присутствие пассажира
в «черном списке»). Вот как это делается:
} И вызываем прове-
} рочную функцию для
каждого пассажира.
return true;
} Если проверочная функция возв
ращает true,
то мы возвращаем false. Иначе
говоря, если
пассажир не прошел проверку (не
заплатил
Если управление передано в эту за билет, входит в «черный спис
ок» и т. д.),
точку, значит, все пассажиры вылет следует запретить!
прошли проверку, и функция
возвращает true.
function checkNotPaid(passenger) {
return (!passenger.paid); А эта функция проверяет, заплатил ли
пассажир за билет. Для этого достаточно
} проверить свойство paid объекта пассажира.
Если билет не оплачен, возвращаем true.
466 глава 10
первоклассные функции
функция
Здесь передается
Передать функцию другой Lis t. Эт о означает, что
функции checkNoFly
несложно: просто укажите sse ngers будет
имя функция processPa ссажира
функции как аргумент. ь ка жд ого па
проверят ном списке».
в «ч ер
на присутствие
Консоль JavaScript
The plane can't take off: we have a passenger on Похоже, полет не состоится:
the no-fly-list. у нас проблемы с пассажирами!
Хорошо, что проверили заранее...
The plane can't take off: not everyone has paid.
Упражнение Ваша очередь: напишите функцию, которая выводит имя пассажира и информацию об оплате,
вызовом console.log. Передайте свою функцию processPassengers, чтобы протестировать ее. Ниже
мы начали писать код; вам остается завершить его. Прежде чем двигаться дальше, сверьтесь
с ответом в конце главы.
function printPassenger(passenger) {
ь
Запишите здес
свой код.
часто
Задаваемые
вопросы
В: Разве нельзя поместить этот код в processPassengers? В: Что именно передается при передаче функции другой
С таким же успехом можно включить все нужные проверки функции?
в одну итерацию, чтобы при каждом проходе цикла выполня-
лись все проверки и выводился список. Разве такое решение
не будет более эффективным?
О: Передается ссылка на функцию. Считайте, что ссылка — это
своего рода указатель на внутреннее представление функции. Сама
468 глава 10
первоклассные функции
function fun(echo) {
console.log(echo); function fun(echo) {
}; console.log(echo);
}
fun
fun("hello");
function boo(aFunction) {
aFunction("boo");
}
boo(fun);
console.log(fun);
fun(boo);
moreFun("hello again");
function echoMaker() {
return fun;
Задание на до-
}
полнительный
балл! (Дает
е
представлени var bigFun = echoMaker();
о том , чт о bigFun("Is there an echo?");
нас ждет...)
е!
итесь в ответах, прежде чем читать дальш
Очень, очень важно: проверьте себя и разбер
function serveCustomer(passenger) {
// Предложить напитки
Начнем с реа-
// Предложить обед лизации заказа
// Забрать мусор напитков.
470 глава 10
первоклассные функции
} else {
alert("Your choice is cola or water."); Купившим билеты
} второго класса предла-
гаем колу или воду.
// Предложить обед
// Забрать мусор
}
В простых сценариях такое решение работает неплохо: мы проверяем
билет пассажира и выводим сообщение в зависимости от типа купленно-
го билета. Но давайте рассмотрим некоторые потенциальные недостатки
этого кода. Конечно, код заказа напитков прост, но что произойдет с
функцией serveCustomer, если задача станет более сложной? Допу-
стим, количество категорий пассажиров увеличилось до трех: первый
класс, второй класс и бизнес-класс. А с улучшенным эконом-классом Например, в рейсах
их становится уже четыре!) А если увеличится ассортимент напитков? на Гавайи в первом
Или если выбор будет зависеть от аэропорта вылета или назначения? классе обычно
подают «Маи
Если нам придется иметь дело с такими сложностями, функция Таи» (во всяком
serveCustomer быстро разрастется, а ее основной задачей станет при- случае, нам так
нятие решений по напиткам, а не обслуживание клиентов. Функции рассказывали).
же рекомендуется проектировать так, чтобы они решали только одну
задачу, но делали это очень хорошо.
Мозговой
штурм
Перечитайте все потенциальные проблемы, перечисленные в двух последних абзацах
на этой странице. Подумайте, какая архитектура кода позволит нам сохранить специали-
зацию serveCustomer, но при этом оставить возможность расширения функциональности
предложения напитков на будущее.
function serveCustomer(passenger) {
if (passenger.ticket = = = "firstclass") {
Удаляем логику
alert("Would you like a cocktail or wine?"); из serveCustomer...
} else {
alert("Your choice is cola or water.");
} Исходная логика, запрограмми-
createDrinkOrder(passenger); рованная «на месте», заменя-
ется вызовом createDrinkOrder
.
472 глава 10
первоклассные функции
function serveCustomer(passenger) {
createDrinkOrder(passenger);
// Предложить обед
createDrinkOrder(passenger); Мы внесли изменения, чтобы
отразить тот факт, что
createDrinkOrder(passenger); функция createDrinkOrder
вызывается несколько раз
// Включить кино за полет.
createDrinkOrder(passenger);
// Забрать мусор
}
function addN(n) {
var adder = function(x) {
return n + x;
};
return adder;
} Запишите
ответ.
474 глава 10
первоклассные функции
Тест-драйвполет
Давайте протестируем наш новый код. Для этого нужно написать функцию, которая перебирает всех
пассажиров и вызывает serveCustomer для каждого пассажира. Добавьте код в файл, загрузите его в бра-
узер и примите несколько заказов.
servePassengers(passengers);
И разумеется, нужно выз-
вать функцию servePassengers,
чтобы вся схема заработала.
(Приготовьтесь — сообщений
будет много!)
часто
Задаваемые
вопросы
В: То есть, вызывая createDrinkOrder,
ет типу пассажира: если пассажир летит
первым классом, то создается версия
(возвращение функций), и он не идеален.
Но раз уж вы нам указали на нашу ошибку...
мы получаем функцию, которую нужно бу- getDrinkOrderFunction, предлагающая Что ж, возьмите листок бумаги и...
дет вызвать снова для заказа напитков? напитки первого класса. Но если пасса-
В: Откуда функция
напитки всем пассажирам, не включают
фильм для всех сразу и т. д.?
зов в зависимости от класса
билета? Использовали бы
getDrinkOrderFunction узнает, какое
сообщение нужно вывести? О: Ага, мы вас проверяли! И вы прош-
вы при этом первоклассные
функции?
О: Потому что мы создали ее специаль-
ли проверку. Да, все именно так; этот код
применяет всю функцию serveCustomer
к одному пассажиру за раз. В реальном
но для пассажира на основании его би-
лета. Взгляните на createDrinkOrder еще мире все не так. Но ведь это всего лишь
раз: возвращаемая функция соответству- пример, демонстрирующий сложную тему
Обязательно примените
первоклассные функции
в реализации!
476 глава 10
первоклассные функции
«Веб-кола»
Фирма «Веб-кола» обратилась к нам за помощью
в управлении ассортиментом продукции. Для начала
взгляните на структуру данных, которая используется
для хранения информации о производимых газиро-
ванных напитках:
Похоже, данные хранятся в массиве
объектов. Каждый объект представляет
некоторый продукт.
var products = [ { name: "Grapefruit", calories: 170, color: "red", sold: 8200 },
{ name: "Orange", calories: 160, color: "orange", sold: 12101 },
{ name: "Cola", calories: 210, color: "caramel", sold: 25412 },
{ name: "Diet Cola", calories: 0, color: "caramel", sold: 43922 },
{ name: "Lemon", calories: 200, color: "clear", sold: 14983 },
{ name: "Raspberry", calories: 180, color: "pink", sold: 9427 },
{ name: "Root Beer", calories: 200, color: "caramel", sold: 9909 },
{ name: "Water", calories: 0, color: "clear", sold: 62123 }
];
Для каждого продукта в объекте хранится
название, калорийность, цвет и количество
проданных бутылок за месяц.
Ведущий аналитик
фирмы «Веб-кола».
478 глава 10
первоклассные функции
Все вместе
Теперь, когда функция сравнения написана, остается лишь вызвать метод sort
для массива numbersArray и передать функцию при вызове. Вот как это делается:
Упражнение Метод sort отсортировал numbersArray по возрастанию, потому что при возвращении
1, 0 и -1 мы сообщаем методу sort:
1: первое значение должно располагаться после второго;
0: значения эквивалентны, их можно не переставлять;
-1: первое значение должно располагаться перед вторым.
Переход к сортировке по убыванию сводится к простой инверсии этой логики:
1 означает, что второе значение должно следовать за первым, а -1 — что второе
значение должно предшествовать первому (с 0 их порядок не важен). Напишите
новую функцию сравнения для сортировки по убыванию:
480 глава 10
первоклассные функции
var products = [ { name: "Grapefruit", calories: 170, color: "red", sold: 8200 },
{ name: "Orange", calories: 160, color: "orange", sold: 12101 },
{ name: "Cola", calories: 210, color: "caramel", sold: 25412 },
{ name: "Diet Cola", calories: 0, color: "caramel", sold: 43922 },
{ name: "Lemon", calories: 200, color: "clear", sold: 14983 },
{ name: "Raspberry", calories: 180, color: "pink", sold: 9427 },
{ name: "Root Beer", calories: 200, color: "caramel", sold: 9909 },
{ name: "Water", calories: 0, color: "clear", sold: 62123 }
];
Помните, что каждый элемент массива products является объектом.
Мы не собираемся сравнивать объекты друг с другом — сравниваться
должны только конкретные свойства объектов (такие, как sold).
Тест-драйв сортировки
Пора протестировать первую версию кода «Веб-колы». Ниже собран код с нескольких последних
страниц (с небольшими изменениями для тестирования). Итак, создайте простую страницу HTML
с приведенным кодом (cola.html) и протестируйте ее:
var products = [ { name: "Grapefruit", calories: 170, color: "red", sold: 8200 },
{ name: "Orange", calories: 160, color: "orange", sold: 12101 },
{ name: "Cola", calories: 210, color: "caramel", sold: 25412 },
{ name: "Diet Cola", calories: 0, color: "caramel", sold: 43922 },
{ name: "Lemon", calories: 200, color: "clear", sold: 14983 },
{ name: "Raspberry", calories: 180, color: "pink", sold: 9427 },
{ name: "Root Beer", calories: 200, color: "caramel", sold: 9909 },
{ name: "Water", calories: 0, color: "clear", sold: 62123 }
];
482 глава 10
первоклассные функции
ы <, > и ==
Подсказка: оператор
ьзо ват ься и для
могут испол !
вит ной сор ти ро вки
алфа
}
function compareCalories(colaA, colaB) {
}
products.sort(compareName); Для каждой новой
функции сравнения
console.log("Products sorted by name:"); мы вызываем sor t
printProducts(products); и выводим резуль-
таты на консоль.
products.sort(compareCalories);
console.log("Products sorted by calories:");
printProducts(products);
products.sort(compareColor);
console.log("Products sorted by color:");
printProducts(products);
КЛЮЧЕВЫЕ
МОМЕНТЫ
484 глава 10
первоклассные функции
Решение упражнений
Не обязательно!
function createMidi() {
// ...
}
if (midi) {
midiInterface = function(type) {
// ...
};
}
486 глава 10
первоклассные функции
a();
b();
// Проверяем удачу в игре “наперстки”
c = a;
c — ссылка на winner
a = b; a — ссылка на loser
b = c; b — ссылка на winner
c — ссылка на loser
c = a; a — ссылка на loser
a — ссылка на winner
a = c;
b — ссылка на loser
a = b; Вызывается...
winner!!!
b = c;
a();
function hawaiianTranslator(word) {
if (word = = "Hello") return "Aloha";
if (word = = "Goodbye") return "Aloha";
}
Функция
sayIt(hawaiianTranslator); hawaiianTranslator
передается функции sayIt.
Ваша очередь: напишите функцию, которая выводит имя пассажира и информацию об оплате,
вызовом console.log. Передайте свою функцию processPassengers, чтобы протестировать ее.
Мы начали писать код, вам остается завершить его. Ниже приведено наше решение.
function printPassenger(passenger) {
var message = passenger.name;
if (passenger.paid = = = true) {
message = message + " has paid";
} else {
message = message + " has not paid";
}
console.log(message);
Консоль JavaScript
return false;
Jane Doloop has paid
} Возвращаемое значение не так
важно, потому что в данном Dr. Evel has paid
случае мы игнорируем резуль- Sue Property has not paid
тат processPassengers.
John Funcall has paid
processPassengers(passengers, printPassenger);
488 глава 10
первоклассные функции
function fun(echo) {
console.log(echo); function fun(echo) {
}; console.log(echo);
}
может
е: ваш браузер
Предупреждени оль другие значения
нс
выводить на ко и boo. Протестируйте
ф ун кц ий fu n браузеров.
для
е в паре разных
это упражнени fun
fun("hello"); hello
function boo(aFunction) {
aFunction("boo");
}
boo(fun); boo
function echoMaker() {
return fun;
Задание на до-
}
полнительный
балл! (Дает
е
представлени var bigFun = echoMaker();
о том , чт о bigFun("Is there an echo?"); Is there an echo?
нас ждет...)
Добавьте в наш код третий класс обслуживания, допустим, «улучшенный экономический». Пасса-
жирам этого класса кроме лимонада и воды предлагается вино. Также реализуйте функцию заказа
обеда getDinnerOrderFunction со следующим меню:
ира
function createDrinkOrder(passenger) { Переводим этого пассаж
в эко ном -кл асс на этот рейс
var orderFunction; а).
if (passenger.ticket = = = "firstclass") { (для тестирования код
orderFunction = function() {
alert("Would you like a cocktail or wine?");
};
} else if (passenger.ticket = = = "premium") {
orderFunction = function() {
Новый код для обработки
alert("Would you like wine, cola or water?");
пассажиров эконом-класса.
}; Теперь мы возвращаем одну
} else { из трех разных функций
orderFunction = function() { заказа в зависимости от
типа билета, купленного
alert("Your choice is cola or water."); пассажиром.
};
}
return orderFunction;
динить
как удобно объе , ко-
} Посмотрите, ун кц ии
в одной ф
всю эту логику ат ь пр ав ил ьную
создав
торая умеет ир а.
для пассаж
функцию заказа
а логи-
и заказа вся эт
При оформлени у нас уже имеется
;
ку уже не нужна едназначенная для
, пр
функция заказа ажира.
сс
конкретного па
490 глава 10
первоклассные функции
function createDinnerOrder(passenger) {
var orderFunction;
if (passenger.ticket = = = "firstclass") {
orderFunction = function() {
alert("Would you like chicken or pasta?"); Мы добавляем
}; новую функцию
} else if (passenger.ticket = = = "premium") { createDinnerOrder,
которая генерирует
orderFunction = function() { функцию заказа обе-
alert("Would you like a snack box or cheese plate?"); да для пассажира.
};
} else { Функция работает
по тому же
orderFunction = function() { принципу, что
alert("Would you like peanuts or pretzels?"); и createDrinkOrder:
}; проверяет тип
} билета пассажира
и возвращает
return orderFunction; функцию заказа,
} адаптированную
для пассажира.
function serveCustomer(passenger) {
var getDrinkOrderFunction = createDrinkOrder(passenger);
var getDinnerOrderFunction = createDinnerOrder(passenger);
Мы создаем функцию
заказа обеда, предна
getDrinkOrderFunction(); значенную для этого
пассажира...
// Предложить обед
getDinnerOrderFunction(); ...а затем вызываем ее всюду,
где нужно будет предложить
getDrinkOrderFunction(); пассажиру обед.
getDrinkOrderFunction();
// Включить кино
getDrinkOrderFunction();
// Забрать мусор
}
function servePassengers(passengers) {
for (var i = 0; i < passengers.length; i++) {
serveCustomer(passengers[i]);
}
}
servePassengers(passengers);
Метод sort отсортировал numbersArray по возрастанию, Функция сравнения, передаваемая sort, возвра-
потому что при возвращении 1, 0 и -1 мы сообщаем щает число большее, равное или меньшее 0
методу sort: в зависимости от сравниваемых значений: если
первое значение больше второго, то возвра-
1: первое значение располагается после второго;
щается число больше 0; если значения равны,
0: значения эквивалентны, их можно не переставлять; возвращается 0; если первое значение меньше
второго, то возвращается число меньше 0.
-1: первое значение располагается перед вторым.
Переход к сортировке по убыванию сводится к простой Можно ли на основе этой информации и факта,
инверсии этой логики: 1 означает, что второе значение что мы сравниваем числа в compareNumbers,
должно следовать за первым, а -1 — что второе значе существенно сократить объем кода?
ние должно предшествовать первому (с 0 их порядок Вот наше решение:
не важен). Напишите новую функцию сравнения для
сортировки по убыванию:
function compareNumbers(num1, num2) {
return num1 - num2;
function compareNumbersDesc(num1, num2) {
}
num2 > _____)
if (_____ num1 {
Чтобы сократить эту функцию до одной
return 1;
строки кода, достаточно вернуть резуль-
} else if (num1 = = num2) { тат вычитания num2 из num1. Проверь-
те на паре примеров — вы поймете, как
return 0;
работает это решение. И помните, что
} else { функция сравнения должна возвращать
return -1; число больше 0, 0 или меньше 0, а не
конкретно 1, 0 и -1 (хотя часто встре-
} чается код, возвращающий именно эти
} значения).
492 глава 10
первоклассные функции
Итак, сортировка по свойству sold успешно заработала. Пора написать функции сравнения
для всех остальных свойств объекта продукта: name, calories и color. Внимательно проверьте
результаты, выводимые на консоль; убедитесь в том, для каждого вида сортировки список
упорядочивается правильно. Ниже приведено наше решение.
products.sort(compareCalories);
console.log("Products sorted by calories:");
printProducts(products);
products.sort(compareColor);
console.log("Products sorted by color:");
printProducts(products);
Серьезные функции
С тех пор как я узнал
об анонимных функциях,
моя выразительность
выросла на 200%.
В переменной handler хранится ссылка на функ- Повторный вызов обработчиков onload нежела-
цию. телен — он может создать проблемы, поскольку
обработчики обычно выполняют операции,
При назначении обработчика свойству window.
относящиеся к инициализации всей страницы.
onload присваивается ссылка на функцию.
Функциональные выражения создают ссылки
Переменная handler существует только для
на функции.
того, чтобы быть назначенной свойству window.
onload. А мы упоминали о том, что при назначении
обработчика window.onload присваивается
Переменная handler больше нигде не исполь-
ссылка на функцию?
зуется — этот код должен выполняться только
при исходной загрузке страницы.
496 глава 11
анонимные функции, область действия и замыкания
Затем функциональное
function() { alert("Yeah, that page loaded!"); } выражение назначается
непосредственно свой-
window.onload = ; ству window.onload.
Мозговой
штурм
window.onload = init;
var cookies = {
instructions: "Preheat oven to 350...",
bake: function(time) {
console.log("Baking the cookies.");
setTimeout(done, time);
}
};
function init() {
var button = document.getElementById("bake");
button.onclick = handleButton;
}
function handleButton() {
console.log("Time to bake the cookies.");
cookies.bake(2500);
}
function done() {
alert("Cookies are ready, take them out to cool.");
console.log("Cooling the cookies.");
var cool = function() {
alert("Cookies are cool, time to eat!");
};
setTimeout(cool, 1000);
}
498 глава 11
анонимные функции, область действия и замыкания
Нельзя ли покороче...
Ладно, нам не хочется поднимать этот вопрос, потому что вы значительно
продвинулись в работе с функциями — вы умеете передавать их, присваи-
вать переменным, возвращать их из функций — и все же ваши программы
остаются чуть более многословными, чем необходимо (можно сказать,
они не так выразительны, как могли бы быть). Пример:
с именем
Вполне обычная функция
larm выв оди т соо бщ ение о том,
cookieA
а вын им ат ь из духовки.
что печень е пор
function cookieAlarm() {
alert("Time to take the cookies out of the oven");
};
Второй аргумент
За именем вызываемой функции следует после
setTimeout следует открывающая функционального
круглая скобка, а затем первый аргу- выражения.
мент — функциональное выражение.
setTimeout(function() {
alert("Time to take the cookies out of the oven");
}, 600000);
500 глава 11
анонимные функции, область действия и замыкания
Упражнение Давайте убедимся в том, что вы правильно поняли синтаксис передачи анонимных
функциональных выражений другим функциям. Преобразуйте следующий код, что-
бы вместо переменной (в данном случае vaccine) в нем использовалось анонимное
функциональное выражение.
function vaccine(dosage) {
if (dosage > 0) {
inject(dosage);
}
}
Запишите здесь
свою версию.
И обязательно
проверьте свой
ответ, прежде
чем двигаться
дальше!
часто
Задаваемые
вопросы
В: Все эти анонимные функции — какая-то экзотика. Мне
лее вразумительным и полезным. Код, использующий анонимные
функции, вам будет встречаться очень часто, так что стоит включить
действительно необходимо это знать? этот прием в ваш творческий арсенал.
О: Да. Анонимные функциональные выражения часто встречаются В: Если первоклассные функции настолько полезны, то по-
в коде JavaScript, поэтому если вы хотите читать код других людей
чему их нет в других языках?
или понимать библиотеки JavaScript, вы должны знать, как работает
этот синтаксис, и узнавать его при использовании.
О:
В: Анонимные фукциональные выражения действительно
Вообще-то есть (и даже там, где нет, их собираются доба-
вить). Например, в таких языках, как Scheme и Scala, реализо-
улучшают код? Мне кажется, что они только усложняют его вана поддержка первоклассных функций на уровне JavaScript.
и затрудняют его чтение и понимание. Другие языки — такие, как PHP, Java (в последней версии), C#
502 глава 11
анонимные функции, область действия и замыкания
var migrating = true; Но при попытке вызова Что происходит при попытке
функции fly происходит вызова неопределенной функции.
нк-
if (migrating) { ошибка, потому что фу
fly ещ е не опр еде лен а...
quack(4); ция
Возможно, вы уже
fly(4); видели подобные ошибки
} ранее (в зависимости от
используемого браузера):
var fly = function(num) { ...потому что TypeError: свойство ‘fly’
for (var i = 0; i < num; i++) {
функция fly оста- объекта [object Object]
ется неопределенной не является функцией.
console.log("Flying!"); вплоть до выполне-
ния этой команды,
}
которая следует
}; уже после вызова fly.
function quack(num) {
for (var i = 0; i < num; i++) {
console.log("Quack!");
}
}
Что же это означает? Прежде всего то, что вы можете размещать объявления функций в любой точке
кода — в начале, в конце, в середине — и вызывать их тогда, когда считаете нужным. Объявления функций
создают функции, которые могут определяться в любой точке кода.
С функциональными выражениями дело обстоит иначе, потому что они остаются неопределенными
до момента их обработки. Итак, даже если функциональное выражение присваивается глобальной пе-
ременной, как это было сделано с fly, вы не сможете использовать эту переменную для вызова до того,
как функция будет определена.
В этом примере обе функции имеют глобальную область действия — то есть обе функции после определения
видны в любой точке кода. Но нам также необходимо учитывать вложенные функции (то есть функции,
определяемые внутри других функций), потому что они влияют на область действия этих функций. Да-
вайте посмотрим, о чем идет речь.
504 глава 11
анонимные функции, область действия и замыкания
Возьмите карандаш и пометьте в этом коде границы области действия функций fly, quack,
Упражнение wingFlapper и quacker. Кроме того, отметьте те места, где, по вашему мнению, эти функции
находятся в области действия, но при этом остаются неопределенными.
506 глава 11
анонимные функции, область действия и замыкания
Образец #1
Постарайтесь сфор- var secret
мулировать свое = "007";
мнение, запишите его, function ge
а затем переверните tSecret() {
страницу. var secret
= "008";
function ge
tValue() {
return secr
} et;
return getV
} alue();
2
Образец # getSecret();
t = "007";
var secre
) {
getSecret(
function "008";
t =
var secre
{
getValue()
function t;
cre
return se
}
tValue;
return ge
);
} getSecret(
lueFun =
var getVa
un();
getValueF
Пока не подсматривайте
в решение, приведенное
в конце главы; мы еще
вернемся к этой задаче
немного позднее.
var justAVar = "Oh, don't you worry about it, I'm GLOBAL";
Та же функция.
function whereAreYou() {
var justAVar = "Just an every day LOCAL";
И замещающая переменная.
function inner() {
Но теперь мы имеем дело с вложенной функцией,
return justAVar; которая обращается к переменной justAVar. Но какой
} именно? И снова выбирается переменная из ближай-
шей внешней функции. Итак, будет использоваться
та же переменная, что и в прошлый раз.
return inner();
} Здесь мы вызываем внутреннюю функцию
и возвращаем ее результат...
508 глава 11
анонимные функции, область действия и замыкания
var justAVar = "Oh, don't you worry about it, I'm GLOBAL";
function whereAreYou() {
var justAVar = "Just an every day LOCAL"; Здесь ничего не изменилось,
те же переменные и функции.
function inner() {
return justAVar;
}
Но вместо того чтобы вызывать
внутреннюю функцию, мы возвращаем ее.
return inner;
}
Я прав!
Нет, я!!!!
Фрэнк: Что значит «ты права»? Это нарушение законов физики... или чего-нибудь в этом роде. Локальная
переменная уже не существует... Я хочу сказать, при выходе из области действия локальная переменная
прекращает существовать. Она дезинтегрируется! Фильм «ТРОН» видела?
Джуди: Может, в ваших немощных языках C++ и Java... но не в JavaScript.
Джим: Серьезно? Функция whereAreYou пропала, а локальная версия justAVar больше не существует.
Джуди: Если бы ты послушал, что я тебе только что сказала... В JavaScript все работает не так.
Фрэнк: Так подскажи нам, Джуди. Как же оно работает?
Джуди: При определении внутренней функции локальная переменная justAVar находится в области
действия этой функции. С лексической областью действия каждый раз, когда вызывается inner, локальная
переменная находится поблизости на тот случай, если она понадобится функции.
Фрэнк: Понятно, но как я уже сказал, это нарушает законы физики. Функция whereAreYou, определив-
шая локальную версию переменной variable, больше не существует.
Джуди: Верно. Функции whereAreYou нет, но ее область действия вокруг inner может использоваться.
Джим: Это как?
Джуди: Давайте посмотрим, что происходит при определении и возвращении функции…
к Джо
ТОРА: Ка
ОТ РЕДАК ять рубашку, пока
ен
успел пом цу?!
ст н ул страни
перели
510 глава 11
анонимные функции, область действия и замыкания
Снова о функциях
Должны вам признаться: мы до сих пор еще не рассказали всего о функциях. Даже когда
вы спросили: «На что на самом деле указывает ссылка на функцию?», мы постарались
обойти этот вопрос. «Лучше думать о них как об указателях на “замороженный” код,
который можно в любой момент извлечь и пустить в дело», — сказали мы.
Пришло время раскрыть все секреты.
Для этого мы посмотрим, что же в действительности происходит во время выполнения
с этим кодом, начиная с функции whereAreYou:
function inner() {
return justAVar;
3 Затем создается функция
} justAVar =
с именем inner. "Just an... LOCAL"
return inner;
} function inner() {
return justAVar;
}
Сначала вызывается whereAreYou. Мы уже знаем, что она возвращает ссылку на функцию,
1 поэтому создаем переменную innerFunction и присваиваем ей полученную функцию. Пом-
ните, что ссылка на функцию связана со своим окружением?
justAVar =
function inner() {
var innerFunction = whereAreYou(); return justAVar;
"Just an... LOCAL"
}
justAVar =
var result = innerFunction(); function inner() { "Just an... LOCAL"
return justAVar;
}
inner
Function
justAVar содержит
значение “Just an
Функция состоит из одной команды, every day LOCAL".
которая возвращает justAVar. Оно и будет воз-
Чтобы получить значение justAVar, вращено.
мы обращаемся к окружению.
512 глава 11
анонимные функции, область действия и замыкания
L"
CA
Наконец, результат функции присваивается переменной result
3
LO
и выводится на консоль.
y
da
y
er
ev
var result = innerFunction();
an
console.log(result); t
innerFunction возвращает значение “Jus
кот орое бере тся
t
an every day LOCA L",
из окружения. Возвращенное значение result
присваивается переменной result.
Консоль JavaScript
Just an every day LOCAL
часто
Задаваемые
вопросы
В: Что вы имеете в виду, когда говорите, что место опре- В: Зачем нужны лексические области действия и окружения?
деления переменной определяется лексической областью Я думал, что в том простом примере ответом будет строка
действия? «Oh, don’t you worry about it, I’m GLOBAL». По крайней мере
В: Как работает окружение для функций, вложенных на не- В: Переменные-параметры тоже включаются в окружение?
сколько уровней?
Ладно, мы согласны, что это определение выглядит не слишком вразумительно. И почему замыкания
называются замыканиями? Давайте разберем этот момент, потому что, честное слово, он может стать
одним из решающих вопросов на собеседовании при приеме на работу или будущем повышении.
Чтобы понять смысл термина замыкание, необходимо понять концепцию «замкнутости» функции.
beingFunny = true;
if (beingFunny) {
ending = " -- I'm just sayin!";
} else if (notSoMuch) {
beingFunny = true;
ending = " -- Not so much."; justSayin = false;
} oocoder = true;
alert(phrase + ending);
} notSoMuch = true;
Замыкание
function justSayin() {
beingFunny = true;
// ...
notSoMuch = false;
}
inConversationWith = "Paul";
ая count.
var count = 0; Глобальная переменн
console.log(counter());
Последовательно уве- 1
личиваем и выводим 2
console.log(counter()); значение счетчика. 3
console.log(counter());
ю
мещается в функци
Переменная count по nt ст ан ови тс я
cou
function makeCounter() { makeCounter. Теперь
ьной переменной.
var count = 0; локальной, а не глобал
518 глава 11
анонимные функции, область действия и замыкания
function counter() { 1
count = count + 1; 2
return count;
3
}
return counter;
} Счетчик работает
var doCount = makeCounter(); как и положено...
console.log(doCount());
console.log(doCount());
console.log(doCount());
Взгляд за кулисы
function counter() {
А теперь проанализируем код шаг за шагом, чтобы count = count + 1; var count = 0;
Ваша очередь. Попробуйте создать замыкания, описанные ниже. Мы понимаем, что эта
Упражнение задача не из простых, поэтому при необходимости можете подсматривать в ответы. Важ-
но, чтобы вы самостоятельно поработали над примерами и в какой-то момент начали
полностью понимать их.
Первая задача (10 очков): функция makePassword получает пароль в аргументе и возвращает
функцию, которая принимает введенную строку и возвращает true, если введенная строка
совпадает с паролем (иногда, чтобы понять суть замыкания, приходится перечитывать опи-
сание несколько раз):
function makePassword(password) {
return __________________________ {
return (passwordGuess = = = password);
};
}
Следующая задача (20 очков): функция multN получает число (назовем его n) и возвращает
функцию. Эта функция также получает число, умножает его на n и возвращает результат.
function multN(n) {
return __________________________ {
return _____________;
};
}
Последняя задача (30 очков): разновидность счетчика, созданного ранее в этой главе. Функция
makeCounter не получает аргументов, но определяет переменную count. Затем она создает и
возвращает объект с единственным методом increment. Этот метод увеличивает переменную
count и возвращает ее.
520 глава 11
анонимные функции, область действия и замыкания
Имеется функция...
setTimeout(function() { нной...
...со свободной переме
alert(doneMessage);
...которая используется как обработчик
}, n); при вызове setTimeout.
function setTimer(doneMessage, n) {
setTimeout(function() {
Здесь создается замыкание.
alert(doneMessage);
}, n); essage
Значение doneM
ен яе т ся по сл е
изм .
im eo ut
doneMessage = "OUCH!"; вызова setT
}
setTimer("Cookies are done!", 1000);
setTimeout(function() {
alert(doneMessage);
}, n);
канием. }
doneMessage = "OUCH!";
522 глава 11
анонимные функции, область действия и замыкания
ждом
Наша цель: при ка
общение
нажатии кнопки со
> об но вл яет ся и в нем
в <div
ли чес тв о нажатий.
выводится ко
Перейдем к написанию кода. Вообще-то код для этого примера можно написать
и без замыканий, но, как вы убедитесь, с замыканием код становится более
компактным и даже чуть более эффективным.
Программа с замыканием
Версия без замыкания выглядит вполне разумно, кроме глобальной переменной,
с которой могут возникнуть проблемы. Давайте перепишем код с использованием
замыкания и сравним его с исходным вариантом. Сейчас мы только приведем сам
код, а потом проанализируем его после тестирования.
по отно-
Теперь все переменные локальны
dow .onlo ad. Ник аких проблем
window.onload = function() { шению к win
var count = 0; с конфликтами имен.
524 глава 11
анонимные функции, область действия и замыкания
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"> Так выглядит страница.
<title>Click me!</title>
<style>
body, button { margin: 10px; }
div { padding: 10px; }
</style>
Обновите свой файл
<script>
divClosure.html.
window.onload = function() {
var count = 0;
var message = "You clicked me ";
var div = document.getElementById("message");
window.onload = function() {
var count = 0;
var message = "You clicked me ";
var div = document.getElementById("message");
А теперь замыкание
назначается свойству onclick
кнопки Click me! на странице. Так, После завершения функции
window.onload ничего не про-
с обработчиком загрузки закончили...
исходит — до того, как будет
Теперь нужно дождаться, пока кто-нибудь нажата кнопка Click me!.
нажмет кнопку.
526 глава 11
анонимные функции, область действия и замыкания
-
button более не суще
И хотя переменная т с за вер ше ни ем
адае
ствует (она проп ки
load), объект кноп
функции window.on и на ше за мы ка ние
DOM,
присутствует в ck .
йстве oncli
oncl
ick сохранило его в сво
function() {
var message =
count++; "You clicked me ";
div.innerHTML = ...;
var count = 0;
}
var div = [object]
Что это у нас?
Замыкание. Значит, я могу
найти значения свободных
переменных в окружении.
в замы-
ие: переменная div
Обратите вниман
function() {
. Пр и ин иц иа ли зации var message =
ъект
кании содержит об
count++; "You clicked me ";
об ъе кт,
d мы сохраняем
div в window.onloa
div.innerHTML = ...;
.ge tE lem en tB yId , var count = 1;
ment
полученный от docu
}
а по лу ча ть
поэтому снов
var div = [object]
в переменной div, ь. Хра-
из DO M не нужно; он уже ест
объект ща ет объем
-чуть сокра
нение объекта чуть ра бо ту ко да .
ет
вычислений и ускоря
Замыкание продолжает
существовать до закры-
тия страницы. Оно готово
взяться за дело каждый раз,
когда нажимается кнопка!
Образец #1
var secret = "007";
Окружение
function getSecret() {
var secret = "008";
function getValue() {
return secret;
}
return getValue();
}
getSecret();
Образец #2
var secret = "007";
Окружение
function getSecret() {
var secret = "008";
function getValue() {
return secret;
}
return getValue;
}
var getValueFun = getSecret();
getValueFun();
528 глава 11
анонимные функции, область действия и замыкания
КЛЮЧЕВЫЕ
МОМЕНТЫ
530 глава 11
анонимные функции, область действия и замыкания
Давайте убедимся в том, что вы правильно поняли синтаксис передачи анонимных функцио-
нальных выражений другим функциям. Преобразуйте следующий код, чтобы вместо перемен-
ной (в данном случае vaccine) в нем использовалось анонимное функциональное выражение.
Ниже приведено наше решение:
Обратите внимание:
-
administer(patient, function(dosage) { ничто не мешает исполь
if (dosage > 0) { зовать мн ого ст роч ные
inject(dosage); функциональные выраже-
е
} ния в аргументах. Будьт
внимательны с синтак-
}, time); сисом; ошибиться проще
простого!
Ваша очередь. Попробуйте создать замыкания, описанные ниже. Мы понимаем, что эта задача
не из простых, поэтому при необходимости можете подсматривать в ответы. Важно, чтобы вы
самостоятельно поработали над примерами и в какой-то момент начали полностью понимать их.
Наши решения:
Первая задача (10 очков): функция makePassword получает пароль в аргументе и возвращает функцию,
которая принимает введенную строку и возвращает true, если введенная строка совпадает с паролем
(иногда, чтобы понять суть замыкания, приходится перечитывать описание несколько раз):
Функция, возвращаемая
авля-
makePassword(password) { из makePassword, предст
ыка ние с окр уже-
ет собой зам
return function guess(passwordGuess) { м, сод ерж ащ им сво бод ную
ние
return (passwordGuess = = = password); переменную password.
}; Мы передаем
} makePassword значение
“secret”, которое со-
var tryGuess = makePassword("secret");
храняется в окружении
console.log("Guessing 'nope': " + tryGuess("nope")); замыкания.
console.log("Guessing 'secret': " + tryGuess("secret"));
Давайте убедимся в том, что вы правильно поняли синтаксис передачи анонимных функцио-
нальных выражений другим функциям. Преобразуйте следующий код, чтобы вместо перемен-
ной (в данном случае vaccine) в нем использовалось анонимное функциональное выражение.
Ниже приведено наше решение (продолжение):
Следующая задача (20 очков): функция multN получает число (назовем его n) и возвращает функцию.
Эта функция также получает число, умножает его на n и возвращает результат.
из multN,
function multN(n) { Функция, возвращаемая
соб ой зам ыкание
представляет
return function multBy(m) { кру жен ием , сод ерж ащ им
с о
свободную пер еме нну ю n.
return n*m;
};
Итак, вызов multN(3)
} возвращает функцию,
var multBy3 = multN(3); которая умножает любое
переданное число на 3.
console.log("Multiplying 2: " + multBy3(2));
console.log("Multiplying 3: " + multBy3(3));
Последняя задача (30 очков): разновидность счетчика, созданного ранее в этой главе. Функция makeCounter
не получает аргументов, но определяет переменную count. Затем она создает и возвращает объект с един-
ственным методом increment. Этот метод увеличивает переменную count и возвращает ее.
532 глава 11
анонимные функции, область действия и замыкания
В переменной handler хранится ссылка на функ- Повторный вызов обработчиков onload нежела-
цию. телен — он может создать проблемы, посколь-
ку обработчики обычно выполняют операции,
При назначении обработчика свойству window.
относящиеся к инициализации всей страницы.
onload присваивается ссылка на функцию.
Функциональные выражения создают ссылки
Переменная handler существует только для
на функции.
того, чтобы быть назначенной свойству window.
onload. А мы упоминали о том, что при назначении об-
работчика window.onload присваивается ссылка
Переменная handler больше нигде не исполь-
на функцию?
зуется — этот код должен выполняться только
при исходной загрузке страницы.
if (beingFunny) {
ending = " -- I'm just sayin!";
} else if (notSoMuch) {
beingFunny = true;
ending = " -- Not so much."; justSayin = false;
} oocoder = true;
alert(phrase + ending);
} notSoMuch = true;
window.onload = init;
var cookies = {
instructions: "Preheat oven to 350...",
bake: function(time) {
console.log("Baking the cookies.");
setTimeout(done, time);
}
};
function init() {
var button = document.getElementById("bake");
button.onclick = handleButton;
}
function handleButton() {
console.log("Time to bake the cookies.");
cookies.bake(2500);
}
function done() {
alert("Cookies are ready, take them out to cool.");
console.log("Cooling the cookies.");
var cool = function() {
alert("Cookies are cool, time to eat!");
};
setTimeout(cool, 1000);
}
534 глава 11
анонимные функции, область действия и замыкания
var cookies = {
instructions: "Preheat oven to 350...",
bake: function(time) {
console.log("Baking the cookies.");
setTimeout(done, time);
}
};
function done() {
alert("Cookies are ready, take them out to cool.");
console.log("Cooling the cookies."); Получите дополн
ительный
балл, если догада
var cool = function() { лись напря-
мую передать фу
нкцию cool
alert("Cookies are cool, time to eat!"); при вызове setTim
eout:
};
setTimeout(cool, 1000);
} setTimeout(function() {
alert("Cookies are cool, time to eat!");
}, 1000);
Реш
ени
е
ДЛЯ НАСТОЯЩИХ ПРОФЕССИОНАЛОВ
Нам нужен эксперт по первоклассным функциям — и нам порекомендовали вас! Теперь, когда
вы знаете, как работают замыкания, сможете ли вы объяснить, почему оба фрагмента дают результат
008? Запишите все переменные, сохраняемые в окружении, для приведенных ниже функций. Учтите,
что окружение вполне может быть пустым. Ниже приведено наше решение.
Образец #1
var secret = "007";
Окружение
function getSecret() { ая
свободн
var secret = "008"; secret —ная
н
переме e... secret = "008"
lu
в getVa
function getValue() {
return secret;
}
return getValue(); ...поэтому она сохраняется в окружении getVa
lue.
Но мы не возвращаем getValue из getSecret,
} поэтому
замыкание никогда не становится видимым
за пре-
getSecret(); делами контекста, в котором оно было созда
но.
Образец #2
var secret = "007";
Окружение
function getSecret() { ая
свободн
var secret = "008"; secret —ная
н
переме e... secret = "008"
g e t Va lu
в
function getValue() {
return secret;
}
return getValue;
...а здесь создается замыкание, возвращаемое
} из getSecret. Соответственно, при вызове
var getValueFun = getSecret(); getValueFun (getValue) в другом контексте
(глобальной области действия) использует
getValueFun(); ся
значение secret из окружения.
536 глава 11
анонимные функции, область действия и замыкания
(function(food) {
if (food = = = "cookies") {
alert("More please");
} else if (food = = = "cake") {
alert("Yum yum");
}
})("cookies");
Ваша задача — не только определить, что делает этот код, но и как он это
делает. Для этого проще пойти в обратном направлении — то есть возьмите
анонимную функцию, присвойте ее переменной, а затем используйте эту
переменную повсюду, где раньше было функциональное выражение. Станет
ли смысл кода более понятным? И что же делает этот код?
Выделяем функцию из при-
веденного кода — здесь мы
var eat = function(food) { назвали ее eat. При желании
if (food = = = "cookies") { также можно было исполь-
зовать объявление функции.
alert("More please");
Конечно, запись } else if (food = = = "cake") {
eat (“cookies”) вы
глядит
привычнее, но зд ес
ь мы
alert("Yum yum");
показываем, как
подста- }
вить eat в привед
енное
выше функционал
ьное };
выражение.
(eat)("cookies"); Итак, этот код всего лишь
определяет встроенное функ-
По сути здесь функция eat вызывается циональное выражение, после
для значения “cookies”. Но откуда взя- чего немедленно вызывает его
лись лишние круглые скобки? с аргументами.
Создание объектов
var taxi = {
make: "Webville Motors",
model: "Taxi",
year: 1955,
color: "yellow",
passengers: 4,
convertible: false,
mileage: 281341,
При использовании объектного
started: false, литерала все части объек-
та записываются в фигурных
скобках. Результат представ-
start: function() { this.started = true;}, ляет собой объект JavaScript,
stop: function() { this.started = false;}, который обычно присваивается
переменной для использования
drive: function { в будущем.
// Код управления машиной
}
};
Мозговой
штурм
Представьте, что вам пришлось создать объекты такси для целого автопарка. Какие проблемы
могут возникнуть из-за использования объектных литералов?
Пальцы устанут набирать такой код! Код методов start, stop и drive придется
повторять снова и снова.
Можно ли гарантировать, что все объекты
имеют одинаковые свойства? А если сде- А если вы решите добавить или удалить
лать ошибку, опечатку, пропустить свойство? свойство (или изменить процедуру запу-
ска или остановки двигателя)? Придется
Много объектных литералов — много кода.
вносить изменения во всех объектах.
Разве это не замедлит загрузку страницы
в браузере? Кому нужны такси, если есть Uber?
540 глава 12
нетривиальное создание объектов
drive: function {
// drive code here
var tbird} = {
var rocketCar = { make: "Ford",
",
make: "Galaxy", }; l: "Thunderbird
mode
model: "4000", year: 1957,
year: 2001, var toyCar
= { passengers: 4,
color: "white", make: "Matte
l", convertible: true,
passengers: 6, model: "Pee
Wee", , started: false,
convertible: false, color: "blu
e", oilLevel: 1.0,
mileage: 60191919, type: "wind
up",
, started: false, price: "2.9
9" start: function() {
}; if (oilLevel > .75) {
start: function() { this.started = true;
this.started = true; }
}, },
542 глава 12
нетривиальное создание объектов
Конструкторы
Конструкторы объектов, или сокращенно «конструкторы», открыва-
ют путь к улучшенному созданию объектов. Конструктор можно
Конструкторы объектов
представить себе как маленькую фабрику, способную создать бес-
конечное множество похожих объектов.
и функции тесно
В программном коде конструктор напоминает функцию, возвра- связаны друг с другом.
щающую объект: он определяется один раз, а затем вызывается
везде, где потребуется создать новый объект. Но как вы вскоре Помните об этом, когда
увидите, создание конструктора потребует чуть больших усилий.
будете учиться создавать
Как проще всего увидеть работу конструктора? Конечно, создать
его. Вернемся к нашему старому знакомому, объекту, представляю- и использовать
щему собаку, и напишем конструктор для создания таких объектов.
Ниже приведена версия объекта собаки, которую мы использовали конструкторы.
ранее — с кличкой (name), породой (breed) и весом (weight).
name
breed
weight
Dog
Если мы захотим определить объект собаки в виде литерала, Лично мне конструкторы
напоминают моего
он будет выглядеть примерно так:
создателя Франкенштейна.
Они тоже строят нечто
var dog = {
Простой объект, создава- новое из составных частей
name: "Fido", емый на основе литерала. функций и объектов. Разве
breed: "Mixed", Теперь нужно придумать, не замечательно?
как создать целую свору
weight: 38 таких объектов.
};
}
Здесь не используются локальные переменные,
как во многих функциях. Вместо них используется
Обратите
Имена свойств ключевое слово this, которое ранее нам встречалось
внимание:
и параметров только внутри объектов.
конструктор
не обязаны совпа-
ничего не воз-
дать, но часто
вращает.
совпадают — это Оставайтесь с нами: сейчас мы объясним,
еще одно удобное как использовать конструкторы, и все
соглашение. встанет на свои места.
544 глава 12
нетривиальное создание объектов
Итак, чтобы создать новый объект собаки с кличкой “Fido”, породой “Mixed”
и весом 38 фунтов, мы ставим ключевое слово new, за которым следует вызов
конструктора с нужными аргументами. После обработки этой команды в пе-
ременной fido будет храниться ссылка на созданный объект.
Определив конструктор для объектов собак, мы можем создавать их и далее:
Гораздо удобнее, чем с литералами, верно? К тому же при таком создании объ-
ектов мы знаем, что все объекты собак содержат необходимый набор свойств:
name, breed и weight.
Вспомните
2 Затем new заносит в this ссылку на новый объект. главу 5: в this
хранится ссылка
this на текущий объ-
ект, с которым
связан выполняе-
3 После подготовки this вызывается функция Dog,
мый код.
которой передаются аргументы "Fido", "Mixed" и 38.
"Fido" "Mixed" 38
breed: "Mixed"
4 Затем вызывается тело функции. Как и боль-
шинство конструкторов, Dog задает значения
this
свойств только что созданного объекта this.
name: "Fido"
breed: "Mixed"
и
При выполнении тела функци
объ ект зап олн яет ся weight: 38
Dog новый this
м
тремя свойствами, которы
ют ся зна чен ия соо т-
присваива
ветствующих параметров.
546 глава 12
нетривиальное создание объектов
name: "Fido"
breed: "Mixed"
fido weight: 38
СТАНЬ браузером
Ниже приведен код JavaScript, содержащий ошибки. Представьте себя на месте брау
зера и найдите ошибки в этом коде. А когда упражнение будет выполнено,
загляните в конец главы и посмотрите, удалось ли вам отыскать все
ошибки до единой. Кстати, мы уже дошли до главы 12, если хотите, можете
делать замечания по стилю программирования. Вы заслужили это право.
548 глава 12
нетривиальное создание объектов
часто
Задаваемые
вопросы
В: Почему имена конструкторов начи-
свойствам объекта. По-настоящему важны
только имена свойств объекта. Впрочем,
ется с только что созданным объектом,
чтобы весь код, выполняемый в конструк-
наются с прописной буквы? для наглядности часто используются оди- торе, мог применяться к этому конкретному
ся разработчиками JavaScript, помогает от- Позднее при вызове метода ссылка this
личить конструкторы от обычных функций.
Почему? Потому что при вызове конструк- В: Объект, созданный конструктором,
указывает на объект, метод которого был
вызван. Таким образом, this в ваших ме-
торов должен использоваться оператор ничем не отличается от объекта, создан- тодах всегда обозначает объект, метод
new. В общем случае такое выделение ного на базе литерала? которого был вызван.
имени конструктора упрощает его иден-
тификацию при чтении кода. О: Да, по крайней мере пока мы не зай- В: Создавать объекты конструктором
О:
объявлять и использовать переменные, и обеспечивает единую структуру объектов.
включать циклы for, вызывать другие функ- Да, объекты можно создавать и так,
ции и т. д. Единственное, чего не следует но, как мы упоминали ранее, при исполь- Но иногда требуется создать объект
делать в конструкторе, так это возвращать зовании new выполняются некоторые до- «на скорую руку», который, возможно, бу-
значение (отличное от this), потому что в полнительные операции. Эта тема более дет использоваться всего один раз. Ком-
этом случае конструктор не вернет объект, подробно рассматривается позднее в этой пактность и выразительность литералов
который ему положено конструировать. главе, а также в главе 13. хорошо подходят для этой задачи.
В: Имена параметров конструкто- В: Я еще не до конца понимаю смысл Итак, на самом деле все зависит от ваших
ра должны соответствовать именам this в конструкторе. Мы используем потребностей. Каждый способ пригодится
свойств? this при назначении свойств объекта, в подходящей ситуации.
550 глава 12
нетривиальное создание объектов
Опасная зона
У конструкторов есть одна особенность, к которой нужно относиться очень внимательно:
не забывайте про ключевое слово new. О нем часто забывают, в конце концов, конструктор —
всего лишь функция, и его можно вызывать без new. Но если вы забудете использовать new
с конструктором, в коде появятся коварные ошибки, которые трудно обнаружить. Давайте
посмотрим, что произойдет, если забыть ключевое слово new...
552 глава 12
нетривиальное создание объектов
откровенно о конструкторе
Интервью недели:
Откровенно о new
Head First: Привет, new, где вы скрывались? Как мы new: Сначала я создаю новый объект. Все думают,
добрались до главы 12, ни разу не увидев вас? что объект создается конструктором, но это делаю я.
Совершенно неблагодарное занятие.
new: Существует великое множество сценариев, ав-
торы которых не используют меня или используют, Head First: Продолжайте…
толком не понимая.
new: Потом я вызываю функцию-конструктор и делаю
Head First: Почему? так, чтобы новый объект, который я создал, был связан
с ключевым словом this в теле функции.
new: Многие разработчики просто используют объ-
ектные литералы или копируют готовый код с моим Head First: Зачем вы это делаете?
участием без понимания того, как я работаю.
new: Чтобы команды в теле функции могли получить
Head First: Это верно… Объектные литералы удоб- доступ к объекту. В конце концов, конструктор нужен
ны, а мне еще не совсем ясно, когда и как вас стоит именно для того, чтобы расширять созданный объект
использовать. новыми свойствами и методами. Если вы используете
new: Пожалуй, вы правы, я отношусь к категории не- конструктор для создания объектов, представляющих
тривиальных возможностей. Xтобы уметь правильно собак или машины, вы захотите дополнить их свой-
использовать меня, необходимо понимать, как рабо- ствами, верно?
тают объекты, как работают функции, как работает Head First: Верно. И что потом?
this... Даже просто для того, чтобы узнать о моем
существовании, потребуется изрядная подготовка! new: Потом я делаю так, чтобы вновь созданный
объект был возвращен из конструктора. Это делается
Head First: Можете рассказать о себе пару слов? Наши просто для удобства, чтобы разработчику не приходи-
читатели уже знают об объектах, функциях и this, лось возвращать его вручную.
самое время им побольше узнать о вас.
Head First: Действительно удобно. Неужели кому-то
new: Дайте подумать… Хорошо, вот: я — оператор, захочется использовать объектные литералы после
который работает совместно с функциями-конструк- знакомства с вами?
торами для создания новых объектов.
new: Вообще-то мы с объектным литералом — старые
Head First: Ммм... Даже неудобно говорить, но это друзья. Он отличный парень, и я сам бы использовал
явно не «в двух словах». его для создания объекта «на скорую руку». А я лучше
new: Ладно вам, я ведь все-таки оператор, а не пиар- подхожу для создания множества похожих объектов,
щик. когда вы хотите организовать повторное использова-
ние кода, обеспечить единство структуры объектов...
Head First: Хорошо, у меня возникло несколько во- а также чтобы использовать некоторые нетривиаль-
просов. Прежде всего — вы оператор? ные возможности, когда будете больше знать по теме.
new: Да! Я оператор. Поставьте меня перед вызовом Head First: Нетривиальные? Расскажите!
функции, и я полностью изменю ситуацию. Оператор
выполняет действия со своими операндами. В моем new: Давайте не будем отвлекаться. Мы подробнее
случае операнд только один, и это вызов функции. поговорим об этом в следующей главе.
Head First: Тогда расскажите в подробностях, как Head First: Надо будет перечитать это интервью.
именно вы работаете. До встречи…
var cadi = {
make: "GM",
var chevy = { model: "Cadillac",
year: 1955,
make: "Chevy", color: "tan",
model: "Bel Air", passengers: 5,
year: 1957, convertible: false,
mileage: 12892,
color: "red", started: false,
passengers: 2, start: function() {...},
convertible: false, var fiat
= { stop: function() {...},
make: "Fia drive: function() {...}
mileage: 1021, model: "5
t",
00", };
started: false, year: 1957
,
color: "M
edium Blue
",
start: function() { passengers
convertibl
: 2,
this.started = true; mileage:
e: false,
88000,
}, started:
false,
start: fu
nction()
stop: func {...},
tion() {.
stop: function() { drive: fu
nc ti
..},
on() {...
}; }
this.started = false;
}, var taxi = {
make: "Webville Motors",
model: "Taxi",
drive: function() { year: 1955,
color: "yellow",
if (this.started) { passengers: 4,
console.log(this.make + " " + convertible: false,
this.model + " goes zoom zoom!"); mileage: 281341,
started: false,
} else { start: function() {...},
console.log("Start the engine first."); stop: function() {...},
drive: function() {...}
} };
}
};
554 глава 12
нетривиальное создание объектов
Используйте все, что вы узнали на последних страницах, для создания конструктора Car.
Упражнение Мы рекомендуем делать это в следующем порядке:
1 Начните с ключевого слова function (мы сделали это за вас), за которым следует имя
конструктора. Затем приведите список параметров; нам понадобится один параметр для
каждого свойства, которому должно присваиваться начальное значение.
2 Затем задайте начальное значение каждого свойства объекта (не забудьте поставить this
перед именем свойства).
function ________(______________________________________________) {
Запишите здесь
результат.
var chevy = new Car("Chevy", "Bel Air", 1957, "red", 2, false, 1021);
var cadi = new Car("GM", "Cadillac", 1955, "tan", 5, false, 12892);
var taxi = new Car("Webville Motors", "Taxi", 1955, "yellow", 4, false, 281341);
var fiat = new Car("Fiat", "500", 1957, "Medium Blue", 2, false, 88000);
Но зачем ограничиваться?
var testCar = new Car("Webville Motors", "Test Car", 2014, "marine", 2, true, 21);
Видите, как легко создавать новые объекты с конструк- Вы можете добавить собственные
торами? А теперь проведем тест-драйв: машины, реальные или вымышленные.
556 глава 12
нетривиальное создание объектов
this.start = function() {
this.started = true;
};
// Остальные методы Такие ошибки трудно об-
} наружить. Если поменять
местами две переменные,
код остается синтаксиче-
Проблема в том, что конструктор Car получает слишком много па- ски правильным, но скорее
раметров; это усложняет его чтение и сопровождение кода. Также всего, работать будет
усложняется написание кода вызова этого конструктора. То, что с ошибками.
на первый взгляд кажется простым неудобством, в итоге создает
больше ошибок, чем вы можете себе представить, — причем часто
ошибок коварных и трудноуловимых. А если пропустить
какое-нибудь значение,
Однако существует стандартный прием, который может использо- возможны самые неожи-
ваться при передаче аргументов как конструктору, так и любой другой данные последствия!
функции. Он работает так: возьмите все аргументы, объедините их в
объектный литерал, а затем передайте этот литерал своей функции.
В этом случае все значения передаются в одном контейнере (литерал),
а вам не нужно беспокоиться о порядке аргументов и параметров.
Давайте перепишем код вызова конструктора Car, а затем слегка пе-
реработаем код конструктора и посмотрим, как работает этот прием.
В нашем примере
сохранен исходный
порядок аргу-
var cadiParams = {make: "GM", ментов, но это
ни в коем случае
model: "Cadillac", не обязательно.
year: 1955,
color: "tan",
passengers: 5,
convertible: false,
mileage: 12892};
mileage: 12892};
558 глава 12
нетривиальное создание объектов
Результат
запишите здесь.
Мозговой
штурм
часто
Задаваемые В: Если typeof не может сказать, представляет ли мой объект
вопросы машину или собаку, то как мне определить, что есть что?
560 глава 12
нетривиальное создание объектов
Экземпляры
Вы не сможете взять объект JavaScript и определить, к какому типу он относится (то есть представляет
ли собаку, машину или что-нибудь еще). В JavaScript объекты являются динамическими структурами
и относятся к типу “object” независимо от того, какие свойства или методы они содержат. Тем не менее
мы можем получить некоторую информацию об объекте, если будем знать, каким конструктором был
создан этот объект.
При каждом вызове конструктора с оператором new вы создаете новый экземпляр объекта. И если в каком-
то случае для этого использовался конструктор Car, то мы можем неформально считать, что созданный
объект представляет машину. А формально созданный объект будет представлять собой экземпляр Car.
var cadiParams = {make: "GM", model: "Cadillac", year: 1955, color: "tan",
passengers: 5, convertible: false, mileage: 12892};
Нам нужна функция с именем dogCatcher, которая возвращает true, если переданный ей объект
Упражнение является собакой (конструктор Dog), и false в противном случае. Напишите эту функцию и проверьте
ее с остальным кодом. Не забудьте свериться с ответом в конце главы, прежде чем читать дальше.
function dogCatcher(obj) {
Добавьте здесь
свой код реализации
.
функции dogCatcher
}
А это тестовый код.
562 глава 12
нетривиальное создание объектов
fido.trust = function(person) {
return (person = = = "Bob");
}; Внимание, анонимная функция!
Мы же предупреждали —
они встречаются очень часто.
Обратите внимание: здесь изменяется только объект fido. Если добавить метод в fido,
то метод появится только в этом объекте. У других объектов, представляющих собак,
его не будет:
Эта команда работает, потому что метод
var notBite = fido.trust("Bob"); trust определен в объекте fido. Переменной
notBite присваивается значение true.
var spot = new Dog("Spot", "Chihuahua", 10); Этот код не работает, потому что
объект spot не содержит метод trust.
notBite = spot.trust("Bob"); Выводится сообщение об ошибке: «TypeError:
Object #<Dog> has no method ‘trust’».
564 глава 12
нетривиальное создание объектов
cadi.chrome = true;
delete cadi.convertible;
566 глава 12
нетривиальное создание объектов
Объект Array
Еще один интересный встроенный объект предназначен для работы с массивами. Хотя
ранее мы создавали массивы в синтаксисе с квадратными скобками [1, 2, 3], массивы
также можно создавать конструктором:
Создает пустой мас-
var emptyArray = new Array(); сив нулевой длины.
Здесь создается новый, пустой объект массива. В него можно в любой момент добавить
новые элементы:
Эта команда выглядит знакомо: так мы всегда
emptyArray[0] = 99; добавляли элементы в массив.
Также можно создать объект массива заданного размера. Допустим, нам нужен массив
для трех элементов:
var n = getNumberOfWidgetsFromDatabase();
В этом коде использу-
var widgets = new Array(n); ются большие массивы,
for(var i=0; i < n; i++) { а конкретный размер
такого массива неиз-
widgets[i] = getDatabaseRecord(i); вестен до выполнения
} программы.
568 глава 12
нетривиальное создание объектов
часто
Задаваемые
вопросы
В: Не понимаю, как работают конструк- может определить, какая информация была В: Оператор instanceof позволяет
торы Date и Array, похоже, они поддержи- передана при вызове, и среагировать соот- узнать, является ли объект экземпляром
вают нуль и более аргументов. В случае ветствующим образом. Также существуют заданного конструктора, но как понять,
Date при вызове без аргументов я по- другие способы, основанные на проверке были ли два объекта созданы одним
лучаю объект текущей даты, но я могу параметров на undefined. конструктором?
передавать аргументы для получения
других дат. Как это делается? В: Мы использовали объект Math ранее О:Это легко проверить:
О: имя начинается с прописной буквы только пляром Object. Считайте, что конструктор
Конечно. Мы эту тему еще не рассма- для того, чтобы вы знали, что это встроен- Object создает наиболее обобщенные объ-
тривали, но каждая функция получает объ- ный объект JavaScript. екты в JavaScript. В следующей главе место
ект со всеми аргументами, переданными Object в системе объектов JavaScript будет
этой функции. По этому объекту конструктор рассмотрено более подробно.
function CarProtoype(
) {
this.make = "Webville
Motors";
this.year = 2013;
this.start = function(
) {...};
Нарисуйте здесь this.stop = function(
) {...};
свою машину. this.drive = function(
) {...};
}
дает?
И что нам это
ую щей главе!
Узнаете в след а глава
и го во ря , эт
Кстат еще
Здесь настройте нц у.. . Но
подходит к ко ев ых
прототип. сп ис ок кл юч
остался
моментов!
570 глава 12
нетривиальное создание объектов
КЛЮЧЕВЫЕ
МОМЕНТЫ
} }
Напишите кон-
структор для
создания уток. P. S. Мы знаем, что вы еще не до конца пони-
Пример объектного маете смысл происходящего, так что пока
литерала. сосредоточимся на синтаксисе.
572 глава 12
нетривиальное создание объектов
анет кон-
Если функция widget ст
, ее им я дол жно начи-
структором W. Ошибкой
бук вы
наться с прописной езное со-
Ставить “var” яет ся, но эт о пол
это не явл соб людать.
ст оит
перед this глашение, которое
не обязательно. function widget(partNo, size) {
Мы не объ-
var this.no = partNo;
являем новые Кроме того, по общепринятому соглаше-
переменные, var this.breed = size; нию имена параметров обычно совпадают
а добавляем с именами свойств — поэтому, вероятно,
}
свойства лучше использовать запись this.partNo
в объ ект. и this.size.
мволов
Вместо си function FormFactor(material, widget) {
ис по ль зу ются
«;»
П ом ните, this.material = material,
запятые. ор
ко нс т ру кт Мы возвращаем this, но это
что ь this.widget = widget,
де рж ат не обязательно — конструк-
должен со
чн ы е ко м ан ды return this; тор сделает это за нас. Эта
обы
елен- команда не приведет к ошибке,
вместо разд }
за пя т ы м и пар но она не нужна.
ных
ач ен ие ».
«имя/зн
Забыли new!
var widgetA = widget(100, "large");
var widgetB = new widget(101, "small");
var formFactorA = newFormFactor("plastic", widgetA);
Между new
и именем var formFactorB = new ForumFactor("metal", widgetB);
конструктора
должен стоять Имя конструктора
записано
пробел. с ошибкой.
574 глава 12
нетривиальное создание объектов
Используйте все, что вы узнали на последних страницах, для создания конструктора Car. Мы рекомендуем
делать это в следующем порядке:
1 Начните с ключевого слова function (мы сделали это за вас), за которым следует имя конструк-
тора. Затем приведите список параметров, понадобится один параметр для каждого свойства,
которому должно присваиваться начальное значение.
2 Затем задайте начальное значение каждого свойства объекта (не забудьте поставить this
перед именем свойства).
2 this.make = make;
car
this.model = model; Каждое свойство объ екта
инициализируется значением
ма-
this.year = year; параметра. Обратите вни
ств
this.color = color; ние на совпадение имен свой
, как то го тр ебует
this.passengers = passengers; и параметров
общепринятое соглашение.
this.convertible = convertible;
this.mileage = mileage;
this.started = false; Свойство started инициали-
зируется значением false.
3
this.start = function() {
this.started = true; Методы остались теми же,
}; но теперь они назначаются
свойствам объекта в несколько
this.stop = function() { ином синтаксисе, потому что
this.started = false; здесь используется конструк-
}; тор, а не объектный литерал.
this.drive = function() {
if (this.started) {
alert("Zoom zoom!");
} else {
alert("You need to start the engine first.");
}
};
}
Консоль JavaScript
Webville Motors limo is a object
Rhapsody In Blue is a object Наш результат.
576 глава 12
нетривиальное создание объектов
Нам нужна функция с именем dogCatcher, которая возвращает true, если переданный ей объект
является собакой (конструктор Dog), и false в противном случае. Напишите эту функцию и проверьте
ее с остальным кодом. Наше решение:
function CarProtoype(
) {
this.make = "Webville
Motors";
this.year = 2013;
Нарисуйте здесь this.start = function(
свою машину. ) {...};
this.stop = function(
) {...};
this.drive = function(
) {...};
}
578 глава 12
13 использование прототипов
Сильные объекты
А если вы не из
учали
Прости, но тебе придется классическое на
следо-
забыть все эти классические штуки вание, считайт
е, вам
повезло — забы
с объектно-ориентированным вать
ничего не прид
наследованием, которые ты ется!
изучала в Java и C++.
580 глава 13
использование прототипов
Итак, использование конструктора дает нам аккуратные, похожие объекты, которые мы можем на-
страивать по своему усмотрению, а также использовать определенные в них методы (в данном случае
такой метод всего один — bark). Кроме того, все объекты получают один и тот же код от конструктора,
что избавит нас от многих хлопот, если в будущем ситуация вдруг изменится. Все это, конечно, хорошо,
но давайте посмотрим, что происходит при выполнении следующего фрагмента:
дубликаты функций.
582 глава 13
использование прототипов
Эй, вы там!
На мобильном
устройстве приложение бы
уже скончалось.
584 глава 13
использование прототипов
Прототип Dog
species: "Canine" Свойства и методы,
bark() общие для всех собак.
run()
Три объекта собак,
wag()
наследующих от прото-
типа. Прототип содер-
жит свойства (в числе
которых и методы),
общие для всех собак.
Сами объекты содержат
свойства, специфические
.
И так для
каждой
Пу
создаваемой
собаки.
Объект содержит
только свойства
name, breed и weight.
586 глава 13
использование прототипов
function bark() {
// code to bark
}
сюда
Начните от порядку: Прототип Dog
и следуй т е по Проверяя прототип Dog,
.
species: "Canine" 4
1, 2, 3, 4, 5 мы видим, что он содержит
bark()
run() function bark() {
// code to bark
метод bark.
}
wag()
function bark() {
// code to bark
}
от общего прототипа.
}
Dog Prototype
function bark() {
species: "Canine" }
// code to bark
bark()
function bark() {
run() }
// code to bark
wag()
Спасибо! Пока
Теперь вы знаете, как использовать наследование для
вы не перешли
создания большого количества объектов собак. Для
на наследование, я тут
всех объектов по-прежнему можно вызывать bark,
едва не загнулся!
но теперь этот метод берется из прототипа. Повторное
использование кода обеспечивается не только тем, что
весь код записывается в одном месте, но и тем, что все
экземпляры используют один и тот же метод bark во
время выполнения, благодаря чему удается избежать
непроизводительных затрат памяти.
С помощью прототипов можно быстро строить объекты,
которые эффективно используют код и могут расши-
ряться новым поведением и свойствами.
588 глава 13
использование прототипов
тается ьзоваться
Прототип ос в fido и fluf
fy.
тем же... Прототип Dog
species: "Canine" function bark() {
// code to bark
}
bark()
run() function bark() {
}
// code to bark
wag()
тод bark
ot получает Нестандартный ме spot).
Но объ ект sp етод bark, (тольк о дл я об ъе кт а
м
собственный е-
водит сообщ Dog
который вы
.
ние “WOOF!” name: “Spot”
breed: “Chihuahua”
weight: 10 function bark() {
// WOOF code
bark() }
Развлечения с магнитами
На холодильнике была выложена диаграмма объектов, но кто-то все перепутал. Сможете ли вы
расставить магниты по местам? На диаграмме будут присутствовать два экземпляра, созданных на
базе прототипа. Первый — Робби (Robby) — создан в 1956 году, принадлежит доктору Морбиусу
(Dr. Morbius), оснащен выключателем и умеет бегать в Starbucks за кофе. Другой робот — Рози
(Rosie) — создан в 1962 году, прибирается в доме Джорджа Джетсона (George Jetson). Удачи!
Кстати, внизу могут остаться лишние магниты...
cleanHouse()
makeCoffee()
onOffSwitch: true
function
makeCoffee() blinkLights() {
// code 4 lights
Robot Robot }
function
name: "Robby" name: "Rosie" cleanHouse() {
// code 4 clean
}
function
makeCoffee() {
// code 4 coffee
function }
owner: Dr. Morbius year: 1956 function speak() { makeCoffee() {
// code to speak // starbucks
} }
owner: George Jetson year: 1962
590 глава 13
использование прототипов
Конструктор для
создания экземпля-
function Dog(name, breed, weight) { ров. Каждый эк-
this.name = name; земпляр содержит Dog
this.breed = breed; отдельный набор name: “Spot”
свойств name, breed
this.weight = weight; и weight, поэтому breed: “Chihuahua”
} они будут включены weight: 10
в конструктор.
592 глава 13
использование прототипов
Тест-драйв прототипа
Сохраните этот код в файл (dog.html) и загрузите его в браузере для тестиро-
вания. Мы приводим весь код с изменениями, внесенными на предыдущей
странице, и добавляем тестовый код. Убедитесь в том, что ваши объекты
ведут себя так, как положено.
g.
function Dog(name, breed, weight) { Конструктор Do
this.name = name;
this.breed = breed;
this.weight = weight;
авляются
} А здесь в прототип доб
свойства и методы.
Dog.prototype.species = "Canine";
Dog.prototype.bark = function() {
if (this.weight > 25) {
console.log(this.name + " says Woof!"); Мы добавляем в прототип
} else { одно свойство и три метода.
console.log(this.name + " says Yip!");
}
};
Dog.prototype.run = function() {
console.log("Run!");
};
Dog.prototype.wag = function() {
console.log("Wag!"); Затем объ екты
...
}; создаются, как обычно
каем
Далее следует остальной код. Мы опус
трон-
его для экономии бумаги, битов в элек
... ной версии, или чего-нибудь еще...
594 глава 13
использование прототипов
Упражнение Помните нашу диаграмму объектов с роботами Robby и Rosie? Сейчас мы реализуем ее в программном
коде. Мы уже написали за вас конструктор Robot, а также добавили тестовый код. Вам остается настроить
прототип и реализовать двух роботов. Не забудьте проверить, что у вас получилось.
Базовый конструктор
Robot. Ваша задача —
function Robot(name, year, owner) { настроить его прототип.
this.name = name;
this.year = year;
Прототип
this.owner = owner; настраивается здесь.
}
Robot.prototype.maker =
Robot.prototype.speak =
Robot.prototype.makeCoffee =
Robot.prototype.blinkLights =
596 глава 13
использование прототипов
Dog.prototype.sit = function() {
И добавляем метод sit.
console.log(this.name + " is now sitting");
}
}
// code to bark
species: "Canine"
bark()
function bark() {
}
// code to bark
2 Добавляем в прототип
run() новый метод sit.
wag() function bark() {
sit() }
// code to bark
function sit() {
// code to sit
3 Вызываем метод barnaby.sit, }
но в объекте barnaby объект sit
отсутствует.
Dog
name: “Barnaby”
breed: “Basset Hound”
weight: 55 1 Создаем новый объект barnaby.
О динамических прототипах
Барнаби теперь выполняет команду «сидеть». Но оказывается, Конечно, работает
эту команду теперь выполняют все наши собаки, потому что после и для свойств.
добавления метода в прототип он может использоваться всеми
объектами, наследующими от этого прототипа:
}
// code to bark
species: "Canine"
bark()
function bark() {
// code to bark
}
run()
wag() function bark() {
sit() }
// code to bark
function sit() {
// код sit
}
Dog
name: “Barnaby”
breed: “Basset Hound” Любой объект собаки
weight: 55 с прототипом Dog
теперь может
использовать метод sit.
часто
Задаваемые
вопросы
В: Итак, когда я добавляю в прототип новый метод или В: Я понимаю, как добавление нового свойства в прототип
свойство, они немедленно проявляются во всех экземплярах делает свойство доступным для объектов, наследующих
объектов, наследующих от этого прототипа? от прототипа. Но если я изменяю существующее свойство
598 глава 13
использование прототипов
Наши роботы используются в компьютерной игре, код которой приведен ниже. В этой игре при достижении
Упражнение игроком уровня 42 у робота включается новая способность: лазерный луч. Допишите приведенный ниже
код, чтобы на уровне 42 лазерный луч включался у обоих роботов, Робби и Рози. Сверьтесь с ответами
в конце главы, прежде чем двигаться дальше.
Консоль JavaScript
Welcome to level 1
function Game() {
Welcome to level 2
this.level = 0; Welcome to level 3
} ...
Welcome to level 41
Game.prototype.play = function() { Welcome to level 42
Rosie is blasting you with
// Действия игрока laser beams.
this.level++;
console.log("Welcome to level " + this.level);
Пример вывода.
this.unlock(); Завершив работу
} над кодом, запу-
стите его и по-
Game.prototype.unlock = function() { смотрите, какой
робот победит!
robby.deployLaser();
rosie.deployLaser();
Начинаем со свойства
Задаем sitting в про-
sitting в прототипе.
тотипе значение Затем в методе sit про-
false — изначально веряем, сидит собака или
Dog.prototype.sitting = false; все собаки стоят. нет. При проверке this.
sitting сначала проверяется
значение в прототипе.
Dog.prototype.sit = function() {
if (this.sitting) {
console.log(this.name + " is already sitting");
} else { Если собака сидит, выводим
this.sitting = true; соответствующее сообщение.
console.log(this.name + " is now sitting");
}
Если собака стоит, мы задаем
}; this.sitting значение true.
Обратите внимание: экземпляр При этом происходит
теперь имеет собственное локаль- переопределение прототипа,
ное свойство sitting, равное true. а значение задается в экземпляре.
В этом коде прежде всего стоит обратить внимание на то, что в начале своего существования экземпляр
наследует значение по умолчанию для sitting. Но при вызове метода sit экземпляр добавляет собст-
венное значение sitting, в результате чего новое свойство создается в экземпляре. Оно переопреде-
ляет унаследованное свойство sitting из прототипа. Таким образом, мы можем определить значение
по умолчанию для всех объектов, а затем, если понадобится, создать его специализированную версию
в каждом конкретном объекте.
600 глава 13
использование прототипов
Рассмотрите эту
2 Обращаемся к про- последовательность
тотипу и видим, что Прототип Dog (в порядке 1, 2, 3, 4),
свойство sitting содер- species: "Canine"
function bark() {
}
// code to bark начиная с этой точки.
жит false. sitting: false
bark()
function bark() {
// code to bark
}
run()
wag() function bark() {
sit() }
// code to bark
function sit() {
// код sit
}
Dog
name: “Spot”
breed: “Chihuahua” 1 Метод spot.sit вызывается
weight: 10 впервые. Объект spot не со-
bark() держит свойства sitting.
Прототип
function bark() {
species: "Canine" }
// code to bark
sitting: false
bark()
function bark() {
// code to bark
}
run()
3 Свойству this.sitting задается wag() function bark() {
Раз уж мы заговорили
о свойствах, можно ли в коде
определить, откуда взято
используемое значение свойства —
из экземпляра или прототипа?
-
раз проверяем, содер
Когда мы в первый йст во sit tin g,
ное сво
жит ли spot собствен se.
ие fal
возвращается значен
Затем мы задаем spot.
spot.hasOwnProperty("sitting"); sitting значение true,
добавляя это свойство
spot.sitting = true; в экземпляр spot.
spot.hasOwnProperty("sitting");
Этот вызов hasOwnProperty
возвращает true, потому
что spot теперь содержит
fido.hasOwnProperty("sitting"); собственное свойство sitting.
602 глава 13
использование прототипов
Мы встроили в наших роботов Робби и Рози новую функцию: теперь они могут сообщать о воз-
Упражнение никающих ошибках при помощи метода reportError. Проанализируйте приведенный ниже код,
обращая особое внимание на то, откуда этот метод получает информацию об ошибках и откуда
берется эта информация: из прототипа или экземпляра.
Запишите внизу результат выполнения кода:
Robot.prototype.maker = "ObjectsRUs";
Robot.prototype.errorMessage = "All systems go.";
Robot.prototype.reportError = function() {
console.log(this.name + " says " + this.errorMessage);
};
Robot.prototype.spillWater = function() {
this.errorMessage = "I appear to have a short circuit!";
};
rosie.reportError();
robby.reportError();
robby.spillWater(); Содержит ли Робби
во
rosie.reportError(); собственное свойст
errorM ess ag e?
robby.reportError();
console.log(robby.hasOwnProperty("errorMessage"));
console.log(rosie.hasOwnProperty("errorMessage"));
А Рози?
Чемпион породы
Наши труды в этой главе окупились. Директор Клуба любителей собак Объект-
виля увидел вашу работу над объектами собак и немедленно понял, что нашел
правильных людей для моделирования собачьей выставки. От вас потребуется
совсем немного: обновить конструктор Dog для создания объектов выставочных
собак. Ведь, как известно, выставочные собаки отличаются от обычных — они не
носятся сломя голову, а демонстрируют экстерьер. Они не выпрашивают сахар,
а выражают желание полакомиться.
А если говорить конкретнее, нужно следующее:
о-
та с конструкт
Прекрасная рабо чь
телось бы привле
ром Dog! Нам хо ов ан ие м
д мод ел ир
вас к работе на ав ка х
ки. На выст
собачь ей выстав ые
ъявляются особ
к собакам пред тся
ому нам понадобя
требования, поэт ж е) .
методы (см. ни
дополнительные
Спасибо!
ля
собак Объ ектви
Клуб любителей
да «стоять».
stack() — коман
ения. Метод
варианты движ
gait() — разные “trot”,
ст роко вы й аргумент: “walk”,
получает
p”.
“pace” или “gallo
баке лакомст во.
bait() — дать со
ние и уход.
groom() — купа
604 глава 13
использование прототипов
Можно
добавить
новые методы
собак в экземпляры
Если мы добавим новые методы выставочных собак,
в существующий конструктор Dog, но тогда мы возвращаемся
то все собаки смогут выполнять к проблемам, описанным
эти методы. Но изменять то, что в начале главы. А если мы напишем
работает, не хочется. конструктор выставочной
собаки с нуля, нам придется
заново реализовать все базовые
методы: bark, run, sit...
Парни, успокойтесь.
В JavaScript прототипов
может быть несколько.
606 глава 13
использование прототипов
bark()
Мы по-прежнему можем создавать
run()
любые экземпляры, наследующие непо-
wag() средственно от прототипа собаки.
Dog
name: “Spot”Dog
Прототип ShowDog name:
breed: Dog
“Fluffy”
“Chihuahua”
league: “Webville” breed:
weight: 10 “Poodle”
name: “Fido”
weight: 30 “Mixed”
breed:
stack()
weight: 38
bait()
gait()
groom()
Новый прототип
выставочной собаки. Но нам также нужно создавать
объекты выставочных собак. Они явля-
ются частным случаем обычной собаки,
но поддерживают поведение, которое
отсутствует у обычных собак.
ShowDog
name: “Scotty”
Во всяком случае,
breed: “Scottish Terrier” так считают их
weight: 15 владельцы...
handler: “Cookie”
Обратите внимание: в ShowDog
теперь содержатся все свойства,
И после этого можно создавать связанные с конкретным экземпля-
экземпляры ShowDog — такие, как ром, — кличка (name), порода (breed),
этот шотландский терьер. вес (weight) и владелец (handler).
function bark() {
// code to bark
от исходного прото-
}
species: "Canine"
типа Dog.
ЗДЕСЬ ЗДЕСЬ
bark()
run() function run() {
// code to bark
}
wag()
function wag() {
// code to bark
}
}
// code to bark
league: “Webville”
НЕ ЗДЕСЬ ЗДЕСЬ ЗДЕСЬ НЕ ЗДЕСЬ stack()
bait() function bait() {
}
// code to bark
gait()
groom()
function gait() {
// code to bark
}
Кличка опреде-
ляется в экзем-
пляре ShowDog.
function groom() {
// code to bark
}
Экземпляр ShowDog
ShowDog
name: “Scotty”
НЕ ЗДЕСЬ НЕ ЗДЕСЬ ЗДЕСЬ НЕ ЗДЕСЬ НЕ ЗДЕСЬ breed: “Scottish Terrier”
weight: 15
handler: “Cookie”
sc
sc
sc
sc
sc
Протестируйте все
ot
ot
ot
ot
ot
ty
ty
ty
ty
.b
.l
.n
.s
ar
ta
ea
pe
k(
ck
gu
e;
ci
прототипов.
);
()
e;
es
;
608 глава 13
использование прототипов
Развлечения с магнитами
Мы выложили на холодильнике очередную диаграмму объектов, и снова кто-то все перепутал.
Снова! Сможете ли вы расставить магниты по местам? Нам понадобится новая линейка косми-
ческих роботов, наследующих свойства от обычных роботов. Космические роботы переопре-
деляют метод speak обычных роботов, а также расширяют функциональность роботов новым
свойством homePlanet. Удачи! (Внизу могут остаться лишние магниты!)
Постройте здесь
function диаграмму объектов.
Прототип Robot speak() {
function
// code to speak
Прототип
makeCoffee() {
}
maker: "ObjectsRUs" // code 4 coffee
роботов.
function
} blinklights() {
speak() // code 4 lights
}
makeCoffee()
blinkLights()
Прототип космических
роботов. Robot Robot
name: "Robby" name: "Rosie"
year: 1956 year: 1962
owner: "Dr. Morbius" owner: "George Jetson"
Прототип SpaceRobot
function speak() {
// code to speak
speak() year: 1977 // in space
}
SpaceRobot SpaceRobot
pilot() year: 2009 name: "C3PO" name: "Simon"
year: 1977 year: 2009
function owner: "L. Skywalker" owner: "Carla Diana"
homePlanet: "Earth" pilot() {
// code to pilot
// spaceship
}
homePlanet: "Tatooine"
Прототип Dog
species: "Canine"
bark()
run()
wag()
То, к чему
Прототип ShowDog Dog
Dog
мы стремимся. league: “Webville” name: “Spot”
name:
breed: Dog
“Fluffy”
“Chihuahua”
stack() breed:
weight: 10 “Poodle”
name: “Fido”
bait() weight: 30 “Mixed”
breed:
Нам нужен прототип gait() weight: 38
выставочной собаки, groom()
наследующий от про-
тотипа собаки...
ShowDog
Экземпляр выставочной
name: “Scotty” Прототип собаки
собаки, наследующий
breed: “Scottish Terrier” и набор объектов,
го.
от прототипа
weight: 15 наследующих от не
handler: “Cookie”
выставочной собаки.
610 глава 13
использование прототипов
ось
var aDog = new Dog(); О том, что случил
нт ам и ко нс труктора,
с аргуме
ем ...
мы сейчас расскаж
Прототип Dog
species: "Canine"
bark()
run()
wag()
aDog
Теперь, когда у нас имеется конструктор, мы можем задать его свойству prototype новый экземпляр собаки:
Итак, подумаем, что же здесь произошло: у нас имеется конструктор ShowDog, который может исполь-
зоваться для создания экземпляров выставочных собак, и прототип выставочной собаки, который
представляет собой экземпляр собаки.
Чтобы диаграмма объектов более точно отражала роли этих объектов, мы заменим надпись «Dog» на
«Прототип ShowDog». Но помните, что прототип выставочной собаки все равно остается экземпляром собаки.
bark() bark()
run() run()
wag() wag() Надпись на диаграмме
«Dog» заменяется надписью
«Прототип ShowDog».
Итак, у нас имеется конструктор ShowDog, и мы подготовили объект прототипа выставочной собаки;
теперь нужно вернуться назад и заполнить некоторые подробности. Сейчас мы более подробно рас-
смотрим конструктор, а также добавим в прототип некоторые свойства и методы, чтобы выставочные
собаки обладали дополнительным поведением.
612 глава 13
использование прототипов
bark()
run()
С этими дополнениями прототип выставочной собаки действительно начинает wag()
выглядеть так, как требуется. Давайте еще раз обновим нашу диаграмму объек-
тов — и наверное, можно переходить к основательному тестированию. Хочется
Прототип ShowDog
надеяться, что Клубу любителей собак понравится результат нашей работы.
league: “Webville”
Мы говорим, что пр
ототип выставочно stack()
собаки «расширяет й
» прототип собаки. bait()
Он наследует свойст gait()
ва из прототипа
собаки и дополняет groom()
их новыми свойствам
и.
ShowDog.prototype.stack = function() {
console.log("Stack");
А это наш экземпляр выставочной
}; собаки. Он наследует от прото-
типа выставочной собаки, который
ShowDog.prototype.bait = function() { в свою очередь наследует от про-
console.log("Bait"); тотипа собаки... Собственно, как
мы и планировали. Вернувшись
};
к с. 592, вы увидите, что цепочка
прототипов завершена.
ShowDog.prototype.gait = function(kind) {
console.log(kind + "ing");
};
ShowDog.prototype.groom = function() {
console.log("Groom"); Новая выставочная
}; собака scotty.
614 глава 13
использование прототипов
___________________.speak = function() {
alert(this.name + " says Sir, If I may venture an opinion...");
};
___________________.pilot = function() {
alert(this.name + " says Thrusters? Are they important?");
};
Упражнение Давайте поближе рассмотрим всех этих собак, которых мы создаем. Мы уже тестировали
объект fido прежде, и знаем, что это действительно собака. Но давайте проверим, является
ли он также и выставочной собакой (чего быть не должно). И как насчет scotty? Понятно, что
он должен быть выставочной собакой, но будет ли он также обычной собакой? Не уверены.
А заодно проверим конструкторы объектов fido и scotty...
Консоль JavaScript
Здесь записывается
ваш результат:
616 глава 13
использование прототипов
Анализ результатов
Результат, который мы получили при тестовом запуске:
Фидо — собака,
как и ожидалось.
Не выставочная собака...
Тоже вполне логично. Консоль JavaScript
Fido is a Dog
Скотти — (1) собака Scotty is a Dog
и (2) выставочная соба- Scotty is a ShowDog
ка, что тоже логично.
Но откуда instanceof Fido constructor is function Dog...
знает об этом? Scotty constructor is function Dog...
ти выво-
Хмм, странно. И для Фидо, и для Скот
я инфо рмац ия о том , что они были созданы
дитс
Но ведь для созда ния Скотти
конструктором Dog.
льзов ался конст рукт ор Show Dog.. .
испо
и предполагалось; ведь объект fido создавался конструктором Dog, Fido constructor is function Dog...
Scotty constructor is function Dog...
Наводим чистоту
Готовый код уже можно отправлять в Клуб любителей собак, но сначала
нужно исправить пару последних мелких недостатков.
Первый мы уже видели: у экземпляров ShowDog неправильно задается
свойство constructor — они наследуют конструктор Dog. Чтобы не было
неясностей: весь код нормально работает и в этом варианте, но на-
значение правильного конструктора в объектах может пригодиться,
когда другой разработчик возьмется за ваш код, начнет изучать объект
выставочной собаки — и сильно удивится.
Чтобы исправить свойство constructor, нужно позаботиться о том, что-
бы он правильно задавался в прототипе ShowDog. В этом случае при
конструировании объект выставочной собаки унаследует правильное
свойство constructor. Вот как это делается:
618 глава 13
использование прототипов
зует
function ShowDog(name, breed, weight, handler) { Этот фрагмент исполь
для обр аботки
Dog.call(this, name, breed, weight); конструктор Dog
в nam e, bre ed и wei ght.
свойст
this.handler = handler;
} Но свойство handler должно
обрабатываться в этом код
потому что конструктор е,
Dog о нем ничего не знает.
}
Но Dog ничего не знает о свой
-
стве handler; это свойство
должно назначаться в ShowD
og.
620 глава 13
использование прототипов
Наши заказчики
Последний тест-драйв будут в восторге!
ShowDog.prototype.bait = function() {
console.log("Bait");
};
ShowDog.prototype.gait = function(kind) {
console.log(kind + "ing");
й код.
влен тестовы
}; Внизу доба
ShowDog.prototype.groom = function() {
console.log("Groom");
}; Создаем несколько
var fido = new Dog("Fido", "Mixed", 38); объ ектов — как
обычных, так и
var fluffy = new Dog("Fluffy", "Poodle", 30); выставочных собак.
var spot = new Dog("Spot", "Chihuahua", 10);
var scotty = new ShowDog("Scotty", "Scottish Terrier", 15, "Cookie");
var beatrice = new ShowDog("Beatrice", "Pomeranian", 5, "Hamilton");
fido.bark();
Убедимся в том, что Консоль JavaScript
fluffy.bark(); все объ екты работаю
т Fido says Woof!
spot.bark(); так, как положено.
Fluffy says Woof!
scotty.bark(); Spot says Yip!
beatrice.bark(); Scotty says Yip!
scotty.gait("Walk"); Beatrice says Yip!
beatrice.groom(); Walking
Groom
О:
собаки. Вместо этого они наследуют свой- времени выполнения часто хорошо справ-
Потому что от этого экземпляра нам ства и методы, помещенные в объектный ляются с оптимизацией таких операций.
нужно только одно: сам факт наследования литерал. В общем случае вам не понадобятся си-
от прототипа собаки. Этот экземпляр не обо-
значает никакую конкретную собаку (Фидо, В: Я случайно разместил строку, в ко-
стемы, требующие многоуровневого на-
следования. А если понадобятся — лучше
пересмотрите структуру своих объектов.
Спота и т. д.); это обобщенный экземпляр торой ShowDog.prototype присваивался
собаки, наследующий от прототипа собаки. экземпляр Dog, ниже, где создавался
объект scotty, и мой код не работал. По- В: А если у меня появится еще одна
Кроме того, все собаки, наследующие от чему? категория собак — скажем, племенные
О:
прототипа выставочной собаки, определяют собаки? Смогу ли я создать прототип
собственные свойства name, breed и weight. Потому что при создании scotty (экземп племенной собаки, который наследует от
Таким образом, даже если экземпляр собаки ляра ShowDog) он получает прототип, ко- того же прототипа собаки, что и прототип
содержал значения этих свойств, мы их торый был назначен ShowDog.prototype на выставочной собаки?
никогда не увидим, потому что экземпляры
выставочной собаки их всегда переопре-
деляют.
момент его создания. Таким образом, если
объект собаки не был назначен prototype О: Да, сможете. Вам придется создать от-
до создания scotty, объект scotty будет ис- дельный экземпляр собаки, который будет
В:
wag()
bark, run или wag, а их свойство species не Да, все изменения в прототипе влияют
будет содержать строку “Canine”. Попро- на все экземпляры, наследующие от этого
буйте сами. Закомментируйте строку кода, прототипа в цепочке, независимо
в которой мы задаем ShowDog.prototype от количества промежуточных ShowDog CompetitionDog
экземпляр new Dog(), и попробуйте вызвать звеньев. name: “Scotty” name: “Ci”
В:
breed: “Scottish breed: “Border Collie”
Terrier”
bark для объекта scotty. Что произойдет? weight: 15 weight: 28
handler: “Mark”
В:
handler: “Cookie”
Ограничена ли длина цепоч-
Могу ли я создать объектный лите- ки прототипов?
рал и использовать его как прототип?
622 глава 13
использование прототипов
Использование наследования...
с переопределением встроенного поведения
При наследовании от встроенных объектов вы можете переопреде-
лять методы, содержащиеся в этих объектах. Типичный пример —
метод toString прототипа Object. Все объекты наследуют от Object,
и поэтому могут использовать метод toString для получения
простого строкового представления любого объекта, например Консоль JavaScript
console.log, для вывода представления объекта на консоль:
[Object object]
function Robot(name, year, owner) {
this.name = name;
this.year = year;
this.owner = owner;
} Метод toString, наследу-
емый от Object, не очень
var toy = new Robot("Toy", 2013, "Avary"); хорошо справляется
со своими обязанностями.
console.log(toy.toString());
console.log(toy.toString());
toString
ся в строку методом
Объект toy преобразует объ ект toy переопределяет
Если
перед конкатенацией.
ng, то он исп оль зует этот метод.
toStri
624 глава 13
использование прототипов
ОПАСНАЯ ЗОНА
Начиная переопределять свойства и методы, легко увлечься. Особенно
важно действовать осторожно при переопределении свойств и методов
встроенных объектов — вы можете случайно изменить поведение дру-
гого кода, зависящего от этих свойств для выполнения определенных
операций.
Итак, если вы собираетесь переопределять свойства в Object, прочи-
тайте сначала правила техники безопасности. Иначе в вашем коде мо-
гут появиться крайне коварные и трудноуловимые ошибки.
НЕ УВЕРЕН — НЕ ПЕРЕОПРЕДЕЛЯЙ!
Свойства Object, которые переопределять не рекомендуется:
нкцию-
r указывает на фу
Свойство constructo ую с прототипом.
занн
конструктор, свя
constructor
Вы уже знаете, что делает метод hasOwnProperty.
hasOwnProperty
С помощью метода isPrototypeOf можно узнать,
isPrototypeOf является ли объект прототипом другого объекта.
МОЖНО ПЕРЕОПРЕДЕЛЯТЬ
Итак, теперь вы разбираетесь в прототипах и умеете правильно
выполнять переопределение. Некоторые свойства Object, которые
могут пригодиться в вашем коде: еобразует объект
g, как и toString, пр
Метод toLocaleStrin д переопределяется для построения
в строку. Этот ме
то ъекта.
ка) с описанием об
toString роки (страны/язы
локализованной ст
toLocaleString valueOf — еще один метод, предназначенный для перео-
valueOf пределения. По умолчанию он просто возвращает объект,
для которого был вызван, но вы можете переопределить
его, чтобы метод возвращал любое другое значение.
Определяем клише,
for (var i = 0; i < cliche.length; i++) { которые нужно
var index = this.indexOf(cliche[i]); найти в тексте.
if (index >= 0) {
return true; А затем используем функцию
indexOf объекта String, чтобы
} узнать, содержит ли стро-
} ка искомые клише. Если поиск
окажется успешным, немедленно
return false; возвращается значение true.
}; Обратите внимание: this —
строка, для которой вызы-
вается метод cliche.
Для тестирования создадим
несколько предложений, пара
Напишем код для тестирования метода: из которых содержит клише.
var sentences = ["I'll send my car around to pick you up.",
"Let's touch base in the morning and see where we are",
"We don't want to open the kimono, we just want to inform them."];
626 глава 13
использование прототипов
и
Буд ьте осто рож ны при рас шир ени
в (таких, как String)
встроенных объекто
вашими собственными методами.
Будьте Следите за тем, чтобы имя, выбранн
ое
осторожны! вами для метода, не конфликт овал о
кта
с
.
именем существующего метода объе
ите все нестанда ртн ые
А при подключении стороннего кода изуч
могу т испо льзоваться (и снова сле-
расширения, которые в нем
некоторые встроенные
дите за конфликтами имен). Наконец,
ирение (как, например,
объекты просто не рассчитаны на расш
ите обстановку, прежде
Array). В общем, основательно изуч
во встроенные объекты.
чем браться за добавление методов
v Ваша очередь. Напишите метод palindrome, который возвращает true, если строка читается
одинаково в обоих направлениях (будем считать, что строка содержит всего одно слово —
Упражнение не будем отвлекаться на палиндромы из нескольких слов). Добавьте метод в String.prototype
и протестируйте результат. Сверьтесь с ответом в конце главы.
JavaScript
Теория великого объединения Всего
Поздравляем, вы взялись за изучение нового языка программи- Мы исходим из того, что
рования (а может, вашего первого языка) и успешно справились примерно 5,9 миллиардов
с этой задачей. Раз вы дочитали до этой страницы, следовательно, человек вообще не знают
JavaScript, так что всех
вы знаете о JavaScript больше, чем практически все остальные. остальных можно рассма-
А если говорить серьезно, то добравшись до конца книги, вы сущест- тривать как ошибку округле-
ния. Из этого следует, что
венно продвинулись на пути к тому, чтобы стать экспертом JavaScript. вы знаете JavaScript практи-
Остается накопить опыт проектирования и программирования чески лучше всех остальных.
веб-приложений (да и любых приложений JavaScript).
function meditate() {
console.log("Everything is an object...");
}
alert(meditate instanceof Object);
628 глава 13
использование прототипов
Dog.constructor
Напоминаем: А это свойство. Это правда! Функции тоже
это функция. являются объектами.
КЛЮЧЕВЫЕ
МОМЕНТЫ
Система объектов JavaScript использует наследо- Если вы добавите свойства в прототип после созда-
вание через прототипы. ния экземпляров, наследующих от него, то все эк-
Экземпляр, создаваемый конструктором, содержит земпляры немедленно унаследуют новые свойства.
собственные свойства и копию методов из конструк- Чтобы узнать, определено ли свойство в экземпляре,
тора. используйте метод hasOwnProperty.
Свойства, добавляемые в прототип конструктора, Метод call может использоваться для вызова функ-
наследуются всеми экземплярами, созданными
ции с указанием объекта, который должен использо-
этим конструктором.
ваться в качестве this в теле функции.
Размещение свойств в прототипе может сократить
дублирование кода объектов на стадии выполнения. Object — объект, от которого в конечном итоге
наследуют все прототипы и экземпляры.
Чтобы переопределить свойства из прототипа,
просто добавьте их в экземпляр. Object содержит свойства и методы, которые на-
следуются всеми объектами (как, например, toString
Конструктор включает прототип по умолчанию,
и hasOwnProperty).
к которому можно обратиться через свойство
prototype конструктора. Вы можете переопределять или добавлять свойства
Вы можете назначить собственный объект свойству во встроенные объекты (такие, как Object или String),
prototype конструктора. но будьте осторожны — такие изменения могут при-
водить к непредвиденным последствиям.
Если вы используете собственный объект как прото-
тип, не забудьте назначить правильный конструктор В JavaScript объектом является практически все —
свойству constructor для предотвращения недора- функции, массивы, многие встроенные объекты и все
зумений. объекты, которые вы создаете самостоятельно.
Потрясающе!
630 глава 13
использование прототипов
Прототип Robot
Прототип, maker: "ObjectsRUs" function speak() {
от которого
// code to speak
speak() }
могут насле- makeCoffee()
довать ваши blinkLights() function
роботы. makeCoffee() {
// code 4 coffee
}
function
blinkLights() {
// code 4 lights
}
Robot Robot
name: "Robby" name: "Rosie"
year: 1956 year: 1962
owner: Dr. Morbius owner: George Jetson
onOffSwitch: true cleanHouse()
makeCoffee()
function
cleanHouse() {
function // code 4 clean
makeCoffee() { }
// starbucks
}
v Помните нашу диаграмму объектов с роботами Robby и Rosie? Сейчас мы реализуем ее в программ-
ном коде. Мы уже написали за вас конструктор Robot, а также добавили тестовый код. Вам остается
настроить прототип и реализовать двух роботов. Не забудьте проверить, что у вас получилось. Ниже
приведено наше решение.
632 глава 13
использование прототипов
v Наши роботы используются в компьютерной игре, код которой приведен ниже. В этой игре при достиже-
нии игроком уровня 42 у робота включается новая способность: лазерный луч. Допишите приведенный
ниже код, чтобы на уровне 42 лазерный луч включался у обоих роботов, Робби и Рози. Ниже приведено
наше решение.
Консоль JavaScript
Welcome to level 1
function Game() {
Welcome to level 2
this.level = 0; Welcome to level 3
} ...
Welcome to level 41
Game.prototype.play = function() { Welcome to level 42
Rosie is blasting you with
// Действия игрока laser beams.
this.level++;
console.log("Welcome to level " + this.level);
Пример вывода.
this.unlock(); Мы вызываем unlock при каждой игре, но новая Завершив работу
я до тех пор,
} способность не активизируетс над кодом, запу-
игне т 42.
пока значение level не дост стите его и по-
Game.prototype.unlock = function() { смотрите, какой
if (this.level = = = 42) { робот победит!
Robot.prototype.deployLaser = function () {
console.log(this.name + " is blasting you with laser beams.");
}
при достижении
} Секрет этой игры:
ип добавляется
} уровня 42 в протот
Эт о озн ачает, что
новый метод.
т способность
function Robot(name, year, owner) { все роботы наследую
применения ла зер ов!
this.name = name;
this.year = year;
this.owner = owner;
}
robby.deployLaser();
rosie.deployLaser();
v Мы встроили в наших роботов Робби и Рози новую функцию: теперь они могут сообщать о возникающих
ошибках при помощи метода reportError. Проанализируйте приведенный ниже код, обращая особое
внимание на то, откуда этот метод получает информацию об ошибках и откуда берется эта информация:
из прототипа или экземпляра.
Ниже приведено наше решение.
rosie.reportError();
robby.reportError(); д spillWater для
Мы вызываем мето
Робби получает
robby.spillWater(); объ екта robby. Так
сво йст во errorMessage,
собственное
rosie.reportError(); яет свойство
которое переопредел
robby.reportError(); в прототипе.
console.log(robby.hasOwnProperty("errorMessage")); true
console.log(rosie.hasOwnProperty("errorMessage")); false
634 глава 13
использование прототипов
function speak() {
Прототип Robot }
// code to speak
дует (и ототип
}
р
ряет) п ы добавили
Robot. М рототип
п
в этот да — speak
м е т о
два е бу-
p il o t , которы ься
и т
следова
Robot дут на ботами.
р о
всеми
function speak() {
Прототип SpaceRobot
name: "Robby" // code to speak
// in space
year: 1956 }
Robot
owner: "Dr. Morbius" speak()
name: "Rosie"
year: 1962 pilot()
owner: "George Jetson" function
pilot() {
// code to pilot
// spaceship
}
SpaceRobot SpaceRobot
name: "C3PO" name: "Simon"
year: 1977 year: 2009
А еще экзем
owner: "L. Skywalker" owner: "Carla Diana" пляры
SpaceRobot
homePlanet: "Tatooine" homePlanet: "Earth" наследу-
ют от прот
отипа
SpaceRobot.
Они насле-
дуют метод
ы
и pilot, а в ка speak
ж
экземпляр до дый
бавля-
ется новое
свойство
homePlanet.
v Ваша очередь. Напишите метод palindrome, который возвращает true, если строка читается одинаково
в обоих направлениях (будем считать, что строка содержит всего одно слово — не будем отвлекаться
на палиндромы из нескольких слов). Добавьте метод в String.prototype и протестируйте результат. Ниже
приведено наше решение.
SpaceRobot.prototype.speak = function() {
alert(this.name + " says Sir, If I may venture an opinion...");
}; Эти два
метода
добавлены
SpaceRobot.prototype.pilot = function() { в прототип.
alert(this.name + " says Thrusters? Are they important?");
};
636 глава 13
использование прототипов
v Ваша очередь. Напишите метод palindrome, который возвращает true, если строка читается одинаково
в обоих направлениях. Добавьте метод в String.prototype и протестируйте результат. Ниже приведено
наше решение (только для палиндромов из одного слова).
String.prototype.palindrome = function() {
длину строки.
var len = this.length-1; Сначала получаем
for (var i = 0; i <= len; i++) { Мы проходим по каждому
яем
if (this.charAt(i) != = this.charAt(len-i)) { символу в строке и провер
аде ние сим вол ов в поз ици -
совп
return false; ях i и len-i.
} Если они не равны,
мы возвращаем false,
if (i = = = (len-i)) { потому что строка
return true; не является палиндромом.
}
строки
} Если i доходит до середины
ся до кон ца, мы
или цикл выполняет
return true; аем tru e, пот ому что строка
возвращ
}; является палиндром ом.
Несколько слов
var phrases = ["eve", "kayak", "mom", "wow", "Not a palindrome"]; для тестирования.
Поздравляем!
Вы добрались до конца.
ООО «Питер Пресс», 192102, Санкт-Петербург, ул. Андреевская (д. Волкова), 3, литер А, пом. 7Н.
Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12.000 — Книги печатные
профессиональные, технические и научные.
Подписано в печать 08.12.14. Формат 84×108/16. Усл. п. л. 67,200. Тираж 2000. Заказ 0000.
Отпечатано в соответствии с предоставленными материалами в ООО «ИПК Парето-Принт».
170546, Тверская область, Промышленная зона Боровлево-1, комплекс № 3А, www.pareto-print.
Разработка приложений для Windows 8
на HTML5 и JavaScript
Д. Эспозито, Ф. Эспозито