Uploaded by andy

Eric T. Freeman, Elisabeth Robson - Head first - Rusz głową! (2015, Helion)

advertisement
JavaScript dla ka¿dego!
Nauka jêzyka JavaScript
jeszcze nigdy nie by³a
tak przyjemna!
Uwa¿aj na czêsto
spotykane pu³apki
i niebezpieczeñstwa
Przeczytaj choæ jeden
rozdzia³, by przyspieszyæ
rozwój swojej kariery
Unikaj ¿enuj¹cych b³êdów
zwi¹zanych z konwersjami typów
Spróbuj rozwik³aæ ponad 120
zagadek i æwiczeñ
O REILLY
®
Dowiedz siê, dlaczego wszystko, co
Twoi znajomi wiedz¹ o funkcjach
i obiektach, najprawdopodobniej
jest jedn¹ wielk¹ pomy³k¹
Eric T. Freeman, Elisabeth Robson
Helion
Pochwały dla książki Programowanie w języku JavaScript. Rusz głową!
Ostrzeżenie! Nie czytaj książki Programowanie w języku JavaScript. Rusz głową!, jeśli nie chcesz w zabawny
i sensowny sposób nauczyć się podstaw programowania w tym języku. Uważaj też na dodatkowy efekt
uboczny — możesz zapamiętać więcej na temat JavaScriptu, niż zapamiętałbyś po przeczytaniu zwyczajnej
książki technicznej.
— Jesse Palmer, starszy programista, Gannett Digital
Gdyby każdy uczeń szkoły podstawowej i gimnazjum przeczytał książki Elisabeth i Erica — Head First
HTML with CSS & XHTML. Edycja polska, Programowanie w języku JavaScript. Rusz głową! oraz HTML5.
Rusz głową! — i gdyby stanowiły one elementy kursów matematyki i informatyki w szkołach wyższych, nasz
kraj zawsze byłby na czele rankingów konkurencyjności.
— Michael Murphy, starszy konsultant do spraw systemów, The History Tree
Seria Rusz głową! korzysta z elementów nowoczesnej teorii nauczania, w tym z konstruktywizmu, by
zapewnić czytelnikom możliwość szybkiej nauki. Książka ta pokazuje, że treści na poziomie eksperckim
można nauczać szybko i wydajnie. Proszę nie popełnić błędu, to jest poważna książka o programowaniu
w języku JavaScript, a mimo to jej lektura jest zabawna!
— Frank Moore, projektant i programista stron WWW
Czy szukasz książki, która bezustannie będzie pobudzać Twoją ciekawość (i rozśmieszać), a jednocześnie
pozwoli zdobyć zaawansowane umiejętności z zakresu programowania? Właśnie taką książką jest
Programowanie w języku JavaScript. Rusz głową!
— Tim Wiliams, właściciel firmy programistycznej
Dodaj tę książkę do swojej biblioteki i to niezależnie od poziomu umiejętności programowania.
— Chris Fuselier, konsultant techniczny
Robson i Freeman znowu to zrobili! Książka Programowanie w języku JavaScript. Rusz głową!, przy użyciu
tego śmiesznego i przepełnionego informacjami stylu, znanego z poprzednich książek tej serii, prezentuje
zabawne i pouczające projekty, które rozdział po rozdziale pozwalają programistom — nawet takim, którzy
jak ja nie są specjalistami — zdobywać solidną wiedzę z zakresu nowoczesnego programowania w języku
JavaScript, z której będą w stanie korzystać podczas rozwiązywania realnych problemów.
— Russel Alleen-Willems, cyfrowy archeolog, DiachronicDesign.com
Freeman i Robson ponownie użyli innowacyjnych metod nauczania, by prezentować czytelnikom wszystko,
od złożonych pojęć po podstawowe zasady.
— Mark Arana, Strategy & Innovation, The Walt Disney Studios
Pochwały dla innych książek Erica Freemana i Elisabeth Robson
Właściwa nuta dla nabuzowanego maniaka i dorywczego mistrza kodowania, który drzemie w każdym z nas.
Prawdziwy podręcznik praktycznych strategii tworzenia oprogramowania — to on sprawia, że mój umysł nie
musi mozolnie przedzierać się przez męczącą i zatęchłą mowę profesora.
— Travis Kalanick, prezes Uber
Wyjątkowa przejrzystość tej książki, jej humor i niesłychany spryt sprawiają, że staje się pozycją nawet dla
nieprogramistów i pomaga im w rozwiązywaniu problemów.
— Cory Doctorow, jeden z współtwórców Boing Boing, pisarz s.f.
Czuję, jakby tony książek wystartowały z mojej głowy.
— Ward Cunningham, twórca Wiki
Jedna spośród bardzo niewielu książek o programowaniu, jakie kiedykolwiek przeczytałem. Jednak wydaje
mi się nieodzowna. (Do tej kategorii zaliczam co najwyżej 10 książek).
— David Gelernter, profesor informatyki, Uniwersytet Yale
Śmiałem się, płakałem, tak poruszyła mnie ta książka.
— Daniel Steinberg, redaktor naczelny, java.net
Nie wyobrażam sobie lepszych przewodników od Erica i Elisabeth.
— Miko Matsumura, VP działu marketingu i kontaktów z programistami firmy Hazelcast,
wcześniej Chief Java Evangelist, Sun Microsystems
Po prostu kocham tę książkę. Przyznam się, że pocałowałem ją, stojąc naprzeciwko mojej żony.
— Satish Kumar
Bardzo graficzne, inkrementalne podejście doskonale odzwierciedla najlepszy sposób nauki tych zagadnień.
— Danny Goodman, autor książki Dynamic HTML: The Definitive Guide
Bez wątpienia Eric i Elisabeth znają swoją pracę. Wraz ze wzrostem złożoności internetu coraz bardziej
krytycznego znaczenia nabiera tworzenie inspirujących stron WWW. U podstaw każdego z rozdziałów
tej książki leży elegancki projekt, a każde pojęcie jest prezentowane z wykorzystaniem równej dozy
pragmatyzmu i humoru.
— Ken Goldstein, wcześniejszy prezes Shop.com i autor książki This is Rage: A Novel of
Silicon Valley and Other Madness
Inne książki z serii Rusz głową
C#. Rusz głową!
Java. Rusz głową! Wydanie II.
Head First Object-Oriented Analysis and Design. Edycja polska.
Head First HTML with CSS & XHTML. Edycja polska.
Head First Design Patterns. Edycja polska.
Head First Servlets & JSP. Edycja polska. Wydanie II.
Head First SQL. Edycja polska.
Head First Software Development. Edycja polska.
Head First JavaScript. Edycja polska.
Head First Ajax. Edycja polska.
Head First Ruby on Rails. Edycja polska.
Head First PHP & MySQL. Edycja polska.
Head First Web Design. Edycja polska.
Head First. Sieci komputerowe. Edycja polska.
HTML5. Rusz głową!
jQuery. Rusz głową!
Helion
,
Eric T. Freeman, Elisabeth Robson
Tytuł oryginału: Head First JavaScript Programming
Tłumaczenie: Piotr Rajca
ISBN: 978-83-246-9883-7
© 2015 Helion S.A.
Authorized Polish translation of the English edition of Head First JavaScript Programming
9781449340131 © 2014 Eric Freeman and 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.
All rights reserved. No part of this book may be reproduced or transmitted in any form
or by any means, electronic or mechanical, including photocopying, recording or by
any information storage retrieval system, without permission from the Publisher.
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu
niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą
kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym,
magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji.
Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź
towarowymi ich właścicieli.
Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce
informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich
wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich.
Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności
za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce.
Wydawnictwo HELION
ul. Kościuszki 1c, 44-100 GLIWICE
tel. 32 231 22 19, 32 230 98 63
e-mail: helion@helion.pl
WWW: http://helion.pl (księgarnia internetowa, katalog książek)
Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl/user/opinie/prjsrg_ebook
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.
• Poleć książkę na Facebook.com
• Kup w wersji papierowej
• Oceń książkę
• Księgarnia internetowa
• Lubię to! » Nasza społeczność
Językowi JavaScript.
Nie urodziłeś się programistyczną potęgą, a jednak zdeklasowałeś
wszystkie inne języki, które rzuciły ci wyzwanie w dziedzinie
przeglądarek.
O autorach
Autorzy książki Programowanie w języku JavaScript. Rusz głową!
Elisabeth Robson
Eric Freeman
Kathy Sierra, współtwórczyni serii Rusz głową!, opisała Erica
jako „jedną z tych nielicznych osób, które płynnie posługują się
językiem, umiejętnościami praktycznymi i kulturą wielu dziedzin,
zaczynając do hipsterskich hackerów, przez wysoko cenionych
pracowników korporacyjnych, a na inżynierach i pracownikach
think-tanków kończąc”.
Jeśli chodzi o karierę zawodową, Eric niedawno skończył niemal
dziesięcioletnią pracę na kierowniczym stanowisku w koncernie
medialnym — był kierownikiem Disney Online & Disney.com
w firmie The Walt Disney Company. Obecnie poświęca swój czas na
rozwój startupu WickedlySmart, który stworzył wspólnie z Elisabeth.
Elisabeth jest projektantką oprogramowania, pisarką
i instruktorką. Pasjonuje się technologią od czasu
studiów na Uniwersytecie Yale, gdzie obroniła pracę
magisterską i zaprojektowała współbieżny, wizualny język
programowania wraz z odpowiednim oprogramowaniem.
Od samego początku Elisabeth interesowała się internetem,
pracowała w zespole tworzącym nagradzaną witrynę The Ada
Project, jedną z pierwszych witryn dla kobiet zajmujących
się informatyką, która pomagała im w szukaniu możliwości
rozwoju kariery i informacji o mentorach.
W wolnym czasie Eric angażuje się w prace związane z muzyką;
jego ostatnim projektem, który tworzył we współpracy z pionierem
muzyki ambient, Stevem Roachem, jest aplikacja na iPhony
o nazwie Immersion Station.
Aktualnie jest współzałożycielką WickedlySmart, internetowego
projektu edukacyjnego poświęconego technologiom związanym
z WWW, gdzie koncentruje się na pisaniu książek, artykułów,
tworzeniu filmów wideo itp. Wcześniej pracowała w O’Reilly
Media jako dyrektor do spraw projektów specjalnych,
tworząc warsztaty i kursy internetowe poświęcone wielu
zagadnieniom technicznym, i tam odkryła pasję, którą jest
tworzenie materiałów edukacyjnych pomagających zrozumieć
technologię. Przed pracą dla O’Reilly Elisabeth rozsiewała
magiczny pył w firmie The Wald Disney Company, gdzie
kierowała badaniami i pracami nad mediami cyfrowymi.
Eric mieszka wraz ze swą żoną i małą córeczką na wyspie
Bainbridge. Córeczka jest częstym gościem w studiu Erica, gdzie
ubóstwia przekręcać pokrętła syntezatorów i aparatury do efektów
dźwiękowych.
Kiedy Elisabeth nie siedzi przed komputerem, chodzi
na wycieczki, jeździ na rowerze lub pływa kajakiem
w pięknych okolicach, zawsze mając pod ręką swoją kamerę;
ewentualnie gotuje wegetariańskie posiłki.
Możesz napisać do niego na adres eric@wickedlysmart.com
lub odwiedzić jego stronę http://ericfreeman.com.
Możesz do niej napisać na adres beth@wickedlysmart.com
lub odwiedzić jej blog na stronie http://elisabethrobson.com.
Z wykształcenia Eric jest informatykiem, a podczas pisania pracy
doktorskiej na Uniwersytecie Yale studiował wraz ze znakomitością
tego kierunku Davidem Gelernterem. Jego dysertacja to ceniona
praca na temat alternatyw dla metafory obszaru roboczego, a także
jedna z pierwszych implementacji strumienia aktywności — pojęcia,
które opracował wspólnie z dr. Gelernterem.
8
Spis treści
Spis treści (skrócony)
Wprowadzenie
25
1. Szybki skok na głębokie wody JavaScriptu: Czas się zamoczyć
39
2. Pisanie prawdziwego kodu: Idziemy dalej
81
3. Przedstawienie funkcji: Stawiamy na funkcjonalność
115
4. Porządkowanie naszych danych: Tablice
159
5. Zrozumieć obiekty: Wycieczka do Obiektowa
205
6. Interakcja ze stronami WWW: Poznajemy DOM
259
7. Typy, równość, konwersje i cały ten jazz: Poważne typy
293
8. Łączenie wszystkiego w całość: Tworzenie aplikacji
343
9. Programowanie asynchroniczne: Obsługa zdarzeń
407
10. Funkcje pierwszej klasy: Wyzwolone funkcje
453
11. Funkcje anonimowe, zasięg i domknięcia: Poważne funkcje
499
12. Zaawansowane sposoby konstruowania obiektów: Tworzenie obiektów
543
13. Stosowanie prototypów: Obiekty ekstramocne
583
A Dziesięć najważniejszych zagadnień (których nie opisaliśmy): Pozostałości
643
Spis treści (pełny)
W
Wprowadzenie
Twój mózg koncentruje się na języku JavaScript. W tym miejscu Ty usiłujesz się czegoś nauczyć,
a Twój mózg robi Ci przysługę i stara się, by wszystkie poznane informacje zostały zapomniane.
Twój mózg myśli sobie: „Lepiej zostawić miejsce na naprawdę ważne informacje, takie jak
dzikie zwierzęta, których należy unikać, albo czy jeżdżenie nago na snowboardzie jest dobrym
pomysłem”. W jaki sposób możesz przekonać swój mózg, by uznał, że Twoje życie zależy
od znajomości JavaScriptu?
Dla kogo jest ta książka?
26
Wiemy, co myślisz
27
Wyobrażamy sobie, że czytelnik tej książki jest uczniem
28
Metapoznanie — myślenie o myśleniu
29
To, co MY zrobiliśmy
30
To, co TY możesz zrobić, aby zmusić swój mózg do posłuszeństwa
31
Przeczytaj to
32
Recenzenci techniczni
35
Podziękowania*
36
9
Spis treści
Szybki skok na głębokie wody JavaScriptu
1
Czas się zamoczyć
JavaScript to dane Ci supermoce. To prawdziwy język programowania internetu,
który pozwala dodawać do stron WWW zachowania. Możesz zapomnieć o suchych, nudnych
i statycznych stronach — z pomocą języka JavaScript będziesz w stanie porozumieć się ze swoimi
użytkownikami, pobierać z internetu dane do wyświetlania na swoich stronach, rysować grafikę
bezpośrednio na stronach i robić wiele innych, świetnych rzeczy. Co więcej, znając JavaScript,
będziesz także mógł zapewniać swoim użytkownikom całkowicie nowe możliwości.
HTML
CSS
przeglądarka
JS
Sposób działania języka JavaScript
40
Jak należy pisać kod JavaScript?
41
Jak umieszczać kod JavaScript na stronie?
42
Dziecinko, JavaScript przebył długą drogę…
44
Jak tworzyć instrukcje?
48
Zmienne i wartości
49
Odsuń się od tej klawiatury!
50
Wyrazić się
53
Wykonywanie operacji więcej niż jeden raz
55
Jak działa pętla while?
56
Podejmowanie decyzji w języku JavaScript
60
A kiedy trzeba podejmować WIELE decyzji…
61
Wyciągnij rękę i nawiąż kontakt z użytkownikami
63
Poznajemy bliżej funkcję console.log
65
Otwieranie konsoli
66
Piszemy poważną aplikację JavaScript
67
Jak mogę dodać kod do strony? (Niech policzę wszystkie sposoby)
70
Będziemy musieli was rozdzielić
71
KURIER
S IE C IO W S K I
-DNXQLNQąüW\FKĪ
HQXMąF\FKEáĊGyZ
ZQD]ZDFK"
0DV]EDUG]RGXĪRVZ
ZGRELHUDQLXQD]Z] RERG\
7ZRU]ąFQD]Z\VNá
UHGDNFMD]HEUDáD]DW PLHQQ\FK
VáyZXĪ\ZDM]DSLVXDGDMąFHVLĊ]NLONX 1D]Z]DF]\Q
camel case
DMąF\F
VLHFLRZLFNLFKHNVSHUHPSRUDG\NLONX
XĪ\ZDMZ\áąF]QLHZKVLĊRGBRUD]
WyZE\XáDWZLü&L :F]HĞQLHMF]\
GRELHUDQLHQD]Z
WHG\NLHG\PDV]
SyĨQLH
QDSUDZGĊSRZDĪQ\S
XWZRU]\ü]PLHQQąUH MEĊG]LHV]PXVLDá
RZyG
SUH]HQWXMąFąQS
:\ELHUDMQD]Z\N
GZXJáR
ZHJRVPRND]
1D]Z\]PLHQQ\FK]D
WyUHFRĞ]QDF]ą
-DN":\VWDUF]\XĪ\ü LHMąFHJRRJQLHP
]QDNXVą]D]Z\F]DM F]\QDMąFHVLĊRG
7DNLHQD]Z\]PLHQQ\
]DSLVX
camel case
]DUH]HUZRZD
ZNWyU\PSLHUZV]HOLWH
OXEIRR]DSHZQH]QD FKMDNBPU
U\SRV]F]HJyOQ\FK GODELEOLRWHN-DYD6FULSWLFKRüQLHN QH
F]ąFR
VáyZ
ĞGOD&
]Z\MąWNLHPSLHUZV]H
WyU]\
DXWRU]\XĪ\ZDMą]QDN
LHELH
MHGQDNVSRáHF]QRĞü
JR
]UyĪQ\PLNRQZHQFMD XB]JRGQLH
QLHUDF]HMQLHFKĊWQLH6LHFLRZLFSDWU]\QD ]DSLV\ZDQHVąZLHONLPLOLWHUDP
E\UDF]HMQLHXĪ\ZDüPLMHGQDN]DOHFDP\
ĪH]F]DVHP]DSHZQ 1LHW\ONRGODWHJR SU]\NáDGWZR+HDGHG'UDJRQ: LRWR
LWK)LUH
7DNLHQD]Z\áDWZRVLĊ
]QDNyZFK\EDĪHPDVĪDGQHJR]W\FK
]QDF]ąOHF]WDNĪHG H]DSRPQLV]FR
WZRU]\SRWUDI
]QDSUDZGĊZDĪQH
EĊG]LHF]\WHOQLHMV]\ODWHJRĪHVDPNRG QLPLSRVáXJLZDüZV]\VF\Z6LHFLR LąVLĊ SRZRG\ EĊG]LHV]ZLHG]
LDáNLHG\ ZLFDFK
DSR]DW\P]DSHZQLD
QD]ZWDNLFKMDNFXU NLHG\XĪ\MHV]
MąGXĪąHODVW\F]QRĞü =DFKRZDMEH
UHQW3UH
ZWZRU
DVXUHOXE
]SLHF]HĔVWZR
]HQLXGRZROQ\
SDVVHG([DP
VSHáQLDMąF\FK7ZRMH FKQD]Z
:\ELHUDMąFQD]Z\G
SRWU]HE\,VWQLHMą
WDNĪHLQQHVSRVRE\SLV
]DFKRZDMEH]SLHF]HĔOD]PLHQQ\FK
]PLHQQ\FKDOHWHQMH DQLDQD]Z
SRUDG]W\P]ZLą]DQ VWZRNLOND
XĪ\ZDQ\ QDZHWZ VWQDMF]ĊĞFLHM
ZNVLąĪFHMHGQDNQ \FKSRGDP\GDOHM
LQQ\FKMĊ]\NDFK E\QD]Z\E\á\]UR]XDUD]LHSDPLĊWDM
VáyZNOXF]RZ\FKL] PLDáHXQLNDM
GHNODUDFMLXPLHV]F]DDZV]HQDSRF]ąWNX
MVáRZRYDU
10
Spis treści
Pisanie prawdziwego kodu
2
Idziemy dalej
Już znasz zmienne, typy, wyrażenia…, możesz zatem pójść krok dalej. Chodzi
o to, że już trochę poznałeś język JavaScript. Wiesz na tyle dużo, by napisać jakiś prawdziwy kod.
Kod, który robi coś interesującego, którego ktoś chciałby używać. Jednak wciąż brakuje
Ci praktycznego doświadczenia w pisaniu kodu. Właśnie mamy zamiar temu zaradzić,
tu i teraz! A w jaki sposób? Skacząc na główkę na głęboką wodę i pisząc prostą grę, w całości
w języku JavaScript. Cel jest bardzo ambitny, jednak będziemy go realizować krok po kroku.
Chodź, zaczynamy! Jeśli zechcesz przy okazji uruchomić kolejny prosty startup, nie będziemy
Ci przeszkadzać — kod jest Twój.
Start
Przygotowanie
gry
Pobranie
sprawdzanego
pola
SXGâR
Sprawdzenie
pola
WUDÀHQLH
zatopienie
Zaznaczenie
RNUĐWXMDNR
zatopionego
:\ĤZLHWOHQLH
wyniku/rankingu
Xİ\WNRZQLND
Koniec
gry
Zaznaczenie
RNUĐWXMDNR
WUDÀRQHJR
Napiszmy grę w okręty
82
Pierwsza próba…
82
Punkt pierwszy: projekt wysokiego poziomu
83
Analiza pseudokodu
85
A… zanim przejdziemy dalej, nie zapomnij o kodzie HTML
87
Pisanie kodu prostej wersji gry w okręty
88
A teraz zajmijmy się logiką gry
89
Krok pierwszy: przygotowanie pętli i pobranie danych
90
Jak działa funkcja prompt?
91
Sprawdzanie komórki wskazanej przez użytkownika
92
Czy użytkownikowi udało się trafić?
94
Dodanie kodu wykrywającego trafienia
95
Prezentacja informacji o zakończonej grze
96
To koniec implementacji logiki
98
Chwilka na zapewnianie jakości
99
Czy możemy pogadać o rozwlekłości Twojego kodu?
103
Kończymy prostą wersję gry w okręty
104
Jak przypisywać wartości losowe?
105
Najlepszy na świecie przepis na generowanie liczb losowych
105
Wróćmy do zapewniania jakości
107
Gratulujemy pierwszego prawdziwego programu w języku JavaScript
i mamy dwa słowa o wielokrotnym używaniu kodu
109
1RLSURV]Ċ3UDZG]LZ\
VFKHPDWEORNRZ\
11
Spis treści
Przedstawienie funkcji
3
Stawiamy na funkcjonalność
Przygotuj się na użycie pierwszej ze swoich supermocy. Zdobyłeś już nieco
umiejętności programistycznych; teraz nadszedł czas, aby rozwinąć je jeszcze bardziej przy
użyciu funkcji. Funkcje zapewniają możliwość pisania kodu, który można stosować we wszelkich
możliwych okolicznościach, kodu używanego wielokrotnie, którym można znacznie łatwiej
zarządzać i w końcu który można wyodrębnić, nadać mu łatwą do zapamiętania nazwę,
zapomnieć o całej jego złożoności i zająć się innymi ważnymi problemami. Przekonasz się, że
funkcje to nie tylko droga, która zmieni Cię z autora skryptów w programistę. Są one kluczowym
czynnikiem określającym styl programowania w języku JavaScript. W tym rozdziale zaczniemy od
podstaw: poznasz mechanikę funkcji i tajniki ich działania, a dalej w tej książce będziesz stopniowo
powiększać swoją wiedzę i umiejętności ich stosowania. A zatem, zacznij budować solidne
podstawy znajomości JavaScriptu i zrób to już teraz.
Co z tym kodem było nie tak?
12
117
Swoją drogą, czy wspominaliśmy już o FUNKCJACH?
119
No dobrze, ale jak to właściwie działa?
120
Co można przekazywać do funkcji?
125
JavaScript przekazuje przez wartość
128
Zakręcone funkcje
130
Funkcje mogą także coś zwracać
131
Śledzenie wykonania funkcji z instrukcją return
132
Zmienne globalne i lokalne
135
Poznawanie zasięgu zmiennych lokalnych i globalnych
137
Krótkie życie zmiennych
138
Nie zapominaj o deklarowaniu zmiennych
139
Spis treści
Porządkowanie naszych danych
4
Tablice
0
1
2
3
60
50
60
58
4
54
5
6
7
8
54
58
50
52
9
54
JavaScript to nie tylko liczby, łańcuchy znaków i wartości logiczne. Dotychczas
pisałeś jedynie kod JavaScript, w którym były używane wartości typów prostych — proste
łańcuchy znaków, liczby i wartości logiczne, takie jak „Burek”, 23 oraz true. Korzystając z takich
wartości, można zrobić naprawdę dużo, jednak w którymś momencie będziesz musiał zacząć
posługiwać się znacznie większą ilością danych. Przykładowo mogą to być wszystkie produkty
umieszczone w koszyku zakupowym albo utwory na liście odtwarzania, albo gwiazdozbiory
i współrzędne poszczególnych gwiazd, albo cały katalog produktów. Jednak do tego
potrzebujesz czegoś bardziej… sexy. W języku JavaScript preferowanym typem danych dla takich
uporządkowanych zbiorów informacji jest tablica, a w tym rozdziale dokładnie przeanalizujemy,
jak umieszczać dane w tablicach, przekazywać tablice oraz jak na nich operować. Dalej w książce
omówimy także kilka innych sposobów strukturyzacji danych, jednak zaczniemy od tablic.
Czy możesz pomóc firmie BańkoCorp?
160
Jak reprezentować wiele wartości w JavaScripcie?
161
Jak działają tablice?
162
A w ogóle jak duża jest tablica?
164
Korpo-zdanio-budowator
166
W międzyczasie w firmie BańkoCorp…
169
Jak pobrać wszystkie elementy tablicy?
172
Chwila, istnieje lepszy sposób iteracji po tablicy
174
Czy możemy porozmawiać o rozwlekłości Twojego kodu?
180
Poprawienie pętli for przy użyciu operatora postinkrementacji
181
Szybka jazda próbna
181
Tworzenie pustej tablicy (i dodawanie do niej danych)
185
Zwycięzcami są…
189
Krótka inspekcja kodu…
191
Piszemy funkcję printAndGetHighScore
192
Refaktoryzacja kodu z użyciem funkcji printAndGetHighScore
193
Zastosowanie zmian…
195
13
Spis treści
Zrozumieć obiekty
5
Wycieczka do Obiektowa
Do tej pory w tworzonym kodzie używałeś jedynie danych typów prostych
oraz tablic. Dodatkowo podchodziłeś do programowania w sposób proceduralny — korzystałeś
z prostych instrukcji, instrukcji warunkowych oraz pętli, ewentualnie umieszczałeś je w funkcjach
— to właściwie nie jest programowanie obiektowe. Prawdę powiedziawszy, to w ogóle nie
jest programowanie obiektowe! Tu i tam, nawet o tym nie wiedząc, użyłeś — co prawda — kilku
obiektów, jednak na razie jeszcze nie napisałeś żadnego własnego obiektu. Nadszedł najwyższy
czas, żeby zostawić to stare i nudne proceduralne miasteczko i zacząć tworzenie własnych
obiektów. W tym rozdziale dowiesz się, dlaczego stosowanie obiektów sprawi, że Twoje życie
stanie się znacznie lepsze — przynajmniej pod względem programistycznym (niestety, w jednej
książce nie możemy poprawić Twojej znajomości mody i jednocześnie nauczyć programowania
w języku JavaScript). I jeszcze jedno ostrzeżenie: kiedy już poznasz obiekty, nigdy nie będziesz
chciał ich porzucić. Wyślij nam pocztówkę, kiedy już dojedziesz do krainy obiektów.
14
Czy ktoś powiedział „obiekty”?
206
Myśląc o właściwościach…
207
W jaki sposób tworzy się obiekty?
209
Czym w ogóle jest programowanie obiektowe?
212
Jak działają właściwości?
213
W jaki sposób zmienna przechowuje obiekt? Ciekawe umysły
chciałyby to wiedzieć…
218
Porównanie danych typów prostych i obiektów
219
Jeszcze inne operacje z wykorzystaniem obiektów…
220
Analiza działania wstępnej selekcji
222
Porozmawiajmy nieco więcej o przekazywaniu obiektów do funkcji
224
Zachowuj się! Jak dodawać zachowania do obiektów?
230
Poprawianie metody drive
231
Dlaczego metoda drive nic nie wie o właściwości started?
234
Jak działa this?
236
Jak zachowanie wpływa na stan? Dodajemy trochę paliwa
242
A teraz niech stan będzie mieć wpływ na zachowanie
243
Gratulujemy utworzenia pierwszych obiektów!
245
Wiesz co? Obiekty są wszędzie dookoła Ciebie (i ułatwiają Ci życie)
246
Spis treści
Interakcja ze stronami WWW
6
Poznajemy DOM
Przebyłeś już długą drogę, poznając JavaScript. Powoli z żółtodzioba zmieniłeś się
w twórcę prostych skryptów, a potem w końcu w programistę. Jednak wciąż Ci czegoś brakuje.
Abyś mógł naprawdę wykorzystać całą swoją znajomość języka JavaScript, musisz dowiedzieć
się, jak prowadzić interakcję ze stronami WWW, w których umieszczasz swoje skrypty. Tylko to
pozwoli Ci tworzyć strony, które są dynamiczne, które reagują, odpowiadają i aktualizują swoją
zawartość już po jej wczytaniu przez przeglądarkę. W jaki sposób można prowadzić interakcję
ze stroną WWW? Służy do tego DOM, nazywany także obiektowym modelem dokumentu.
W tym rozdziale opiszemy go szczegółowo i wyjaśnimy, jak można z niego korzystać i jak go
używać wraz z językiem JavaScript, by nauczyć nasze strony wykonywania wielu nowych sztuczek.
To ja: przeglądarka!
Właśnie wyświetlam
stronę i tworzę jej DOM.
W poprzednim rozdziale miałeś wykonać niewielką misję
— misję złamania kodu
260
Co robi ten kod?
261
Jak naprawdę wygląda interakcja JavaScriptu ze stroną WWW?
263
Jak wypiec swój własny DOM?
264
Pierwszy smak DOM
265
Pobieranie elementu przy użyciu metody getElementById
270
Co pobieramy z DOM?
271
Dostęp do kodu HTML w elemencie
272
Co się dzieje, kiedy zmieniamy DOM?
274
Jazda próbna wokół planet
277
Nawet nie myśl o uruchamianiu mojego kodu, zanim strona
nie zostanie w całości wczytana
279
Ty mówisz: „przeglądarka”, ja mówię: „wywołanie zwrotne”
280
Jak ustawiać atrybuty przy użyciu metody setAttribute?
285
Więcej zabawy z atrybutami (wartości atrybutów można także POBIERAĆ) 286
Nie zapominaj, że także metoda getElementById może zwracać null
286
Tymczasem w systemie słonecznym…
287
document
html
head
p id =”greenplanet”
Wszystko jest
ZSRU]ċGNX
body
p id =”redplanet”
Nie ma tu nic
ciekawego
p id =”blueplanet”
Wszystkie systemy
sprawne
15
Spis treści
Typy, równość, konwersje i cały ten jazz
7
Poważne typy
Nadszedł czas, by poważnie przyjrzeć się typom. Jedną ze wspaniałych cech
JavaScriptu jest to, że można w nim zrobić całkiem dużo bez jego szczegółowej znajomości.
Aby jednak perfekcyjnie opanować język, dostać awans i zacząć robić to, co naprawdę chcesz
robić w życiu, musisz się zaprzyjaźnić z typami. Czy pamiętasz, co już dawno, na samym początku
książki powiedzieliśmy na temat JavaScriptu? Że nie może się poszczycić rozpuszczoną, popularną,
akademicką definicją? No cóż… To prawda, jednak życie akademickie nie zatrzymało ani Steve’a
Jobsa ani Billa Gatesa, nie zatrzymało także języka JavaScript. Oznacza to jednak, że JavaScript
nie ma… hm… doskonale przemyślanego systemu typów i znajdziemy w nim sporo dziwactw.
Nie obawiaj się jednak — w tym rozdziale dokładnie wszystko wyjaśnimy, dzięki czemu już
niebawem nauczysz się unikać tych wszystkich zawstydzających problemów z typami.
16
Gdzieś tam jest ukryta prawda…
294
Uważaj, możesz natknąć się na undefined, kiedy będziesz się
tego najmniej spodziewać…
296
Jak używać null?
299
Stosowanie wartości NaN
301
Sprawy stają się jeszcze dziwniejsze
301
Musimy coś wyznać
303
Zrozumienie operatora równości (pseudonim: ==)
304
Jak wykonywana jest konwersja operandów operatora równości
(brzmi groźniej, niż jest w rzeczywistości)?
305
Jak ściśle podejść do zagadnienia równości?
308
Jeszcze więcej konwersji typów…
314
Jak określić, czy dwa obiekty są równe?
317
Gdzieś tam leży prawda…
319
JavaScript uwzględnia fałsz
320
Sekretne życie łańcuchów znaków
322
Dlaczego łańcuch może wyglądać jak dana typu prostego oraz jak obiekt?
323
Pięciominutowa wycieczka po metodach (i właściwościach)
łańcuchów znaków
325
Wojna o fotel
329
Spis treści
Łączenie wszystkiego w całość
8
Tworzenie aplikacji
Włóż to do swojego przybornika z narzędziami, czyli do skrzynki, w której umieszczasz
wszystkie swoje nowe umiejętności programistyczne, wiedzę dotyczącą DOM, a nawet
HTML i CSS. W tym rozdziale wykorzystasz wszystkie te umiejętności, by utworzyć pierwszą
prawdziwą aplikację internetową. Wystarczy już prymitywnych namiastek gier, w których na
jednowymiarowej planszy pływa jeden okręt. W tym rozdziale wykonasz pełne doświadczenie:
dużą, atrakcyjną planszę do gry, wiele okrętów oraz obsługę wprowadzania danych przez
użytkownika. Struktura strony, na której będzie prowadzona gra, powstanie w języku HTML,
jej wygląd określony zostanie przy użyciu stylów CSS, a zachowanie samej gry napisane w języku
JavaScript. Przygotuj się: to będzie jazda na całego, rozdział, w którym pełnym gazem będziesz
zmierzać w kierunku pisania naprawdę poważnego kodu.
Tym razem napiszemy PRAWDZIWĄ grę w okręty
344
Krok wstecz… do HTML i CSS
345
Tworzenie strony HTML: postać ogólna
346
Dodawanie stylów
350
Stosowanie klas hit i miss
353
Jak zaprojektować grę?
355
Implementacja widoku
357
357
Jak działają metody displayHit oraz displayMiss?
359
Model
362
W jaki sposób będziemy reprezentować okręty?
364
Implementacja obiektu modelu
367
A
Rozmyślamy o metodzie fire
368
B
Implementacja kontrolera
375
Przetwarzanie pola wskazanego przez użytkownika
376
Planowanie kodu…
377
C
D
Okręt1
Jak działa metoda displayMessage?
Okręt2
E
Implementacja funkcji parseGuess
378
F
W międzyczasie w kontrolerze…
381
IE
Okręt3 TRAFIEN
G
0
1
2
3
4
5
6
Dodanie procedury obsługi zdarzeń do przycisku Ognia!
385
Przekazywanie współrzędnych do kontrolera
386
Jak rozmieszczać okręty?
390
Implementacja metody generateShip
391
Generacja początkowego pola okrętu
392
Dokończenie metody generateShip
393
17
Spis treści
Programowanie asynchroniczne
9
Obsługa zdarzeń
Po przeczytaniu tego rozdziału zdasz sobie sprawę z tego, że to już nie
są przelewki i nie jesteśmy już w Kansas. Do tej pory pisałeś kod, który zazwyczaj
wykonywany był od samego początku do końca — oczywiście Twój kod mógł być nieco bardziej
skomplikowany i zawierać kilka funkcji, obiektów i metod, jednak w jakimś momencie ten kod
po prostu był wykonany wiersz po wierszu. Cóż, jest nam bardzo przykro, że mówimy Ci to
tak późno, jednak typowy kod JavaScript nie jest pisany w taki sposób. Kod pisany w tym
języku reaguje na zdarzenia. Jakiego rodzaju zdarzenia? Może to być kliknięcie strony przez
użytkownika, przesłanie danych z serwera, upływ pewnego okresu czasu w przeglądarce, jakaś
zmiana wprowadzona w DOM oraz wiele innych. W rzeczywistości, w niewidoczny dla nas sposób,
w przeglądarce cały czas zachodzą jakieś zdarzenia. W tym rozdziale jeszcze raz przemyślisz swoje
podejście do sposobu pisania kodu JavaScript i dowiesz się, dlaczego trzeba pisać kod reagujący
na zdarzenia oraz jak należy to robić.
18
Czym są zdarzenia?
409
Czym jest procedura obsługi zdarzeń?
410
Jak napisać pierwszą procedurę obsługi zdarzeń?
411
Jazda próbna procedury obsługi zdarzeń
412
Poznajemy zdarzenia, pisząc grę
414
Implementacja gry
415
Jazda próbna
416
Dodajmy więcej obrazków
420
Teraz musimy przypisać tę samą procedurę obsługi zdarzeń
do właściwości onclick każdego obrazka
421
Jak użyć tej samej funkcji do obsługi wszystkich obrazków?
422
Jak działa obiekt zdarzenia?
425
Zaprzęgamy obiekt zdarzenia do pracy
427
Testujemy obiekt zdarzenia i właściwość target
428
Zdarzenia i kolejki
430
Jeszcze więcej zdarzeń
433
Jak działa funkcja setTimeout?
434
Kończenie gry
438
Jazda testowa z licznikiem czasu
439
Spis treści
Funkcje pierwszej klasy
10
Wyzwolone funkcje
Poznaj funkcje, a potem baw się na całego. Każda sztuka, rzemiosło czy też dyscyplina
sportowa mają swoją kluczową cechę, która odróżnia graczy przeciętnych od wirtuozów.
W przypadku języka JavaScript jest to prawdziwe i dokładne zrozumienie funkcji. W języku JavaScript
funkcje mają kluczowe znaczenie, a wiele technik służących do projektowania i organizacji kodu
bazuje na zaawansowanej znajomości funkcji i umiejętności korzystania z nich. Droga prowadząca do
poznania funkcji na takim poziomie jest interesująca i niejednokrotnie wymagająca dla mózgu, zatem
dobrze się do niej przygotuj. W tym rozdziale będziesz się czuł jak dziecko oprowadzane przez pana
Willy’ego Wonkę po fabryce czekolady — będziesz w nim kontynuował poznawanie funkcji w języku
JavaScript, a przy okazji zobaczysz dziwaczne, wariackie i cudowne rzeczy.
Tajemnicze, podwójne życie słowa kluczowego function
454
Deklaracje funkcji a wyrażenia funkcyjne
455
Przetwarzanie deklaracji funkcji
456
I co dalej? Przeglądarka wykonuje kod
457
Idziemy dalej… Instrukcja warunkowa
458
O tym, dlaczego funkcje są także wartościami
463
Czy wspominaliśmy już, że w JavaScripcie funkcje
mają status „pierwszej klasy”?
466
Latanie pierwszą klasą
467
Piszemy kod do przetwarzania i sprawdzania pasażerów
468
Przetwarzanie listy pasażerów
470
Przekazywanie funkcji do funkcji
471
Zwracanie funkcji z funkcji
474
Pisanie kodu do wydawania napojów
475
Pisanie kodu do wydawania napojów — inne podejście
476
Przyjmowanie zamówień z wykorzystaniem funkcji pierwszej klasy
478
Cola sieciowicka
481
Jak działa metoda sort tablic?
483
Łączymy wszystko w całość
484
Weźmy teraz sortowanie na jazdę próbną
486
19
Spis treści
Funkcje anonimowe, zasięg i domknięcia
11
A niech to!
Judyta znowu miała
rację.
20
Poważne funkcje
W poprzednim rozdziale rozłożyłeś funkcje na czynniki pierwsze, ale wciąż
musisz się o nich jeszcze sporo dowiedzieć. W tym rozdziale będzie prawdziwa
jazda na całego. Pokażemy Ci, jak naprawdę korzysta się z funkcji. To nieszczególnie długi
rozdział, jednak bardzo intensywny, a po jego przeczytaniu siła wyrazu tworzonego przez Ciebie
kodu JavaScript będzie większa, niż mógłbyś przypuszczać. Co więcej, mamy w nim zamiar
przedstawić pewne ogólnie przyjęte idiomy i konwencje związane z tworzeniem i stosowaniem
funkcji w języku JavaScript, dzięki czemu będziesz już mógł skorzystać z kodu pisanego przez
współpracowników lub czerpanego z ogólnie dostępnych bibliotek JavaScript. A jeśli jeszcze nigdy
nie słyszałeś o funkcjach anonimowych i domknięciach (ang. closure), to wiedz, że znalazłeś się
w odpowiednim miejscu.
Chwila… Czy Judyta nie
wspominała o domknięciach?
Wygląda na to, że one są
powiązane z tym, co robimy.
Zobaczmy, czy będziemy mogli
dowiedzieć się czegoś na ich
temat i zagiąć Judytę.
Rzut oka na inną stronę funkcji…
500
Jak używać funkcji anonimowych?
501
Musimy ponownie pomówić o rozwlekłości Twojego kodu
503
Kiedy funkcja zostaje zdefiniowana? To zależy…
507
Co się właśnie stało? Dlaczego funkcja fly nie była zdefiniowana?
508
Zagnieżdżanie funkcji
509
Jaki wpływ na zasięg ma zagnieżdżanie funkcji?
510
Krótka powtórka z zasięgu leksykalnego
512
Miejsce, w którym zasięg leksykalny sprawia,
że sprawy stają się interesujące
513
Funkcje raz jeszcze
515
Wywoływanie funkcji (po raz wtóry)
516
Czym właściwie są domknięcia?
519
Domykanie funkcji
520
Zastosowanie domknięć w celu zaimplementowania magicznego licznika
522
Zaglądamy za kulisy…
523
Tworzenie domknięcia poprzez przekazanie wyrażenia funkcyjnego
jako argumentu
525
Domknięcia zawierają rzeczywiste środowisko, a nie jego kopię
526
Tworzenie domknięć jako procedur obsługi zdarzeń
527
Jak działa domknięcie liczące kliknięcia?
530
Spis treści
Zaawansowane sposoby konstruowania obiektów
12
Tworzenie obiektów
Dotychczas wszystkie obiekty tworzyłeś własnoręcznie. Opracowując każdy
z nich, korzystałeś z literału obiektowego, w którym podawałeś wszystkie właściwości
i metody. Na niewielką skalę takie rozwiązanie będzie się sprawdzać, jednak podczas tworzenia
poważnego kodu będziesz potrzebował czegoś lepszego. Właśnie w tym miejscu do akcji
wkraczają konstruktory obiektów. Konstruktory sprawiają, że tworzenie obiektów jest znacznie
łatwiejsze, a wszystkie budowane obiekty mogą być zgodne z jednym wzorcem — oznacza to, że
konstruktorów używamy po to, by zapewnić, że wszystkie obiekty będą miały te same właściwości
i udostępniały te same metody. Kiedy korzystasz z konstruktorów, kod obiektów może być
znacznie bardziej zwięzły i mniej podatny na występowanie błędów, zwłaszcza w przypadkach,
gdy tworzysz bardzo dużo obiektów. Po przeczytaniu tego rozdziału będziesz stosował
konstruktory z taką wprawą, jakbyś dorastał w Obiektowie.
Tworzenie obiektów przy użyciu literałów obiektowych
544
Stosowanie konwencji podczas tworzenia obiektów
545
Prezentacja konstruktorów obiektów
547
Jak utworzyć konstruktor?
548
Jak należy używać konstruktorów?
549
Sposób działania konstruktorów
550
W konstruktorach można także umieszczać metody
552
Nadszedł czas na produkcję masową
558
Weźmy nowe samochody na jazdę próbną
560
Nie zapominaj jeszcze o literałach obiektowych
561
Przekazywanie argumentów przy użyciu literału obiektowego
562
Modyfikacja konstruktora Car
563
Zrozumieć instancje obiektów
565
Nawet obiekty utworzone przy użyciu konstruktora mogą mieć
własne właściwości
568
Konstruktory stosowane w praktyce
570
Obiekt Array
571
Jeszcze więcej zabawy z wbudowanymi obiektami JavaScriptu
573
21
Spis treści
Stosowanie prototypów
13
Obiekty ekstramocne
Nauka tworzenia obiektów była jedynie początkiem. Nadszedł czas na wzmocnienie
obiektów. Potrzebujesz więcej sposobów, by tworzyć wzajemne związki pomiędzy obiektami
oraz zapewniać możliwość współdzielenia kodu przez takie powiązane obiekty. Dodatkowo
potrzebujesz także sposobów na rozszerzanie i zwiększanie możliwości istniejących obiektów.
Innymi słowy, potrzebujesz więcej narzędzi. W tym rozdziale przekonasz się, że JavaScript
dysponuje naprawdę użytecznym modelem obiektowym, choć jednocześnie jest on nieco
odmienny od modeli stosowanych w innych obiektowych językach programowania. Zamiast
typowego modelu obiektów bazującego na klasach, w JavaScripcie wykorzystano model bazujący
na prototypach, w którym obiekty mogą dziedziczyć po innych obiektach i rozszerzać ich
zachowania. A co on daje? Już wkrótce się przekonasz. A zatem zaczynajmy…
Object
toString()
hasOwnProperty()
iwiċcej
Prototyp Dog
species: "Psowate"
bark()
run()
wag()
Prototyp ShowDog
league: “Sieciowice”
stack()
bait()
gait()
groom()
ShowDog
name: "Szatan",
breed: "terier szkocki",
weight: 8
handler: "Grzesiu"
22
Hej, zanim zaczniemy, mamy lepszy sposób rysowania
diagramów obiektów
585
Ponowna analiza konstruktorów: wielokrotnie
używamy kodu, ale czy robimy to efektywnie?
586
Czy powielanie metod jest poważnym problemem?
588
Czym są prototypy?
589
Dziedziczenie po prototypie
590
Jak działa dziedziczenie?
591
Przesłanianie prototypu
593
Jak przygotować prototyp?
596
Prototypy są dynamiczne
602
Bardziej interesująca implementacja metody sit
604
Jeszcze jeden raz: sposób działania właściwości sitting
605
Jak zaprojektować psa pokazowego?
609
Tworzenie łańcucha prototypów
611
Jak działa dziedziczenie w łańcuchu prototypów?
612
Tworzenie prototypu psa pokazowego
614
Tworzenie instancji psa pokazowego
618
Analiza wyników ćwiczenia
621
Łańcuch prototypów nie kończy się na psie
627
Wykorzystanie dziedziczenia na swoją korzyść… Przesłonięcie
domyślnych metod
628
Stosowanie dziedziczenia do własnych celów… Rozszerzanie
wbudowanych obiektów
630
Wielka Jednolita Teoria JavaScriptu
632
Łączenie wszystkiego w całość
633
Co dalej?
633
Spis treści
Pozostałości
A
Dziesięć najważniejszych rzeczy (których nie opisaliśmy)
Opisaliśmy naprawdę sporo zagadnień i już niemal
udało Ci się skończyć tę książkę. Będziemy za Tobą
tęsknili, jednak zanim pozwolimy Ci odejść, musimy jeszcze coś
powiedzieć, bo nie czulibyśmy się w porządku, wypuszczając Cię
w świat bez tych informacji. Nie ma możliwości, byśmy w tym
stosunkowo niewielkim rozdziale zdołali zmieścić wszystko, co
ewentualnie mogłoby się przydać. Prawdę mówiąc, wcześniej
zamieściliśmy w tym rozdziale wszystko to, co przydałoby się,
żebyś wiedział o programowaniu w języku JavaScript, lecz
musieliśmy zmniejszyć rozmiar czcionki do 0,00004 punktu.
Wszystko się zmieściło, ale nikt nie był w stanie tego przeczytać.
Dlatego większość tego tekstu odrzuciliśmy, pozostawiając tu
jedynie Dziesięć Najważniejszych Zagadnień. I to naprawdę
jest koniec tej książki. Oczywiście z wyjątkiem indeksu, który
koniecznie musisz przeczytać!
S
Skorowidz
1. jQuery
644
2. Więcej operacji na DOM
646
3. Obiekt window
647
4. Obiekt arguments
648
5. Obsługa wyjątków
649
6. Dodawanie procedur obsługi zdarzeń przy użyciu
metody addEventListener
650
7. Wyrażenia regularne
652
8. Rekurencja
654
9. JSON
656
10. JavaScript po stronie serwera
657
661
23
-DNNRU]\VWDÊ]WHMNVLÈĝNL
Wprowadzenie
Nie mogę uwierzyć, że
coś takiego umieścili
w książce o JavaScripcie!
iadamy na palące
W tym rozdziale odpoworzy UMIEŚCILI to
aut
ego
acz
pytanie: „Dl
języku JavaScript?”.
wszystko w książce o
jesteś tutaj 25
Jak korzystać z tej książki
Dla kogo jest ta książka?
Jeżeli możesz odpowiedzieć twierdząco na wszystkie poniższe pytania,
książka ta jest dla Ciebie.
1 Czy masz dostęp do komputera z zainstalowaną nowoczesną
przeglądarką i edytorem tekstów?
2 Czy chcesz nauczyć się, zrozumieć i zapamiętać, jak pisze się
Za nowoczesne uważamy
zaktualizowane wersje przeglądarek
Safari, Chrome, Firefox oraz IE
w wersji 9. lub nowszej.
programy w języku JavaScript przy użyciu najlepszych technik
i najnowocześniejszych standardów?
3 Czy od drętwych i nudnych publikacji akademickich
wolisz wciągające dyskusje przy posiłku?
[Uwaga z działu marketingu: ta książka
jest dla każdego, kto ma kartę kredytową.]
Kto raczej nie powinien sięgać po tę książkę?
Jeżeli możesz odpowiedzieć twierdząco na którekolwiek z poniższych pytań,
ta książka nie jest dla Ciebie.
1 Czy nie miałeś żadnego kontaktu z zagadnieniami tworzenia
stron WWW?
Czy HTML i CSS są dla Ciebie pojęciami całkowicie obcymi? Jeśli tak,
zapewne będziesz wolał sięgnąć po jakąś książkę poświęconą językom
HTML i CSS, aby najpierw zrozumieć zasady tworzenia stron WWW,
a dopiero potem zacząć naukę języka JavaScript.
2 Czy jesteś świetnym programistą aplikacji internetowych poszukującym
książki o charakterze encyklopedycznym?
3 Czy boisz się spróbować czegoś nowego? Wolałbyś raczej poddać się
leczeniu kanałowemu, niż zdecydować na połączenie pasków ze szkocką
kratą? Czy naprawdę uważasz, że nie można traktować poważnie książki
technicznej, jeśli obiekty JavaScript są antropomorfizowane?
26
Wprowadzenie
Wprowadzenie
Wiemy, co myślisz
Jakim cudem może to być poważna książka?
Po co te wszystkie obrazki?
Czy w ten sposób naprawdę mogę się czegokolwiek nauczyć?
Twój mózg myśli, że
właśnie TO jest istotne.
Wiemy, co myśli Twój mózg
Twój mózg pragnie nowości. Zawsze szuka, przegląda i wyczekuje czegoś niezwykłego.
Tak jest skonstruowany i dzięki temu masz szansę przeżyć.
W dzisiejszych czasach jest mało prawdopodobne, abyś stał się przekąską dla tygrysa.
Jednak Twój mózg wciąż obserwuje. W końcu nigdy nic nie wiadomo.
Zatem, co Twój mózg robi z tymi wszystkimi rutynowymi, zwyczajnymi,
normalnymi informacjami, jakie do niego docierają? Robi wszystko, co tylko
może, aby nie przeszkadzały w jego najważniejszym zadaniu — zapamiętywaniu
rzeczy, które mają prawdziwe znaczenie. Twój mózg nie traci czasu i energii
na zapamiętywanie nudnych informacji; one nigdy nie przechodzą przez filtr
odrzucający od razu wszystko, co jest „całkowicie nieistotne”.
Skąd w takim razie Twój mózg wie, co jest istotne? Wyobraź sobie, że wędrujesz
po lesie i nagle wyskakuje na Ciebie tygrys. Co dzieje się w Twojej głowie
i Twoim ciele?
Wspaniale. Pozostało
jeszcze jedynie 630
głupich, nudnych
i drętwych stron.
Neurony płoną. Emocje szaleją. Adrenalina napływa falami.
I właśnie dlatego Twój mózg wie, że…
To musi być ważne! Nie zapominaj o tym!
ózg
Twój m, że
uważa nie warto
TEGO iętywać.
zapam
Wyobraź sobie teraz, że siedzisz w domu albo w bibliotece. Jest bezpiecznie i miło,
a w okolicy nie grasują żadne tygrysy. Uczysz się. Przygotowujesz się do egzaminu.
A może rozgryzasz jakiś trudny problem techniczny, którego rozwiązanie, według szefa,
powinno zająć Ci tydzień, a najdalej dziesięć dni.
Jest tylko jeden, drobny problem. Twój mózg stara się pomóc. Podejmuje
próby, by te w oczywisty sposób nieistotne informacje nie zajęły cennych
zasobów w Twojej głowie. Zasobów, które powinny zostać wykorzystane
na zapamiętanie naprawdę ważnych rzeczy. Takich jak tygrysy. Takich
jak zagrożenie, jakie niesie ze sobą pożar. Takich jak jeżdżenie na
snowboardzie w krótkich spodenkach.
Co gorsza, nie ma żadnego sposobu, aby powiedzieć mózgowi: „Hej,
mózgu mój, dziękuję ci bardzo, ale niezależnie od tego, jak nudna jest
tak książka i jak nieznaczne są emocje, których aktualnie doznaję,
to jednak naprawdę chciałbym zapamiętać wszystkie te informacje”.
jesteś tutaj 27
Jak korzystać z tej książki
że czytelnik
Wyobrażamy sobie,
.
tej książki jest uczniem
poznać, a następnie
erw powinieneś to coś
jpi
Na
yć?
ucz
na
h
ś
go
żeby się cze
zenie do głowy suchyc
Zatem, czego trzeba,
dzi tu jedynie o wtłoc
cho
nie
I
ć.
nie
om
ii
zap
czegoś nie
acji, neurobiolog
postarać się, by tego
przyswajania inform
.
wadzone w dziedzinie
pro
nia
da
ba
ze
tylko czytania tekstu
ws
niż
jno
faktów. Na
wymaga czegoś więcej
się
e
eni
ucz
że
,
ują
ia pokaz
i psychologii nauczan
działania.
budzić nasze mózgi do
po
fi
tra
po
co
,
My wiemy
Kod źródłowy skryptów pisanych
wą!
w języku JavaScript, w odróżnieniu
ążkach z serii Rusz gło
ksi
w
w
oró
aut
od
ce
innych języków, jest dostarczany
ują
owiąz
bezpośrednio do przeglądarki.
Oto wybrane zasady ob
To
znacząca
różnica!
st
amiętania niż sam tek
cznie łatwiejsze do zap
zna
są
y
raz
Ob
ąć
j.
ągn
lizu
osi
Wizua
ktywna (można
je się o wiele bardziej efe
i sprawiają, że nauka sta
u i przekazywaniu
ani
min
ypo
wności w prz
nawet 89% wzrost efekty
ane informacje stają się
i sprawiają, że przekazyw
unk
rys
,
cej
wię
ku
Co
i).
informacj
bezpośrednio na rysun
starczy umieścić słowa
Wy
.
iałe
zum
o,
zro
stw
ej
ień
dzi
bar
, i prawdopodob
Serwer WWW
´=QDOD]âHPNRGSURV]ĐRWRRQµ
nie na następnej stronie
(lub w jego okolicy), a
którego te słowa
m,
ble
pro
zać
wią
roz
nie
sta
w
ą
będ
się
ące
że osoby ucz
l dwukrotnie.
dotyczą, wzrośnie niema
szych
nifikuj. Według najnow
, jeśli treść była
Stosuj dialogi i perso
wali wynik i o 40% lepsze
ski
uzy
ci
den
stu
ych
cow
koń
h
i rozmowy,
tac
ncj
tes
we
w
ań
kon
bad
rwszej osobie i w
sób bezpośredni, w pie
spo
w
waj jęz yka
kod
ana
że
Uży
,
i.
zyw
żam
yjk
eka
uwa
tor
prz
dę
his
Napraw
wykładania opowiadaj
t
ias
być
Zam
n
.
inie
lny
pow
ma
pt
for
Scri
Java
a nie w sposób
byś bardziej uważny —
cie
zbyt poważnie. Kiedy był
bie
umieszczany w elemen
sie
j
ktu
tra
Nie
go.
potoczne
<head>.
czy podczas wykładu?
rozmowy prz y obiedzie
podczas stymulującej
siebie
Innymi słowy, jeśli nie
Skoro już zwróciłam na
głębszych przemyśleń.
żebyś
Zachęć czytelników do
nie zdarzy się nic
wie
Twoją uwagę, warto,
gło
w
u,
siłk
wy
o
neg
yw
nie
y
zaczął bardziej rozważ
zmusisz neuronów do akt
ngażowany, zaciekawion
alnych.
si być zmotywowany, zaa
używać zmiennych glob
ków
ios
wn
wielkiego. Czytelnik mu
iem
wyciągan
zywaniem problemów,
i podekscytowany rozwią
ystkiego jest możliwe
wsz
o
teg
ie
ięc
ągn
Osi
dzy.
i zdobywaniem nowej wie
rozwiązywania ćwiczeń
do
ie
ań, zapraszan
poprzez stawianie wyzw
ia oraz poprzez
ających do zastanowien
i zadawanie pytań zmusz
ania obu
żow
które wymagają zaanga
nakłanianie do działań,
lu zmysłów.
ko!
półkul mózgowych i wie
k szyb
ie
Nie ta wpływ
agę i zainteresowan
To ma dajność
yciągnij na dłużej uw
prz
z
ora
ź
ąd
ob
ia
Zd
ji, gdy bardzo
na wy wczytywan
lazł się kiedyś w sytuac
czytelnika. Każdy zna
i czas !
pierwszej strony.
niu
yta
ecz
prz
y
po
stron
czyć, lecz zasypiał
ykuwające
chciał się czegoś nau
prz
interesujące, dziwne,
musi być
na rzeczy niezw ykłe,
agę
uw
hnicznego wcale nie
a
tec
rac
ia
zw
ien
zg
Mó
wanie nowego zagadn
zna
ej.
po
bci
nak
szy
ie
Jed
czn
ne.
wa
zna
oi je
wzrok , nieoczeki
ące, Twój mózg prz ysw
zagadnienie interesuj
e
nudne. Jeśli będzie to
i są w znacznej mierz
amiętywania informacj
zap
do
ści
lno
my
zdo
uje
że
,
ięt
już wiemy
nam zależy. Zapam
Wyzwól emocje. Teraz
iętujemy to, na czym
torii
i emocjonalnej. Zapam
ośc
art
zaw
myśli wzruszających his
ich
na
od
tu
e
zależn
Oczywiście nie mamy
.
my
wanie,
wa
yto
czu
ksc
od
de
po
coś
e
h
osn
ie, ciekawość, rad
w sytuacjach, w któryc
zen
koc
zas
jak
e
ocj
em
Chodzi o tak ie
poprawnym
jak ie odczuwamy po
o chłopcu i jego psie.
ji — „Jestem wielki!” —
akc
ysf
sat
e, lub zdaniu sobie
e
dn
uci
tru
ucz
i
za
”
zi
„a niech to…
co powszechnie uchod
ś,
go
cze
się
niu
cze
nau
ału inż ynierii.
roz wiązaniu zagadk i,
icznych niż Zenek z dzi
cej szczegółów techn
spraw y, że znamy wię
<html>
<head>
<title>
My Playlist
</title>
<head>
<body>
<h1>Kick’n Tunes
</h1>
<p>BT - Satellite: nice
28
Wprowadzenie
Wprowadzenie
Metapoznanie — myślenie o myśleniu
Jeśli naprawdę chcesz się czegoś nauczyć, a dodatkowo chcesz się tego nauczyć szybciej
i bardziej dogłębnie, zwracaj uwagę na to, jak Ci na tym zależy. Pomyśl o tym, jak myślisz.
Poznaj sposób, w jaki się uczysz.
Większość z nas w okresie dorastania nie uczestniczyła w zajęciach z metapoznania albo
teorii nauczania. Oczekiwano od nas, że będziemy się uczyć, jednak nie uczono nas, jak
mamy to robić.
Zastanawiam się,
jak zmusić mózg
do zapamiętania
tych informacji…
Jednak zakładamy, że jeśli trzymasz w ręku tę książkę, chcesz nauczyć się JavaScriptu.
I prawdopodobnie nie chcesz na to tracić zbyt wiele czasu. Jeśli chcesz wykorzystać to,
co przeczytałeś w tej książce, musisz to zapamiętać. A oprócz tego musisz to zrozumieć.
Aby w możliwie jak największym stopniu wykorzystać zarówno tę, jak i dowolną inną
książkę lub jakikolwiek inny sposób uczenia się, musisz wziąć odpowiedzialność
za swój mózg. Myśl o tym, czego się uczysz.
Sztuczka polega na tym, aby przekonać mózg, że poznawany materiał jest
naprawdę ważny. Kluczowy dla Twojego dobrego samopoczucia. Tak ważny
jak tygrys stojący naprzeciw Ciebie. W przeciwnym razie będziesz prowadzić
nieustającą wojnę z własnym mózgiem, który ze wszystkich sił będzie się
starać, aby nowe informacje nie zostały utrwalone.
W jaki sposób ZMUSIĆ mózg, aby potraktował
programowanie w JavaScripcie jak głodnego tygrysa?
Można to zrobić w sposób wolny i męczący lub szybki i bardziej efektywny. Wolny sposób polega
na wielokrotnym powtarzaniu. Oczywiście wiesz, że jesteś w stanie nauczyć się i zapamiętać nawet
najnudniejsze zagadnienie, mozolnie je „wkuwając”. Po odpowiedniej ilości powtórzeń Twój mózg
stwierdzi: „Wydaje się, że to nie jest dla niego szczególnie ważne, lecz w kółko to czyta i powtarza,
więc przypuszczam, że jakąś wartość to jednak musi mieć”.
Szybszy sposób polega na zrobieniu czegokolwiek, co zwiększy aktywność mózgu, zwłaszcza wtedy,
kiedy czynność ta wyzwoli kilka różnych typów aktywności. Wszystkie zagadnienia, o jakich pisaliśmy
na poprzedniej stronie, są kluczowymi elementami rozwiązania i udowodniono, że wszystkie mogą
pomóc w zmuszeniu mózgu do tego, aby pracował na Twoją korzyść. Przykładowo badania wykazują,
że umieszczenie słów na opisywanych rysunkach (a nie w innych miejscach tekstu na stronie, np.
w nagłówku lub wewnątrz akapitu) sprawia, że mózg stara się zrozumieć relację pomiędzy słowami
a rysunkiem, a to z kolei zwiększa aktywność neuronów. Większa aktywność neuronów to większa
szansa, że mózg uzna informacje za warte zainteresowania i — ewentualnie — zapamiętania.
Prezentowanie informacji w formie konwersacji pomaga, gdyż ludzie zdają się wykazywać większe
zainteresowanie, kiedy uważają, że biorą udział w rozmowie i oczekuje się od nich, iż będą śledzić
jej przebieg i brać w niej czynny udział. Zadziwiające jest to, iż mózg zdaje się nie zważać na to,
że rozmowa jest prowadzona z książką! Z drugiej strony, jeśli sposób przedstawiania informacji jest
formalny i suchy, mózg postrzega to tak samo jak w sytuacji, gdy uczestniczysz w wykładzie na sali pełnej
sennych studentów: nie trzeba wykazywać jakiejkolwiek aktywności.
Jednak rysunki i przedstawianie informacji w formie rozmowy to jedynie początek…
jesteś tutaj 29
Jak korzystać z tej książki
To, co MY zrobiliśmy
Prototyp Dog
species: "Psowate"
Używaliśmy rysunków, ponieważ Twój mózg zwraca większą uwagę na obrazy niż na tekst. Dla mózgu
faktycznie jeden obraz jest wart tysiąca słów. W sytuacjach, gdy pojawiał się zarówno tekst, jak i rysunek,
umieszczaliśmy tekst na rysunku, gdyż mózg działa bardziej efektywnie, gdy tekst jest wewnątrz czegoś,
co opisuje, niż kiedy jest umieszczony w innym miejscu i stanowi część większego fragmentu.
Stosowaliśmy powtórzenia, wielokrotnie podając tę samą informację na różne sposoby i przy
wykorzystaniu różnych środków przekazu oraz odwołując się do różnych zmysłów. Wszystko po to,
aby zwiększyć szansę, że informacja zostanie zakodowana w większej ilości obszarów Twojego mózgu.
bark()
run()
wag()
Prototyp ShowDog
league: “Sieciowice”
stack()
bait()
gait()
groom()
ShowDog
name: "Szatan",
breed: "terier szkocki",
weight: 8
handler: "Grzesiu"
Używaliśmy pomysłów i rysunków w nieoczekiwany sposób, ponieważ Twój mózg oczekuje i pragnie
nowości, poza tym staraliśmy się zawrzeć w nich chociaż trochę emocji, gdyż mózg jest skonstruowany
w taki sposób, iż zwraca uwagę na biochemię związaną z emocjami. Prawdopodobieństwo zapamiętania
czegoś jest większe, jeśli „to coś” sprawia, że coś poczujemy, nawet jeśli to uczucie nie jest niczym więcej
jak lekkim rozbawieniem, zaskoczeniem lub zainteresowaniem.
Używaliśmy bezpośrednich zwrotów i przekazywaliśmy treści w formie konwersacji, gdyż mózg
zwraca większą uwagę, jeśli sądzi, że prowadzisz rozmowę, niż gdy jesteś jedynie biernym
słuchaczem prezentacji. Mózg działa w ten sposób nawet wtedy, gdy czytasz zapis rozmowy.
Zamieściliśmy w książce ponad 100 ćwiczeń, ponieważ mózg uczy się i pamięta więcej, gdy coś robi,
niż gdy o czymś czyta. Ćwiczenia te stanowią wyzwania, choć nie są przesadnie trudne, gdyż właśnie
takie preferuje większość osób.
Stosowaliśmy wiele stylów nauczania, gdyż Ty możesz preferować instrukcje opisujące krok po
kroku sposób postępowania, ktoś inny analizowanie zagadnienia opisanego w ogólny sposób,
a jeszcze inne osoby — przejrzenie przykładowego fragmentu kodu. Jednak niezależnie od
ulubionego sposobu nauki każdy skorzysta na tym, że te same informacje będą przedstawiane
kilkakrotnie na różne sposoby.
Podawaliśmy informacje przeznaczone dla obu półkul Twojego mózgu, gdyż im bardziej mózg
będzie zaangażowany, tym większe jest prawdopodobieństwo nauczenia się i zapamiętania
podawanych informacji i tym dłużej możesz koncentrować się na nauce. Ponieważ angażowanie
tylko jednej półkuli mózgu często oznacza, że druga będzie mogła odpocząć, zatem będziesz mógł
uczyć się bardziej produktywnie przez dłuższy okres czasu.
Dodatkowo zamieszczaliśmy opowiadania i ćwiczenia prezentujące więcej niż jeden punkt widzenia,
ponieważ mózg uczy się łatwiej, gdy jest zmuszony do przetwarzania i podawania własnej opinii.
Stawialiśmy przed Tobą wyzwania zarówno proponując ćwiczenia, jak i stawiając pytania, na które
nie zawsze można odpowiedzieć w prosty sposób, a to dlatego, że mózg uczy się i pamięta, gdy
musi nad czymś popracować. Pomyśl sam — nie można uzyskać dobrej kondycji, obserwując
ćwiczenia w telewizji. Jednak dołożyliśmy wszelkich starań, aby zapewnić, że gdy pracujesz, robisz
dokładnie to, co trzeba. Postaraliśmy się, aby ani jeden dendryt nie musiał przetwarzać trudnego
przykładu ani analizować tekstu zbyt lapidarnego lub napisanego niezrozumiałym żargonem.
Przedstawialiśmy ludzi. W opowiadaniach, przykładach, na rysunkach itd. robiliśmy to, gdyż Ty też
jesteś człowiekiem. Dlatego Twój mózg zwraca więcej uwagi na ludzi niż na rzeczy.
Skorzystaliśmy z zasady 80/20. Zakładamy, że jeśli chcesz zostać świetnym programistą w języku
JavaScript, nie będzie to jedyna książka, po którą sięgniesz. Dlatego nie pisaliśmy w niej
o wszystkim — a jedynie o tym, co będzie Ci naprawdę potrzebne.
30
Wprowadzenie
BĄDŹ przeglądarką
CELNE SPOSTRZEŻENIA
Zagadki
Wprowadzenie
To, co TY możesz zrobić,
aby zmusić swój mózg do posłuszeństwa
Wytnij
te porady
i przyklej
na lodówce
A zatem zrobiliśmy, co było w naszej mocy. Reszta zależy od Ciebie. Możesz zacząć od
poniższych porad. Posłuchaj swojego mózgu i określ, które sposoby sprawdzają się w Twoim
przypadku, a które nie dają pozytywnych rezultatów. Wypróbuj kilka z nich.
1 Zwolnij. Im więcej rozumiesz, tym mniej musisz
6
zapamiętać.
Mówienie aktywuje odmienne fragmenty mózgu. Jeśli
próbujesz coś zrozumieć lub zwiększyć szansę na zapamiętanie
informacji na dłużej, powtarzaj je na głos. Jeszcze lepiej —
staraj się je głośno komuś wytłumaczyć. W ten sposób nauczysz
się szybciej, a oprócz tego będziesz mógł odkryć kwestie,
o których nie wiedziałeś podczas czytania tekstu książki.
Nie ograniczaj się jedynie do czytania. Przerwij na chwilę
lekturę i pomyśl. Kiedy znajdziesz w tekście pytanie, nie
zaglądaj od razu na stronę z odpowiedzią. Wyobraź sobie,
że ktoś faktycznie zadaje Ci pytanie. Im bardziej zmusisz swój
mózg do myślenia, tym większa będzie szansa, że się czegoś
nauczysz i zapamiętasz.
2 Wykonuj ćwiczenia. Rób notatki.
7
głupie pytania”.
8
4 Niech lektura tej książki będzie ostatnią rzeczą,
jaką robisz przed pójściem spać. A przynajmniej
ostatnią rzeczą stanowiącą wyzwanie intelektualne.
5 Pij wodę. Dużo wody.
Twój mózg pracuje najlepiej, gdy dostarcza mu się dużo
płynów. Odwodnienie (które może następować nawet wtedy,
zanim poczujesz pragnienie) obniża zdolność percepcji.
Poczuj coś!
Twój mózg musi wiedzieć, że to, czego się uczysz, ma
znaczenie. Z zaangażowaniem śledź zamieszczane w tekście
opowiadania. Nadawaj własne tytuły zdjęciom. Zalewanie się
łzami ze śmiechu po przeczytaniu głupiego dowcipu i tak jest
lepsze od braku jakiejkolwiek reakcji.
Wszystkie. Nie są to fragmenty opcjonalne — stanowią część
podstawowej zawartości książki! Nie pomijaj ich.
Pewne elementy procesu uczenia się (a szczególnie
przenoszenie informacji do pamięci długoterminowej)
następują po odłożeniu książki. Twój mózg potrzebuje trochę
czasu dla siebie i musi dodatkowo przetworzyć dostarczone
informacje. Jeśli podczas tego koniecznego na wykonanie
dodatkowego „przetwarzania” czasu zmusisz go do innej
działalności, część z przyswojonych informacji może zostać
utracona.
Posłuchaj swojego mózgu.
Uważaj, kiedy Twój mózg staje się przeciążony. Jeśli
spostrzeżesz, że zaczynasz czytać pobieżnie i zapominać to,
o czym przeczytałeś przed chwilą, to najwyższy czas, żeby zrobić
przerwę. Po przekroczeniu pewnego punktu nie będziesz się
uczył szybciej, „wciskając” do głowy więcej informacji; co gorsza,
może to zaszkodzić całemu procesowi nauki.
Umieszczaliśmy je w tekście, jeśli jednak zrobilibyśmy je za
Ciebie, niczym nie różniłoby się to od sytuacji, w której ktoś
za Ciebie wykonywałby ćwiczenia fizyczne. I nie ograniczaj się
jedynie do czytania ćwiczeń. Używaj ołówka. Można znaleźć
wiele dowodów na to, że fizyczna aktywność podczas nauki
może poprawić jej wyniki.
3 Czytaj fragmenty oznaczone jako „Nie istnieją
Rozmawiaj o zdobywanych informacjach. Głośno.
9
Pisz jak najwięcej kodu.
Wykorzystaj zdobytą wiedzę w swojej aktualnej pracy
lub zmodyfikuj któryś ze starych projektów. Zrób coś, by
zdobyć jakieś doświadczenia wykraczające poza ćwiczenia
prezentowane w książce. Będziesz potrzebował jedynie ołówka
i problemu do rozwiązania… problemu, który będziesz mógł
rozwiązać, pisząc kod JavaScript.
10 Wysypiaj się.
Aby nauczyć się języka programowania, musisz wykreować
w swoim mózgu wiele nowych połączeń nerwowych. Śpij dużo
— to pomaga!
jesteś tutaj 31
Jak korzystać z tej książki
Przeczytaj to
Książka ta jest doznaniem poznawczym, a nie podręcznikiem. Celowo usunęliśmy z niej wszystko, co mogłoby Ci
przeszkadzać w uczeniu i poznawaniu materiału zamieszczonego w konkretnym miejscu książki. Podczas pierwszej
lektury tej książki powinieneś zaczynać od samego początku, gdyż jej dalsze fragmenty bazują na wiedzy, którą
powinieneś zdobyć wcześniej.
Uczymy DOBRYCH aspektów JavaScriptu, a ostrzegamy przed ZŁYMI.
JavaScript nie jest językiem programowania, który narodził się w kręgach akademickich i było sporo czasu, by go
dokładnie przestudiować. JavaScript z konieczności został od razu wrzucony na głębokie wody i od samego początku
dorastał w sąsiedztwie wczesnych wersji przeglądarek. Dlatego uważaj: niektóre aspekty tego języka są doskonałe,
a inne… niekoniecznie. Jednak ogólnie rzecz biorąc, JavaScript jest świetnym językiem, o ile tylko potrafimy z niego
inteligentnie korzystać.
W tej książce uczymy w maksymalnym stopniu korzystać z tych wartościowych elementów języka, a jednocześnie
ostrzegamy przed złymi i radzimy, jak ich unikać.
Nie opisujemy wyczerpująco każdego aspektu języka.
JavaScript to obszerne zagadnienie i na jego temat można się dowiedzieć bardzo wiele. Jednak to nie jest książka
o charakterze encyklopedycznym, to książka do nauki, dlatego też nie przedstawiamy w niej wszystkiego, co na temat
JavaScriptu można by napisać. Naszym celem jest nauczenie podstaw korzystania z tego języka, tak byś mógł sięgnąć
po dowolną inną książkę na jego temat i zastosować go do dowolnych celów.
Książka ta uczy JavaScriptu używanego po stronie przeglądarki.
Przeglądarki stanowią nie tylko najpopularniejsze środowisko, w jakim są wykonywane skrypty JavaScript, jest to
jednocześnie środowisko najwygodniejsze (każdy dysponuje komputerem wyposażonym w przeglądarkę WWW
i edytor tekstów, a dokładnie tego potrzeba, by zacząć programowanie w JavaScripcie). Pisanie skryptów działających
w przeglądarce ma jeszcze jedną, ogromną zaletę — wystarczy napisać kod, odświeżyć stronę w przeglądarce,
a efekty naszej pracy staną się od razu widoczne.
W tej książce propagujemy tworzenie czytelnego kodu o doskonałej strukturze, pisanego
zgodnie z najlepszymi praktykami.
Zależy Ci na pisaniu kodu, który zarówno Ty, jak i inni programiści będziecie w stanie przeczytać i zrozumieć;
kodu, który będzie działał także w przeglądarkach, jakie pojawią się za kilka lat. Zależy Ci na pisaniu kodu
w możliwie najprostszy sposób, tak by zrobić to, co do Ciebie należy, i zabrać się za ciekawsze rzeczy. W tej książce
uczymy pisania przejrzystego, dobrze zorganizowanego kodu, który od samego początku przewiduje możliwości
wprowadzania zmian w przyszłości. Kodu, z którego będziesz mógł być dumny, który będziesz chciał oprawić w ramki
i powiesić na ścianie (pamiętaj tylko, żeby go ściągnąć, zanim zaprosisz kogoś na romantyczny obiad).
Zachęcamy, żebyś — wykonując ćwiczenia i przykłady — korzystał z więcej niż jednej
przeglądarki.
Uczymy pisania kodu JavaScript bazującego na standardach, jednak wciąż możesz napotkać drobne różnice
w sposobie jego interpretacji w różnych przeglądarkach. Choć robimy wszystko, co w naszej mocy, by zapewnić,
że kod prezentowany w tej książce będzie działał poprawnie we wszystkich nowoczesnych przeglądarkach, a nawet
32
Wprowadzenie
Wprowadzenie
pokazujemy kilka sztuczek, które pozwolą Ci upewnić się, że kod będzie w nich działał, to jednak
sugerujemy, żebyś wybrał przynajmniej dwie przeglądarki i testował w nich swoje skrypty. W ten sposób
nabierzesz doświadczenia w odnajdywaniu różnic pomiędzy przeglądarkami oraz w tworzeniu kodu
JavaScript, który w spójny sposób będzie pracował w wielu różnych przeglądarkach.
Programowanie to poważne zajęcie. Będziesz musiał pracować,
czasami nawet naprawdę ciężko.
Jeśli już wcześniej miałeś kontakt z programowaniem, wiesz, o co nam chodzi. Jeśli już znasz języki HTML
i CSS, na pewno zauważysz, że pisanie kodu JavaScript jest nieco… hm… inne. Programowanie wymaga
trochę innego sposobu myślenia. Programowanie wymaga logiki, czasami nawet bardzo abstrakcyjnej,
wymaga także myślenia algorytmicznego. Ale nie martw się, nauczymy Cię tego wszystkiego w sposób
przyjazny dla Twojego mózgu. Zdobywaj wiedzę fragmentami, dobrze się odżywiaj i dużo śpij. Dzięki temu
nowe pojęcia związane z programowaniem doskonale utrwalą się w pamięci.
Aktywności NIE są opcjonalne.
Ćwiczenia i aktywności nie są jedynie dodatkami, są elementem treści tej książki. Niektóre z nich mają
wspomóc Twoją pamięć, inne — ułatwić zrozumienie, a jeszcze inne — pomóc w wykorzystaniu nabytej
wiedzy i umiejętności. Nie pomijaj tych ćwiczeń!
Powtórzenia są celowe i ważne.
Jedną z cech, która wyróżnia książki z serii Rusz głową!, jest to, że nam naprawdę zależy, żebyś wszystko
zrozumiał. Chcemy także, byś kończąc lekturę tej książki, pamiętał wszystko, co w niej przeczytałeś.
Większość podręczników i wydawnictw encyklopedycznych nie zwraca uwagi na przyswojenie
i zapamiętanie informacji, jednak w tej chodzi o naukę, dlatego znajdziesz w niej wiele pojęć, które
pojawiają się kilka razy.
Przykładowe kody są jak najbardziej zwięzłe.
Czytelnicy niejednokrotnie zwracali nam uwagę, że przeglądanie 200 wierszy kodu w poszukiwaniu dwóch
linijek, które należy zrozumieć, może być frustrujące. Kod większości przykładów zamieszczonych w tej
książce został w jak największym stopniu skrócony; dzięki temu fragmenty, których musisz się nauczyć, są
przejrzyste i proste. Nie należy zatem oczekiwać, że podawane przykłady będą solidne ani nawet kompletne
— zostały one opracowane wyłącznie pod kątem nauki, a nie zapewnienia pełnej funkcjonalności.
Wszystkie przykłady prezentowane w tej książce umieściliśmy na internecie. Możesz je pobrać z serwera
FTP wydawnictwa Helion: ftp://ftp.helion.pl/przyklady/prjsrg.zip
Do ćwiczeń z cyklu „Wysil szare komórki” nie podawaliśmy odpowiedzi.
Do niektórych w ogóle nie można podać jednej dobrej odpowiedzi, w innych przypadkach to
doświadczenie, które zdobywasz, rozwiązując te ćwiczenia, ma dać Ci możliwość określenia, czy i kiedy
podana odpowiedź będzie poprawna. W niektórych ćwiczeniach z tej serii znajdziesz także podpowiedzi,
które ułatwią znalezienie rozwiązania.
jesteś tutaj 33
Jak korzystać z tej książki
Bardzo często przedstawiamy jedynie kod skryptu, a nie całej strony WWW.
Z wyjątkiem pierwszego, może dwóch pierwszych rozdziałów tej książki, będziemy zazwyczaj
prezentowali jedynie kod JavaScript, zakładając, że sam umieścisz go w odpowiednim kodzie HTML.
Poniżej przedstawiamy kod prostej strony WWW, której możesz używać do uruchamiania większości
kodu JavaScript prezentowanego w tej książce. Jeśli będziemy chcieli, byś użył innego kodu HTML,
poinformujemy o tym.
<!DOCTYPE html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Twoja strona WWW</title>
<script>
Swój kod JavaScript zazwyczaj
będziesz umieszczał tutaj.
</script>
</head>
<body>
A tu będziesz umieszczał
treść stron WWW.
</body>
</html>
na
Ale nie przejmuj się, tko
początku książki wszys
dokładnie wyjaśnimy.
Przykłady do książki, pomoc oraz informacje dodatkowe.
Wszystkie przykłady do książki możesz znaleźć na serwerze FTP wydawnictwa Helion, ftp://ftp.helion.
pl/przyklady/prjsrg.zip. Dodatkowe informacje oraz materiały pomocnicze można także znaleźć na
anglojęzycznej stronie autorów książki: http://wickedlysmart.com/hfjs.
34
Wprowadzenie
Wprowadzenie
Recenzenci techniczni
raw
Jeff St
Ci panowie naprawdę dali czadu; byli
dla nas dostępni podczas całego procesu
pisania tej książki i przekazywali
niesłychanie cenne i szczegółowe
informacje na każdy temat!
Ismaël Martin „Bing”
Demiddel
Frank D. Moore
Bruce Forkush
Javier Ruedas
Alfred J. Speller
Dziękujemy naszemu niesamowitemu zespołowi recenzentów technicznych!
Spośród wszystkich książek, które kiedykolwiek napisaliśmy, ta została sprawdzona najdokładniej. W naszym
programie WickedlySmart Insider wzięło udział ponad 270 osób, które czytały tekst i wyrażały swoje opinie na jego
temat już w czasie, kiedy pisaliśmy. Takie rozwiązanie sprawdziło się lepiej, niż mogliśmy się spodziewać, i miało
kluczowy wpływ na postać wszystkich aspektów tej książki. Chcieliśmy serdecznie podziękować wszystkim, którzy
wzięli udział w tym programie — dzięki Waszej pracy książka jest znacznie lepsza.
Nasi fantastycznie recenzenci techniczni, których zdjęcia zamieściliśmy na początku tej strony, przekazali użyteczne
uwagi i każdy z nich wniósł znaczący wkład w postać tej książki. Oprócz nich znaczny wkład w postać różnych
aspektów tej książki mają także następujący recenzenci:
Galina N. Orlova, J. Patrick Kelley, Claus-Peter Kahl, Rob Cleary, Rebeca Dunn-Krahn, Olaf
Schoenrich, Jim Cupec, Matthew M. Hanrahan, Russell Alleen-Willems, Christine J. Wilson, Louis-Philippe
Breton, Timo Glaser, Charmaine Gray, Lee Beckham, Michael Murphy, Dave Young, Don Smallidge, Alan
Rusyak, Eric R. Liscinsky, Brent Fazekas, Sue Starr, Eric (Orange Pants) Johnson, Jesse Palmer, Manabu
Kawakami, Alan McIvor, Alex Kelley, Yvonne Bichsel Truhon, Austin Throop, Tim Williams, J. Albert Bowden
II, Rod Shelton, Nancy DeHaven Hall, Sue McGee, Francisco Debs, Miriam Berkland, Christine H Grecco,
Elhadji Barry, Athanasios Valsamakis, Peter Casey, Dustin Wollam and Robb Kerley.
jesteś tutaj 35
Podziękowania*
Szanowany recenzent,
David Powers
Jesteśmy także niezwykle wdzięczni naszemu powszechnie szanowanemu
recenzentowi technicznemu Davidowi Powersowi. Prawdę mówiąc, nie
piszemy już książek, kiedy nie mamy w zespole Davida — po prostu zbyt wiele
razy uratował nasze głowy. Staliśmy się trochę jak Elton i Bernie, zaczynamy
zadawać sobie pytanie, czy bylibyśmy w stanie napisać książkę bez jego
pomocy. David pomaga nam zmusza nas, by nasze książki były porządniejsze
i dokładniejsze pod względem technicznym, a jego drugie zamiłowanie, jakim
jest prezentowanie krótkich monologów satyrycznych, niezwykle pomaga
w dopracowywaniu humorystycznych fragmentów książki. Davidzie, jeszcze
raz chcieliśmy Ci bardzo podziękować — jesteś doskonałym profesjonalistą
i śpimy lepiej, wiedząc, że nasza książka przeszła Twoją techniczną weryfikację.
W wydawnictwie O’Reilly
Chcieliśmy także
ogromnie podziękować
naszej redaktorce
Meghan
Blanchette, która
Nie daj się zwieść tym uśmiechem,
ten gość to prawdziwy
twardziel
oczyściła drogę przed
(oczywiście pod względem recenzji
technicznych).
tą książką, usunęła
wszelkie przeszkody
stojące na drodze do jej
zakończenia, poświęcała jej czas przeznaczony dla swojej rodziny
i cierpliwie czekała na dokończenie projektu. Jest osobą, dzięki której
nasze relacje z wydawnictwem O’Reilly były rozsądne (dotyczy to
także relacji wydawnictwa O’Reilly z nami). Kochamy Cię i wprost nie
możemy się doczekać okazji do dalszej współpracy!
Meghan Blanchette
Chcieliśmy także
bardzo podziękować
naszemu Emerytowanemu Kierownikowi Recenzentów Mike’owi
Hendricksonowi, który od samego początku przewodził pracom nad
tą książką. Mike, dziękujemy Ci jeszcze raz; żadna z naszych książek nie
powstałaby bez Ciebie. Od ponad dekady jesteś naszym mistrzem i kochamy
Cię za to!
* Podziękowań jest tak dużo dlatego, że sprawdzamy teorię, która zakłada, iż każdy
wymieniony w podziękowaniach kupi przynajmniej jeden egzemplarz książki, a
może nawet więcej, bo przecież ma rodzinę i znajomych. Gdybyś chciał znaleźć się
w podziękowaniach w naszej następnej książce i masz dużą rodzinę, napisz do nas.
Mike Hendrickson
36
Wprowadzenie
Wprowadzenie
Podziękowania dla innych osób z wydawnictwa O’Reilly
Chcieliśmy także szczerze podziękować całemu zespołowi wydawnictwa O’Reilly: Melanie Yarbrough,
Bobowi Pfahlerowi, Danowi Fauxsmithowi, którzy nadali tej książce kształt; Edowi Stephensonowi,
Huguette Barriere oraz Leslie Crandell, bo oni zajmowali się marketingiem i ich niekonwencjonalne
podejście bardzo podziwiamy. Dziękujemy także Ellie Volkhausen, Randiemu Comerowi oraz Karen
Montgomery za inspirujący projekt okładki, który cały czas świetnie nam służy. Jak zwykle dziękujemy
także Rachel Monaghan za bezkompromisową redakcję (i zapewnienie, że praca była jednocześnie
zabawą) oraz Bertowi Batesowi za cenne uwagi.
jesteś tutaj 37
1.6]\ENLVNRNQDJïÚERNLHZRG\-DYD6FULSWX
Czas się zamoczyć
Chodźcie, woda
jest świetna! Pójdziemy
od razu na całość — dowiemy
się, co to jest JavaScript,
napiszemy trochę kodu, uruchomimy
go i sprawdzimy, jak współpracuje
z przeglądarką! Ani się obejrzycie,
jak będziecie pisać kod.
JavaScript to dane Ci supermoce. To prawdziwy język programowania
internetu, który pozwala dodawać do stron WWW zachowania. Zapomnij o suchych,
nudnych i statycznych stronach — korzystając z języka JavaScript, będziesz w stanie
porozumiewać się ze swoimi użytkownikami, pobierać z internetu dane do wyświetlania
na swoich stronach, rysować grafikę bezpośrednio na nich i robić wiele innych, świetnych
rzeczy. Co więcej, znając JavaScript, będziesz także mógł zapewniać swoim użytkownikom
całkowicie nowe możliwości.
Jako programista posługujący się językiem JavaScript znajdziesz się w dobrym towarzystwie
— jest on nie tylko jednym z najbardziej popularnych języków programowania, lecz
także językiem obsługiwanym przez wszystkie nowoczesne (i większość nieco starszych)
przeglądarki, a nawet więcej, jest implementowany w wielu środowiskach, które nie
są powiązane z przeglądarkami. Więcej o tym napiszemy dalej w tej książce, a na razie
bierzmy się do roboty!
to jest nowy rozdział
39
Jak działa JavaScript?
Sposób działania języka JavaScript
Jeśli jesteś już przyzwyczajony do określania struktury, układu, stylów i podawania treści
tworzonych stron WWW, to może nadszedł już czas, by dodać do nich także trochę
dynamicznych zachowań? Obecnie strony WWW nie muszą biernie czekać. Te najlepsze
strony powinny być dynamiczne, interaktywne i muszą współpracować z użytkownikami
na nowe sposoby. I właśnie w takich sytuacjach na scenę wkracza JavaScript. Zacznijmy
od szybkiego rzutu oka na miejsce języka JavaScript w ekosystemie stron WWW.
HTML
Już wiesz, że języka HTML
(ang. Hypertext Markup
Language, czyli język
znaczników hipertekstu)
używa się do określania
całej zawartości
tworzonych stron oraz ich
struktury, czyli akapitów,
nagłówków i sekcji.
JS
40
Rozdział 1.
CSS
przeglądarka
Wiesz także, że style CSS (ang.
Cascading Style Sheets, czyli
kaskadowe arkusze stylów)
są wykorzystywane do
prezentacji kodu HTML, czyli
kolorów, czcionek, obramowań,
marginesów oraz układu stron.
CSS określa styl i robi to w sposób
niezależny od struktury strony.
Wprowadźmy zatem do tej rodzinki język JavaScript,
kuzyna HTML i CSS, specjalistę od obliczeń. JavaScript
pozwala dodawać do stron WWW zachowania.
Przykładowo strona musi zareagować na kliknięcie
przycisku „Możesz to kupić jeszcze tylko przez 30 sekund!”,
a może na bieżąco upewnić się, że informacje wpisywane
przez użytkownika są prawidłowe? A może pobrać
i wyświetlić jakieś komunikaty opublikowane na serwisie
Twitter? Uśmiechnij się o to do JavaScriptu. To właśnie
on pozwala dodawać do stron programy, które mogą coś
wyliczać, reagować, rysować, komunikować, ostrzegać,
zmieniać, aktualizować itd. Wszystko to, co jest dynamiczne
na stronach WWW, to właśnie dzieło JavaScriptu.
Szybki skok na głębokie wody JavaScriptu
Jak należy pisać kod JavaScript?
W świecie programowania JavaScript jest dosyć unikalnym językiem. Typowe języki wymagają
napisania kodu programu, skompilowania go, konsolidacji i wdrożenia. JavaScript jest znacznie
bardziej płynny i elastyczny. W jego przypadku musisz tylko umieścić kod JavaScript bezpośrednio
w dokumencie HTML i wyświetlić go w przeglądarce. To wystarczy, by przeglądarka zaczęła
radośnie wykonywać ten kod. Przyjrzyjmy się dokładniej, jak to działa.
<html>
<head>
<title>Lody</title>
<script>
var x = 49;
</script>
<body>
<h1>Smaki lodów</h1>
<h2><em>49 smaków</em></h2>
<p>Wszystkie twoje ulubione
smaki!</p>
</body>
</html>
Pisanie
1
Strony możesz tworzyć
tak samo jak wcześniej:
piszesz ich treść w kodzie
HTML i określasz postać
przy użyciu CSS. Możesz
także dodawać do nich
kod JavaScript. Jak się
przekonasz, podobnie jak
w przypadku kodów HTML
i CSS, można wszystko
umieścić w jednym pliku
lub przeznaczyć dla kodu
JavaScript osobny plik,
który następnie zostanie
dołączony do strony.
Za chwilę zastanowimy
się, który ze sposobów
jest najlepszy…
JS
css
kod
html
JS
przeglądarka
Wczytywanie
przeglądarka
Wykonanie
2
Wystarczy w standardowy
sposób podać w przeglądarce
adres strony. Kiedy przeglądarka
napotka kod, natychmiast zacznie
go analizować i przygotowywać
do wykonania. Pamiętaj: jeśli
przeglądarka znajdzie błędy
w kodzie JavaScript, zrobi
wszystko, by przeanalizować
dalszy kod strony, podobnie jak
w przypadku błędów w kodach
HTML i CSS. Ostatnią rzeczą, jakiej
życzyłaby sobie przeglądarka,
jest niemożność wyświetlenia
użytkownikowi strony.
Na przyszłość powini
ene
że przeglądarka tworzy ś wiedzieć,
dokumentu HTML, któ „model obiektów”
rego możesz użyć
w kodzie JavaScript.
Za
w pamięci, bo za jakiś chowaj to gdzieś
czas do tego wrócimy…
3
Przeglądarka zaczyna
wykonywać kod, gdy tylko
znajdzie go dokumencie,
i wykonuje tak długo, jak
długo będzie wyświetlana
strona. Nowoczesny JavaScript,
w odróżnieniu od wczesnych
wersji języka, jest naprawdę
potężny i wykorzystuje
zaawansowane techniki
kompilacji, by wykonywać kod
z niemal taką samą szybkością,
jaką zapewniają języki
kompilowane.
html
head
title
body
script
h1
h2
p
em
jesteś tutaj
41
Umieść kod JavaScript na stronie
Jak umieszczać kod JavaScript na stronie?
Zacznijmy od początku. Nie na wiele przyda się znajomość języka JavaScript, jeśli nie
będziemy wiedzieli, jak umieszczać pisany w nim kod na stronach WWW. A zatem,
jak to robić? Oczywiście przy użyciu elementu <script>.
W ramach przykładu weźmy nudną, zwyczajną stronę WWW i dodajmy do niej trochę
dynamizmu za pomocą elementu <script>. Na razie nie przejmuj się tym, co należy
umieszczać wewnątrz elementu <script> — celem jest napisanie i uruchomienie
prostego kodu w języku JavaScript.
Oto nasza standardowa deklaracja doctype
języka HTML5 oraz elementy <html> i <head>
<!doctype html>
Mamy też całkiem ogólny element <body>.
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
O… ale dodaliśmy do sekcji
nagłówka strony element <script>.
<title>Ogólna strona WWW</title>
<script>
setTimeout(wakeUpUser, 5000);
function wakeUpUser() {
aOert(ĵ&]\ mas] ]amiar Muĝ ]aws]e JapiÊ siÚ na tÚ nuGnÈ stronÚ"ĵ);
}
</script>
A wewnątrz niego umieściliśmy
trochę kodu JavaScript.
</head>
<body>
K!7DNLWDPRJöOQ\QDJïöZHNK!
Także w tym przypadku nie przejmuj się tym, co ten kod
robi. Możemy się założyć, że będziesz chciał przeanalizować
ten kod i przekonać się, czy jesteś w stanie określić,
do czego służą jego poszczególne fragmenty.
<p>Nie znajdziesz tu zbyt wiele do poczytania. Jestem tym biednym,
RERZLÈ]NRZ\PDNDSLWHPWHNVWXZSU]\NïDG]LHVNU\SWX-DYD6FULSW6]XNDP
F]HJRĂFRVSUDZLĝHPRMHĝ\FLHVWDQLHVLÚEDUG]LHMDWUDNF\MQHS!
</body>
</html>
Mała jazda próbna
No dalej, wpisz ten kod do pliku i zapisz go pod nazwą behavior.html.
Następnie przeciągnij plik i upuść na przeglądarce lub wczytaj go do
niej, wybierając opcje Plik/Otwórz. Jak działa nasz kod JavaScript?
Podpowiedź: aby się o tym przekonać, musisz poczekać pięć sekund.
42
Rozdział 1.
Szybki skok na głębokie wody JavaScriptu
Spokojnie
Czy to sposób
utworzenia
fragmentu kodu
nadającego się
do wielokrotnego
stosowania
i nadania
mu nazwy
„wakeUpUser”?
Tylko spokojnie! Na tym etapie książki nie oczekujemy, byś czytał kod JavaScript, jakbyś się
z nim wychowywał całe życie. Obecnie zależy nam tylko na tym, byś po prostu zobaczył,
jak taki kod wygląda.
Mimo to, nie możesz sobie tak zupełnie odpuścić, bo Twój mózg musi być pobudzony i sprawny.
Czy pamiętasz kod podany na poprzedniej stronie? Przeanalizujemy go szczegółowo, byś mógł
się zorientować, jak działa.
Czy można w ten sposób odliczyć 5 sekund?
Podpowiedź: 1000 milisekund to 1 sekunda.
setTimeout(wakeUpUser, 5000);
function wakeUpUser() {
aOert(ĵ&]\ mas] ]amiar Muĝ ]aws]e JapiÊ siÚ na tÚ nuGnÈ stronÚ"ĵ);
}
Najwyraźniej to jest sposób wyświetlenia komunikatu.
Nie istnieją
głupie pytania
P: Słyszałem, że JavaScript jest słabym językiem.
Czy to prawda?
O: W początkach swego istnienia JavaScript z pewnością nie
należał do mocarzy, jednak od tego czasu jego znaczenie na
internecie bardzo wzrosło, a w efekcie zainwestowano bardzo
wiele zasobów (w tym wysiłek intelektualny najtęższych umysłów
w branży) na poprawienie jego wydajności. Ale wiesz co? Nawet
wcześniej JavaScript był bardzo szybki; zawsze był rewelacyjnym
językiem. A sam się przekonasz, że za jego pomocą można tworzyć
naprawdę użyteczne rozwiązania.
P: Czy JavaScript jest jakoś powiązany z Javą?
O: Z Javą wiąże go chyba tylko nazwa. JavaScript powstawał
w czasie, gdy Java była niezwykle popularna, a twórcy JavaScriptu
chcieli skorzystać z tej popularności i wykorzystali nazwę „Java”.
Składnia obu języków bazuje na C, jednak pod wszystkimi innymi
względami są one całkowicie odmienne.
P: Czy JavaScript jest najlepszym sposobem tworzenia
dynamicznych stron? A co z takimi rozwiązaniami jak Flash?
O: Faktycznie był czas, kiedy Flash mógł zostać preferowanym
rozwiązaniem do tworzenia interaktywnych i bardziej dynamicznych
stron WWW, jednak kierunek rozwoju branży wyraźnie przechyla się na
korzyść języków HTML5 i JavaScript. W połączeniu z HTML5 JavaScript
stanowi obecnie standardowy język na stronach WWW. Wiele zasobów
poświęca się na zapewnienie szybkości i wydajności JavaScriptu oraz na
utworzenie API rozszerzających możliwości przeglądarek.
P: Mój kolega używa JavaScriptu w Photoshopie,
a przynajmniej tak twierdzi. Czy to możliwe?
O
: Tak. JavaScript zaczyna się pojawiać także poza przeglądarkami i jest
stosowany jako ogólny język skryptowy w wielu aplikacjach, od narzędzi
graficznych, przez aplikacje muzyczne, do serwerów internetowych.
Zatem inwestycja w naukę JavaScriptu może się w przyszłości zwrócić
na wiele sposobów, niekoniecznie związanych ze stronami WWW.
P: Napisaliście, że wiele innych języków wymaga kompilacji.
Co to dokładnie oznacza i dlaczego JavaScript jej nie wymaga?
O: Podczas stosowania tradycyjnych języków programowania, takich
jak C, C++ lub Java, przed wykonaniem kodu należy go skompilować.
Kompilacja przekształca kod źródłowy i powoduje wygenerowanie
jego wydajnej, maszynowej reprezentacji, która zazwyczaj jest
zoptymalizowana pod kątem wydajności działania. Języki skryptowe
są z natury językami interpretowanymi, co oznacza, że przeglądarka
wykonuje każdy wiersz kodu JavaScript, jak tylko go napotka. Języki
skryptowe nie przykładają tak dużego znaczenia do wydajności działania
i w większym stopniu są przeznaczone do tworzenia prototypów,
kodowania interaktywnego i zapewnienia elastyczności. Tak też było
zaraz po powstaniu JavaScriptu i dlatego przez wiele lat jego wydajność
nie była aż tak duża. Istnieje także rozwiązanie pośrednie; kod języków
interpretowanych może być na bieżąco kompilowany i właśnie tą drogą
poszli twórcy przeglądarek w przypadku nowoczesnego JavaScriptu.
Obecnie JavaScript zapewnia wygodę języka interpretowanego
i wydajność języka kompilowanego. Swoją drogą, w tej książce będziemy
używać terminów interpretować, przetwarzać i wykonywać. W zależności
od kontekstu mogą one mieć nieco inne znaczenia, jednak dla naszych
celów będą oznaczać to samo.
jesteś tutaj
43
Historia języka JavaScript
Dziecinko, JavaScript przebył długą drogę…
JavaScript 1.0
Być może w Twoich czasach firma
Netscape należała już do historii, jednak
to ona była pierwszym prawdziwym twórcą
przeglądarek. W latach 90. ubiegłego wieku
konkurencja była bardzo ostra, zwłaszcza
z firmą Microsoft, więc dodawanie
nowych, fascynujących możliwości miało
priorytetowe znaczenie.
Dążąc do tego celu, firma Netscape chciała
opracować język skryptowy, który pozwoliłby
dodawać skrypty do stron WWW. Tak
powstał LiveScript, opracowany w szybkim
tempie język, który miał realizować te
potrzeby. Jeśli nigdy o nim nie słyszałeś,
to zapewne dlatego, że w tym samym czasie
firma Sun Microsystems stworzyła język Java,
windując dzięki niemu kurs swoich akcji do
niebotycznego poziomu. Dlaczego by więc nie
skorzystać na tym sukcesie i nie zmienić nazwy
z LiveScript na JavaScript? W końcu kto
zwróci na to uwagę, jeśli i tak nie interesuje się
ani jednym, ani drugim językiem? Prawda?
Czy jednak nie zapomnieliśmy o Microsofcie?
Firma ta, na krótko po tym, jak Nestscape
udostępniła LiveScript, opracowała własny
język skryptowy, o nazwie... JScript, który był
bardzo podobny do JavaScriptu. I tak zaczęły
się wojny przeglądarek.
1995
44
Rozdział 1.
JavaScript 1.3
W latach 1996 – 2000 JavaScript
notuje okres rozwoju. Firma Netscape
przesłała go do standaryzacji i tak
powstał język ECMAScript. Nigdy
o nim nie słyszałeś? Nic nie szkodzi,
nadrobiłeś ten brak. Wystarczy
wiedzieć, że ECMAScript służy jako
standardowa definicja dla wszystkich
implementacji JavaScriptu (zarówno
używanych w przeglądarkach, jak
i w innych programach).
W tym okresie programiści borykali
się z JavaScriptem, a jednocześnie
byli ofiarami wojen przeglądarek (ze
względy na ich różnice), lecz w końcu
powszechne stało się stosowanie języka
JavaScript. I choć nieznaczne różnice
pomiędzy językami JavaScript i JScript
wciąż powodowały u programistów
ból głowy, to jednak z upływem czasu
oba te języki zaczęły się do siebie
upodabniać.
JavaScript wciąż nie mógł zmienić
swojej reputacji języka dla amatorów;
jednak wkrótce sytuacja ta miała ulec
zmianie…
2000
JavaScript 1.8.5
W końcu JavaScript osiąga
pełnoletniość i zyskuje uznanie
profesjonalnych programistów!
Choć można powiedzieć, że stało
się tak dzięki istnieniu doskonałego
standardu, jakim był ECMAScript
5 zaimplementowany obecnie
we wszystkich nowoczesnych
przeglądarkach, to jednak firma
Google pchnęła wykorzystanie
JavaScriptu na tory profesjonalne,
korzystając z niego w 2005 roku
w swojej usłudze Google Maps
i dobitnie pokazując światu, co
naprawdę można osiągnąć z jego
pomocą.
Ta nowa popularność sprawiła, że
wiele najtęższych umysłów świata
programowania skoncentrowało swe
wysiłki na poprawieniu interpretera
JavaScriptu i dokonał się ogromny
postęp w wydajności jego działania.
Obecny język JavaScript odróżnia
od tego początkowego jedynie kilka
zmian, a pospieszne narodziny nie
zaszkodziły mu w staniu się językiem
potężnymi i ekspresyjnym.
2012
Szybki skok na głębokie wody JavaScriptu
Zaostrz ołówek
Popatrz, jak łatwo można
pisać kod JavaScript
var price = 28.99;
Jeszcze nie znasz języka JavaScript, a jesteśmy niemal pewni, że
odgadnięcie, jak działa napisany w nim kod, nie przysporzy Ci
większych problemów. Spójrz na każdy z pokazanych poniżej wierszy
kodu i spróbuj odgadnąć, co robi. Odpowiedzi zapisz w tabeli poniżej.
Aby ułatwić Ci zadanie, pierwszą odpowiedź napisaliśmy za Ciebie.
Jeśli utkniesz, wszystkie odpowiedzi znajdziesz na następnej stronie.
Tworzy zmienną o nazwie price i przypisuje jej wartość
28.99.
var discount = 10;
var total =
price - (price * (discount / 100));
if (total > 25) {
freeShipping();
}
var count = 10;
while (count > 0) {
juggle();
count = count - 1;
}
var dog = {name: ”Burek”, weight: 35};
if (dog.weight > 30) {
alert(”HAU HAU”);
} else {
alert(”hau hau”);
}
var circleRadius = 20;
var circleArea =
Math.PI * (circleRadius * circleRadius);
jesteś tutaj
45
Rozwiązanie ćwiczenia
Popatrz, jak łatwo
można pisać kod
JavaScript
var price = 28.99;
var discount = 10;
var total =
price - (price * (discount / 100));
if (total > 25) {
freeShipping();
Zaostrz ołówek
Rozwiązanie
-HV]F]HQLH]QDV]MĊ]\ND-DYD6FULSWDMHVWHĞP\QLHPDOSHZQL
ĪHRGJDGQLĊFLHMDNG]LDáDQDSLVDQ\ZQLPNRGQLHSU]\VSRU]\&L
ZLĊNV]\FKSUREOHPyZ6SyMU]QDNDĪG\]SRND]DQ\FKSRQLĪHMZLHUV]\
NRGXLVSUyEXMRGJDGQąüFRUREL2GSRZLHG]L]DSLV]ZWDEHOLSRQLĪHM
$E\XáDWZLü&L]DGDQLHSLHUZV]ąRGSRZLHGĨQDSLVDOLĞP\]D&LHELH
$RWRQDV]HRGSRZLHG]L
Tworzy zmienną o nazwie price i przypisuje jej wartość
28.99.
Tworzy zmienną o nazwie discount i przypisuje jej wartość 10.
Wyznacza nową cenę poprzez zastosowanie zniżki, a wynik
zapisuje w zmiennej total.
Porównuje wartość zmiennej total z liczbą 25 i jeśli wartość
zmiennej jest większa…
…to wywołuje funkcję freeShipping.
}
To jest koniec instrukcji if.
var count = 10;
Tworzy zmienną o nazwie count i przypisuje jej wartość 10.
while (count > 0) {
Tak długo, jak wartość zmiennej count jest większa od 0…
juggle();
count = count - 1;
…wywołuje funkcję juggle…
…zmniejsza wartość zmiennej count o 1.
}
To koniec pętli while.
var dog = {name: ”Burek”, weight: 35};
Tworzy psa o imieniu i wadze.
if (dog.weight > 30) {
Jeśli waga psa jest większa od 30…
alert(”HAU HAU”);
} else {
alert(”hau hau”);
…wyświetla w oknie przeglądarki ostrzeżenie „HAU HAU”.
W przeciwnym razie…
…wyświetla w oknie przeglądarki ostrzeżenie „hau hau”.
}
To jest koniec instrukcji if/else.
var circleRadius = 20;
Tworzy zmienną circleRadius i zapisuje w niej wartość 20.
var circleArea =
Tworzy zmienną o nazwie circleArea…
Math.PI * (circleRadius * circleRadius);
46
Rozdział 1.
…i zapisuje w niej wartość tego wyrażenia
(1256.6370614359173)
Szybki skok na głębokie wody JavaScriptu
Popatrz, jeśli
chcesz wyjść poza tworzenie
jedynie statycznych stron WWW,
będziesz musiała spotkać się
z JavaScriptem.
To prawda
HTML i CSS pozwalają na tworzenie pięknie wyglądających stron. Kiedy jednak
poznasz już JavaScript, możesz tworzone strony wprowadzić na wyższy poziom.
I to nawet do takiego stopnia, że właściwie możesz zacząć o nich myśleć jak
o aplikacjach (a może nawet doznaniach!), a nie o stronach.
Być może myślisz sobie teraz: „Przecież to wiem. Jak sądzicie, dlaczego czytam
tę książkę?”. No cóż, w zasadzie chcieliśmy tylko skorzystać z tej możliwości, by
porozmawiać trochę na temat uczenia się JavaScriptu. Jeśli już znasz jakiś język
programowania lub jakiś język skryptowy, pewnie masz jakieś pojęcie o tym, co
Cię czeka. Jeśli jednak do tej pory zajmowałeś się wyłącznie kodem HTML i CSS,
powinieneś wiedzieć, że nauka języka programowania jest czymś zupełnie innym.
I zazwyczaj
powiększyć
także kwotę
na fakturze
do klienta!
Kiedy korzystasz z języków HTML i CSS, to, co robisz, ma zazwyczaj charakter
deklaratywny — np. deklarujesz, że jakiś tekst jest akapitem bądź elementy klasy „sale”
mają być wyświetlone na czerwono. Jednak w przypadku JavaScriptu dodajesz do
strony zachowania, a w tym celu musisz opisywać obliczenia. Musisz opisywać różne
rzeczy w sposób przypominający poniższe zdania: „wynik użytkownika oblicz, sumując
wszystkie prawidłowe odpowiedzi”, „wykonaj tę akcję dziesięć razy” albo „kiedy
użytkownik kliknie ten przycisk, odtwórz dźwięk oznaczający zwycięstwo”, albo nawet:
„ściągnij moje najnowsze komunikaty z Twittera i wyświetl je na tej stronie”.
Aby można było zapisać takie rzeczy, potrzebujesz języka, który będzie całkowicie
odmienny od języków HTML i CSS. Przekonasz się, jak bardzo odmienny…
jesteś tutaj
47
Instrukcje języka JavaScript
Jak tworzyć instrukcje?
Kiedy stosujemy kod HTML, zazwyczaj oznaczamy tekst, określając w ten sposób
jego strukturę; do tego celu używane są elementy, atrybuty i wartości.
W języku HTML oznaczamy teks
t,
opisując w ten sposób jego stru
kturę.
Oto przykład: „Chcę mieć duży
nagł
Mocha Caffe Latte; to nazwa drink ówek
A poniżej ma być akapit tekstu”. a.
<K cOass ĵGrinkĵ>Mocha Caffe Latte</h1>
<p>(VSUHVVRJRUÈFHPOHNRLV\URSF]HNRODGRZ\
przygotowane tak jak lubisz.</p>
CSS jest nieco inny. W tym języku piszemy zestawy reguł, przy czym każda z tych
reguł odnosi się do pewnych elementów strony; do tych reguł dołączane są style
określające postać wybranych elementów.
W języku CSS piszemy reguły korzystające
z selektorów, takich jak h1.drink lub p,
określających, w jakich elementach strony
zostaną zastosowane podane style.
h1Grink {
color: brown;
}
p {
font-family: sans-serif;
}
Upewnijmy się, że wszystkie
nagłówki z nazwami drinków będą
miały brązowy kolor…
…a oprócz tego chcemy, żeby wszystkie akapity
tekstu były wyświetlane czcionką bezszeryfową.
Z kolei, tworząc kod w języku JavaScript, piszemy instrukcje. Każda z takich
instrukcji stanowi niewielki fragment obliczeń, a wszystkie razem określają
zachowanie strony.
Zestaw instrukcji.
Yar aJe
5;
var name = ”Olek”;
if (aJe > 1) {
Każda instrukcja wykonuje jakieś małe
zadanie, takie jak zadeklarowanie zmiennej,
która będzie przechowywać jakąś wartość.
Tutaj tworzymy zmienną, określającą wiek, age,
na 25; oprócz niej potrzebujemy także zmiennej
zawierającej wartość „Olek”.
Instrukcja może też podejmować decyzje,
np. czy wiek użytkownika przekracza 14 lat?
alert(”3r]ykro mi, ta strona Mest tylko Gla G]ieci”);
} else {
alert(”:itaM, ” name ””);
}
48
A jeśli faktycznie przekroczy, może
wyświetlić na stronie komunikat.
W przeciwnym razie witamy użytkownika,
wyświetlając komunikat: „Witaj, Olek!”.
(Jednak Olek ma 25 lat, więc w tym
przypadku tego nie zrobimy).
Rozdział 1.
Szybki skok na głębokie wody JavaScriptu
Zmienne i wartości
Być może zauważyłeś, że w języku JavaScript instrukcje zazwyczaj są w jakiś
sposób związane ze zmiennymi, a zmienne służą do przechowywania wartości.
Jakich wartości? Poniżej przedstawiliśmy kilka przykładów.
2
LąĪ
³.V
var winners = ;
Ċ´
Ta instrukcja deklaruje zmienną
o nazwie winners i zapisuje
w niej wartość liczbową 2.
winners
var is(liJible = false;
I w końcu ta instrukcja zapisuje
wartość false w zmiennej o nazw
isEligible. Wartości true i false ie
nazywamy wartościami logicznym
i
lub boolowskimi.
Oprócz liczb, łańcuchów i wartości logicznych, w zmiennych można
także zapisywać inne wartości, czym zajmiemy się już niedługo. Jednak
niezależnie od zawartości, wszystkie zmienne są tworzone w taki sam
sposób. Przyjrzyjmy się zatem nieco bliżej sposobowi deklarowania
zmiennych.
Deklarację zmiennej
zawsze zaczynamy od
słowa kluczowego var.
name
VH
var name = ”.siÈĝÚ”;
IDO
Z kolei ta instrukcja zapisuje
łańcuch znaków (skrótowo
nazywamy je po prostu łańcuchami)
w zmiennej o nazwie name.
isEligible
Należy to wymawiać:
„bu-low-ski-mi”.
Zwróć uwagę, że wartości
logiczne nie są zapisywane
w cudzysłowach.
OD TEGO NIE MA WYJĄTKÓW!
Bez względu na to, że JavaScript
nie skarży się, jeśli pominiemy
słowo kluczowe var. Nieco później
dowiesz się, dlaczego tak się dzieje…
Następnie podawana
jest nazwa zmiennej.
var winners = ;
Instrukcja przypisania zawsze
kończy się znakiem średnika.
Opcjonalnie można także przypisać zmiennej wartość,
umieszczając za jej nazwą znak równości i wartość.
Napisaliśmy „opcjonalnie”, gdyż nic nie stoi na przeszkodzie, by
utworzyć zmienną, która nie będzie miała wartości początkowej.
Zawsze można przypisać ją później. Aby utworzyć zmienną bez
wartości, wystarczy pominąć fragment zawierający przypisanie,
tak jak pokazaliśmy na poniższym przykładzie.
var losers;
Pomijając znak równości
i wartość, deklarujemy
zmienną przeznaczoną
do późniejszego użycia.
Bez wartości? I co
ja mam teraz zrobić?
To takie upokarzające.
losers
jesteś tutaj
49
Słowa kluczowe języka JavaScript
Odsuń się od tej klawiatury!
Już wiesz, że zmienne mają nazwy oraz wartości.
Wiesz także, że wśród danych, które mogą zawierać zmienne, są m.in. liczby, łańcuchy znaków
oraz wartości logiczne.
Jakie nazwy możemy nadawać zmiennym? Czy mogą być one zupełnie dowolne? Otóż nie, nazwy
zmiennych nie mogą być zupełnie dowolne, jednak reguły ich określania są proste. Jeśli będziesz
stosował się do dwóch poniższych reguł, zawsze utworzysz prawidłowe nazwy zamiennych.
1 1D]ZD]PLHQQHMSRZLQQD]DF]\QDüVLĊRGOLWHU\
]nDNu SRGNUeĞOeniD B OuE ]nDNu GRODUD 2 -DNR GUuJi ]nDN i nDVWĊSne mRĪnD VWRVRwDü OiWeU\ F\IU\
]nDNi SRGNUeĞOeniD RUD] GRODUD
No i jeszcze jedna sprawa. Naprawdę nie chcemy wprowadzać w błąd interpretera JavaScriptu,
co moglibyśmy zrobić, używając któregokolwiek z wbudowanych słów kluczowych, takich jak
var, function lub false. Nie wolno ich stosować jako nazw zmiennych. Większość z tych słów
kluczowych przedstawimy dalej w książce i wyjaśnimy, do czego służą, na razie jednak wystarczy,
że przejrzysz ich listę. Oto ona.
EUeDN
GeOeWe
IRU
OeW
VuSeU
YRiG
FDVe
GR
IunFWiRn
new
VwiWFK
wKiOe
FDWFK
eOVe
iI
SDFNDJe
WKiV
wiWK
FODVV
enum
imSOemenWV
SUiYDWe
WKURw
\ieOG
FRnVW
e[SRUW
imSRUW
SURWeFWeG
WUue
FRnWinue
e[WenGV
in
SuEOiF
WU\
GeEuJJeU
IDOVe
inVWDnFeRI
UeWuUn
W\SeRI
GeIDuOW
IinDOO\
inWeUIDFe
VWDWiF
YDU
Nie istnieją
P: Czym jest słowo kluczowe?
O: Słowo kluczowe to słowo
zarezerwowane przez język JavaScript.
JavaScript używa ich na własne potrzeby,
a wykorzystywanie ich jako nazw
zmiennych byłoby bardzo mylące zarówno
dla Ciebie, jak i dla przeglądarki.
50
Rozdział 1.
głupie pytania
P: A co się stanie, kiedy użyję słowa P: Czy JavaScript zwraca uwagę na
kluczowego jako fragmentu nazwy
zmiennej? Czy np. można użyć nazwy
ifOnly (czyli użyć „if” w nazwie
zmiennej)?
O: To — oczywiście — można zrobić,
trzeba tylko pamiętać, by nazwa zmiennej
nie była identyczna z jakimś słowem
kluczowym. Warto także pisać zrozumiały
kod, zatem — ogólnie rzecz biorąc —
należy unikać nazw, takich jak elze,
które można pomylić z else.
wielkość liter? Innymi słowy, czy nazwa
myvariable to to samo, co MyVariable?
O: Jeśli wcześniej tworzyłeś kod HTML,
możesz być przyzwyczajony do języków,
w których wielkość liter nie ma znaczenia;
w końcu przeglądarki tak samo traktują
znaczniki <head> i <HEAD>. Jednak
w języku JavaScript wielkość liter, używanych
w nazwach zmiennych, słów kluczowych,
funkcji oraz właściwie we wszystkich innych
miejscach kodu, ma znaczenie. A zatem
zwracaj uwagę, czy coś zapisujesz wielkimi,
czy małymi literami.
Szybki skok na głębokie wody JavaScriptu
KURIER
SIECIOWSKI
-DNXQLNQąüW\FKĪ
HQXMąF\FKEáĊGyZ
ZQD]ZDFK"
0DV]EDUG]RGXĪRV
ZRERG\
ZGRELHUDQLXQD]Z
7ZRU]ąFQD]Z\VN
UHGDNFMD]HEUDáD]D]PLHQQ\FK
VáyZXĪ\ZDM]DSLVáDGDMąFHVLĊ]NLONX 1D]Z]DF]
WHP
SR
\QDMąF\
UDG\NLONX
Xcamel case
VLHFLRZLFNLFKHNVS
XĪ\ZDMZ\áąF]QLH FKVLĊRGBRUD]
ZWHG\NLHG\PDV
GRELHUDQLHQD]Z HUWyZE\XáDWZLü&L :F]HĞQLHMF]\SyĨQLHMEĊG]
QD
]
SU
LHV
DZ
]P
GĊ
XVL
SR
XWZRU]\ü]PLHQQą
Dá
ZDĪQ\SRZyG
UH
SUH
]HQ
WXM
:\ELHUDMQD]Z\
ąFą
QS
GZXJáRZHJRVPRND
1D
]Z
NWyUHFRĞ]QDF]ą
\]
PL
HQQ\FK]DF]\QDMąF
-DN":\VWDUF]\XĪ ]LHMąFHJRRJQLHP
HVLĊRG
]QDNXVą
7DNLHQD]Z\]PLHQ
ZNWyU\PSLHUZV]H\ü]DSLVXcamel case GODELEOLRWH]D]Z\F]DM]DUH]HUZRZDQH
OXEIRR]DSHZQH]QQ\FKMDNBPU
OLW
NHU\
DY
D6
SR
FUL
V]F
SW
]HJ
LF
DF
KR
yOQ
]ąFRĞGOD&LHELH VáyZ ]Z\MąWNLHP
üQLHNWyU]\
\FK DXWRU]\XĪ\Z
MHGQDNVSRáHF]QRĞ
SLHUZV]HJR
DMą]Q
]UyĪQ\PLNRQZHQ DNXB]JRGQLH
QLHUDF]HMQLHFKĊWQü6LHFLRZLFSDWU]\QD ]DSLV\ZDQHVąZLHONLPLOLWHUDP
FMD
PL
LR
MH
LH
WR
1
GQDN]DOHF
SU]\NáDGWZR+
LHW\ONRGODWH
E\UDF]HMQLHXĪ\Z
ĪH]F]DVHP]DSH
DüĪDGQHJR]W\FK DP\
ZQH]DSRPQLV]FR JR 7DNLHQD]Z\á HDGHG'UDJRQ:LWK)LUH
]QDNyZFK\EDĪHP
]QDF]ąOHF]WDNĪHG
DWZRVLĊWZRU]\SR
DV]
QD
WUD
ODW
SUD
ILąVLĊ SRZRG\ EĊ
HJRĪHVDPNRG QLPLSRVáXJLZDüZV
ZGĊZDĪQH
EĊG]LHF]\WHOQLHMV
G]LHV]ZLHG]LDáNLHG
DSR]DW\P]DSHZQ ]\VF\Z6LHFLRZLFDFK
\
QD]ZWDNLFKMDNFX]\NLHG\XĪ\MHV]
LDM
ąG
XĪ
ąH
=D
ODV
FK
W\F
UUH
RZ
ZWZRU]HQLXGRZR
QW3UHDVXUHOXE
]QRĞü
DMEH]SLHF]HĔVWZ
SDVVHG([DP
OQ\
R
FK
QD
]Z
VSHáQLDMąF\FK7ZRMH
:\ELHUDMąFQD]Z\
WDNĪHLQQHVSRVRE\SSRWU]HE\,VWQLHMą
]DFKRZDMEH]SLHF GOD]PLHQQ\FK
]PLHQQ\FKDOHWHQ LVDQLDQD]Z
SRUDG]W\P]ZLą]]HĔVWZRNLOND
DQ
XĪ\ZDQ\ QDZHWZMHVWQDMF]ĊĞFLHM
Z
NVLąĪFHMHGQDN \FKSRGDP\GDOHM
LQQ\FKMĊ]\NDFK E\QD]Z\E\á\]U QDUD]LHSDPLĊWDM
VáyZNOXF]RZ\FKLR]XPLDáHXQLNDM
GHNODUDFMLXPLHV] ]DZV]HQDSRF]ąWNX
F]DMVáRZRYDU
=DEDZ\]HVNïDGQLÈ
■ Każda instrukcja kończy się średnikiem.
x = x + 1;
■ Komentarze jednowierszowe zaczynają się od dwóch znaków
ukośnika. Komentarze to takie notatki dotyczące kodu dla Ciebie
oraz innych programistów. Komentarze nie są wykonywane.
// Oto ja: Komentarz!
■ Odstępy nie mają znaczenia (prawie zawsze).
x
=
2233;
■ Łańcuchy znaków należy zapisywać w cudzysłowach
(lub apostrofach, można używać obu, ale trzeba to robić
konsekwentnie).
ĵ-XOLDQU]ÈG]Lĵ
‘A Mort nie!‘
■ Wartości logicznych true i false nie należy zapisywać
w cudzysłowach.
rockin = true;
■ Zmiennym nie trzeba przypisywać wartość w momencie
deklarowania.
var width;
■ W języku JavaScript, w odróżnieniu do języka HTML, wielkość
liter ma znaczenie. Zmienna counter to nie to samo, co
zmienna Counter.
jesteś tutaj
51
Ćwiczenia ze składni
BĄDŹ przeglądarką
Poniżej znajdziesz kod JavaScript,
w którym celowo wprowadziliśmy
parę błędów. Twoim zadaniem jest
wcielić się w rolę przeglądarki
i wykryć te błędy.
Kiedy już wykonasz
to ćwiczenie, możesz
zajrzeć na koniec
rozdziału, by sprawdzić,
czy udało Ci się znaleźć
wszystkie błędy.
A
// Test ĝartöw
var Moke = ”3r]ychoG]i -ava6cript Go lekar]aij;
var tolG-oke = ”false”;
var $punchline =
”3anie Goktor]e, mam ĂreGniki w oc]ach”
Nie przejmuj się na razie tym,
jak
działa przedstawiony kod; skoncent
się na wyszukaniu błędów w nazw ruj
ach
zmiennych i składni.
var entaJe = 0;
var result
if (tolG-oke == true) {
Alert($punchline);
} else
alert(joke);
}
B
\\ Noc filmowa
var ]ip coGe = 10;
var joeijs)avorite0ovie = =aka]ana planeta;
var movieTicket$ = ;
if (movieTicket$ >= ) {
alert(”To juĝ ]a Guĝo”);
} else {
alert(”Tera] oJlÈGniemy film: ” joeijs)avorite0ovie);
}
52
Rozdział 1.
Szybki skok na głębokie wody JavaScriptu
Wyrazić się
Do pełnego wyrażenia siebie w języku JavaScript potrzebne są wyrażenia. Wyrażenia są
obliczane i zwracają wartości. W przedstawionych przykładach spotkałeś się już z kilkoma
wyrażeniami. Przeanalizujmy np. wyrażenie przedstawione poniżej.
Oto instrukcja, która zapisuje w zmiennej total wynik
będący wartością wyrażenia.
var total = price - (price
To jest nasz
zmienna totaa
l.
I przypisanie.
Znak * oznacza mnożenie,
a znak / oznacza dzielenie.
(Giscount / 100));
wyrażeniem.
A to wszystko jest jednym
Jeśli kiedykolwiek uczyłeś się w szkole matematyki, kontrolowałeś stan konta
lub obliczałeś podatki, takie wyrażenia arytmetycznie nie będą niczym nowym.
Wyrażenie to reprezentuje
cenę pomniejszoną o zniżkę,
będącą pewnym procentem
ceny. A zatem, jeśli cena
wynosi 10, a zniżka 20,
w wyniku uzyskamy 8.
Istnieją także wyrażenia łańcuchowe, poniżej podaliśmy kilka przykładów.
”'roJi ” ”&]ytelniku” ”,”
”super” + ”ultra” + youKnowTheRest
phoneNumbersubstrinJ(0,)
adza
ie, łączy, czy też przeprow
Wyrażenie to dodaje do sieb ów znaków, tworząc w ten
uch
łańc
ch
konkatenację, dwó
„Drogi Czytelniku,”.
sposób jeden nowy łańcuch
To samo dzieje się tutaj, przy czym w wyrażeniu
została użyta zmienna zawierająca łańcuch
znaków. Wyrażenie to może zwrócić łańcuch:
„superultratotalekstrem czadowa impreza”.*
A to kolejny przykład wyrażenia zwracającego łańcuch
znaków. To, jak ono działa, wyjaśnimy dokładniej dalej
w tej książce, jednak w tym przypadku wyrażenie zwraca
numer kierunkowy telefonu stacjonarnego.
Istnieją także wyrażenia, które przyjmują wartości true lub false.
Są one nazywane wyrażeniami logicznymi. Przeanalizuj każdy
z poniższych przykładów, by przekonać się, jaką wartość zwracają.
aJe < 1
Jeśli wiek osoby jest mniejszy od 14, wyrażenie przyjmie wartość
true; w przeciwnym razie jego wartością będzie false. Możemy go
użyć, by sprawdzić, czy ktoś jest dzieckiem, czy nie.
cost >= Jeśli zmienna cost jest większa od 3.99, wyrażenie przyjmi
e wartość
true; w przeciwnym razie jego wartością będzie false. Przygot
do szybkiej akcji na wyprzedaży, kiedy przyjmie ono wartość uj się
false.
animal == ”nieGěwieGě”
Wyrażenie przyjmie wartość true, jeśli zmienna animal będzie
mieć
wartość „niedźwiedź”. Jeśli faktycznie tak będzie, uważaj
!
Wyrażenia mogą także zwracać wartości kilku innych typów; omówimy je wszystkie
dalej w tej książce. Na razie jednak najważniejsze jest to, byś zdał sobie sprawę,
że wszystkie wyrażenia zwracają jakąś wartość: może nią być liczba, łańcuch znaków
lub wartość logiczna. Idźmy zatem dalej i przekonajmy się, co nam to daje!
* Oczywiście przy założeniu, że zmienna youKnowTheRest będzie zawierać
łańcuch znaków „totalekstrem czadowa impreza”.
jesteś tutaj
53
Ćwiczebne wyrażenia
Zaostrz ołówek
Sięgnij po ołówek, bo nadszedł czas na przeanalizowanie kilku wyrażeń. Zostały one przedstawione poniżej, a dla
każdego z nich masz wyliczyć i zapisać wartość. Owszem: ZAPISAĆ! Zapomnij o tym, co matka mówiła o pisaniu
w książkach, w tej możesz zapisywać odpowiedzi! Nie zapomnij, by porównać swoje odpowiedzi z prawidłowymi,
podanymi na końcu rozdziału.
ć „przeliczanie
Czy potrafisz powiedzie Fahrenheita”?
na
sza
lsju
Ce
ze stopni
( / 5)
temp + To jest wyrażenie logiczne.
Operator == sprawdza, czy
dwie wartości są sobie
równe.
Jaki będzie wynik, gdy zmienna temp będzie mieć wartość 10? ___________
color == Ĵpomarañc]owy”
Przy założeniu, że zmienna color ma wartość “różowy”, to wyrażenie przyjmie
wartość true czy false? ___________
A co będzie w przypadku, gdy zmienna przyjmie
wartość “pomarańczowy”? ___________
name + Ĵ, Ĵ + ĴwyJraïaĂ”
Jaką wartość przyjmie wyrażenie, jeśli zmienna name będzie mieć
wartość “Marta”? _________________________
yourLevel > 5
Ten operator
sprawdza, czy
pierwsza wartość
Jaka będzie wartość wyrażenia, gdy yourLevel będzie mieć wartość 2? ___________
jest większa od
drugiej. Można także Jaka będzie wartość wyrażenia, gdy yourLevel będzie mieć wartość 5? ___________
użyć operatora >=,
Jaka będzie wartość wyrażenia, gdy yourLevel będzie mieć wartość 7? ___________
by sprawdzić, czy
pierwsza wartość
jest większa lub
równa drugiej.
(level * points) + bonus
Dobra, level ma wartość 5, points wartość 30000, a bonus 3300.
Jaką wartość przyjmie wyrażenie? ___________
color = Ĵpomarañc]owy”
Przy założeniu, że zmienna color ma wartość “pomarańczowy”,
to wyrażenie przyjmie wartość true czy false? ___________
Operator != sprawdza, czy dwie
wartości NIE są sobie równe.
Za to dostaniesz
DODATKOWE PUNKTY!
1000 + Ĵ10”
54
Rozdział 1.
Czy w tym przypadku może być
więcej niż jedna odpowiedź?
Tylko jedna jest prawidłowa.
Którą byś wybrał? ___________
.RGRZDQLHQDSRZDĝQLH
Czy zwróciłeś uwagę, że operator = jest
używany w instrukcjach przypisania, a operator
== podczas sprawdzania równości? Czyli
jednego znaku równości używamy, by zapisywać
wartości w zmiennych. Z kolei dwóch znaków równości
używamy, by sprawdzić, czy dwie wartości są sobie równe.
Nieprawidłowe zastosowanie tych dwóch operatorów jest
bardzo częstym błędem programistycznym.
Szybki skok na głębokie wody JavaScriptu
while (juggling) {
keepBallsInAir();
}
Wykonywanie operacji więcej niż jeden raz
Wiele czynności wykonujemy więcej niż jeden raz.
Nabierz, nałóż i powtórz…
W górę i w dół…
Bierzemy kolejny cukierek z miseczki, dopóty coś w niej jeszcze jest.
Oczywiście, także w kodzie często musimy wykonywać czynności więcej
niż jeden raz, a JavaScript udostępnia kilka pętli, które na to pozwalają;
są to pętle while, for, for in oraz forEach. Później przedstawimy je
wszystkie, jednak na razie skoncentrujemy się na pętli while.
Wspominaliśmy już o wyrażeniach, które zwracają wartość logiczną,
takich jak scoops > 0. Musisz wiedzieć, że właśnie takie wyrażenia mają
kluczowe znaczenie dla działania pętli while. A tak się to dzieje.
,nVWUuNFMD wKiOe UR]SRF]\nD
ViĊ RG VáRwD NOuF]RweJR
wKiOe
3ĊWOD wKiOe uĪ\wD w\UDĪeniD
ORJiF]neJR nD]\wDneJR
WeVWem wDUunNRw\m DOER ²
w VNUyFie ² wDUunNiem
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka”);
scoops = scoops - 1;
}
A MeĞOi wDUuneN EĊG]ie VSeániRn\ SR
w\NRnDniu EORNu NRGu wUDFDm\ nD SRF]ąWeN
SĊWOi i SU]eWwDU]Dm\ JR SRnRwnie -eĞOi W\m
UD]em wDUuneN SU]\Mmie wDUWRĞü IDOVe SĊWOD
]RVWDnie ]DNRĔF]RnD
-eĞOi wDUuneN MeVW VSeániRn\ F]\Oi
SU]\Mmie wDUWRĞü WUue FDáD ]DwDUWRĞü
EORNu NRGu ]RVWDnie w\NRnDnD
A F]\m MeVW EORN NRGu" BORN WR
wV]\VWNR FR MeVW umieV]F]Rne
SRmiĊG]\ SDUą nDwiDVyw
NODmURw\FK nS ^ RUD] `
Jak już powiedzieliśmy:
nabierz, nałóż i powtórz!
jesteś tutaj
55
Iteracje w języku JavaScript
Jak działa pętla while?
A teraz przeanalizujemy pętlę while, jakbyśmy widzieli ją po raz pierwszy, by dokładnie
dowiedzieć się, jak działa. Zauważ, że dodaliśmy do kodu deklarację zmiennej scoops
i przypisaliśmy jej wartość początkową 5.
A teraz spróbujemy wykonywać ten kod. Najpierw przypisujemy zmiennej
scoops wartość 5.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
Teraz docieramy do instrukcji while. Kiedy zaczynajmy ją wykonywać, pierwszą
rzeczą, którą robimy, jest przetworzenie wyrażenia logicznego, by sprawdzić,
czy przyjmuje ono wartość true, czy false.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
Czy zmienna scoops
jest większa od zera?
Wygląda na to, że tak!
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
Ponieważ wyrażenie warunkowe ma wartość true, zatem zaczynamy
wykonywać blok kodu. Jego pierwsza instrukcja zapisuje w przeglądarce
tekst ”Kolejna Jaïka<br>”.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
56
Rozdział 1.
Szybki skok na głębokie wody JavaScriptu
Następna instrukcja odejmuje jeden od liczby gałek, a następnie zapisuje tę
nową wartość, w naszym przypadku będzie to , w zmiennej scoops.
Zniknęła 1 gałka,
4 zostały!
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
To jest ostatnia instrukcja w bloku, wracamy zatem do wyrażenia
warunkowego i zaczynamy pętlę od początku.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
Ponownie przetwarzamy warunek, tym razem zmienna scoops ma wartość .
Jednak to wciąż więcej niż 0.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
Wciąż jeszcze
sporo zostało!
I ponownie zapisujemy w przeglądarce tekst ”Kolejna Jaïka<br>”.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
jesteś tutaj
57
Pętla while
Następna instrukcja odejmuje jeden od liczby gałek, a następnie zapisuje tę
nową wartość, w naszym przypadku będzie to 3, w zmiennej scoops.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
2 gałki zniknęły,
pozostały 3!
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
To jest ostatnia instrukcja w bloku, wracamy zatem do wyrażenia
warunkowego i zaczynamy pętlę od początku.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
Ponownie przetwarzamy warunek, tym razem zmienna scoops ma wartość 3,
ale to wciąż więcej niż 0.
Wciąż jeszcze sporo zostało!
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
I ponownie zapisujemy w przeglądarce tekst ”Kolejna Jaïka<br>”.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
58
Rozdział 1.
Szybki skok na głębokie wody JavaScriptu
3 gałki zniknęły,
pozostały 2!
I tak to się powtarza… za każdym razem w pętli dekrementujemy
(pomniejszamy o 1) wartość zmiennej scoops, zapisujemy łańcuch znaków
w przeglądarce, sprawdzamy warunek itd.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
I tak dalej…
4 gałki zniknęły, pozostała 1!
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
5 gałek zniknęło,
Aż do ostatniej iteracji, bo w tym przypadku jest inaczej. Wartość zmiennej
nie została żadna!
scoops wynosi 0, a zatem warunek pętli przyjmie wartość false. I to jest to!
Ludzie — już nigdy więcej nie wejdziemy do tej pętli, nie wykonamy już jej bloku.
Tym razem go pomijamy i przechodzimy do instrukcji umieszczonej bezpośrednio za pętlą.
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
Teraz wykonujemy kolejne wywołanie metody Gocumentwrite, które zapisuje
w przeglądarce łańcuch znaków ”¿ycie be] loGöw nie jest juĝ takie samo”.
I to już koniec!
var scoops = 5;
while (scoops > 0) {
Gocumentwrite(”Kolejna Jaïka<br>”);
scoops = scoops - 1;
}
Gocumentwrite(”¿ycie be] loGöw nie jest juĝ takie samo”);
jesteś tutaj
59
Instrukcje warunkowe
if (cashInWallet > 5) {
order = “Ja wezmę zestaw: cheeseburger, frytki i colę”;
} else {
order = “Ja poproszę tylko o szklankę wody”;
}
Podejmowanie decyzji w języku JavaScript
Widziałeś już przykład zastosowania wyrażeń warunkowych w celu podejmowania decyzji, czy należy
kontynuować działanie pętli while. Wyrażeń logicznych można także używać podczas podejmowania
decyzji przy użyciu instrukcji if. Instrukcja ta pozwala wykonać umieszczony w niej blok kodu
wyłącznie w przypadku, gdy warunek przyjmie wartość true. Oto przykład.
zostało
Oto słowo kluczowe if, po którym e i blok kodu.
umieszczone wyrażenie warunkow
Ten warunek sprawdza, czy zostało
nam już mniej niż trzy gałki lodów.
if (scoops < 3) {
alert(”LoGy siÚ koñc]È”);
A jeśli liczba pozostałych gałek jest mniejsza od
trzech, zostaje wykonany blok kodu instrukcji if.
}
Metoda alert pobiera łańcuch znaków i wyświetla go w oknie
dialogowym przeglądarki. Koniecznie je wypróbuj!
Instrukcja if pozwala także na tworzenie sekwencji warunków; wystarczy
w tym celu zastosować jedną lub więcej klauzul else if. Oto przykład.
if (scoops < 3) {
alert(”LoGy siÚ koñc]È”);
unku,
Możemy użyć jednego war
o
a następnie dodać do nieg
if.
kolejny, używając if/else
} else if (scoops >= 5) {
alert(”-eG] s]ybciej, loGy siÚ ]ara] ro]topiÈ”);
}
60
Rozdział 1.
Za pomocą „else if” można doda
wać
dowolnie wiele warunków, przy
czym
będzie mieć własny blok kodu, któr każdy
wykonany, jeśli warunek zostanie y będzie
spełniony.
Szybki skok na głębokie wody JavaScriptu
A kiedy trzeba podejmować WIELE decyzji…
Możesz połączyć dowolnie wiele instrukcji if/else, a jeśli będzie trzeba, możesz także dodać
do nich ostatnią klauzulę else; za jej pomocą wykonasz odpowiedni kod, jeśli żaden z wcześniejszych
warunków nie zostanie spełniony. Oto przykład.
if (scoops >= 5) {
Ten kod sprawdza, czy zostało jeszcze
przynajmniej pięć gałek lodów…
alert(”-eG] s]ybciej, loGy siÚ ]ara] ro]topiÈ”);
} else if (scoops == 3) {
alert(”LoGy siÚ koñc]È”);
…a ten, czy zostały dokładnie trzy gałki…
} else if (scoops == ) {
alert(”'wa”);
} else if (scoops == 1) {
alert(”-eGen”);
} else if (scoops == 0) {
…ten, czy zostały, odpowiednio,
2, 1 i żadnej gałki; w każdym
z tych przypadków wyświetlany
jest odpowiedni komunikat.
alert(”I koniec”);
} else {
alert(”0amy jes]c]e Guĝo loGöw, pr]yjGě i skos]tuj”);
}
A jeśli żaden z powyższych waru
nie przyjmie wartości true, zost nków
anie
wykonany ten fragment kodu.
Nie istnieją
głupie pytania
P: Czym dokładnie jest blok kodu?
O: Z punktu widzenia składni języka blok
kodu (który skrótowo nazywamy blokiem)
jest zbiorem instrukcji, których może być
dowolnie wiele, zapisanych pomiędzy
nawiasami klamrowymi. Kiedy już realizacja
programu dotrze do takiego bloku,
wszystkie tworzące go instrukcje zostaną
potraktowane jako grupa, która ma być
wykonana wspólnie. Jeśli np. warunek pętli
while przyjmie wartość true, zostaną
wykonane wszystkie instrukcje umieszczone
w jej bloku. Dokładnie to samo dotyczy
bloku kodu instrukcji if lub else if.
P: Widziałem w kodzie warunek,
którym była sama zmienna i to taka,
która nie zawierała wartości logicznej,
lecz np. łańcuch. Jak taki warunek
działa?
O: Takie sztuczki opiszemy dalej w tej
książce, jednak, mówiąc najprościej,
JavaScript bardzo elastycznie podchodzi
do tego, co uważa za wartość logiczną.
Przykładowo dowolna zmienna zawierająca
dowolny łańcuch znaków (czyli niepusta) jest
traktowana jako wartość true; natomiast
zmienna, której nie przypisano wartości
— jest traktowana jako false. Niedługo
zajmiemy się tym bardziej szczegółowo.
P: Napisaliście, że wyrażenia mogą
zwracać nie tylko łańcuchy, liczby
i wartości logiczne. Co jeszcze mogą
zwracać?
O: Obecnie koncentrujemy się na tzw.
typach prostych, czyli właśnie liczbach,
łańcuchach znaków i wartościach
logicznych. Później zajmiemy się bardziej
złożonymi typami danych, takimi jak tablice
(będące kolekcjami wartości), obiekty
i funkcje.
P: Wspominaliście także
o wartościach boolowskich,
skąd pochodzi ta nazwa?
O: Określenie „boolowskie” pochodzi
od nazwiska angielskiego matematyka
George’a Boole’a, twórcy algebry Boole’a.
W programach typ boolean jest często
zapisywany jako „Boolean”, by podkreślić,
że jest to swoisty hołd oddany George’owi
Boole’owi.
jesteś tutaj
61
Ćwiczenie programistyczne, magnesiki z kodem
Magnesy z kodem
3URJUDPQDSLVDQ\ZMĐ]\NX-DYD6FULSW]RVWDâSRFLĐW\QDNDZDâNLLXPLHV]F]RQ\
QDORGyZFH&]\SRWUDILV]SRXNâDGDþPDJQHVLNLZRGSRZLHGQLFKPLHMVFDFK
WDNDE\RGWZRU]\þG]LDâDMċF\SURJUDPNWyU\Z\JHQHUXMHZ\QLNLSRND]DQHQLİHM"
3RUyZQDMVZyMSURJUDP]SUDZLGâRZċRGSRZLHG]LċSRGDQċQDNRĚFXUR]G]LDâX
Poukładaj magnesiki, by złożyć
działający program.
GRFXPHQWZULWH 1LHFKĝ\MHQDPHQDPEU! document.write("Sto lat, sto lat.<br>")
;
var i = 0;
var name = "Józek";
i = i + 1;
GRFXPHQWZULWH 1LHFKĝ\MHĝ\MHQDPEU! while (i < 2) {
Skorzystaj z tego miejsca,
by odtworzyć program.
62
Rozdział 1.
}
Odtworzony program powinien
generować następujące wyniki.
Szybki skok na głębokie wody JavaScriptu
Wyciągnij rękę i nawiąż kontakt z użytkownikami
Wspominaliśmy o tym, by tworzyć strony w taki sposób, by były
bardziej interaktywne, a w tym celu konieczne jest komunikowanie się
z użytkownikami. Okazuje się, że można to robić na kilka sposobów,
niektóre już poznałeś. Tutaj opiszemy je pobieżnie, a dalej w książce
omówimy dokładniej.
Wyświetlanie komunikatów informacyjnych
Jak się już przekonałeś, przeglądarka zapewnia możliwość wyświetlania
komunikatów. Służy do tego funkcja alert. W jej wywołaniu należy podać
łańcuch znaków stanowiący komunikat, a przeglądarka wyświetli go
użytkownikowi w estetycznym oknie dialogowym. W tym miejscu musimy się
do czegoś przyznać: nadużywaliśmy trochę funkcji alert, gdyż korzystanie
z niej jest takie proste; należy ją stosować wyłącznie wtedy, kiedy naprawdę
chcemy wszystko zatrzymać i przekazać informację użytkownikowi.
Zapisywanie bezpośrednio w przeglądarce
Stronę WWW można sobie wyobrazić jako dokument (w końcu właśnie tak
nazywają ją przeglądarki). Można skorzystać z funkcji document.write,
by w dowolnym momencie zapisać dowolny kod HTML bezpośrednio na
stronie. Ogólnie rzecz biorąc, takie rozwiązanie nie jest uznawane za dobre,
choć czasami można się spotkać z przykładami jego wykorzystania. Użyliśmy
go kilka razy w tym rozdziale, gdyż stanowi bardzo prosty sposób rozpoczęcia
pisania skryptów.
Tych metod używaliśmy
już w tym rozdziale.
Wykorzystanie konsoli
Każde środowisko języka JavaScript dysponuje konsolą pozwalającą na
rejestrowanie komunikatów generowanych przez kod. W celu zapisania
komunikatu na konsoli używa się funkcji console.log; w jej wywołaniu
przekazywany jest łańcuch znaków, który chcemy wyświetlić (już za chwilę
podamy więcej szczegółowych informacji na jej temat). Funkcja ta jest
doskonałym narzędziem do rozwiązywania problemów z kodem, jednak
użytkownicy zazwyczaj nigdy nie będą z niej korzystać, zatem jako narzędzie
komunikacji nie jest szczególnie efektywna.
Konsola jest naprawdę wygodnym
narzędziem do odnajdywania błędów
w kodzie! Jeśli popełniłeś jakiś
błąd typograficzny, np. zapomniałeś
cudzysłowu, JavaScript zazwyczaj
wyświetli w konsoli odpowiedni
komunikat, który ułatwi Ci
poprawienie.
Bezpośrednie operacje na dokumencie
To już wysoka liga. Właśnie tak chcesz prowadzić interakcję z użytkownikami
i stronami: korzystając z języka JavaScript, możesz odwoływać się bezpośrednio
do strony, odczytywać i zmieniać jej zawartość, a nawet modyfikować strukturę
i stosowane style. Wszystkie te możliwości są realizowane za pośrednictwem
obiektowego modelu dokumentu (ang. Document Object Model, w skrócie
DOM, o którym piszemy dalej w tej książce). Jak się przekonasz, właśnie to jest
najlepszy sposób komunikowania się z użytkownikami. Jednak korzystanie
z DOM wymaga znajomości struktury strony oraz interfejsu programistycznego
pozwalającego na odczyt i zapis elementów strony. Już niebawem zajmiemy
się tymi wszystkimi zagadnieniami. Jednak najpierw musisz się jeszcze trochę
poduczyć JavaScriptu.
To jest właśnie cel, do którego dążymy.
Kiedy tam dotrzemy, będziemy mogli
odczytywać strony, modyfikować je
i operować na nich na wszelkie
możliwe sposoby.
jesteś tutaj
63
Porównanie metod generacji danych
:
7
?
7
KTO CO ROBI
7
Wszystkie przedstawione wcześniej metody komunikacji zostały zaproszone na imprezę kostiumową.
Czy możesz pomóc je zdemaskować? Dopasuj opisy podane z prawej strony do nazw podanych po lewej.
Jedną odpowiedź dopasowaliśmy za Ciebie.
document.write
console.log
alert
obiektowy model dokumentu
64
Rozdział 1.
Zatrzymam wszystko w miejscu
i wyğwietlċ uīytkownikowi
krytki komunikat inIormacyjny
$by kontynuowaý, uīytkownik
musi kliknĆý przycisk ÅOk”
PotraIiċ dodawaý do strony
krytkie Iragmenty kodu +70/
i tekstu 1ie jestem najbardziej
eleganckim sposobem
wyğwietlania komunikatyw dla
uīytkownikyw, jednak mogċ
pracowaý w kaīdej przeglĆdarce
8īywajĆc mnie, dysponujesz
caâkowitĆ kontrolĆ nad stronĆ:
moīesz pobieraý wartoğci
wpisane przez uīytkownika
w polach, zmieniaý kod +70/
strony, modyIikowaý style,
a nawet aktualizowaý
zawartoğý strony
Sâuīċ jedynie do prostego
testowania strony 8īyj mnie,
a w rewanīu wyğwietlċ jakiğ
tekst na specjalnej konsoli
dla programistyw
Szybki skok na głębokie wody JavaScriptu
Poznajemy bliżej funkcję console.log
Teraz omówimy bliżej działanie funkcji console.log; dążymy do tego, byś umiał użyć
jej w tym rozdziale do pokazania wyników generowanych przez kod, a dalej w książce
do wyświetlania wyników działania kodu oraz jego testowania. Musisz jednak pamiętać,
że konsola nie jest jedną z tych możliwości przeglądarki, z których korzysta wielu
użytkowników, dlatego też raczej nie będziesz jej stosował w produkcyjnych wersjach
stron. Niemniej jednak funkcja ta stanowi doskonały sposób, by podczas poznawania
podstaw języka JavaScript przekonać się, co robi nasz kod. Poniżej pokazujemy, jak
działa funkcja console.log.
Bierzemy dowolny
łańcuch znaków…
var message = "Siema" + " " + "partnerze";
console.log(message);
…przekazujemy go w wywołaniu console.log,
a on pojawi się w oknie konsoli przeglądarki,
tak jak pokazaliśmy na poniższym rysunku.
Ta konsola przedstawia
wszystkie wyniki
wygenerowane przez kod.
Nie istnieją
głupie pytania
P: Rozumiem, że funkcja console.log
służy do wyświetlania łańcuchów
znaków, ale co to w zasadzie jest?
Chodzi mi o to, dlaczego „console”
i „log” są oddzielone kropką?
wewnątrz pary nawiasów. Pamiętaj jednak,
że obiektami zajmiemy się znacznie bardziej
szczegółowo dalej w tej książce. To, co
wiesz, wystarczy na razie, by używać funkcji
console.log.
przeglądarkach. Warto pamiętać, że
konsola jest narzędziem, którym dysponują
wszystkie nowoczesne przeglądarki, jednak
nie stanowi elementu żadnej formalnej
specyfikacji.
O: Doskonałe pytanie. Wyprzedzamy
P: Czy konsola może robić coś
P: O rany, konsola wygląda świetnie,
nieco fakty, ale wyobraź sobie konsolę jako
obiekt, który coś robi. Robi takie rzeczy,
które zazwyczaj robią konsole. Jedną
z takich rzeczy jest właśnie rejestrowanie
i wyświetlanie łańcuchów znaków; a żeby
poinformować konsolę, że ma coś dla nas
wyświetlić, używamy składni „console.log”
i przekazujemy łańcuch do wyświetlenia
innego, czy tylko wyświetla łańcuchy
znaków?
O: Tak, jednak zazwyczaj jest używana
tylko do tego. Istnieje kilka bardziej
zaawansowanych sposobów rejestrowania
i korzystania z konsoli, jednak zazwyczaj
różnią się one w poszczególnych
ale gdzie mogę ją znaleźć? Używam jej
w swoim kodzie, ale nigdzie nie widzę
wyników!
O: W większości przeglądarek trzeba
ją własnoręcznie otworzyć. Szczegóły
znajdziesz na następnej stronie.
jesteś tutaj
65
Stosowanie konsoli JavaScript
Otwieranie konsoli
W każdej przeglądarce konsola została zaimplementowana nieco inaczej. A żeby dodatkowo
skomplikować sprawę, sposób implementacji konsoli zmienia się stosunkowo często — może nie
w jakiś znaczący sposób, jednak na tyle zauważalnie, by do czasu, gdy sięgniesz po tę książkę,
konsola wyświetlana w Twojej przeglądarce wyglądała inaczej, niż pokazaliśmy w tej książce.
Niżej pokazujemy, jak można wyświetlić konsolę JavaScript w przeglądarce Chrome (wersja 37)
dla Windows; informacje dotyczące wyświetlania konsoli w większości najpopularniejszych
przeglądarek można znaleźć na (anglojęzycznej) stronie http://wickedlysmart.com/hfjsconsole/.
Kiedy już nauczysz się wyświetlać konsolę w jednej przeglądarce, w miarę łatwo zorientujesz
się, jak to robić w innych; zachęcamy także, byś wypróbował korzystanie z konsoli przynajmniej
w dwóch przeglądarkach, żeby się dobrze z nimi zaznajomić.
Aby wyświetlić
konsolę
w przeglądarce
Chrome (w systemie
Windows), należy
kliknąć przycisk
z prawej strony
paska adresu
i wybrać opcje
Narzędzia/Konsola
JavaScript.
Konsola zostanie
wyświetlona w dolnej rki.
części okna przegląda
Upewnij się, że na pasku
z kartami umieszczonym
w górnej części okna konsoli
jest zaznaczona karta Console.
Na razie nie przejmuj się pozostałymi kartami ani ich
przeznaczeniem. Są przydatne, jednak teraz najważniejsza
jest karta Console, bo dzięki niej możemy oglądać
komunikaty wyświetlane przy użyciu funkcji console.log.
66
Rozdział 1.
Wszystkie komunikaty
przekazane w wywołaniach
funkcji console.log powinny
zostać wyświetlone w tej
części okna.
Szybki skok na głębokie wody JavaScriptu
Piszemy poważną aplikację JavaScript
Wykorzystamy wszystkie zdobyte wiadomości dotyczące języka JavaScript oraz
znajomość funkcji console.log do czegoś praktycznego. Będziemy potrzebować
kilku zmiennych, instrukcji while oraz instrukcji if i else. Wystarczy, że dodasz
trochę szyku i zanim się sam zorientujesz, uzyskasz superpoważną aplikację
biznesową. Jednak przed obejrzeniem kodu zastanów się, w jaki sposób napisałbyś
kod wyświetlający klasyczną piosenkę o „99 butelkach piwa”.
var worG = ”butelek”;
var count = ;
while (count > 0) {
consoleloJ(count + ” ” + worG + ” piwa na Ăcianie”);
consoleloJ(count + ” ” + worG + ” piwa,”);
consoleloJ(”-eGnÈ weě i poGaj w koïo,”);
count = count - 1;
if (count > 0) {
consoleloJ(count + ” ” + worG + ” piwa na Ăcianie”);
} else {
consoleloJ(”Nie ma juĝ ” + worG + ” piwa na Ăcianie”);
}
}
WYSIL
SZARE KOMÓRKI
W tym kodzie jest pewna niewielka usterka. Aplikacja działa
prawidłowo, jednak generowane wyniki nie są w 100%
prawidłowe. Czy jesteś w stanie znaleźć błąd i go poprawić?
jesteś tutaj
67
Znacznik script
Czy nie powinniśmy
umieścić tego kodu na stronie
WWW, tak żeby można było zobaczyć
generowane teksty? Czy będziemy
zapisywali odpowiedzi na papierze?
Słusznie! Najwyższa pora, by umieścić skrypt
na stronie. Jednak chcieliśmy, żebyś — zanim
do tego dojdziemy — dysponował na tyle dużą
znajomością języka JavaScript, by zrobić coś
ciekawego. Przekonałeś się już na początku tego
rozdziału, że kod JavaScript jest dodawany do
dokumentów HTML, podobnie jak style CSS,
czyli zapisuje się go bezpośrednio w dokumencie,
pomiędzy odpowiednimi znacznikami, a konkretnie,
znacznikami <script>.
Oczywiście, tak samo jak w przypadku arkuszy
stylów CSS, kod JavaScript można także umieszczać
w odrębnych plikach.
Naszą poważną aplikację biznesową umieścimy
najpierw bezpośrednio w kodzie strony, a następnie
po dokładnym przetestowaniu przeniesiemy ją do
odrębnego pliku.
68
Rozdział 1.
Szybki skok na głębokie wody JavaScriptu
No dobrze, wyświetlmy zatem jakiś kod w przeglądarce… Postępuj zgodnie
z poniższymi instrukcjami, by uruchomić swoją poważną aplikację biznesową.
Nasze wyniki pokazaliśmy poniżej.
Jazda próbna
Wszystkie przykłady do książki oraz używane
w nich dodatkowe pliki możesz pobrać z serwera FTP
wydawnictwa Helion: ftp://ftp.helion.pl/przyklady/prjsrg.zip
1
Sprawdź poniższy kod HTML; to właśnie w nim umieścisz kod JavaScript. Wpisz ten
kod, a następnie skopiuj do niego kod aplikacji przedstawiony dwie strony wcześniej,
pamiętając, by wstawić go pomiędzy znaczniki <script>. Możesz użyć programu
Notatnik (Windows) lub TextEdit (Mac), upewnij się tylko, że zapisujesz stronę
jako zwyczajny plik tekstowy. Jeśli masz jakiś inny, ulubiony edytor HTML, taki jak
Dreamweaver, Coda lub WebStorm, możesz go wykorzystać.
Piszemy powa
żną
}
<Goctype html>
kod.
Wpisz ten
<html lanJ=”pl”>
Szybki skok
aplikację Java
Script
na głębokie
Wykorzystam
y wszystkie
zdobyte wiadom
znajomość
funkcji consol
kilku zmienn
e.log do czegośości dotyczące języka
JavaScript
ych, instruk
praktycznego
oraz
cji while oraz
trochę szyku
. Będziemy
i zanim się
instrukcji if
sam zorien
biznesową.
i else. Wystar potrzebować
tujesz, uzyska
Jednak przed
sz superpoważn czy, że dodasz
kod wyświe
obejrzeniem
tlający klasycz
kodu zastan
ą
ów się, w jaki aplikację
ną piosenkę
o „99 butelk
sposób napisa
ach piwa”.
łbyś
wody JavaS
criptu
var word
= ”butelek”;
var count
= 99;
while (count
> 0) {
consoleloJ(
count + ”
” + word
console.log(
+ ” piwa
count + ”
na Ăciani
e”);
” + word
console.log(
+ ” piwa,”
”-ednÈ weě
);
i podaj w
count = count
koïo,”);
- 1;
if (count
> 0) {
console.log(
count + ”
” + word
} else {
+ ” piwa
na Ăciani
e.”);
console.log(
”Nie ma juĝ
}
” + word
+ ” piwa
na Ăciani
e.”);
WYSIL
SZARE KOM
ÓRK
W tym kodzie
I
prawidłowo, jest pewna niewielka
usterka. Aplikac
jednak genero
prawidłowe.
Czy jesteś w wane wyniki nie są w ja działa
100%
stanie znaleźć
błąd i go popraw
ić?
<heaG>
jesteś tutaj
<meta charset=”utf-”>
67
<title>0öj pierws]y skrypt -ava6cript</title>
</heaG>
<boGy>
<script>
To są znaczniki <script>. Teraz już powinieneś
wiedzieć, gdzie masz umieścić kod JavaScript.
</script>
</boGy>
</html>
2
Zapisz ten plik jako „index.html”.
3
Wczytaj plik do przeglądarki. Możesz go przeciągnąć i upuścić
na jej oknie bądź wybrać z menu opcję Plik/Otwórz lub Plik/
Otwórz plik.
4
Na samej stronie niczego nie zobaczysz, gdyż wszystkie wyniki są
pokazywane w oknie konsoli przy użyciu funkcji console.log.
A zatem wyświetl okno konsoli i pogratuluj sobie uruchomienia
pierwszej, poważnej aplikacji biznesowej.
Oto testowe wykonanie naszej aplikacji. Kod
tworzy pełny tekst piosenki o 99 butelkach
piwa, wyświetlając go w oknie konsoli.
jesteś tutaj
69
Jak dodawać kod do strony?
Jak mogę dodać kod do strony? (Niech policzę wszystkie sposoby)
Już wiesz, że elementy <script> zawierające kod JavaScript można dodawać do sekcji <head> strony bądź też wewnątrz
elementu <body>. Jednak skrypty można dodawać do strony także na kilka innych sposobów. Przyjrzymy się teraz wszystkim
miejscom, w którym można umieszczać skrypty JavaScript (i przekonamy się, dlaczego jedne mogą być lepsze od innych).
Kod można umieszczać wewnątrz
elementu <head>. Najczęściej stosowanym
sposobem dodawania kodu JavaScript do
stron WWW jest umieszczanie elementów
<script> wewnątrz elementu <head>.
Oczywiście, dzięki temu kod można
łatwiej znaleźć i wydaje się, że jest
Twoja strona HTML
to całkiem logiczne miejsce do
umieszczania kodu, jednak okazuje
<head>
się, iż nie zawsze jest najlepsze.
<script>
Dlaczego? Czytaj dalej…
LQVWUXNFMD
</script>
<script src=”mycode.js”></script>
Ewentualnie można także
dodać kod wewnątrz treści
strony. W tym celu elementy
<script> należy umieścić
wewnątrz elementu <body>
(zazwyczaj tuż przed jego końcem).
To jest nieco lepsze rozwiązanie.
Dlaczego? Kiedy przeglądarka
wczytuje stronę, zawartość elementu
<head> jest wczytywana przed
zawartością elementu <body>. Jeśli
zatem kod jest umieszczony w sekcji
<head>, może się zdarzyć, że użytkownik
będzie musiał trochę poczekać, zanim
zobaczy całą stronę. Z drugiej strony,
jeśli kod skryptu zostanie wczytany
po kodzie HTML umieszczonym
w elemencie <body>, użytkownik będzie
już mógł oglądać treść strony, czekając
jednocześnie na wczytanie kodu skryptu.
Czy są jeszcze inne, lepsze sposoby?
Czytaj dalej…
70
Rozdział 1.
<body>
<script>
LQVWUXNFMD
LQVWUXNFMD
</script>
<script src=”somecode.js”></script>
Można także umieścić kod skryptu
w osobnym pliku i dołączyć go w sekcji
<head>. To rozwiązanie bardzo przypomina
dołączanie zewnętrznych plików CSS. Jedyna
różnica polega na tym, że w tym przypadku
używany jest atrybut src elementu
<script>, w którym podawany jest
adres URL pliku JavaScript.
Kiedy kod skryptu znajduje się
w odrębnym pliku, jego utrzymanie
jest łatwiejsze (bo znajduje się poza
kodem HTML), a poza tym można
go używać na wielu różnych
stronach. Jednak i to rozwiązanie
ma wadę, gdyż taki kod musi być
pobrany przed pobraniem treści
strony. Czy zatem jest jeszcze
lepsze rozwiązanie? Czytaj dalej…
W końcu można dołączyć
zewnętrzny plik w treści
strony. O tak, to połączenie dwóch
najlepszych rozwiązań. Zapewnia
możliwość wygodnego przechowywania
kodu JavaScript w zewnętrznych plikach
i odwoływania się do nich pod koniec
treści strony, a zatem będą pobierane, gdy
przeglądarka już wczyta i wyświetli stronę.
Świetna sprawa.
Pomimo dowodów
świadczących o czymś innym,
wciąż uważam, że sekcja
nagłówka strony jest doskonałym
miejscem do umieszczania kodu.
Szybki skok na głębokie wody JavaScriptu
Będziemy musieli was rozdzielić
Rozdzielanie zawsze boli, jednak zdajemy sobie sprawę, że musimy to zrobić. Nadszedł czas, byś zaznaczył
cały swój kod JavaScript i przeniósł go do odrębnego pliku. Niżej opisujemy, jak możesz to zrobić.
1
2WZyU]SOLNÅLQGH[KWPOµL]D]QDF]FDâ\XPLHV]F]RQ\ZQLPNRG-DYD6FULSW³
ZV]\VWNRFRMHVW]DSLVDQHSRPLĐG]\]QDF]QLNDPLVFULSW!
<doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-”>
<title>0öj pierws]y skrypt -ava6cript</title>
</head>
i <script>; nie
Zaznacz tylko kod, ale nie znacznik
pliku z kodem…
m
nowy
w
wał
będziesz ich potrzebo
<body>
<script>
var word = ”butelek”;
var count = 99;
while (count > 0) {
console.log(count + ” ” + word + ” piwa na Ăcianie”);
console.log(count + ” ” + word + ” piwa,”);
console.log(”-ednÈ weě i podaj w koïo,”);
count = count - 1;
if (count > 0) {
console.log(count + ” ” + word + ” piwa na Ăcianie.”);
} else {
console.log(”Nie ma juĝ ” + word + ” piwa na Ăcianie.”);
}
}
</script>
</body>
</html>
2
$WHUD]ZHG\WRU]HXWZyU]QRZ\SOLNRQD]ZLHÅFRGHMVµLXPLHĤþZQLP
VNRSLRZDQ\NRG=DSLV]SOLNSRGQD]ZċÅFRGHMVµ
code.js
jesteś tutaj
71
Zewnętrzny kod JavaScript
3
7HUD]PXVLV]XPLHĤFLþRGZRâDQLHGRSOLNXÅFRGHMVµZSOLNXÅLQGH[KWPOµWDNİHE\]RVWDâ
RQZF]\WDQ\ZPRPHQFLHSRELHUDQLDLZ\ĤZLHWODQLDVWURQ\:W\PFHOXXVXĚ]SOLNX
ÅLQGH[KWPOµFDâ\NRG-DYD6FULSWOHF]]RVWDZVDPH]QDF]QLNLVFULSW!1DVWĐSQLH
ZDWU\EXFLHsrc]QDF]QLNDRWZLHUDMċFHJR<script> XPLHĤþZDUWRĤþ"code.js".
<doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-”>
<title>0öj pierws]y skrypt -ava6cript</title>
</head>
<body>
<script src=”code.js”>
</script>
</body>
</html>
4
Użyj atrybutu src elementu <script>,
by dołączyć do strony zewnętrzny
plik JavaScript.
Tu wcześniej był kod.
jącego
Wierz nam bądź nie, ale wciąż potrzebujesz zamyka
ikami
znacznika <script>, mimo że pomiędzy oboma znaczn
nie ma żadnego kodu.
2SHUDFMD]RVWDâD]DNRĚF]RQD7HUD]WU]HED
MċSU]HWHVWRZDþ2GĤZLHİVWURQĐÅLQGH[KWPOµ
ZSU]HJOċGDUFH³SRZLQLHQHĤ]REDF]\þGRNâDGQLH
WDNLHVDPHZ\QLNLMDNZF]HĤQLHM=ZUyþXZDJĐ
İHNLHG\Xİ\ZDP\DWU\EXWXZSRVWDFLVUF µFRGHMVµ
]DNâDGDP\İHSOLN-DYD6FULSW]QDMGXMHVLĐZW\P
VDP\PNDWDORJXFRSOLN+70/
Powinieneś uzyskać takie same
wyniki jak wcześniej. Jednak
teraz kody HTML i JavaScript są
umieszczone w odrębnych plikach. Czy
nie uważasz, że takie rozwiązanie jest
bardziej eleganckie, mniej stresujące
i w dodatku ułatwia zarządzanie kodem.
72
Rozdział 1.
Szybki skok na głębokie wody JavaScriptu
Anatomia elementu script
Już wiesz, jak używać elementu <script>, który pozwala dodawać do stron WWW kod
JavaScript. Aby jednak lepiej utrwalić tę wiedzę, przeanalizuj z nami ten element jeszcze
raz, by upewnić się, że naprawdę znasz każdy jego szczegół.
Atrybut type informuje przeglądarkę, że kod umieszczony w elemencie jest
napisany w języku JavaScript. Okazuje się jednak, że jeśli go pominiemy,
przeglądarka i tak założy, że używany jest JavaScript. Dlatego sugerujemy,
żeby pomijać ten atrybut; tak też zalecają osoby tworzące standardy.
Otwierający
znacznik
<script>.
<script type="text/javascript" >
alert(":itaj, Ăwiecie");
</script>
Nie zapomnij o zamykającym
nawiasie kątowym
otwierającego znacznika
<script>.
Wszystko, co znajdzie się pomiędzy
oboma znacznikami <script>, musi
być prawidłowym kodem JavaScript.
Skrypt zawsze musi się kończyć
zamykającym znacznikiem </script>!
W przypadku odwoływania się w kodzie HTML do zewnętrznego pliku JavaScript elementu
<script> należy używać w następujący sposób.
Dodaj atrybut src, aby określić
adres URL pliku JavaScript.
<script src="my-ava6cript.js" >
Pliki JavaScript powinny
mieć rozszerzenie „.js”.
</script>
Także w tym przyp
ad
nie wolno Ci zapom ku
o zamykającym zna nieć
czn
</script>! Potrzebuje iku
go nawet w przypad sz
dołączania zewnętrz ku
pliku JavaScript. nego
Kiedy odwołujesz się do zewnętrznego
pliku JavaScript, wewnątrz elementu
<script> nie jest umieszczany żaden kod
JavaScript.
2EHMU]\MWR
Nie można jednocześnie podawać kodu i odwoływać
się do pliku.
Odwołując się do zewnętrznego pliku JavaScript przy użyciu
atrybutu src, nie można podawać kodu wewnątrz elementu
<script> — takie rozwiązanie nie zadziała. W takich sytuacjach
konieczne będzie zastosowanie dwóch elementów <script>.
<script src=”goodies.js”>
YDU ĵQLH]ïDV]WXF]NDĵ
</script>
'
%þď
jesteś tutaj
73
Wywiad z JavaScriptem
JavaScript bez tajemnic
Temat dzisiejszego wywiadu brzmi:
Poznajemy JavaScript
Rusz głową: Witamy, JavaScripcie. Wiemy, że jesteś bardzo
zajęty w internecie, gdzie zajmujesz się tymi wszystkimi
stronami WWW, dlatego jesteśmy ci bardzo wdzięczni,
że znalazłeś trochę czasu, by z nami porozmawiać.
JavaScript: Nie ma sprawy. Choć faktycznie ostatnio jestem
bardziej zajęty niż kiedykolwiek wcześniej; teraz wszyscy
używają mnie na wszystkich możliwych stronach WWW i to
praktycznie do wszystkiego: zaczynając od prostych menu,
a kończąc na pisaniu złożonych gier! To jest naprawdę
fascynujące!
Rusz głową: To naprawdę niesamowite, ponieważ jeszcze kilka
lat temu ktoś powiedział, że jesteś „na wpół popsutym, słabym
językiem skryptowym”. A teraz jesteś wszędzie.
JavaScript: Nie przypominajcie mi o tym. Od tamtego czasu
przebyłem długą drogę i wiele znakomitych umysłów pracowało
nad tym, by mnie poprawić.
Rusz głową: Poprawić? Ale jak? Wygląda na to, że twoje
podstawowe możliwości jako języka są takie same…
JavaScript: Cóż, teraz jestem lepszy na kilka sposobów. Przede
wszystkim jestem diabelnie szybki. Choć wciąż myśli się o mnie
jak o języku skryptowym, moja obecna wydajność jest bardzo
zbliżona do wydajności języków kompilowanych.
Rusz głową: A oprócz tego?
JavaScript: Możliwości tego, co mogę robić w przeglądarce,
zostały drastycznie rozszerzone. Korzystając z bibliotek
JavaScript działających we wszystkich nowoczesnych
przeglądarkach, można już określać położenie, odtwarzać audio
i wideo, rysować na stronie i robić wiele innych rzeczy. Jednak
żeby to robić, trzeba znać JavaScript.
Rusz głową: Wróćmy do krytyki ciebie jako języka. Słyszeliśmy
trochę niezbyt pochlebnych głosów… To chyba było określenie
„pokręcony język”?
JavaScript: Cóż, pozostanę na gruncie posiadanych informacji.
Jestem jednym z najczęściej używanych języków na świecie.
Stoczyłem także wojnę z konkurentami i wygrałem ją. Pamiętacie
Javę w przeglądarce? To dopiero był żart. A VBScript? Phi.
JScript? Flash? Silverlight? Mógłbym tak długo wymieniać.
Powiedzcie zatem, czy mógłbym być aż tak kiepski?
Rusz głową: Byłeś także krytykowany za, powiedzmy,
„prostotę”.
74
Rozdział 1.
JavaScript: Szczerze mówiąc, to moja największa siła. To, że
możecie uruchomić przeglądarkę, wpisać kilka wierszy kodu
JavaScript i wszystko będzie gotowe, jest fantastyczne i użyteczne.
A poza tym jest to bardzo wygodne dla początkujących
programistów. Słyszałem, że nie ma lepszego języka dla
początkujących od JavaScriptu.
Rusz głową: Jednak ta prostota ma swoją cenę, prawda?
JavaScript: Najlepsze jest to, że moja prostota odnosi się do
łatwości rozpoczęcia pisania w języku JavaScript. Jednak pod
innymi względami jestem głęboki i pełen konstrukcji dostępnych
we wszystkich innych, nowoczesnych językach programowania.
Rusz głową: Czyli jakich?
JavaScript: No… może np. dynamicznych typów danych,
pierwszorzędnych funkcji i domknięć?
Rusz głową: Muszę się przyznać, że zupełnie nie wiem, o czym
mówisz.
JavaScript: Liczby… w porządku, jeśli będziecie czytać dalej,
w końcu do tego dojdziecie.
Rusz głową: Ale powiedz w skrócie, o co chodzi.
JavaScript: Powiem tak: JavaScript powstał, by istnieć
w dynamicznym środowisku stron WWW, fascynującym
środowisku, w którym użytkownicy prowadzą interakcję
ze stronami, na których dane są na bieżąco pobierane
i wysyłane, gdzie mogą zachodzić przeróżne zdarzenia…
I język odzwierciedla taki styl programowania. Zrozumiecie
to dobrze, kiedy lepiej poznacie JavaScript.
Rusz głową: No dobrze, a zatem mówisz, że jesteś językiem
doskonałym? Czy tak?
JavaScript szlocha…
JavaScript: Wiecie, w odróżnieniu od innych języków
programowania nie dorastałem w porośniętych bluszczem
ścianach ośrodków akademickich. Urodziłem się w prawdziwym
internecie i musiałem nauczyć się w życiu szybko pływać, by nie
dać się pożreć. Wziąwszy to pod uwagę, nie jestem doskonały;
bez wątpienia mam „złe cechy”.
Rusz głową, z delikatnym uśmiechem Barbary Walters:
Dziś zobaczyliśmy twoją nową twarz. Myślę, że to dobry powód,
by w przyszłości spotkać się i jeszcze porozmawiać.
JavaScript: Nie osądzajcie mnie źle na podstawie moich kilku
złych cech, poznajcie te dobre i ich się trzymajcie!
Szybki skok na głębokie wody JavaScriptu
CELNE SPOSTRZEŻENIA
Q
JavaScript jest używany do dodawania
zachowań do stron WWW.
Q
Obecnie silniki przeglądarek są w stanie
wykonywać kod JavaScript znacznie
szybciej niż jeszcze kilka lat temu.
Q
Q
Q
Q
Istnieje tylko kilka reguł i wytycznych
związanych z określaniem nazw
zmiennych w języku JavaScript i ważne
jest, by postępować zgodnie z nimi.
Q
Trzeba pamiętać, by podczas określania
nazw zmiennych unikać stosowania słów
kluczowych JavaScriptu.
Q
Kod JavaScript można dodawać do
stron, zapisując go wewnątrz elementu
<script>.
Wyrażenia w języku JavaScript obliczają
wartości.
Q
Kod JavaScript można umieszczać
bezpośrednio wewnątrz kodu strony bądź
w odrębnym pliku dołączanym do strony.
Trzema najczęściej spotykanymi rodzajami
wyrażeń są wyrażenia liczbowe,
łańcuchowe i logiczne.
Q
Instrukcje if/else pozwalają na
podejmowanie decyzji w kodzie.
Przeglądarki zaczynają wykonywać kod
JavaScript, jak tylko napotkają go w kodzie
strony.
Q
Aby dołączyć do strony zewnętrzny plik
JavaScript, należy użyć atrybutu src
elementu <script>.
Q
Instrukcje while/for to pętle
pozwalające na wielokrotne wykonywanie
fragmentu kodu.
Q
Kod HTML deklaruje strukturę i zawartość
strony; natomiast JavaScript oblicza
wartości i dodaje do niej zachowania.
Q
Zamiast funkcji alert lepiej użyć
console.log, która pozwala na
wyświetlanie komunikatów na konsoli.
Q
Programy pisane w języku JavaScript
składają się z instrukcji.
Q
Q
Jedną z najpopularniejszych instrukcji
języka JavaScript jest deklaracja zmiennej.
Korzysta ona ze słowa kluczowego
var w celu zadeklarowania zmiennej
oraz operatora przypisania (=) w celu
określenia jej wartości.
Komunikaty wyświetlane na konsoli
powinny być używane przede wszystkim
w celu testowania kodu, gdyż użytkownicy
najprawdopodobniej nigdy ich nie zobaczą.
Q
Język JavaScript najczęściej jest stosowany
w celu dodawania zachowań do stron
WWW, jednak oprócz tego używany jest
także w takich aplikacjach jak Photoshop,
OpenOffice oraz Google Apps; jest także
stosowany jako język pisania skryptów
wykonywanych po stronie serwera.
jesteś tutaj
75
Rozwiązanie ćwiczenia
BĄDŹ przeglądarką. Rozwiązanie
Poniżej znajdziesz kod JavaScript,
w którym celowo wprowadziliśmy parę
błędów. Twoim zadaniem jest wcielenie
się
w rolę przeglądarki i wykrycie
tych błędów. Kiedy już
zrobisz to ćwiczenie,
możesz zajrzeć na koniec
rozdziału, by sprawdzić,
czy udało Ci się znaleźć
Zapisuj
wszystkie błędy.
A
iędzy dwoma
łańcuchy znaków pom strofu (‘).
lub apo
(„)
u
łow
zys
cud
i
znakam
!
Jednak nie mieszaj ich
// Test ĝartöw
var joke = ”3r]ychod]i -ava6cript do lekar]a....ij;
var told-oke = ”false”;
Nie zapisuj wartości logicznych
w cudzysłowach, chyba że chcesz
uzyskać łańcuch znaków.
Można zaczynać nazwę zmiennej od znaku $, choć nie jest to zalecane.
var $punchline =
”3anie doktor]e, mam Ăredniki w oc]ach.” Nie zapominaj kończyć
instrukcji średnikiem.
W nazwach zmiennych nie
można używać znaku %.
var entage = 0;
var result
I kolejny brakujący średnik.
if (told-oke == true) {
Alert($punchline);
} else
alert(joke);
Tu ma być alert, a nie Alert
— JavaScript zwraca uwagę
na wielkość liter.
Tu brakuje otwierają
nawiasu klamroweg cego
o.
}
Komentarze zaczynają się od
sekwencji znaków //, a nie \\.
B
\\ Noc filmowa
var ]ip code = 910;
W nazwach zmiennych nie można
używać znaków odstępu.
var joeijs)avorite0ovie = =aka]ana planeta;
var movieTicket$ = 9;
W nazwach zmiennych
nie można używać
znaków apostrofu
lub cudzysłowu.
Ale łańcuch znaków
„Zakazana planeta”
musi być zapisany
w cudzysłowach.
if (movieTicket$ >= 9) {
alert(”To juĝ ]a duĝo”);
} else {
alert(”Tera] oglÈdniemy film: ” + joeijs)avorite0ovie);
}
76
Rozdział 1.
Ta instrukcja if/else nie
działa, gdyż zawiera zmienną
o nieprawidłowej nazwie.
Szybki skok na głębokie wody JavaScriptu
Zaostrz ołówek
Rozwiązanie
Sięgnij po ołówek, bo nadszedł czas na przeanalizowanie kilku wyrażeń. Zostały one przedstawione poniżej,
a dla każdego z nich masz wyliczyć i zapisać jego wartość. Owszem: ZAPISAĆ! Zapomnij o tym, co matka
mówiła o pisaniu w książkach, w tej możesz zapisywać odpowiedzi! Oto odpowiedzi.
ć „przeliczanie
Czy potrafisz powiedzie Fahrenheita”?
na
sza
lsju
Ce
ze stopni
(9 / 5) * temp + 3
50
Jaki będzie wynik, gdy zmienna temp będzie mieć wartość 10? ___________
To jest wyrażenie logiczne.
Operator == sprawdza, czy
Przy założeniu, że zmienna color ma wartość “różowy”, to wyrażenie przyjmie
dwie wartości są sobie
false
równe.
wartość true czy false? ___________
color == Ĵpomarañc]owy”
name + Ĵ, Ĵ + ĴwygraïaĂ”
yourLevel > 5
A co będzie w przypadku, gdy zmienna przyjmie
true
wartość “pomarańczowy”? ___________
Jaką wartość przyjmie wyrażenie, jeśli zmienna name będzie mieć
"Marta, wygrałaś!"
wartość “Marta”? _________________________
Ten operator
sprawdza, czy
false
pierwsza wartość
Jaka będzie wartość wyrażenia, gdy yourLevel będzie mieć wartość 2? ___________
jest większa od
false
drugiej. Można także Jaka będzie wartość wyrażenia, gdy yourLevel będzie mieć wartość 5? ___________
użyć operatora >=,
true
Jaka będzie wartość wyrażenia, gdy yourLevel będzie mieć wartość 7? ___________
by sprawdzić, czy
pierwsza wartość
jest większa lub
równa drugiej.
(level * points) + bonus
Dobra, level ma wartość 5, points wartość 30000, a bonus 3300.
153300
Jaką wartość przyjmie wyrażenie? ___________
color = Ĵpomarañc]owy”
Przy założeniu, że zmienna color ma wartość “pomarańczowy”,
true
to wyrażenie przyjmie wartość true czy false? ___________
Operator != sprawdza, czy dwie
wartości NIE są sobie równe.
Za to dostaniesz
DODATKOWE PUNKTY!
1000 + Ĵ10”
Czy w tym przypadku może być
więcej niż jedna odpowiedź?
Tylko jedna jest prawidłowa.
"1000108"
Którą byś wybrał? ___________
.RGRZDQLHQDSRZDĝQLH
Czy zwróciłeś uwagę, że operator = jest
używany w instrukcjach przypisania, a operator
== podczas sprawdzania równości? Czyli
jednego znaku równości używamy, by zapisywać
wartości w zmiennych. Z kolei dwóch znaków równości
używamy, by sprawdzić, czy dwie wartości są sobie równe.
Nieprawidłowe zastosowanie tych dwóch operatorów jest
bardzo częstym błędem programistycznym.
jesteś tutaj
77
Rozwiązanie ćwiczenia
Magnesy z kodem. Rozwiązanie
3URJUDPQDSLVDQ\ZMĐ]\NX-DYD6FULSW]RVWDâSRFLĐW\QDNDZDâNLLXPLHV]F]RQ\
QDORGyZFH&]\SRWUDILV]SRXNâDGDþPDJQHVLNLZRGSRZLHGQLFKPLHMVFDFK
WDNE\RGWZRU]\þG]LDâDMċF\SURJUDPNWyU\Z\JHQHUXMHZ\QLNLSRND]DQLHSRQLİHM"
2WRQDV]HUR]ZLċ]DQLH
Poukładaj magnesiki, by złożyć
działający program.
var name = "Józek";
var i = 0;
while (i < 2) {
document.write("Sto lat, sto lat.<br>")
;
GRFXPHQWZULWH 1LHFKĝ\MHĝ\MHQDPEU! i = i + 1;
}
GRFXPHQWZULWH 1LHFKĝ\MHQDPHQDPEU! Odtworzony program powinien
generować następujące wyniki.
78
Rozdział 1.
Szybki skok na głębokie wody JavaScriptu
:
7
?
7
KTO CO ROBI?
7
ROZWIĄZANIE
Wszystkie przedstawione wcześniej metody komunikacji zostały zaproszone na imprezę kostiumową.
Czy możesz pomóc je zdemaskować? Dopasuj opisy podane z prawej strony do nazw podanych po lewej.
Jedną odpowiedź dopasowaliśmy za Ciebie. Oto rozwiązanie.
document.write
console.log
alert
obiektowy model dokumentu
Zatrzymam wszystko w miejscu i wyğwietlċ
uīytkownikowi krytki komunikat inIormacyjny $by
kontynuowaý, uīytkownik musi kliknĆý przycisk ÅOk”
PotraIiċ dodawaý do strony krytkie Iragmenty kodu
+70/ i tekstu 1ie jestem najbardziej eleganckim
sposobem wyğwietlania komunikatyw dla uīytkownikyw,
jednak mogċ pracowaý w kaīdej przeglĆdarce
8īywajĆc mnie, dysponujesz caâkowitĆ kontrolĆ nad
stronĆ: moīesz pobieraý wartoğci wpisane przez
uīytkownika w polach, zmieniaý kod +70/ strony,
modyIikowaý style, a nawet aktualizowaý zawartoğý
strony
Sâuīċ jedynie do prostego testowania strony
8īyj mnie, a w rewanīu wyğwietlċ jakiğ tekst
na specjalnej konsoli dla programistyw
jesteś tutaj
79
80
Rozdział 1.
2. Pisanie prawdziwego kodu
Idziemy dalej
Wiesz,
bawiłem się troszkę
pisaniem w JavaScripcie.
Phi… Jeśli chcesz, by
nasza znajomość poszła
o krok dalej, musiałbyś mieć
naprawdę duże doświadczenie
w pisaniu kodu JavaScript.
Już znasz zmienne, typy, wyrażenia…, możesz pójść krok dalej.
Chodzi o to, że już trochę poznałeś język JavaScript. Wiesz na tyle dużo, by móc
napisać jakiś prawdziwy kod. Kod, który robi coś interesującego, którego ktoś
chciałby używać. Jednak wciąż brakuje Ci praktycznego doświadczenia w pisaniu
kodu. Właśnie mamy zamiar temu zaradzić, tu i teraz! A w jaki sposób? Skacząc na
główkę na głęboką wodę i pisząc prostą grę, w całości w języku JavaScript. Cel jest
bardzo ambitny, jednak będziemy go realizować krok po kroku. Chodź, zaczynamy!
Jeśli zechcesz przy okazji uruchomić kolejny prosty startup, nie będziemy Ci
przeszkadzać — kod jest Twój.
to jest nowy rozdział
81
Pisanie gry w okręty
Napiszmy grę w okręty
To będzie pojedynek między Tobą a przeglądarką:
przeglądarka ukrywa okręty, a Twoim zadaniem jest je
znaleźć i zniszczyć. Oczywiście, w odróżnieniu od tradycyjnej
wersji gry, Ty nie będziesz miał żadnych własnych okrętów.
Twoim zadaniem jest odnalezienie i zatopienie okrętów
przeglądarki w jak najmniejszej liczbie prób.
Cel: zatopić statki przeglądarki w jak najmniejszej liczbie
prób. Po zakończeniu rozgrywki gra wyświetli wynik, zależny
od tego, jak dobrze sobie poradziłeś.
Przygotowania: kiedy gra zostanie wczytana, komputer
rozmieszcza swoje statki na wirtualnej planszy w kształcie
siatki. Po rozmieszczeniu statków gracz jest proszony
o pierwszy ruch.
Przebieg gry: przeglądarka wyświetla okno z prośbą
o podanie sprawdzanego pola na planszy. W odpowiedzi
na próbę gracza wyświetlany są komunikaty: „Trafienie”,
„Pudło” lub „Zatopiłeś okręt”.
Pierwsza próba…
To jest to, do czego dążymy: ładna siatka
7×7, z trzema okrętami do zatopienia. Teraz
zaczniemy od czegoś prostszego, jednak
kiedy poznasz JavaScript nieco lepiej,
dokończymy grę, by wyglądała jak ta powyżej
— z atrakcyjną grafiką i wszystkimi innymi
elementami… Obsługę dźwięku zostawimy
Ci jako ćwiczenie do zrobienia we własnym
zakresie.
…Uproszczony okręt
W pierwszym podejściu spróbujemy napisać coś prostszego
od pełnej wersji gry z graficzną planszą 7×7 i trzema
statkami. Zaczniemy skromnie od jednowymiarowej siatki
składającej się z siedmiu pól i jednego statku. Będzie
to prosta wersja, jednak celem w tym momencie jest
zaprojektowanie podstawowego kodu gry, a nie jej finalnego
wyglądu i sposobu działania.
Nie przejmuj się, zaczynając od takiej uproszczonej wersji
gry, zapewniasz sobie doskonałą pozycję wyjściową do
późniejszych prac nad pełną wersją. Dodatkowo będzie
to doskonały krok do napisania pierwszego prawdziwego
programu w języku JavaScript (oczywiście, nie licząc
„poważnej aplikacji biznesowej” z rozdziału 1.). Dlatego też
w tym rozdziale napiszemy prostszą wersję gry, a wersją pełną
zajmiemy się dalej w tej książce, kiedy już poznasz JavaScript
nieco lepiej.
82
Rozdział 2.
Zamiast siatki 7×7, jak pokazana powyżej,
zaczniemy od siatki 1×7. I będziemy
poszukiwać tylko jednego okrętu.
Zauważ, że każdy okręt
zajmuje dokładnie trzy
komórki siatki (podobnie
jak w prawdziwej grze).
Piszemy prawdziwy kod
Punkt pierwszy: projekt wysokiego poziomu
Doskonale wiemy, że będziemy potrzebowali zmiennych, a do tego liczb,
łańcuchów znaków, instrukcji if, wyrażeń warunkowych i pętli… Jednak
gdzie i ile ich będzie? I jak te wszystkie elementy połączymy w jeden
program? Aby odpowiedzieć na te pytania, potrzebujemy więcej informacji
na temat tego, co gra powinna robić.
1
Najpierw powinniśmy określić ogólny przepływ sterowania w grze.
Podstawowy pomysł jej działania wygląda następująco.
1
8İ\WNRZQLNXUXFKDPLDJUĐ
A
2
3
znacza
Kółko o k
e
t
począ iec.
lub kon
*UDXPLHV]F]DRNUĐW
ZORVRZ\PPLHMVFXVLDWNL
=DF]\QDVLĐUR]JU\ZND
2
ąt jest
Prostok y do
n
a
używ ntacji
repreze wanej
wykony
akcji.
Start
Przygotowanie
gry
A
Pobranie
sprawdzanego
pola
A
3RZWDU]DP\SRQLİV]HF]\QQRĤFL
DİGR]DWRSLHQLDRNUĐWX
A
3\WDP\Xİ\WNRZQLNDRVSUDZG]DQHSROHVLDWNL LWG B
3RUyZQXMHP\SRGDQHSROH
]SRâRİHQLHPRNUĐWXRNUHĤODMċF
SU]\W\PF]\Xİ\WNRZQLNWUDÀâ
VSXGâRZDâF]\WHİ]DWRSLâRNUĐW
SXGâR
Romb
reprezentuje
punkt
decyzyjny.
Sprawdzenie
pola
WUDÀHQLH
B
Zaznaczenie
RNUĐWXMDNR
WUDÀRQHJR
zatopienie
Zaznaczenie
RNUĐWXMDNR
zatopionego
.RQLHFJU\
:\ĤZLHWODP\NODV\ÀNDFMĐ
Xİ\WNRZQLNDRNUHĤORQċQD
SRGVWDZLHOLF]E\SUyE
Teraz mamy już ogólne pojęcie o tym, co
program musi robić. Później określimy kilka
dodatkowych szczegółów dotyczących czynności
wykonywanych w poszczególnych krokach.
3
:\ĤZLHWOHQLH
wyniku/rankingu
Xİ\WNRZQLND
Koniec
gry
1RLSURV]Ċ3UDZG]LZ\
VFKHPDWEORNRZ\
jesteś tutaj
83
Projektowanie gry
Kilka dodatkowych szczegółów
Po przeanalizowaniu ogólnego projektu już całkiem dobrze
wiemy, jak gra ma działać, i dysponujemy profesjonalnym
schematem blokowym, jednak zanim zajmiemy się pisaniem
kodu, warto ustalić jeszcze kilka szczegółów.
1
.RPyUNLWHVċMHG\QLHOLF]EDPLFDâNRZLW\PL
QSRNUĐWSRND]DQ\QDSRQLİV]\PU\VXQNX
]DMPXMHNRPyUNLL
Reprezentacja okrętu
Zacznijmy od określenia sposobu reprezentacji statku
na siatce. Musisz pamiętać, że siatka ta jest, powiedzmy
to wyraźnie, wirtualna. Innymi słowy, nie istnieje nigdzie
w programie. Tak długo jak zarówno gra, jak i użytkownik
wiedzą, że okręt jest ukryty w trzech sąsiadujących komórkach
siatki (przy czym komórek jest siedem, a pierwsza z nich ma
numer 0), sama siatka nie musi być reprezentowana w kodzie.
Możesz mieć ochotę, by utworzyć coś, co pozwoli zapisać te
siedem komórek, a następnie oznaczyć trzy z nich jako zajęte
przez okręt. Jednak nie jest to konieczne. Musimy jedynie
wiedzieć, jakie komórki siatki zajmuje okręt, np. 1., 2. i 3.
Gra po uruchomieniu WZRU]\RNUĐW
LXPLHV]F]DJRZWU]HFKVċVLDGXMċF\FK]H
VREċNRPyUNDFKZLHUV]DVNâDGDMċFHJRVLĐ
ZVXPLH]VLHGPLXNRPyUHN
0
1
2
2
3
4
5
6
=DF]\QDVLĐUR]JU\ZND3URVLP\Xİ\WNRZQLND
RZ\W\SRZDQLHNRPyUNL
A
Pobieranie danych od użytkownika
A co z pobieraniem danych od użytkownika? Można w tym
celu użyć funkcji prompt. Zawsze wtedy, gdy będziemy musieli
pobrać nową komórkę do sprawdzenia, użyjemy tej funkcji, by
wyświetlić komunikat i pobrać liczbę z zakresu od 0 do 6.
Wyświetlanie wyników
A wyniki? Na razie do prezentacji wyników gry będziemy
korzystali z funkcji alert. Jest to nieco toporne rozwiązanie,
ale działa. (W pełnej wersji gry zamieszczonej dalej w tej
książce będziemy modyfikowali samą stronę WWW, jednak
zanim do tego dojdziemy, czeka nas jeszcze długa droga).
84
Rozdział 2.
B
3
6SUDZG]DP\F]\Xİ\WNRZQLNRZLXGDâR
VLĐWUDÀþZNWyUċNROZLHN]NRPyUHN
]DMPRZDQ\FKSU]H]RNUĐW/LF]EĐWUDÀHĚ
QDOHİ\]DSDPLĐW\ZDþZMDNLHMĤ]PLHQQHM
*UDVLĐNRĚF]\, kiedy wszystkie trzy
NRPyUNL]DMPRZDQHSU]H]RNUĐW]RVWDQċ
WUDÀRQHZDUWRĤþ]PLHQQHMSU]HFKRZXMċFHM
OLF]EĐWUDÀHĚRVLċJQLH:W\PPRPHQFLH
Z\ĤZLHWODP\Xİ\WNRZQLNRZLLQIRUPDFMĐLOH
SUyESRWU]HERZDâE\]DWRSLþRNUĐW
Przykładowa interakcja z grą
Piszemy prawdziwy kod
Analiza pseudokodu
Musimy mieć jakiś sposób na planowanie i zapisywanie naszego kodu. Zaczniemy od napisania pseudokodu.
Pseudokod to coś pomiędzy normalnym kodem w języku JavaScript i zwyczajnymi zdaniami opisującymi
działanie programu. Jak się zaraz przekonasz, pomoże przemyśleć i zaplanować poszczególne czynności,
które program ma wykonywać, bez konieczności pisania faktycznego kodu.
Poniższy pseudokod reprezentujący prostą wersję gry w okręty składa się z sekcji opisującej potrzebne
zmienne oraz sekcji opisującej logikę programu. Zmienne informują o tym, jakie informacje musimy śledzić
i przechowywać w kodzie; natomiast logika opisuje, jaki kod musimy wiernie zaimplementować, by powstała
działająca gra.
ZADEKLARUJ trzy zmienneGRSU]HFKRZ\ZDQLDSRâRĤHQLDNDĤGHM]WU]HFKNRPyUHNRNUĆWX1D]ZLMMH
location1, location2 oraz location3
ZADEKLARUJ zmiennüSU]HFKRZXMüFüNRPyUNĆZVND]DQüSU]H]XĤ\WNRZQLNDGRVSUDZG]HQLD1D]ZLMMüguess
ZADEKLARUJ zmiennü SU]HFKRZXMüFüOLF]EĆWUDILHĎ1D]ZLMMühitsLSRF]üWNRZRprzypiszZDUWRĘþ0
Zmienne,
których będziemy
potrzebowali.
ZADEKLARUJ zmiennüSU]HFKRZXMüFüOLF]EĆSUyE1D]ZLMMüguessesLSRF]üWNRZRprzypiszZDUWRĘþ0
ZADEKLARUJ zmiennüSU]HFKRZXMüFüLQIRUPDFMĆF]\RNUĆW]RVWDâ]DWRSLRQ\F]\QLH1D]ZLMMüisSunk
i przypiszZDUWRĘþSRF]üWNRZüfalse
3Ą7/$GRSyNiRNUĆWnieMeVWzDWRSiRn\
A to jest logika gry.
POBIERZNRPyUNĆGRVSUDZG]HQLD
PORÓWNAJSRGDQüZDUWRĘþ]GRSXV]F]DOQ\PLSUDZLGâRZ\PLZDUWRĘFLDPL
IFZDUWRĘþSRGDQDSU]H]XĤ\WNRZQLNDQLHMHVWSUDZLGâRZüOLF]Eü
32352ĖXĤ\WNRZQLNDRSRGDQLHSUDZLGâRZHMOLF]E\
:35=(&,:1<05$=,(
DODAJMHGHQGRZDUWRĘFL]PLHQQHMguesses
IFSRâRĤHQLHSRGDQHSU]H]XĤ\WNRZQLNDRGSRZLDGDMHGQHM]NRPyUHNRNUĆWX
DODAJMHGHQGROLF]E\WUDILHĎ
IFOLF]EDWUDILHĎZ\QRVL
To nie jest JavaScript, ale
już zapewne widzisz, w jaki
sposób zacząć implementować
tę logikę w kodzie.
867$:ZDUWRĘþ]PLHQQHMisSunk na true
:<Ė:,(7/NRPXQLNDW´=DWRSLâHĘPyMRNUĆWµ
.21,(&,)
.21,(&,)
.21,(&:35=(&,:1<05$=,(
.21,(&3Ą7/,
:<Ė:,(7/VWDW\VW\NLXĤ\WNRZQLND
Zauważ, że używamy odpowiednich
wcięć, żeby ułatwić Ci analizę
pseudokodu. Dokładnie w taki
sam sposób będziemy formatowali
faktyczny kod gry.
jesteś tutaj
85
Ćwiczenie okrętowe
Zaostrz ołówek
=DáyĪĪHQDV]DZLUWXDOQDVLDWNDZ\JOąGDZQDVWĊSXMąF\VSRVyE
0
1
2
3
4
5
6
$NRPyUNL]DMPRZDQHSU]H]RNUĊWVą]DSLVDQHZWU]HFK]PLHQQ\FKORNDOQ\FK
location1 = 3;
location2 = 4;
location3 = 5;
3U]\MPLMĪHXĪ\WNRZQLNZSLVDáQDVWĊSXMąFąVHNZHQFMĊVSUDZG]DQ\FKNRPyUHN
1, 4, 2, 3, 5
Jeśli potrzebujesz
wskazówki, zajrzyj
do naszej odpowiedzi
podanej pod koniec
tego rozdziału.
$WHUD]RSLHUDMąFVLĊQDSVHXGRNRG]LHSU]HGVWDZLRQ\PQDSRSU]HGQLHMVWURQLHSU]HMGĨ
NDĪG\NURNNRGXL]REDF]MDNEĊG]LHZ\NRQ\ZDQ\ZSU]\SDGNXSRGDQLDSRZ\ĪV]HM
VHNZHQFMLGDQ\FKZHMĞFLRZ\FK6ZRMHQRWDWNL]DSLV]QLĪHM.LONDSRF]ąWNRZ\FKNURNyZ
]DSLVDOLĞP\]D&LHELH-HĞOLSLHUZV]\UD]DQDOL]XMHV]LSUyEXMHV]SU]HĞOHG]LüG]LDáDQLH
SVHXGRNRGXQLHVSLHV]VLĊLVSUyEXMJRGREU]H]UR]XPLHü
location1
location2
3
4
5
3
4
5
Pierwszy wiersz pokazuje
początkowe wartości zmiennych,
zanim użytkownik podał pierwszą
sprawdzaną komórkę siatki.
86
location3
Rozdział 2.
guess
1
guesses
hits
isSunk
0
0
false
1
0
false
Piszemy prawdziwy kod
A… zanim przejdziemy dalej,
nie zapomnij o kodzie HTML
Nie uda Ci się dojść daleko, jeśli zapomnisz o kodzie HTML, do którego będziesz mógł
dołączyć kod JavaScript. A zatem wpisz poniższy kod w edytorze i zapisz go w nowym pliku
o nazwie „battleship.html”. Kiedy to zrobisz, wrócimy do pisania kodu aplikacji.
jest bardzo
Kod HTML strony gry w okręty wyłącznie
a
zebn
potr
jest
ta
na
stro
prosty;
bo to
do dołączenia pliku JavaScript,
się działo.
właśnie w nim wszystko będzie
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Battleship</title>
</head>
<body>
<h1>=agraj w okrÚty!</h1>
<div id=”results”></div>
<script src=”battleship.js”></script>
</body>
</html>
Odwołanie do pliku JavaScript zost
umieszczone u dołu elementu <bod ało
kiedy zatem przeglądarka zacznie y>;
wykonywać kod JavaScript, stro
na
już będzie wyświetlona.
A to zobaczysz po wyświetleniu
strony w przeglądarce. Musimy ie
napisać jakiś kod, który rozpoczn
i przeprowadzi rozgrywkę!
WYSIL
SZARE KOMÓRKI
Być może wyprzedzamy nieco fakty, ale spróbuj się zastanowić, jakiego rodzaju kodu można by użyć w celu
wygenerowania losowego położenia okrętu, tak by po każdym wczytaniu strony w przeglądarce było ono
inne? Jakie czynniki trzeba wziąć pod uwagę, by prawidłowo umieścić okręt? Zachęcamy, żebyś swoje
pomysły zapisał poniżej.
jesteś tutaj
87
Pisanie kodu gry
Pisanie kodu prostej wersji gry w okręty
Przedstawiony wcześniej pseudokod wykorzystamy jako wzorzec do napisania
prawdziwego kodu JavaScript. Najpierw zajmiemy się wszystkimi niezbędnymi
zmiennymi. Spójrz na pseudokod, aby sprawdzić, jakich zmiennych
potrzebujemy.
ZADEKLARUJ trzy zmienneGRSU]HFKRZ\ZDQLDSRâRĤHQLDNDĤGHM
]WU]HFKNRPyUHNRNUĆWX1D]ZLMMHlocation1, location2 oraz
location3
Potrzebujemy tych trzech
zmiennych, aby zapisać
położenie okrętu.
ZADEKLARUJ zmiennüSU]HFKRZXMüFüNRPyUNĆZVND]DQüSU]H]
XĤ\WNRZQLNDGRVSUDZG]HQLD1D]ZLMMüguess
ZADEKLARUJ zmiennü SU]HFKRZXMüFüOLF]EĆWUDILHĎ1D]ZLMMü
hitsLSRF]üWNRZRprzypiszZDUWRĘþ0
Trzy kolejne zmienne
(guess, hits oraz guesses)
są związane z próbami
zatopienia okrętu.
ZADEKLARUJ zmiennüSU]HFKRZXMüFüOLF]EĆSUyE1D]ZLMMü
guessesLSRF]üWNRZRprzypiszZDUWRĘþ0
ZADEKLARUJ zmiennüSU]HFKRZXMüFüLQIRUPDFMĆF]\RNUĆW
]RVWDâ]DWRSLRQ\F]\QLH1D]ZLMMüisSunk i przypiszZDUWRĘþ
SRF]üWNRZüfalse
I jeszcze jedna, która przechowuje
informację, czy okręt został
zatopiony, czy nie.
A teraz zapiszmy te zmienne w pliku JavaScript. Utwórz
nowy plik o nazwie „battleship.js” i wpisz w nim poniższe
deklaracje zmiennych.
var location1 = 3;
var location2 = 4;
var location3 = 5;
ętu.
określające położenie okr
Oto nasze trzy zmienne
prostu
po
i
zić
wod
roz
tym
Nie będziemy się nad
3, 4 i 5.
przypiszemy im wartości
Dalej w tym rozdziale wrócimy do
tych zmiennych i napiszemy kod, który
generuje losowe położenie okrętu, by
nieco utrudnić użytkownikowi zadanie.
Zmienna guess nie będzie miała wartości
aż do momentu, gdy użytkownik określi
komórkę, którą chce sprawdzić.
var guess;
var hits = 0;
var guesses = 0;
var isSunk = false;
Tym dwóm zmiennym
przypiszemy początkowe
wartości 0.
I w końcu zmiennej isSunk
przypisujemy wartość false.
Zmienimy ją na true, kiedy
okręt zostanie zatopiony.
88
Rozdział 2.
.RGRZDQLHQDSRZDĝQLH
Jeśli nie przypiszemy zmiennej
wartości
początkowej, JavaScript przypisz
e
jej wartość domyślną, którą jest
undefined. Wartość tę można
sobie
wyobrazić jako stwierdzenie: „tej
zmiennej
nie została jeszcze przypisana żad
na wartość”.
O undefined oraz kilku innych
dziwnych
wartościach porozmawiamy dale
j w tej książce.
Piszemy prawdziwy kod
A teraz zajmijmy się logiką gry
Poradziliśmy sobie ze zmiennymi, zatem możemy zająć się implementacją
pseudokodu opisującego logikę gry. Podzielimy go na kilka fragmentów.
Pierwszą rzeczą, którą trzeba się zająć, jest zaimplementowanie pętli: pętla
musi działać aż do zatopienia okrętu. Następnie zajmiemy się pobraniem
od użytkownika numeru komórki, którą chce sprawdzić, i weryfikacją jego
poprawności — no wiesz, musimy się upewnić, że jest to liczba z zakresu
od 0 do 6. Później zajmiemy się napisaniem logiki, która sprawdzi trafienie
okrętu i ustali, czy został zatopiony, czy nie. Na samym końcu zadbamy
o wyświetlenie użytkownikowi niewielkiego raportu z informacją, ilu prób
wymagało zatopienie okrętu.
KROK 1. Przygotować
pętlę, pobrać dane od
użytkownika i sprawdzić
ich poprawność.
3Ą7/$ GRSyNiRNUĆWnieMeVWzDWRSiRn\
POBIERZNRPyUNĆGRVSUDZG]HQLD
PORÓWNAJSRGDQüZDUWRĘþ]GRSXV]F]DOQ\PLSUDZLGâRZ\PLZDUWRĘFLDPL
IFZDUWRĘþSRGDQDSU]H]XĤ\WNRZQLNDQLHMHVWSUDZLGâRZüOLF]Eü
32352ĖXĤ\WNRZQLNDRSRGDQLHSUDZLGâRZHMOLF]E\
:35=(&,:1<05$=,(
DODAJMHGHQGRZDUWRĘFL]PLHQQHMguesses
KROK 2. Sprawdzić
wskazaną komórkę. Czy
mamy trafienie, czy pudło?
IFSRâRĤHQLHSRGDQHSU]H]XĤ\WNRZQLNDRGSRZLDGDMHGQHM]NRPyUHNRNUĆWX
DODAJMHGHQGROLF]E\WUDILHĎ
KROK 3. Sprawdzić, czy
okręt został zatopiony.
IFOLF]EDWUDILHĎZ\QRVL
867$:ZDUWRĘþ]PLHQQHMisSunk na true
:<Ė:,(7/NRPXQLNDW´=DWRSLâHĘPyMRNUĆWµ
.21,(&,)
Do zrobienia:
.21,(&,)
.21,(&:35=(&,:1<05$=,(
.21,(&3Ą7/,
:<Ė:,(7/VWDW\VW\NLXĤ\WNRZQLND
KROK 4. Wyświetlić
użytkownikowi
komunikat z wynikami.
1DSLVDþSĐWOĐLSREUDþNRPyUNĐ
GRVSUDZG]HQLD
6SUDZG]LþWUDILHQLH
6SUDZG]LþF]\RNUĐW]RVWDâ]DWRSLR
:\ĤZLHWOLþXİ\WNRZQLNRZL
VWDW\VW\NLJU\
jesteś tutaj
Q\
89
Dane wejściowe gry — pętla
Krok pierwszy: przygotowanie pętli i pobranie danych
Teraz zaczniemy przekształcać logikę gry opisaną w pseudokodzie na prawdziwy kod
JavaScript. Odwzorowanie pseudokodu na kod JavaScript nie jest bezpośrednie, zatem tu
i tam pojawią się drobne różnice. Pseudokod ma przedstawiać ideę tego, co kod powinien
robić, a teraz musimy napisać kod JavaScript, który będzie wiedział, jak to wykonać.
1DSLVDþSĐWOĐLSREUDþNRPyUNĐ
GRVSUDZG]HQLD
6SUDZG]LþWUDILHQLH
6SUDZG]LþF]\RNUĐW]RVWDâ]DWRSLRQ\
:\ĤZLHWOLþXİ\WNRZQLNRZLVWDW\VW\NLJU\
Zacznijmy od kodu, jakim już dysponujemy, a później zajmiemy się tylko tymi
fragmentami, które musimy dodać (aby zaoszczędzić kilka drzew tu i tam lub kilka
elektronów, jeśli czytasz tę książkę w wersji elektronicznej).
=$'(./$58-]PLHQQH
Zmiennymi już się
zajmowaliśmy, jednak
dołączyliśmy je tutaj,
by niczego nie pominąć.
var location1 = 3;
var location2 = 4;
var location3 = 5;
var guess;
var hits = 0;
var guesses = 0;
var isSunk = false;
3Ą7/$ GRpyNi RNrĆW
QiH MHsW zDWRpiRQy
while (isSunk == false) {
POBIERZNRPyUNĆ
GRVSUDZG]HQLD
guess = prompt(”*otöw, cel, pal! (podaj lic]bÚ ] ]akresu od 0-):”);
}
90
Rozdział 2.
óki okręt
To jest początek pętli. Dopuujemy grę;
nie jest zatopiony, kontyn
lę.
a zatem wykonujemy pęt
while używa
Pamiętaj, że instrukcja by określić, czy
wyrażenia warunkowego,
W tym przypadku
pętla ma zostać wykonana.isSunk ma
a
enn
zmi
czy
y,
zam
sprawd
zostanie zatopiony,
wartość false. Gdy okręt
e.
tru
ć
toś
war
jej
przypiszemy
Za każdym razem gdy zaczniemy wykonywać
pętlę while, poprosimy użytkownika o podanie
komórki, którą chce sprawdzić. Do tego
celu użyjemy wbudowanej funkcji prompt.
Więcej informacji na jej temat znajdziesz
na następnej stronie…
Piszemy prawdziwy kod
Jak działa funkcja prompt?
Przeglądarka udostępnia wbudowaną funkcję pozwalającą na pobieranie danych od
użytkowników; nosi ona nazwę prompt. W dużym stopniu przypomina funkcję alert —
podobnie jak ona, powoduje wyświetlenie okna dialogowego z podanym komunikatem.
Jednak oprócz tego w oknie znajduje się pole tekstowe, w którym użytkownik może coś
wpisać. Ta odpowiedź użytkownika, w formie łańcucha znaków, jest przekazywana jako
wynik wywołania funkcji prompt. Jeśli jednak użytkownik zamknie okno dialogowe lub
niczego w nim nie wpisze, zwrócona zostanie wartość null.
sujemy
W tym miejscu przypi i
wynik wywołania funkcj ss.
prompt do zmiennej gue
guess = prompt("*otöw, cel, pal! (podaj lic]bÚ ] ]akresu od 0-):");
Głównym zadaniem funkcji prompt
jest pobranie danych od użytkownika.
W zależności od urządzenia, oznacza to
zazwyczaj wyświetlenie okna dialogowego.
Do funkcji prompt przekazywany jest
łańcuch znaków, który następnie jest
wyświetlany w oknie dialogowym
jako instrukcje dla użytkownika.
"5"
użytkownika,
Kiedy funkcja pobierze już dane wejściowe od m przypadku
zwraca je do kodu, który ją wywołał. W naszy zapisywane
są
dane wejściowe, w formie łańcucha znaków,
.
guess
nej
w zmien
Może Cię kusić, by wypróbować ten nowy kod…
Obejrzyj to!
Jednak nie rób tego. Jeśli to zrobisz, przeglądarka zacznie wykonywać pętlę nieskończoną; będzie
bezustannie prosić o podanie numeru komórki do sprawdzenia, jednak nie dysponuje jeszcze
możliwością zakończenia realizacji pętli (może z wyjątkiem skorzystania z możliwości usunięcia
procesu przeglądarki przy wykorzystaniu narzędzi systemowych).
jesteś tutaj
91
Weryfikacja danych podanych przez użytkownika
Sprawdzanie komórki wskazanej przez użytkownika
Jeśli zajrzysz do pseudokodu, będziesz wiedział, że pierwszą rzeczą, jaką należy zrobić
w celu sprawdzenia komórki podanej przez użytkownika, jest upewnienie się, iż
informacje podane przez użytkownika są prawidłowe. Jeśli będą prawidłowe, następnie
trzeba sprawdzić, czy udało się trafić okręt, czy też próba użytkownika zakończyła się
pudłem. Zacznijmy zatem od sprawdzenia poprawności danych wejściowych i jeśli
okażą się prawidłowe, powiększymy o 1 wartość zmiennej guesses. Kiedy to zrobimy,
sprawdzimy, czy próba użytkownika zakończyła się trafieniem, czy pudłem.
1DSLVDþSĐWOĐLSREUDþNRPyUNĐ
GRVSUDZG]HQLD
6SUDZG]LþWUDILHQLH
6SUDZG]LþF]\RNUĐW]RVWDâ]DWRSLRQ\
:\ĤZLHWOLþXİ\WNRZQLNRZLVWDW\VW\NLJU\
// Tu sÈ umies]c]one deklaracje ]miennych
while (isSunk == false) {
guess = prompt(Ĵ*otöw, cel, pal! (podaj lic]bÚ ] ]akresu od 0-):”);
if (guess < 0 __ guess > ) {
alert(Ĵ3ros]Ú podaÊ prawidïowy numer komörki!”);
} else {
Jeśli podana liczba nie jest
prawidłowa, poinformujemy
o tym użytkownika, wyświetlając
stosowny komunikat.
guesses = guesses + 1;
}
}
Poprawność sprawdzamy,
upewniając się, że
podana liczba jest
z zakresu od 0 do 6.
chodzimy
A jeśli liczba jest poprawna, prze
nnej
zmie
do
n
jede
dalej i dodajemy
ą liczbą
guesses, by dysponować prawidłow
ika.
prób wykonanych przez użytkown
Przyjrzyjmy się nieco dokładniej warunkom sprawdzającym poprawność podanej liczby.
Już wiesz, że mamy sprawdzić, czy należy do przedziału od 0 do 6; ale jak dokładnie
działa to wyrażenie warunkowe? Przeanalizujemy je fragment po fragmencie.
Spróbujmy to przeczytać w sposób zrozumiały dla człowieka: to wyrażenie będzie prawdziwe,
jeśli wartość zmiennej guess będzie mniejsza od zera LUB jeśli wartość guess będzie większa
od sześciu. Jeśli którykolwiek z tych warunków zostanie spełniony, będzie to oznaczało, że
dane wejściowe wpisane przez użytkownika nie są prawidłowe.
if (guess < 0 __ guess > ) {
Naprawdę są to jed
ynie
dwa, połączone ze
sob
proste warunki. Pie ą,
z nich sprawdza, czyrwszy
wartość zmiennej gu
jest mniejsza od zer ess
a.
92
Rozdział 2.
A ten warunek sprawdza,
czy wartość guess jest
większa od 6.
A to jest operator alternatywy logicznej — OR — łączy
on ze sobą dwa warunki w taki sposób, że całe wyrażenie
przyjmie wartość true, jeśli którykolwiek z warunków
będzie mieć wartość true. Jeśli jednak oba warunki będą
mieć wartość false, to także całe wyrażenie przyjmie
wartość false — a to będzie oznaczać, że podana liczba
jest z zakresu od 0 do 6, czyli jest prawidłowa.
Piszemy prawdziwy kod
Nie istnieją
głupie pytania
P: Zauważyłem, że w oknie
wyświetlanym przy użyciu funkcji
prompt jest przycisk Anuluj. Jaką
wartość zwraca funkcja, jeśli
klikniemy ten przycisk?
O: Jeśli klikniemy przycisk anulujący okno
dialogowe, funkcja prompt nie zwróci
łańcucha znaków, lecz wartość null.
Pamiętasz zapewne, że wartość ta oznacza
„brak wartości”, co jest odpowiednie w tej
sytuacji, gdyż użytkownik zamknął okno
bez podawania wartości. Moglibyśmy
skorzystać z faktu, że wartość zwrócona
przez funkcję prompt wynosi null, by
sprawdzać, czy użytkownik nie kliknął
przycisku Anuluj, a jeśli tak się stało, to
moglibyśmy np. zakończyć grę. W naszym
kodzie tego nie robimy, jednak warto
zapamiętać to rozwiązanie, gdyż możemy
z niego skorzystać dalej w tej książce.
P: Powiedzieliście, że funkcja
prompt zawsze zwraca łańcuch
znaków. A zatem w jaki sposób
możemy porównywać wartość
łańcuchową, taką jak “0” lub “6”,
z liczbami, takimi jak 0 i 6?
O: Aby w takim przypadku wykonać
porównania guess < 6 oraz guess > 0,
JavaScript próbuje skonwertować łańcuch
znaków zapisany w zmiennej guess na
liczbę. O ile tylko wpiszemy samą liczbę,
np. 4, JavaScript będzie wiedział, jak
skonwertować łańcuch „4”
na liczbę 4. Zagadnieniami konwersji
danych zajmiemy się bardziej szczegółowo
dalej w tej książce.
P: A co się stanie, gdy użytkownik
wpisze w oknie coś innego niż liczbę;
np. ”jeden” lub ”koniec”?
Dwuminutowy przewodnik
po operatorach boolowskich
O: W takim przypadku JavaScript nie
będzie w stanie skonwertować łańcucha
na liczbę i wykonać porównania. A zatem
będziemy porównywali ”jeden” z 6 oraz
”koniec” z 6; oba porównania zwrócą
wartość false, co będzie oznaczało
PUDŁO. W lepszej wersji gry dane
wprowadzane przez użytkownika będziemy
sprawdzali bardziej uważnie, by upewnić się,
że użytkownik najpierw wpisał liczbę.
P: Czy operator OR zwraca true,
jeśli tylko jeden z warunków przyjmie
wartość true, czy także wtedy, jeśli
oba będą spełnione?
O: Także wtedy, jeśli oba warunki przyjmą
wartość true. Wynikiem operatora
OR (||) jest true, jeśli którykolwiek
z warunków ma wartość true, bądź jeśli
oba warunki mają wartość true. Jeśli oba
warunki mają wartość false, to także
operator LUB zwróci wartość false.
P: A jakie jest działanie operatora
koniunkcji logicznej?
O: Operator AND (&&) działa podobnie
do operatora OR, ale przyjmuje wartość
true, jeśli oba łączone warunki mają
wartość true.
P: Co to jest pętla nieskończona?
O: Doskonałe pytanie. Pętla
nieskończona to jeden z wielu problemów
prześladujących programistów. Pamiętasz
zapewne, że pętla wymaga warunku
i działa tak długo, jak długo warunek ten
będzie spełniony. Jeśli nasz kod nie robi
niczego, by zmienić wartość warunku
pętli, tak by warunek pętli kiedyś przyjął
wartość false, pętla będzie działać
w nieskończoność. Na wieki. Dopóki
nie zamkniesz przeglądarki lub ponownie
nie uruchomisz komputera.
Operatory boolowskie są używane
w wyrażeniach logicznych, które mogą
zwracać wartości true bądź false.
Istnieją dwa rodzaje takich operatorów:
operatory porównania oraz operatory
logiczne.
Operatory porównania
Operatory porównania porównują
dwie wartości. Poniżej przedstawiamy
najczęściej używane operatory
porównania:
<
oznacza „mniejszy”,
>
oznacza „większy”,
==
oznacza „równy”,
=== oznacza „dokładnie równy”,
<=
oznacza „mniejszy lub równy”,
>=
oznacza „większy lub równy”,
!=
oznacza „różny”.
Operatory logiczne
Operatory logiczne łączą dwa
wyrażenia logiczne i zwracają jedną
wartość logiczną (true lub false).
Istnieją dwa, dwuargumentowe
operatory logiczne.
||
oznacza alternatywę logiczną
(ang. OR); zwraca wartość true,
jeśli którekolwiek z łączonych
wyrażeń ma wartość true.
&& koniunkcja logiczna (ang. AND);
zwraca true wyłącznie wtedy,
kiedy oba wyrażenia mają wartość
true.
Istnieje jeszcze jeden operator logiczny
— NOT — operator negacji logicznej;
przy czym operuje on nie na dwóch,
a na jednym wyrażeniu.
!
oznacza logiczną negację (ang.
NOT); zwraca true, jeśli wartością
wyrażenia jest false.
jesteś tutaj
93
Wykrywanie trafień
Czy użytkownikowi udało się trafić?
1DSLVDþSĐWOĐLSREUDþNRPyUNĐ
GRVSUDZG]HQLD
Właśnie tutaj zaczyna się robić ciekawie — użytkownik podał numer komórki, którą
chce sprawdzić, a my musimy napisać kod, który określi, czy udało mu się trafić
okręt. Konkretnie rzecz biorąc, musimy napisać kod, który sprawdzi, czy podana
liczba odpowiada jednej z trzech komórek zajmowanych przez okręt. Jeśli tak będzie,
powiększymy o jeden wartość zmiennej hits.
6SUDZG]LþWUDILHQLH
6SUDZG]LþF]\RNUĐW]RVWDâ]DWRSLRQ\
:\ĤZLHWOLþXİ\WNRZQLNRZLVWDW\VW\NLJU\
Poniżej przedstawiliśmy pierwsze podejście do napisania kodu wykrywającego
trafienia; teraz przeanalizujemy go dokładnie.
if (guess == location1) {
hits = hits + 1;
} else
if (guess == location2) {
hits = hits + 1;
} else if (guess == location3) {
hits = hits + 1;
}
A jeśli żaden z tych warunków nie
zostanie spełniony, wartość zmiennej hits
nie zmieni się.
Jeśli podany numer komórki
odpowiada położeniu zapisanemu
w zmiennej location1, okręt został
trafiony, a zatem inkrementujemy
zmienną hits.
W przeciwnym razie, jeśli podany numer
komórki odpowiada położeniu zapisanemu
w zmiennej location2, robimy podobnie.
I w końcu, jeśli podany numer komórki odpowiada
położeniu zapisanemu w zmiennej location3,
inkrementujemy wartość zmiennej hits.
Zwróć uwagę na zastosowanie wcięć we
wszystkich blokach if/else. Dzięki nim kod
jest łatwiejszy do analizy zwłaszcza wtedy,
kiedy występuje w nim wiele zagnieżdżonych
bloków.
Zaostrz ołówek
Co myślisz na temat naszego pierwszego podejścia do napisania kodu wykrywającego trafienie okrętu?
Czy kod nie wygląda, jakby był bardziej skomplikowany, niż to konieczne? Czy nie powtarzamy kodu
w sposób wyglądający na, powiedzmy, nadmiarowy? Czy nie można go jakoś uprościć? Czy umiałbyś
uprościć ten kod, bazując na tym, co już wiesz o operatorze || (czyli operatorze alternatywy logicznej)?
Zanim przejdziesz dalej, koniecznie sprawdź naszą odpowiedź podaną na końcu tego rozdziału.
94
Rozdział 2.
Piszemy prawdziwy kod
Dodanie kodu wykrywającego trafienia
1DSLVDþSĐWOĐLSREUDþNRPyUNĐ
GRVSUDZG]HQLD
6SUDZG]LþWUDILHQLH
Poskładajmy w całość wszystkie elementy przedstawione na kilku
poprzednich stronach.
6SUDZG]LþF]\RNUĐW]RVWDâ]DWRSLRQ\
:\ĤZLHWOLþXİ\WNRZQLNRZLVWDW\VW\NLJU\
// Tu sÈ umies]c]one deklaracje ]miennych
3Ą7/$GRpyNi RNrĆW
QiH MHsW zDWRpiRQy
POBIERZNRPyUNĆ
GRVSUDZG]HQLD
while (isSunk == false) {
guess = prompt("*otöw, cel, pal! (podaj lic]bÚ ] ]akresu od 0-):");
if (guess < 0 __ guess > ) {
alert("3ros]Ú podaÊ prawidïowy numer komörki!");
} else {
DODAJMHGHQGRZDUWRĘFL
]PLHQQHMguesses
Sprawdzamy komórkę
podaną przez użytkownika.
Numer wydaje się prawidłowy, zatem
powiększamy liczbę prób o jeden.
guesses = guesses + 1;
IFSRâRĤHQLHSRGDQHSU]H]
XĤ\WNRZQLNDRGSRZLDGDMHGQHM
]NRPyUHNRNUĆWX
if (guess == location1 || guess == location2 || guess == location3) {
DODAJMHGHQGROLF]E\WUDILHĎ
}
hits = hits + 1;
Jeśli wartość zmiennej guess
odpowiada jednej z trzech
komórek zajmowanych przez okręt,
inkrementujemy licznik trafień.
}
}
Wszystkie trzy warunki połączyliśmy w jedno
wyrażenie, używając operatorów || (alternatywy
logicznej). A zatem należy je czytać w następujący
sposób: jeśli guess jest równa location1 LUB guess
jest równa location2 LUB guess jest równa location3,
inkrementujemy wartość zmiennej hits.
1DSLVDþSĐWOĐLSREUDþNRPyUNĐ
GRVSUDZG]HQLD
Hej, zatopiłeś mój okręt!
6SUDZG]LþWUDILHQLH
To już prawie koniec, już niemal w całości zaimplementowaliśmy logikę gry.
Kiedy spojrzymy na pseudokod, zobaczymy, że pozostało jeszcze sprawdzenie,
czy gracz nie uzyskał trzech trafień. Jeśli to mu się udało, zatopił nasz okręt.
A jeśli okręt został zatopiony, musimy przypisać zmiennej isSunk wartość true
i poinformować użytkownika o zniszczeniu okrętu. Napiszmy ten fragment kodu
osobno, zanim dodamy go do naszego programu.
Najpierw sprawdzamy,
czy są trzy trafienia.
if (hits == 3) {
isSunk = true;
6SUDZG]LþF]\RNUĐW]RVWDâ]DWRSLRQ\
:\ĤZLHWOLþXİ\WNRZQLNRZLVWDW\VW\NLJU\
Przyjrzyj się jeszcze raz pętli while.
Co się stanie, kiedy zmienna isSunk
przyjmie wartość true?
Jeśli tak, przypisujemy zmiennej
isSunk wartość true.
DOHUW =DWRSLïHĂPöMRNUÚW }
Dodatkowo informujemy użytkownika
o zatopieniu okrętu!
jesteś tutaj
95
Przekazywanie informacji użytkownikowi
Prezentacja informacji o zakończonej grze
Po przypisaniu zmiennej isSunk wartości true pętla while zostanie zakończona.
Program, który tak dobrze poznaliśmy, zakończy wykonywanie ciała pętli while
i zanim się zorientujemy, nasza gra zacznie zmierzać do zakończenia. Jednak wciąż
powinniśmy wyświetlić użytkownikowi jakieś informacje na temat tego, jak mu poszło.
Poniżej przedstawiliśmy kod, który będzie to robić.
1DSLVDþSĐWOĐLSREUDþNRPyUNĐ
GRVSUDZG]HQLD
6SUDZG]LþWUDILHQLH
6SUDZG]LþF]\RNUĐW]RVWDâ]DWRSLRQ\
:\ĤZLHWOLþXİ\WNRZQLNRZLVWDW\VW\NLJU\
var stats = ”3otr]ebowaïeĂ ” + guesses + ” pröb, by ]atopiÊ okrÚt, ” +
”c]yli Twoja efektywnoĂÊ wynosi: ” + (3/guesses) ”.”;
znaków, który zawiera komunikat
W tej instrukcji tworzymy łańcuch potrzebował do zatopienia okrętu oraz
prób
ilu
informujący użytkownika,
ielony
óć uwagę, że łańcuch jest podz
jaką osiągnął efektywność. Zwr umieścić w nim wartość zmiennej
h),
na fragmenty (zarówno po to, by
go było zapisać w wielu wierszac
guesses, jak i po to, by łatwiej po prostu wpisz kod w takiej postaci,
razie
Na
+.
em
ator
połączone oper
śnienia nieco później.
w jakiej go przedstawiliśmy. Wyja
A zatem dodajmy do naszego programu komunikat oraz kod do wykrywania zatopienia.
// Tu sÈ umies]c]one deklaracje ]miennych
3Ą7/$GRpyNi RNrĆW while (isSunk == false) {
QiH MHsW zDWRpiRQy
guess = prompt("*otöw, cel, pal! (podaj lic]bÚ ] ]akresu od 0-):");
POBIERZNRPyUNĆ
if (guess < 0 || guess > ) {
GRVSUDZG]HQLD
alert("3ros]Ú podaÊ prawidïowy numer komörki!");
} else {
guesses = guesses + 1;
DODAJMHGHQGRZDUWRĘFL
]PLHQQHMguesses
IFSRâRĤHQLHSRGDQHSU]H]
XĤ\WNRZQLNDRGSRZLDGDMHGQHM
]NRPyUHNRNUĆWX
DODAJMHGHQGROLF]E\WUDILHĎ
if (guess == location1 || guess == location2 || guess == location3) {
hits = hits + 1;
IFOLF]EDWUDILHĎZ\QRVL
if (hits == 3) {
isSunk = true;
867$:ZDUWRĘþ]PLHQQHM
isSunkQDWUXH
:<Ė:,(7/NRPXQLNDW
Å=DWRSLâHĘPyMRNUĆWµ
alert("=atopiïeĂ möj okrÚt!");
}
}
}
:<Ė:,(7/
VWDW\VW\NL
XĤ\WNRZQLND
96
}
var stats = "3otr]ebowaïeĂ " + guesses + " pröb, by ]atopiÊ okrÚt, " +
"c]yli Twoja efektywnoĂÊ wynosi: " + (3/guesses) ".";
alert(stats);
Rozdział 2.
Piszemy prawdziwy kod
Czy pamiętasz, jak stwierdziliśmy, że pseudokod nie jest idealny? Cóż, pominęliśmy pewne
fragmenty naszego początkowego pseudokodu: nie informujemy użytkownika o tym, czy jego
próba zakończyła się TRAFIENIEM, czy PUDŁEM. Czy potrafisz wstawić te fragmenty kodu
w odpowiednich miejscach naszego programu?
Ćwiczenie
DOHUW 75$),21< else {
}
DOHUW 38'á2 Oto kod, który masz wstawić do programu.
// Tu sÈ umies]c]one deklaracje ]miennych
while (isSunk == false) {
guess = prompt("*otöw, cel, pal! (podaj lic]bÚ ] ]akresu od 0-):");
if (guess < 0 || guess > ) {
alert("3ros]Ú podaÊ prawidïowy numer komörki!");
} else {
guesses = guesses + 1;
if (guess == location1 || guess == location2 || guess == location3) {
hits = hits + 1;
if (hits == 3) {
isSunk = true;
alert("=atopiïeĂ möj okrÚt!");
}
}
}
Tu jest naprawdę sporo nawiasów klamrowych, które
musisz dopasować. Jeśli masz z tym jakiś problem,
narysuj pionowe linie, bezpośrednio tu — w książce.
}
var stats = "3otr]ebowaïeĂ " + guesses + " pröb, by ]atopiÊ okrÚt, " +
"c]yli Twoja efektywnoĂÊ wynosi: " + (3/guesses) ".";
alert(stats);
jesteś tutaj
97
Złożenie kodu w jedną całość
To koniec implementacji logiki
Świetnie! Udało się przekształcić cały pseudokod na prawdziwy kod w języku JavaScript.
Udało się nawet znaleźć niezaimplementowane fragmenty pseudokodu i uzupełnić kod
programu. Poniżej zamieściliśmy kompletny kod programu do gry w okręty. Upewnij się,
że wpisałeś go w całości i zapisałeś w pliku o nazwie „battleship.js”:
1DSLVDþSĐWOĐLSREUDþNRPyUNĐ
GRVSUDZG]HQLD
6SUDZG]LþWUDILHQLH
6SUDZG]LþF]\RNUĐW]RVWDâ]DWRSLRQ\
:\ĤZLHWOLþXİ\WNRZQLNRZLVWDW\VW\NLJU\
var location1 = 3;
var location2 = 4;
var location3 = 5;
var guess;
var hits = 0;
var guesses = 0;
var isSunk = false;
while (isSunk == false) {
guess = prompt(”*otöw, cel, pal! (podaj lic]bÚ ] ]akresu od 0-):”);
if (guess < 0 || guess > ) {
alert(”3ros]Ú podaÊ prawidïowy numer komörki!”);
} else {
guesses = guesses + 1;
if (guess == location1 || guess == location2 || guess == location3) {
alert(”TRAFIONY!”);
hits = hits + 1;
if (hits == 3) {
isSunk = true;
alert(”=atopiïeĂ möj okrÚt!”);
}
} else {
alert(”3U'’O”);
}
}
}
var stats = ”3otr]ebowaïeĂ ” + guesses + ” pröb, by ]atopiÊ okrÚt, ” +
”c]yli Twoja efektywnoĂÊ wynosi: ” + (3/guesses) ”.”;
alert(stats);
98
Rozdział 2.
Piszemy prawdziwy kod
Chwilka na zapewnianie jakości
Sprawdzanie jakości (określane często jako „QA” od angielskich
słów Quality Assurance) jest procesem testowania jakości
oprogramowania w celu wykrycia defektów. Spróbujemy zatem
przetestować trochę nasz kod. Kiedy będziesz gotów, wyświetl
w przeglądarce stronę „battleship.html” i zacznij grać. Próbuj
robić różne rzeczy. Czy program działa bez zastrzeżeń? A może
zauważyłeś jakieś problemy? Jeśli tak, zapisz je tutaj. Wyniki
naszych testów możesz znaleźć na następnej stronie.
Uwagi z
testów
Zapisz wszystko, co nie działa tak,
jak powinno, albo to, co można by
poprawić.
Tak wygląda nasza interakcja
z grą.
Najpierw wpisujemy
nieprawidłowy numer
komórki, czyli 9.
Następnie wpisujemy
0, żeby było pudło.
A teraz trzy trafienia
pod rząd!
Po ostatnim, trzecim trafieniu
udało się nam zatopić okręt.
I spójrz, wymagało to
4 prób, a efektywność
wynosiła 0,75.
jesteś tutaj
99
Stosowanie operatorów logicznych
Logika działania gry jest dla
mnie całkiem jasna, z wyjątkiem
tych operatorów logicznych.
Czy chodzi w nich o to, żeby
można było łączyć wyrażenia
warunkowe?
Operatory logiczne pozwalają tworzyć
bardziej złożone wyrażenia logiczne
Widziałeś już na tyle dużo wyrażeń warunkowych, by sprawdzić
np., czy temperatura jest większa od 32 stopni. Albo czy zmienna
określająca, czy produkt jest w magazynie, ma wartość true. Jednak
takie testy czasami nie wystarczają. Niekiedy musimy wiedzieć nie
tylko to, czy wartość zmiennej jest większa od 32, lecz też to, czy
jednocześnie jest mniejsza od 100. Albo czy produkt jest w magazynie,
a także czy jest na wyprzedaży. Albo czy produkt jest na wyprzedaży
tylko w czwartek i to w dodatku tylko dla osób uznawanych za VIP-y.
Jak widzisz, warunki mogą być znacznie bardziej złożone.
Przeanalizujemy teraz kilka takich warunków, by lepiej zrozumieć,
jak działają.
Załóżmy, że musimy sprawdzić, czy produkt jest dostępny w magazynie (inStock) I jest
oferowany na wyprzedaży (onSale). Moglibyśmy to zrobić w następujący sposób.
if (inStock == true) {
Najpierw sprawdzamy, czy produkt
jest dostępny w magazynie.
if (onSale == true) {
A jeśli jest, to czy jest oferowany
na wyprzedaży.
// to wyglÈda na Ăwietny interes!
alert(”kupuj, kupuj, kupuj!”);
}
}
A jeśli jest, to można wykonać
jakąś akcję, np. go kupić!
Zwróć uwagę, że ten kod zostanie
wykonany wyłącznie w przypadk
u, gdy
oba warunki zostaną spełnione!
Kod ten możemy uprościć, łącząc oba warunki w jeden. W odróżnieniu od prostej wersji gry
w okręty, w której sprawdzaliśmy, czy numer komórki jest mniejszy od zera LUB większy od
sześciu, teraz chcemy wiedzieć, czy zmienna inStock ma wartość true I czy zmienna onSale
ma wartość true. A tak to można zrobić.
100
Rozdział 2.
Piszemy prawdziwy kod
Oto nasz operator I. Za jego pomocą to połączone wyrażenie przyjmie
wartość true wyłącznie wtedy, kiedy zarówno pierwszy, jak I drugi
warunek przyjmą wartość true.
if (inStock == true && onSale == true) {
Ten kod nie tylko jest bardziej zwięzły,
lecz także bardziej czytelny. Porównaj go
z kodem przedstawionym na poprzedniej
stronie.
// to wyglÈda na Ăwietny interes!
alert(”kupuj, kupuj, kupuj!”);
}
Jednak wcale nie musimy na tym poprzestawać; operatory logiczne można
łączyć na wiele różnych sposobów.
Teraz w jednym wyrażeniu warunkowym użyliśmy zarówno operatora I, jak i LUB.
W tym przypadku ma ono następujące znaczenie: jeśli produkt jest w magazynie I
jest na wyprzedaży LUB jego cena jest mniejsza od 60, możemy go kupić.
if (inStock == true && (onSale == true || price < 0)) {
// to wyglÈda na Ăwietny interes!
alert(”kupuj, kupuj, kupuj!”);
}
Zwróć uwagę na zastosowanie nawiasów
w celu połączenia warunków, dzięki czemu
najpierw zostanie określony wynik operatora
LUB, a dopiero potem będzie on użyty do
określenia wyniku wyrażenia z operatorem I.
Zaostrz ołówek
Mamy tutaj całą grupę wyrażeń logicznych, które czekają na określenie wartości. Wypełnij puste miejsca,
a następnie, nim przejdziesz do dalszej lektury, sprawdź wyniki na końcu tego rozdziału.
var temp = 81;
var keyPressed = ”N”;
var willRain = true;
var points = 142;
var humid = (temp > 80 && willRain == true);
var level;
Jaka jest wartość zmiennej humid? _______________
if (keyPressed == ”Y” ||
(points > 100 && points < 200)) {
level = 2;
var guess = ;
var is9alid = (guess >= 0 && guess <= );
Jaka jest wartość zmiennej isValid? _______________
var kB = 1287;
var tooBig = (kB > 1000);
} else {
level = 1;
}
Jaka jest wartość zmiennej level? _______________
var urgent = true;
var sendFile =
(urgent == true || tooBig == false);
Jaka jest wartość zmiennej sendFile? _______________
jesteś tutaj
101
Operatory boolowskie w ćwiczeniach
Ćwiczenie
Jakub i Wilhelm, obaj z księgowości, pracują nad nową aplikacją
do sprawdzania cen na firmowej witrynie. Obaj napisali instrukcje
warunkowe, korzystając w nich z wyrażeń warunkowych. Obaj są
pewni, że ich kod jest prawidłowy. Który z księgowych ma rację?
Czy księgowi w ogóle powinni pisać kod? Zanim przejdziesz dalej,
sprawdź swoją odpowiedź z prawidłową, którą zamieściliśmy pod
koniec rozdziału.
Jakub
if (price < 200 || price > 00) {
alert(”&ena jest ]byt niska lub ]byt wysoka! Nie kupowaÊ tego gadĝetu.”);
} else {
alert(”&ena jest w por]Èdku! 0oĝna kupowaÊ.”);
}
if (price >= 200 || price <= 00) {
alert(”&ena jest w por]Èdku! 0oĝna kupowaÊ.”);
} else {
alert(”&ena jest ]byt niska lub ]byt wysoka! Nie kupowaÊ tego gadĝetu.”);
}
102
Rozdział 2.
Wilhelm
Piszemy prawdziwy kod
Czy możemy pogadać o rozwlekłości Twojego kodu?
Nie wiemy, jak zwrócić Ci uwagę, jednak Twój sposób zapisu wyrażeń warunkowych jest
trochę rozwlekły. O co nam chodzi? Pokażemy to na poniższym przykładzie.
W wyrażeniach warunkowych
często porównujemy zmienne
logiczne z wartościami true
bądź false.
if
(inStock == true) {
...
}
A inStock jest zmienną logiczną
przechowującą wartość true lub false.
Jak się okazuje, taki zapis jest pewną przesadą. Cały sens wyrażenia warunkowego polega na
tym, że ma zwrócić wartość true lub false, ale nasza zmienna inStock już zawiera jedną
z tych wartości. A zatem nie musimy już jej z niczym porównywać — wystarczy zapisać samą
zmienną. Oznacza to, że powyższą instrukcję można zapisać w następujący sposób.
if
}
Kiedy użyjemy samej zmiennej logicznej i przyjmie
ona wartość true, całe wyrażenie warunkowe także
przyjmie wartość true, więc blok instrukcji if
zostanie wykonany.
(inStock) {
...
A jeśli zmienna inStock przyjmie
wart
wyrażenie warunkowe także przyjmie ość false,
i blok instrukcji if zostanie pominięty tę wartość
.
Choć niektórzy mogliby twierdzić, że nasza początkowa, dłuższa wersja kodu jest jednocześnie
bardziej zrozumiała, jednak w praktyce częściej można się spotkać z tą bardziej zwięzłą formą zapisu.
Sam się przekonasz, że ten krótszy zapis jest łatwiejszy do odczytu.
Ćwiczenie
Poniżej zamieściliśmy dwie instrukcje z wyrażeniami korzystającymi ze zmiennych
logicznych onSale oraz inStock. Obie te instrukcje określają wartość zmiennej
buyIt. Dla obu instrukcji rozpatrz wszystkie możliwe kombinacje wartości zmiennych
inStock oraz onSale. Która z wersji instrukcji narazi Cię na większe wydatki?
var buyIt = (inStock || onSale);
onSale
inStock
true
true
false
false
true
false
true
false
buyIt
buyIt
var buyIt = (inStock && onSale);
jesteś tutaj
103
Poprawianie wartości podanych na stałe
Mówię ci, to było takie brutalne.
Niezależnie od tego, co robiliśmy,
wyglądało to tak, jakby, jakby… oni
dokładnie wiedzieli, gdzie jest nasz
okręt!
Czy sądzisz,
że to mogło mieć coś
wspólnego z zakodowaniem
jego położenia na stałe?
Kończymy prostą wersję gry w okręty
Owszem, wciąż mamy do załatwienia jeszcze jedną sprawę: aktualnie położenie okrętu jest podane
na stałe — niezależnie od tego, jak wiele razy zagramy w naszą grę, okręt zawsze będzie zajmował
komórki numer 3, 4 i 5. Takie rozwiązanie zdaje egzamin w czasie testowania gry, żeby jednak gra była
nieco bardziej interesująca dla użytkownika, okręt musi być rozmieszczany losowo.
Cofnijmy się zatem nieco i zastanówmy, jaki jest prawidłowy sposób rozmieszczania okrętu
w jednowymiarowej siatce składającej się z siedmiu komórek. Potrzebujemy położenia startowego,
które umożliwi umieszczenie okrętu w trzech kolejnych komórkach siatki. A to oznacza, że nasze
początkowe położenie musi mieć wartości z zakresu od 0 do 4.
0
1
2
3
4
5
6
Możemy zacząć rozmieszczać okręt
w komórkach o numerach: 0, 1, 2, 3
oraz 4 i wciąż w siatce pozostanie na
tyle dużo miejsca, by umieścić okręt
w trzech kolejnych komórkach.
104
Rozdział 2.
0
1
0
1
(
/
ĭ
2
3
4
5
6
2
3
4
5
6
Natomiast rozpoczęcie rozmieszczania od
komórki 5. lub 6. nie pozwoli umieścić
w siatce całego okrętu.
Piszemy prawdziwy kod
Jak przypisywać wartości losowe?
Teraz, kiedy już wiemy, gdzie zacząć rozmieszczanie okrętu (w komórce o numerze od
zera do cztery), wystarczy użyć tej wartości do określenia dwóch kolejnych, zajmowanych
przez okręt komórek siatki.
var location1 = randomLoc;
var location2 = location1 + 1;
var location3 = location2 + 1;
Używamy losowo
wybranego położenia
oraz dwóch kolejnych
komórek siatki.
No dobrze, ale w jaki sposób możemy wygenerować liczbę losową? Z tym zadaniem
zwrócimy się do języka JavaScript oraz jego wbudowanych funkcji matematycznych,
a zwłaszcza dwóch, które pozwalają na generowanie liczb losowych. Teraz przyjrzymy się
nieco dokładniej wbudowanym funkcjom języka, natomiast bardziej ogólne informacje
o funkcjach podamy dalej w tej książce. Na razie chodzi tylko o skorzystanie z funkcji,
by zrobić to, co musimy.
Najlepszy na świecie przepis
na generowanie liczb losowych
Zaczniemy od funkcji Math.random. Wywołanie tej funkcji zwraca w wyniku losową
liczbę dziesiętną.
To jest nasza zmienna — randomLoc.
Chcemy zapisać w niej liczbę losową
z zakresu od 0 do 4.
Math.random jest standardowym
elementem języka JavaScript —
funkcją zwracającą liczbę losową.
var randomLoc = 0ath.random();
Jedyny problem polega na tym, że zwraca
ona liczby, takie jak 0,128 lub 0,830 albo
0,9, czyli liczby z zakresu od 0 do 1 (przy
czym liczba 1 nigdy nie jest zwracana).
A zatem musimy wymyślić jakiś sposób,
by uzyskać liczbę z zakresu od 0 do 4.
Potrzebujemy liczby losowej z zakresu od 0 do 4 — czyli 0, 1, 2, 3 i 4 — a nie liczby
dziesiętnej, takiej jak 0,34. Możemy zacząć od pomnożenia przez 5 liczby zwróconej
przez element Math.random, aby uzyskać wynik nieco bardziej zbliżony do tego,
czego potrzebujemy. Chodzi o coś takiego.
jesteś tutaj
105
Korzystanie z liczb losowych
Jeśli pomnożymy liczbę losową przez 5,
uzyskamy liczbę losową z zakresu od 0 do 5,
lecz mniejszą niż 5, czyli uzyskamy takie liczby
jak 0,13983, 0,4231, 2,33451 lub np. 4,999.
var randomLoc = 0ath.random() * 5;
Pamiętaj, że * oznacza mnożenie.
Już jesteśmy znacznie bliżej! Teraz pozostaje jedynie pozbyć się części
ułamkowej, by uzyskać liczbę całkowitą. Możemy skorzystać z kolejnej
wbudowanej funkcji JavaScriptu, a konkretnie z funkcji Math.floor.
Możemy skorzystać z wbudowanej funkcji
Math.floor, by przekształcić dowolną liczbę na
największą, ale mniejszą od niej, liczbę całkowitą.
var randomLoc = 0ath.floor(0ath.random() * 5);
A zatem liczba taka jak 0,12983 zostaje
przekształcona na 0, liczba 2,34 na 2, a 4,999 na 4.
Nie istnieją
głupie pytania
P: Dlaczego wtedy, gdy zależy nam
na liczbie z zakresu od 0 do 4, w kodzie
używamy liczby 5 w wyrażeniu
0ath.floor(0ath.random() * 5)?
O: Dobre pytanie. Przede wszystkim
funkcja Math.random generuje liczbę
z zakresu od 0 do 1, lecz mniejszą od 1.
Maksymalną wartością, jaką może
zwrócić Math.random, jest 0,999. Jeśli
pomnożymy ją przez 5, możemy uzyskać
co najwyżej wartość 4,999… Z kolei funkcja
Math.floor zaokrągla liczbę w dół,
a zatem 1,2 zostanie zaokrąglona do 1,
podobnie jak 1,999. Jeśli wygenerujemy
liczbę od 0 do 4,999, to uzyskamy
w efekcie liczbę z zakresu od 0 do 4.
Nie jest to jedyny sposób, w jaki można to
zrobić, a w innych językach programowania
często wykonuje się to inaczej; jednak takie
rozwiązanie jest najczęściej spotykane
w kodzie JavaScript.
106
Rozdział 2.
P: Gdybym zatem chciał uzyskać
P: Nie jestem w stanie uruchomić
O: Właśnie tak! Mnożąc wynik przez 101
O: Właśnie w takich sytuacjach może się
liczbę losową z zakresu od 0 do
100 (włącznie), musiałbym użyć
wyrażenia 0ath.floor(0ath.
random() * 101)?
i zaokrąglając go w dół, masz gwarancję,
że uzyskana liczba losowa będzie mniejsza
lub równa 100.
P: Po co te nawiasy za
0ath.random()?
O: Nawiasów używamy zawsze wtedy,
gdy „wywołujemy” funkcję. Czasami do
funkcji trzeba przekazać wartość, tak jak
np. podczas wyświetlania komunikatu,
czasami przekazywanie wartości nie jest
konieczne. Jednak zawsze wtedy, gdy
wywołujemy funkcję (niezależnie od tego,
czy jest to funkcja wbudowana, czy nie),
konieczne jest użycie pary nawiasów.
Nie przejmuj się tym na razie, wszystkie
szczegóły związane z funkcjami wyjaśnimy
w następnym rozdziale.
swojej wersji gry w okręty. Na stronie
nie widzę nic z wyjątkiem nagłówka
„Zagraj w okręty!”. Jak mogę
sprawdzić, co robię źle?
przydać konsola JavaScript. Jeśli zrobisz
jakiś błąd, taki jak pominięcie cudzysłowu
w łańcuchu znaków, JavaScript zazwyczaj
poskarży się na błąd składniowy w kodzie,
a może nawet wyświetli numer wiersza,
w którym wystąpił błąd. Jednak czasami
błędy są znacznie bardziej subtelne. Jeśli
np. przez pomyłkę napiszesz isSunk =
false zamiast isSunk == false, to
żaden błąd JavaScriptu się nie pojawi,
a mimo to program nie będzie działał
zgodnie z oczekiwaniami. W przypadku
błędów tego typu można spróbować
rejestrowania wartości zmiennych
w różnych miejscach kodu, przy użyciu
funkcji console.log, i w ten sposób
zlokalizować błąd.
Piszemy prawdziwy kod
Uwagi z
testów
Wróćmy do zapewniania jakości
Mamy już wszystko, czego potrzebujemy. Poskładajmy zatem wszystkie fragmenty kodu
(zrobiliśmy to już poniżej) i zastąpmy wcześniejszy kod określający położenie okrętu nową
wersją. Kiedy Ty też to zrobisz, wypróbuj grę kilka razy, by przekonać się, jak szybko jesteś
w stanie zatopić okręt wroga.
var
var
var
var
var
var
var
var
randomLoc = 0ath.floor(0ath.random() * 5);
location1 = randomLoc;
location2 = location1 + 1;
location3 = location2 + 1;
guess;
hits = 0;
guesses = 0;
isSunk = false;
Dalej — zastąp stare deklaracje
zmiennych location tymi nowymi
instrukcjami.
while (isSunk == false) {
guess = prompt(”*otöw, cel, pal! (podaj lic]bÚ ] ]akresu od 0-):”);
if (guess < 0 || guess > ) {
// tu naleĝy umieĂciÊ res]tÚ kodu....
Poniżej przedstawiliśmy przebieg jednej z naszych sesji
testowych. Teraz, kiedy okręt jest umieszczany w losowym
miejscu, gra stała się nieco bardziej interesująca. Uważamy,
że udało się nam uzyskać całkiem dobry wynik…
Udało się
nam trafić za
pierwszym
razem.
Za drugim
razem
spudłowaliśmy.
Jednak później
uzyskaliśmy dwa
trafienia z rzędu.
Za ostatnim razem
zatopiliśmy okręt!
jesteś tutaj
107
Ćwiczenia w znajdowaniu błędów
Chwileczkę, zauważyliśmy coś, co nie wygląda dobrze. Wskazówka: kiedy wpiszemy 0, 1, 1, 1,
wszystko się psuje! Czy potrafisz powiedzieć, co się dzieje?
Ćwiczenie
Oto nasze próby...
Za pierwszym
razem
spudłowaliśmy.
Za drugim razem
udało się nam
znaleźć okręt.
A później zaczęliśmy
wpisywać tę samą
cyfrę i za każdym
razem uzyskiwaliśmy
trafienie!
Po trzecim trafieniu
udało się nam zatopić
okręt! Ale tu coś nie
jest w porządku! Nie
powinniśmy zatopić
okrętu, trafiając trzy razy
w to samo miejsce.
Wpisaliśmy 0, 1, 1, 1,
a okręt znajduje się
w komórkach 1., 2. i 3.
Znaleźl
iśmy bł
ąd!
Cóż za nerwy!
Wszystko wisi na włosku!
Czy uda się nam znaleźć błąd?
Czy będziemy umieli go poprawić?
Oczekuj w napięciu na znacznie lepszą,
poprawioną wersję gry w okręty
zmieszczoną dalej w tej książce.
Czy masz jakiś pomysł na to,
jak poprawić ten błąd.
108
Rozdział 2.
Uwagi z
testów
Wpisyw
anie
tej sam
ej
cyfry bę
dącej
trafieni
em okrę
tu
pozwala
go
zatopić,
choć nie
powinno
.
Piszemy prawdziwy kod
Gratulujemy pierwszego prawdziwego programu w języku
JavaScript i mamy dwa słowa o wielokrotnym używaniu kodu
Prawdopodobnie zwróciłeś uwagę, że skorzystaliśmy już z kilku wbudowanych funkcji języka
JavaScript, takich jak alert, prompt, console.log oraz Math.random. Funkcje te przy bardzo małym
nakładzie pracy pozwalają na wyświetlanie okien dialogowych, zapisywanie wyników w oknie konsoli
i generowanie liczb losowych, a wszystko w niemal magiczny sposób. Jednak te wbudowane funkcję
są jedynie przygotowanym kodem, który ktoś dla nas napisał, a ich siła polega na tym, że można
ich wielokrotnie używać, wykonując odpowiednie wywołanie tam, gdzie chcemy i kiedy jest to
potrzebne.
Możemy podać wiele informacji na temat wbudowanych funkcji JavaScriptu — jak
należy z nich korzystać, jakie wartości należy przekazywać w ich wywołaniach itd.
— zaczniesz je powoli poznawać w następnym rozdziale, poświęconym tworzeniu
własnych funkcji.
Jednak zanim tam dotrzesz, masz jeszcze do przejrzenia „Celne spostrzeżenia”
i rozwiązania wszystkich ćwiczeń. A tak… i spokojny sen, żeby wszystkie te informacje
ułożyły się w Twoim mózgu.
CELNE SPOSTRZEŻENIA
Q
Logikę działania programu pisanego w języku JavaScript
można przedstawić w formie schematu blokowego,
pokazującego punkty decyzyjne oraz akcje.
Q
Operatory logiczne łączą dwie wartości logiczne.
Przykładowo true || false zwraca true, a true &&
false zwraca false.
Q
Zanim zaczniesz pisać program, warto naszkicować
sposób jego działania, zapisując go w formie pseudokodu.
Q
Q
Pseudokod to przybliżenie wszystkich czynności, które
musi realizować prawdziwy kod.
Liczbę losową z zakresu od 0 do 1 (włącznie z 0, lecz
z wyłączeniem 1) można wygenerować przy użyciu
funkcji 0ath.random.
Q
Funkcja 0ath.floor zaokrągla przekazaną liczbę
dziesiętną w dół do najbliższej liczby całkowitej.
Q
Pamiętaj, by używając funkcji 0ath.floor oraz
0ath.random, zawsze zapisywać 0ath wielką literą,
a nie małą.
Q
Funkcja prompt wyświetla okno dialogowe
z komunikatem oraz miejscem, w którym użytkownik
może wpisać jakąś wartość.
Q
W tym rozdziale używaliśmy funkcji prompt, by pobierać
dane od użytkownika, oraz funkcji alert, by wyświetlać
w przeglądarce wyniki gry w okręty.
Q
Q
Istnieją dwa rodzaje operatorów boolowskich: operatory
porównania oraz operatory logiczne. Operatory te
zastosowane w wyrażeniach zawsze zwracają wartości
logiczne, czyli true lub false.
Operatory porównania porównują dwie wartości
i zwracają wartości logiczne true lub false.
Przykładowo operatora porównania < („mniejszy”)
możemy używać w następujący sposób: 3 < 6.
To wyrażenie zwróci wartość true.
jesteś tutaj
109
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
=DáyĪĪHQDV]DZLUWXDOQDVLDWNDZ\JOąGDZQDVWĊSXMąF\VSRVyE
0
1
2
3
4
5
6
$NRPyUNL]DMPRZDQHSU]H]RNUĊWVą]DSLVDQHZWU]HFK]PLHQQ\FKORNDOQ\FK
location1 = 3;
location2 = 4;
location3 = 5;
3U]\MPLMĪHXĪ\WNRZQLNZSLVDáQDVWĊSXMąFąVHNZHQFMĊVSUDZG]DQ\FKNRPyUHN
1, 4, 2, 3, 5
$WHUD]RSLHUDMąFVLĊQDSVHXGRNRG]LHSU]HGVWDZLRQ\PQDSRSU]HGQLHM
VWURQLHSU]HMGĨNDĪG\NURNNRGXL]REDF]MDNEĊG]LHZ\NRQ\ZDQ\ZSU]\SDGNX
SRGDQLDSRZ\ĪV]HMVHNZHQFMLGDQ\FKZHMĞFLRZ\FK6ZRMHQRWDWNL]DSLV]QLĪHM
.LONDSRF]ąWNRZ\FKNURNyZ]DSLVDOLĞP\]D&LHELH2WRQDV]HUR]ZLą]DQLH
location1
110
location2
location 3
3
4
5
3
4
5
3
4
3
guesses
hits
isSunk
0
0
false
1
1
0
false
5
4
2
1
false
4
5
2
3
1
false
3
4
5
3
4
2
false
3
4
5
5
5
3
true
Rozdział 2.
guess
Piszemy prawdziwy kod
Ćwiczenie
Rozwiązanie
Poniżej zamieściliśmy dwie instrukcje z wyrażeniami korzystającymi ze zmiennych
logicznych onSale oraz inStock. Obie te instrukcje określają wartość zmiennej
buyIt. Dla obu instrukcji rozpatrz wszystkie możliwe kombinacje wartości zmiennych
inStock oraz onSale. Która z wersji instrukcji narazi Cię na większe wydatki?
var buyIt = (inStock || onSale);
onSale
inStock
buyIt
buyIt
true
true
false
false
true
false
true
false
true
true
true
false
true
false
false
false
var buyIt = (inStock && onSale);
Zaostrz ołówek
Rozwiązanie
Mamy tutaj całą grupę wyrażeń logicznych, które czekają na określenie wartości. Wypełnij puste miejsca.
Oto nasze rozwiązanie:
var temp = 81;
var keyPressed = ”N”;
var willRain = true;
var points = 142;
var humid = (temp > 80 && willRain == true);
var level;
true
Jaka jest wartość zmiennej humid? _______________
if (keyPressed == ”Y” ||
(points > 100 && points < 200)) {
level = 2;
var guess = ;
var isValid = (guess >= 0 && guess <= );
true
Jaka jest wartość zmiennej isValid? _______________
var kB = 1287;
var tooBig = (kB > 1000);
} else {
level = 1;
}
2
Jaka jest wartość zmiennej level? _______________
var urgent = true;
var sendFile =
(urgent == true || tooBig == false);
true
Jaka jest wartość zmiennej sendFile? _______________
jesteś tutaj
111
Rozwiązanie ćwiczenia
Ćwiczenie
Rozwiązanie
Jakub i Wilhelm, obaj z księgowości, pracują nad nową aplikacją do sprawdzania cen na firmowej
witrynie. Obaj napisali instrukcje warunkowe, korzystając w nich z wyrażeń warunkowych. Obaj są
pewni, że ich kod jest prawidłowy. Który z księgowych ma rację? Czy księgowi w ogóle powinni pisać
kod? Oto nasze rozwiązanie:
if (price < 200 || price > 00) {
alert(”&ena jest ]byt niska lub ]byt wysoka! Nie kupowaÊ tego gadĝetu.”);
} else {
alert(”&ena jest w por]Èdku! 0oĝna kupowaÊ.”);
Jakub
}
if (price >= 200 || price <= 00) {
alert(”&ena jest w por]Èdku! 0oĝna kupowaÊ.”);
} else {
alert(”&ena jest ]byt niska lub ]byt wysoka! Nie kupowaÊ tego gadĝetu.”);
}
Wilhelm
Jakub jest lepszym programistą (i zapewne także lepszym
księgowym). Rozwiązanie Jakuba działa, a Wilhelma nie. Aby
przekonać się, dlaczego tak jest, wypróbujmy trzy różne wartości
zmiennej price (zbyt dużą, zbyt małą i odpowiednią) i dla każdej
z nich przekonajmy się, jakie wartości przyjmują wyrażenia
zastosowane przez obu księgowych.
price
Jakub
Wilhelm
100
true
true
komunikat: Nie kupuj!
komunikat: Kupuj!
true
true
komunikat: Nie kupuj!
komunikat: Kupuj!
false
true
komunikat: Kupuj!
komunikat: Kupuj!
700
400
Jeśli wartość zmiennej price wyniesie
100, to 100 jest mniejsze od 200,
zatem wyrażenie Jakuba jest prawdziwe
(pamiętajmy, że w razie użycia operatora
LUB wystarczy, by jedno z łączonych
wyrażeń było prawdziwe, żeby wartość
całego wyrażenia także była prawdą),
a zatem wyświetlamy komunikat,
by NIE kupować.
Jednak wyrażenie Wilhelma także jest
prawdziwe, a to ze względu na użycie
warunku price <= 600! A zatem wartością
całego wyrażenia Wilhelma także jest true,
co spowoduje wyświetlenie użytkownikowi
komunikatu zachęcającego do kupna, choć
cena jest zbyt niska.
Okazuje się, że wyrażenie Wilhelma zawsze przyjmuje
wartość true, zupełnie niezależnie od wartości zmiennej
price, a zatem jego kod zawsze będzie zachęcał do
kupowania. Wilhelm powinien ograniczyć się do przeglądania
ksiąg rachunkowych.
112
Rozdział 2.
Piszemy prawdziwy kod
Czy pamiętasz, jak stwierdziliśmy, że pseudokod nie jest idealny? Cóż, pominęliśmy pewne
fragmenty naszego początkowego pseudokodu: nie informujemy użytkownika o tym, czy jego
próba zakończyła się TRAFIENIEM, czy PUDŁEM. Czy potrafisz wstawić te fragmenty kodu
w odpowiednich miejscach naszego programu?
Ćwiczenie
Rozwiązanie
// Tu sÈ umies]c]one deklaracje ]miennych
while (isSunk == false) {
guess = prompt("*otöw, cel, pal! (podaj lic]bÚ ] ]akresu od 0-):");
if (guess < 0 || guess > ) {
alert("Pros]Ú podaÊ prawidïowy numer komörki!");
} else {
guesses = guesses + 1;
if (guess == location1 || guess == location2 || guess == location3) {
DOHUW 75$),21< hits = hits + 1;
if (hits == 3) {
isSunk = true;
alert("=atopiïeĂ möj okrÚt!");
}
else {
}
DOHUW 38'á2 }
}
}
var stats = "Potr]ebowaïeĂ " + guesses + " pröb, by ]atopiÊ okrÚt, " +
"c]yli Twoja efektywnoĂÊ wynosi: " + (3/guesses) ".";
alert(stats);
jesteś tutaj
113
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Co myślisz na temat naszego pierwszego podejścia do napisania kodu wykrywającego trafienie
okrętu? Czy kod nie wygląda, jakby był bardziej skomplikowany, niż to konieczne? Czy nie
powtarzamy kodu w sposób wyglądający na, powiedzmy, nadmiarowy? Czy nie można go
jakoś uprościć? Czy umiałbyś uprościć ten kod, bazując na tym, co już wiesz o operatorze ||
(czyli operatorze alternatywy logicznej)? A oto rozwiązanie.
Wielokrotnie używamy
tego samego kodu.
if (guess == location1) {
hits = hits + 1;
} else if (guess == location2) {
hits = hits + 1;
} else if (guess == location3) {
hits = hits + 1;
Gdybyśmy kiedyś musieli zmienić sposób
aktualizowania trafień, musielibyśmy
wprowadzać modyfikacje w trzech
miejscach. Zmiany tego typu często są
przyczyną błędów i problemów w kodzie.
}
Zresztą nie tylko o to chodzi. Ten kod jest po prostu
znacznie bardziej złożony, niż to konieczne. Dodatkowo
jest także trudniejszy do analizy, dłuższy i wymaga więcej
wpisywania.
Jednak dzięki zastosowaniu logicznego operatora LUB
możemy połączyć wszystkie testy w taki sposób, że jeśli
wskazana przez użytkownika komórka odpowiada wartości
którejkolwiek ze zmiennych, location1, location2 lub location3,
to całe wyrażenie przyjmie wartość true i liczba trafień
zostanie zaktualizowana.
if (guess == location1 || guess == location2 || guess == location3) {
hits = hits + 1;
}
Czy to wyrażenie nie wygląda znacznie
prościej? Nie wspominamy w ogóle o tym,
że jest łatwiejsze do zrozumienia.
A jeśli kiedykolwiek będziemy musieli zmienić
sposób aktualizowania liczby trafień, wystarczy to
zrobić w jednym miejscu, co w znacznie mniejszym
stopniu naraża nas na błędy.
114
Rozdział 2.
3. Przedstawienie funkcji
Stawiamy na funkcjonalność
Przygotuj się na użycie pierwszej ze swoich supermocy. Zdobyłeś
Więcej informacji
na ten temat
zamieścimy dalej
w tej książce.
już nieco umiejętności programistycznych; teraz nadszedł czas, aby rozwinąć
je jeszcze bardziej przy użyciu funkcji. Funkcje zapewniają możliwość pisania
kodu, który można stosować we wszelkich możliwych okolicznościach, kodu
używanego wielokrotnie, którym można znacznie łatwiej zarządzać i w końcu
który można wyodrębnić, nadać łatwą do zapamiętania nazwę, zapomnieć
o całej jego złożoności i zająć się innymi ważnymi problemami. Przekonasz się,
że funkcje to nie tylko droga, która zmieni Cię z autora skryptów w programistę.
Są one kluczowym czynnikiem określającym styl programowania w języku
JavaScript. W tym rozdziale zaczniemy od podstaw: poznasz mechanikę funkcji
i tajniki ich działania, a dalej w tej książce będziesz stopniowo powiększać swoją
wiedzę i umiejętności ich stosowania. A zatem, zacznij budować solidne podstawy
znajomości JavaScriptu i zrób to już teraz.
to jest nowy rozdział
115
Analiza kodu
Zaostrz ołówek
Przeprowadź krótką analizę poniższego kodu. Jak działa? Wybierz dowolne odpowiedzi podane poniżej
lub zapisz swoją własną analizę.
var dogName = ”Burek”;
var dogWeight = 23;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
dogName = ”Mops”;
dogWeight = 13;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
dogName = ”Reksio”;
dogWeight = 53;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
dogName = ”Saba”;
dogWeight = 17;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
______________________________________________
$7HQNRGZ\GDMHVLĊEDUG]R
QDGPLDURZ\
%*G\E\ĞP\FKFLHOL]PLHQLü
JHQHURZDQHZ\QLNLOXEGRGDüLQQH
LQIRUPDFMHRSVDFKZ\PDJDáRE\WR
VSRURSUDF\
116
Rozdział 3.
&:SLV\ZDQLHJRE\áRE\PĊF]ąFH
'7RQLHMHVWQDMEDUG]LHMF]\WHOQ\NRG
MDNLZLG]LDáHPZĪ\FLX
E. ____________________________
__________________
Przedstawienie funkcji
Co z tym kodem było nie tak?
Zauważyłeś na pewno, że fragmenty tego kodu wielokrotnie się powtarzały.
Co w tym złego? Na pierwszy rzut oka, nic. W końcu kod działa jak należy,
prawda? Przyjrzyjmy się mu nieco dokładniej.
var dogName = ”Burek”;
var dogWeight = 23;
if (dogWeight > 20) {
W tych fragmentach porównujemy wagę
psa do 20, a jeśli okaże się większa,
wyświetlamy „HAU HAU” zapisane
wielkimi literami. Jeśli waga psa będzie
mniejsza lub równa 20, wyświetlamy
„hau hau” zapisane małymi literami.
console.log(dogName + ” szczeka HAU HAU”);
} else {
console.log(dogName + ” szczeka hau hau”);
}
...
dogName = ”Saba”;
dogWeight = 17;
A ten fragment kodu robi… O kurcze!
Robi DOKŁADNIE to samo itd. kilka
razy w całym przykładzie.
if (dogWeight > 20) {
console.log(dogName + ” szczeka HAU HAU”);
} else {
console.log(dogName + ” szczeka hau hau”);
}
Oczywiście ten przykład wygląda dosyć niewinnie, jednak i tak wpisywanie
go byłoby męczące, odczyt i analiza kłopotliwe, a wprowadzanie zmian,
gdyby w przyszłości takie się pojawiły, problematyczne. Wraz z nabieraniem
doświadczenia w programowaniu przekonasz się, że zwłaszcza ten ostatni
punkt jest szczególnie prawdziwy — każdy kod z czasem się zmienia,
a przedstawiony powyżej jest prawdziwym koszmarem czekającym w ukryciu,
gdyż cały składa się z wielokrotnie powtórzonego tego samego fragmentu
logiki; gdyby trzeba było ją zmienić, musielibyśmy to zrobić w kilku miejscach.
A im większy byłby program, tym więcej takich zmian należałoby w nim
wprowadzić, a im więcej zmian, tym więcej okazji do popełnienia błędów.
Potrzebujemy zatem jakiegoś sposobu, by cały powtarzający się kod umieścić
w jednym miejscu i wielokrotnie go używać, kiedy będzie potrzebny.
dogName = ”Reksio”;
dogWeight = 53;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
dogName = ”Saba”;
dogWeight = 17;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
WYSIL
SZARE KOMÓRKI
W jaki sposób możemy poprawić ten kod? Poświęć
kilka minut, by pomyśleć nad różnymi możliwościami.
Czy język JavaScript udostępnia jakieś sposoby, które
mogą w tym pomóc?
jesteś tutaj
117
Gdybym tylko mogła znaleźć
sposób wielokrotnego stosowania kodu, tak
by za każdym razem, gdy będę potrzebować, po
prostu go użyć, a nie od nowa wpisywać. I jeszcze
jakiś sposób, by nadać mu odpowiednią nazwę,
którą mogłabym łatwo zapamiętać. No i jeszcze
sposób, dzięki któremu wszystkie zmiany mogłabym
wprowadzać tylko w jednym miejscu, a nie w wielu.
To byłoby cudowne. Jednak wiem, że to tylko
moje fantazje…
118
Rozdział 3.
Przedstawienie funkcji
Swoją drogą, czy wspominaliśmy
już o FUNKCJACH?
Przedstawiamy funkcje. Funkcje w języku JavaScript pozwalają wyodrębnić fragment
kodu, nadać mu nazwę, a następnie odwoływać się do niego wszędzie tam, gdzie jest
to potrzebne. Wygląda na to, że funkcje są spełnieniem naszych potrzeb.
Załóżmy, że piszemy kod, w którym będzie sporo „szczekania”. Jeśli w grę wchodzi
duży pies, ma szczekać „HAU HAU”. Z kolei małe psy szczekają „hau hau”.
Ta podstawowa funkcjonalność szczekania będzie używana wiele razy. Napiszmy
zatem funkcję bark, z której będziemy mogli wielokrotnie skorzystać.
cji
Definicja funkę do
rozpoczyna si ego
w
słowa kluczo
.
on
ti
nc
fu
Następnie
podawana jest
nazwa funkcji,
taka jak bark.
Kiedy już dojdzie do użycia funkcji,
przekażemy do niej dwie informacje:
imię psa oraz jego wagę.
To są tzw. parametry. Umieszczamy je
w nawiasach, za nazwą funkcji.
function bark(name, weight) {
}
Później napiszemy jakiś kod, który będzie
wykonywany podczas wywoływania funkcji.
To jest tzw. ciało funkcji, czyli
wszystko to, co jest zapisane
pomiędzy nawiasami { oraz }.
Teraz musimy napisać kod tej funkcji; w naszym przypadku kod będzie sprawdzał wagę
psa i wyświetlał komunikat, zapisany literami w odpowiedniej wielkości.
imy
Najpierw mus ę psa,
sprawdzić wag
a następnie…
Zwróć uwagę, że nazwy
zmiennych użytych w tym
kodzie odpowiadają
parametrom funkcji.
function bark(name, weight) {
if (weight > 20) {
console.log(name + " szczeka HAU HAU");
} else {
console.log(name + " szczeka hau hau");
}
}
...wyświetlić jego imię i odpowiedni
komunikat: HAU HAU lub hau hau.
sz
Teraz dysponujesz już funkcją, której może działa.
to
jak
zysz,
zobac
z
Zara
e.
kodzi
w
używać
jesteś tutaj
119
Jak działa funkcja
No dobrze, ale jak to właściwie działa?
Najpierw przepiszemy istniejący kod z użyciem funkcji bark.
function bark(name, weight) {
if (weight > 20) {
O… To jest niezłe…
Cała logika kodu znalazła
się w jednym miejscu.
console.log(name + ” szczeka HAU HAU”);
} else {
console.log(name + ” szczeka hau hau”);
}
}
bark(”Burek”, 23);
bark(”Mops”, 13);
nie do
Teraz cały kod sprowadza się jedy ych
któr
do
,
bark
cji
funk
ołań
kilku wyw
psów.
przekazywane są imiona i waga
bark(”Reksio”, 53);
bark(”Saba”, 17);
O rany, teraz kod
jest taki prosty!
Nieźle… Teraz kod jest naprawdę znacznie krótszy — będzie także zapewne bardziej czytelny dla
Twojego współpracownika, który — być może — będzie musiał go przeanalizować i wprowadzić
w nim kilka zmian. Udało się także umieścić całą logikę kodu w jednym, wygodnym miejscu.
No dobrze, ale jak to właściwie wszystko do siebie pasuje i działa? Przeanalizujemy działanie tego
kodu krok po kroku.
Jeszcze raz, to są parametry;
podczas wywoływania funkcji
są im przypisywane konkretne
wartości.
Przede wszystkim mamy funkcję
A zatem na samym początku kodu została
umieszczona funkcja bark. Przeglądarka
odczytuje ten kod, dochodzi do wniosku,
że ma do czynienia z funkcją, a następnie
analizuje instrukcje umieszczone wewnątrz niej.
Przeglądarka wie, że jeszcze nie ma wykonywać
ciała funkcji — poczeka z tym aż do momentu,
gdy funkcja zostanie wywołana w jakimś innym
miejscu kodu.
Można też zauważyć, iż funkcja jest
sparametryzowana, co oznacza, że w momencie
wywoływania trzeba będzie do niej przekazać
imię oraz wagę psa. Dzięki temu funkcję będzie
można wywoływać dla dowolnie wielu psów.
Za każdym razem jej logika będzie operować
na przekazanych danych o psie.
120
Rozdział 3.
function bark(name, weight) {
if (weight > 20) {
console.log(name + ” szczeka HAU HAU”);
} else {
console.log(name + ” szczeka hau hau”);
}
}
A wszystko to, co jest
umieszczone wewnątrz funkcji,
nazywamy jej ciałem.
Przedstawienie funkcji
A teraz wywołajmy funkcję
Aby wywołać funkcję, należy podać jej nazwę, następnie
nawias otwierający, wszystkie wartości, które chcemy
przekazać do funkcji (rozdzielone przecinkami), a na końcu
nawias zamykający. Wartości umieszczone w nawiasach
nazywamy argumentami. Dla funkcji bark potrzebujemy
dwóch argumentów: imienia psa oraz jego wagi.
Tutaj przekazujemy do
funkcji dwa argumenty,
nazwę psa oraz jego
wagę.
Nazwa naszej funkcji.
bark(”Burek”, 23);
A tak wygląda wywołanie funkcji.
”Burek”
W momencie wywoływania funkcji bark
argumenty są przypisywane nazwom
parametrów.
23
function bark(name, weight) {
if (weight > 20) {
console.log(name + “ szczeka HAU HAU”);
} else {
console.log(name + “ szczeka hau hau”);
A w kodzie funkcji wszędzie
tam, gdzie pojawią się nazwy
parametrów, zostaną użyte
przekazane wartości.
}
}
Po przypisaniu wartości argumentów
nazwom parametrów możemy już wykonać
instrukcje umieszczone w ciele funkcji.
Po wywołaniu funkcji jej ciało załatwi
całą resztę
Kiedy już będziemy znali wartości wszystkich
parametrów funkcji — np. że parametr name ma
wartość ”Burek”, a weight wartość 23 — będziemy
mogli wykonać ciało funkcji.
Instrukcje umieszczone w ciele funkcji są
wykonywane od góry ku dołowi, dokładnie tak samo
jak każdy inny kod. Jedyna różnica polega na tym,
że parametrom name oraz weight są przypisywane
wartości przekazane w wywołaniu funkcji.
function bark(name, weight) {
if (weight
23 > 20) {
console.log(name
”Burek” + ” szczeka HAU HAU”);
} else {
console.log(name
”Burek” + ” szczeka hau hau”);
}
}
Parametry pełnią w ciele funkcji rolę
zmiennych, którym zostały przypisane
wartości argumentów przekazanych
w wywołaniu funkcji.
jesteś tutaj
121
Funkcje ćwiczebne
Skorzystaj z opcji Narzędzia, by wyświetlić
konsolę JavaScript i zobaczyć komunikat.
A kiedy funkcja zostanie zakończona
Logika umieszczona wewnątrz funkcji została już wykonana
(a w naszym przypadku przekonamy się, że Burek, pies
ważący 23 kilogramy, szczeka HAU HAU), a funkcja —
zakończona. Kiedy realizacja funkcji zostaje zakończona,
sterowanie jest przekazywane do następnej instrukcji
umieszczonej za wywołaniem bark.
function bark(name, weight) {
"Burek szczeka HAU HAU"
if (weight > 20) {
console.log(name + ” szczeka HAU HAU”);
} else {
console.log(name + ” szczeka hau hau”);
}
}
Wykonaliśmy to…
bark(”Burek”, 23);
bark(”Mops”, 13);
…więc
przechodzimy
do następnej
instrukcji.
bark(”Reksio”, 53);
Kiedy realizacja funkcji zostanie zakończona, przeglądarka
zaczyna wykonywać następną instrukcję, umieszczoną
za jej wywołaniem.
W naszym przypadku ponownie wywołuje tę samą funkcję,
przekazując do niej inne argumenty; a zatem cały proces
rozpoczyna się od początku!
bark(”Saba”, 17);
Zaostrz ołówek
Poniżej przedstawiliśmy kilka kolejnych wywołań funkcji bark. Obok każdego z nich zapisz, jakie,
według Ciebie, powinny być jego wyniki, ewentualnie czy kod spowoduje wystąpienie błędu.
Nie zapomnij sprawdzić wyników na końcu rozdziału.
bark(”Reksio”, 20); _______________________________________________
bark(”Skuter”, -1); _______________________________________________
bark(”Dino”, 0, 0); _______________________________________________
bark(”Fido”, ”20”); _______________________________________________
122
bark(”Saba”, 10);
_______________________________________________
bark(”Agent”, 21);
_______________________________________________
Rozdział 3.
Napisz jakie, według
Ciebie, będą wyniki
wyświetlone w oknie
konsoli.
Masz jakieś pomysły
na temat tego, co tu
się stanie?
Przedstawienie funkcji
Magnesy z kodem
,
']LDâDMċF\NRG-DYD6FULSW]RVWDâSRFLĐW\QDNDZDâHF]NL
SU]\F]HSLRQ\QDORGyZFHLFDâNRZLFLHZ\PLHV]DQ\&]\
SRWUDILV]SRXNâDGDþJR]SRZURWHPZG]LDâDMċF\SURJUDP
JHQHUXMċF\SU]HGVWDZLRQHSRQLİHMZ\QLNL"=ZUyþXZDJĐ
İHQDORGyZFHPRİHE\þWURFKĐQLHSRWU]HEQHJRNRGX
]DWHP³E\þPRİH³QLHEĐG]LHV]PXVLDâXİ\þZV]\VWNLFK
PDJQHVLNyZ3RUyZQDMVZRMċRGSRZLHGĮ]QDV]ċ
SU]HGVWDZLRQċQDNRĚFXUR]G]LDâX
,
,
}
}
}
(
function
)
{
whatShallIWear(30);
else {
t-shirt.");
console.log("Ubierz
}
whatShallIWear
else if (temp < 25) {
console.log("Ubierz sweter.");
}
temperature
if (temp < 15) {
console.log("Ubierz kurtkÚ.");
}
temp
whatShallIWear(15);
whatShallIWear(10)
;
Konsola JavaScript
Ubierz sweter.
Ubierz t-shirt.
8ELHU]NXUWNÚ
Używamy takiego rysunku,
by przedstawić okno konsoli
JavaScript.
jesteś tutaj
123
Wywiad z funkcją
Funkcja bez tajemnic
Temat dzisiejszego wywiadu brzmi:
Intymne strony funkcji
Rusz głową: Witamy Funkcjo! Już nie możemy się
doczekać, by wygrzebać na wierzch wszystkie informacje
o tym, czym jesteś i do czego służysz.
Rusz głową: A, no tak… Prawda, alert i prompt mają
sens, ale ta Math.random, ona nawet nie wygląda na
funkcję.
Funkcja: Ja również się cieszę, że tu jestem.
Funkcja: Math.random jest funkcją, choć została
skojarzona z kolejnym potężnym narzędziem, z którego
początkujący rzadko korzystają — z obiektem.
Rusz głową: Zauważyliśmy, że wielu początkujących
użytkowników JavaScriptu trochę cię ignoruje. Po prostu
piszą swój kod, od góry do dołu i wcale nie używają funkcji.
Czy w ogóle jesteś potrzebna?
Funkcja: Ci nowicjusze wiele tracą. Trochę szkoda, bo
daję ogromne możliwości. Pomyśl, że dzięki mnie możesz
napisać kod raz i używać dowolnie wiele razy.
Rusz głową: No cóż, wybacz, że to powiem, ale skoro
pozwalasz im napisać kod raz, a potem wykonujesz go wiele
razy… Czy to nie jest nudne?
Funkcja: O nie… Funkcje są parametryzowane — innymi
słowy, zawsze wtedy, gdy używasz funkcji, przekazujesz do niej
argumenty, zatem może ona obliczać to, co jest potrzebne.
Rusz głową: A tak, obiekty… Przypuszczam, że nasi
czytelnicy dowiedzą się o nich w następnych rozdziałach.
Funkcja: W porządku, zatem nie będę strzępić języka,
opowiadając o nich teraz.
Rusz głową: Przejdźmy do tych argumentów i parametrów
— trochę to zagmatwane.
Funkcja: Wyobraź to sobie tak: każdy parametr spełnia
w ciele funkcji rolę zmiennej. Kiedy wywołujesz funkcję,
każda przekazana wartość jest zapisywana w parametrze.
Rusz głową: A czym są argumenty?
Rusz głową: Przykładowo co?
Funkcja: A to tylko inna nazwa wartości przekazywanych
do funkcji; to argumenty wywołania funkcji.
Funkcja: Załóżmy np., że musisz pokazać użytkownikowi,
ile będą kosztowały produkty w jego koszyku. W tym celu
możesz napisać funkcję computeShoppingCartTotal.
A później możesz przekazywać do tej funkcji koszyki wielu
użytkowników, a ona obliczy wartość charakterystyczną dla
konkretnego koszyka.
Rusz głową: Cóż, nie wydajesz się wcale taka wspaniała,
choć jesteś w porządku, pozwalasz wielokrotnie używać
kodu, dajesz możliwość przekazywania wartości przy użyciu
parametrów. Ale czy to wszystko? Nie rozumiem całej tej
tajemnicy, jaka wokół ciebie panuje.
Rusz głową: Skoro jesteś taka super, czemu więcej
początkujących programistów nie korzysta z twoich
możliwości?
Funkcja: Och, to tylko podstawy; potrafię znacznie więcej:
mogę zwracać wartości, mogę być anonimowa, mogę
tworzyć takie fajne rzeczy nazywane domknięciami i mogę
nawiązywać intymne kontakty z obiektami.
Funkcja: To jest całkowicie błędne stwierdzenie, używają
mnie cały czas; spójrz: alert, prompt, Math.random,
document.write. Trudno napisać coś sensownego bez
stosowania funkcji. Nie jest prawdą, że początkujący nie
używają funkcji, oni raczej nie definiują swoich własnych
funkcji.
124
Rozdział 3.
Rusz głową: NAPRAWDĘ?! Czy mogę dostać wyłączność
na następny wywiad o tych kontaktach?
Funkcja: Zastanowię się nad tym…
Przedstawienie funkcji
Co można przekazywać do funkcji?
Kiedy wywołujemy funkcję, przekazujemy do niej argumenty, a one
zostają dopasowane do parametrów podanych w definicji funkcji. Takim
argumentem może być właściwie dowolna wartość dostępna w języku
JavaScript, np. łańcuch znaków, wartość logiczna lub liczba.
być dowolna
Przekazywanym argumentem może pt.
Scri
Java
ku
języ
w
ępna
wartość dost
saveMyProfile(”Krzysiu”, 1991, 3.81, false);
Wewnątrz funkcji każdy
argument jest przekazywany do
odpowiadającego mu parametru.
function saveMyProfile(name, birthday, GPA, newuser) {
if (birthday >= 2004) {
// kod obsïugi dziecka
A każdy parametr pełni
w funkcji rolę zmiennej.
}
// reszta kodu funkcji
}
Argumentami przekazywanymi w wywołaniach funkcji mogą być także
zmienne, prawdę powiedziawszy, takie rozwiązanie jest najczęściej
spotykane. Poniżej przedstawiliśmy kilka przykładów wywołań funkcji,
w których zostały użyte zmienne.
ości
Teraz każda z przekazywanych wart
encie
jest zapisana w zmiennej. W mom nnych
zmie
wywołania funkcji wartości tych
ty.
men
argu
jako
e
wan
kazy
prze
są
var student = ”Krzysiu”;
var year = 1991;
var GPA = 381/100;
var status = ”oczekujÈcy”;
var isNewUser = (status == ”nowyBuĝytkownik”);
saveMyProfile(student, year, GPA, isNewUser);
A zatem w tym miejscu wartość zmiennej
student, czyli “Krzysiu”, jest przekazywana
jako argument do parametru name.
Pozostałymi trzema argumentami
także są zmienne.
Argumentami mogą być nawet wyrażenia.
var student = ”Krzysiu”;
var status = ”oczekujÈcy”;
var year = 1991;
Tak, nawet takie
wyrażenia z powodzeniem
mogą być argumentami!
W każdym z tych przypadków
najpierw obliczana jest wartość
wyrażenia, która zostaje
przekazana do funkcji.
saveMyProfile(student, year, 381/100, status == ”nowyBuĝytkownik”);
Możemy obliczać wartość
wyrażenia liczbowego…
…oraz logicznego, takiego jak to, które
spowoduje, że do funkcji zostanie
przekazana wartość false.
jesteś tutaj
125
Parametry a argumenty
Wciąż nie jestem pewna, czy
rozumiem różnicę pomiędzy
parametrem a argumentem — czy to
są dwie różne nazwy określające
tę samą rzecz?
Nie, to dwie różne rzeczy
Kiedy definiujesz funkcję, możesz w jej definicji umieścić
jeden lub więcej parametrów.
Tutaj definiujemy trzy parametry:
degrees, mode oraz duration.
function cook(degrees, mode, duration) {
// tu bÚdzie kod funkcji
}
Natomiast później, wywołując funkcję, wywołujesz ją
i przekazujesz argumenty.
cook(180.0, ”pieczenie”, 45);
To są argumenty. W tych wywołaniach mamy trzy argumenty:
liczbę zmiennoprzecinkową, łańcuch znaków oraz liczbę całkowitą.
cook(150.0, ”opiekanie”, 10);
A zatem parametry definiujesz tylko raz, jednak funkcję będziesz
zapewne wywoływał, używając wielu różnych argumentów.
WYSIL
SZARE KOMÓRKI
function doIt(param) {
param = 2;
}
var test = 1;
doIt(test);
console.log(test);
126
Rozdział 3.
Co wyświetli ten kod? Czy jesteś tego pewny?
Przedstawienie funkcji
Poniżej znajdziesz fragment kodu JavaScript zawierający zmienne, definicje funkcji
oraz ich wywołania. Twoim zadaniem jest wskazanie wszystkich zmiennych, funkcji,
argumentów oraz parametrów. Ich nazwy zapisz w odpowiednich ramkach z prawej
strony. Porównaj swoje odpowiedzi z naszymi, podanymi pod koniec rozdziału.
Ćwiczenie
Zmienne
function dogYears(dogName, age) {
var years = age * 7;
console.log(dogName + ” ma ” + years + ” lata.”);
}
var myDog = ”Fido”;
dogYears(myDog, 4);
Funkcje
function makeTea(cups, tea) {
console.log(”ParzÚ ” + cups + ” filiĝanek herbaty ” + tea
+ ”.”);
}
var guests = 5;
makeTea(guests, ”Earl Grey”);
function secret() {
Funkcje wbudowane
console.log(”TajemnicÈ bytu jest liczba 42.”);
}
secret();
function speak(kind) {
var defaultSound = ””;
if (kind == ”pies”) {
Argumenty
alert(”Hau”);
} else if (kind == ”kot”) {
alert(”Miau”);
} else {
alert(defaultSound);
}
Parametry
}
var pet = prompt(”Podaj rodzaj zwierzaka: ”);
speak(pet);
jesteś tutaj
127
Przekazywanie przez wartość
JavaScript przekazuje przez wartość
To oznacza przekazywanie kopii
Ważne jest, by zrozumieć, jak są przekazywane argumenty w języku JavaScript. Argumenty
są przekazywane do funkcji przez wartość. Oznacza to, że każdy argument jest kopiowany do
zmiennej parametru. Aby zrozumieć jak to działa, przeanalizujemy prosty przykład.
1
'HNODUXMHP\]PLHQQċageLSU]\SLVXMHP\MHMZDUWRĤþSRF]ċWNRZċ
7
var age = 7;
2
age
$WHUD]GHNODUXMHP\IXQNFMĐaddOneNWyUDEĐG]LHPLHþMHGHQDUJXPHQW
RQD]ZLHx)XQNFMDEĐG]LHSRZLĐNV]DþZDUWRĤþSDUDPHWUXR
function addOne(x) {
x = x + 1;
}
3
$WHUD]Z\ZRâXMHP\IXQNFMĐaddOneLSU]HND]XMHP\GRQLHMMDNRDUJXPHQW
ZDUWRĤþ]PLHQQHMage:DUWRĤþ]PLHQQHMMHVWNRSLRZDQDGRSDUDPHWUXx
addOne(age);
4
x
age
To jest KOPIA
zmiennej age.
x
7HUD]ZDUWRĤþ]PLHQQHM[MHVWSRZLĐNV]DQDRMHGHQ3DPLĐWDMMHGQDN
İH xMHVWNRSLċD]DWHP]PLHQLDQDMHVWMHG\QLHZDUWRĤþ]PLHQQHMx
DQLH]PLHQQHMage
Inkrementujemy
zmienną x.
function addOne(x) {
x = x + 1;
}
7
age
Wartość zmiennej age
nie zmienia się, choć
zmienia się wartość
zmiennej x.
128
7
7
Rozdział 3.
8
Zmienna
x została
zmieniona
wewnątrz
funkcji addOne.
x
Przedstawienie funkcji
Co myślisz o przekazywaniu
przez wartość? Z jednej strony,
wygląda na całkiem proste,
ale z drugiej, mam poczucie,
że czegoś mi w nim brakuje.
&LHV]\P\ VLĕ ĵH o W\P P\ĩOLV] Zrozumienie sposobu,
w jaki JavaScript przekazuje wartości do funkcji, jest bardzo
istotne. Z jednej strony, jest on bardzo prosty: kiedy argument jest
przekazywany do funkcji, jego wartość jest najpierw kopiowana,
a następnie przypisywana odpowiedniemu parametrowi. Z drugiej
strony, jeśli źle to zrozumiesz, możesz przyjąć błędne założenia
dotyczące wzajemnych zależności i współdziałania funkcji,
argumentów i parametrów.
W przypadku przekazywania przez wartość prawdziwe znaczenie
ma to, że wszelkie zmiany parametrów wewnątrz funkcji mają
wpływ wyłącznie na parametr, a nie na zmienną przekazywaną
w wywołaniu. I to w sumie tyle.
Oczywiście, od każdej reguły są jakieś wyjątki, więc będziemy
musieli wrócić do tej rozmowy nieco później, kiedy zajmiemy
się obiektami, które poznamy później. Jednak nie martw się,
dysponując solidną wiedzą na temat przekazywania przez wartość,
masz doskonałe przygotowanie, by przystąpić do tej dyskusji.
A na razie wystarczy, żebyś zapamiętał, że podczas przekazywania
przez wartość wszystko, co się dzieje z parametrem wewnątrz funkcji,
zostaje w tej funkcji. Trochę podobnie jak w Las Vegas.
WYSIL
SZARE KOMÓRKI
PONOWNIE
function doIt(param) {
Pamiętasz to ćwiczenie? Czy teraz, wiedząc już o przekazywaniu
przez wartość, patrzysz na nie nieco inaczej? A może już za
pierwszym razem podałeś prawidłową odpowiedź?
param = 2;
}
var test = 1;
doIt(test);
console.log(test);
jesteś tutaj
129
Więcej o argumentach funkcji
Zakrecone
funkcje
,
Dotychczas spotkałeś się jedynie ze zwyczajnymi sposobami korzystania z funkcji. A co by się stało,
gdybyśmy trochę poeksperymentowali, np. przekazując do funkcji zbyt wiele argumentów?
Albo zbyt mało? Wygląda groźnie. Zobaczmy zatem, co się wydarzy.
EKSPERYMENT 1. Co się stanie, kiedy przekażemy zbyt mało argumentów?
&KRþEU]PLU\]\NRZQLHMHGQDNZWDNLPSU]\SDGNXG]LHMHVLĐW\ONRWRİH
ZV]\VWNLHSDUDPHWU\GODNWyU\FKQLHSRGDQRDUJXPHQWyZSU]\MPXMċZDUWRĤþ
GRP\ĤOQċundefined2WRSU]\NâDG
function makeTea(cups, tea) {
console.log(”ParzÚ ” + cups + ” filiĝanki herbaty ” + tea + ”.”);
}
makeTea(3);
Konsola JavaScript
Zwróć uwagę, że wartością
parametru tea jest undefined, gdyż
nie przekazaliśmy odpowiadającego
mu argumentu.
3DU]ÚILOLĝDQNLKHUEDW\XQGHILQHG
EKSPERYMENT 2. Co się stanie, kiedy przekażemy zbyt dużo argumentów?
1RFyİZWDNLPSU]\SDGNX-DYD6FULSWSRSURVWXLJQRUXMHGRGDWNRZH
DUJXPHQW\2WRSU]\NâDG
function makeTea(cups, tea) {
console.log(”ParzÚ ” + cups + ” filiĝanki herbaty ” + tea + ”.”);
}
makeTea(3, ”Earl Gray”, ”czoïem!”, 42);
Działa świetnie, funkcja
ignoruje nadmiarowe
argumenty
Konsola JavaScript
3DU]ÚILOLĝDQNLKHUEDW\(DUO*UD\
Okazuje się, że istnieje sposób,
jednak na razie nie będziemy zaprby pobrać te dodatkowe argumenty,
zątać sobie nim głowy.
EKSPERYMENT 3. Co się stanie, kiedy funkcja nie ma żadnych argumentów?
:RJyOHVLĐW\PQLHSU]HMPXMZLHOHIXQNFMLQLHPDİDGQ\FKDUJXPHQWyZ
function barkAtTheMoon() {
console.log(”Auuuuuuuuuuuuuuuuuu!”
}
barkAtTheMoon();
130
Rozdział 3.
Konsola JavaScript
Auuuuuuuuuuuuuuuuuu!
Przedstawienie funkcji
Funkcje mogą także coś zwracać
Wiesz już, jak komunikować się z funkcjami w jednym kierunku,
czyli wiesz, jak przekazywać argumenty do funkcji. A co z odwrotnym
rozwiązaniem? Czy funkcja może przekazać coś na zewnątrz?
Wypróbujmy instrukcję return.
function bake(degrees) {
Oto nasza nowa funkcja bake, do
której można przekazać temperaturę
piekarnika.
var message;
if (degrees > 250) {
message = ”Nie jestem reaktorem atomowym!”;
wartości
Funkcja na podstawie isuje
zap
s
ree
deg
u
parametr
ni łańcuch
w zmiennej odpowied
znaków.
} else if (degrees < 20) {
message = ”Nie jestem lodöwkÈ!”;
} else {
message = ”To odpowiednia temperatura dla mnie.”;
setMode(”pieczenie”);
setTemp(degrees);
}
return
Można sądzić, że właśnie tu są
wykonywane jakieś faktycznie ważne
operacje, ale nimi na razie nie
będziemy się przejmować…
message;
}
var status = bake(200);
Teraz najważniejsze jest to,
że instrukcja return zwraca
komunikat, który stanie się
wynikiem wykonania funkcji.
Teraz, kiedy funkcja zostanie wywołana,
zwróci łańcuch znaków, który zostanie
zapisany w zmiennej status.
W tym przypadku, gdyby zmienna status
została wyświetlona, zawierałaby łańcuch
znaków “To odpowiednia temperatura dla mnie.”
Przeanalizuj kod i upewnij się, że rozumiesz,
dlaczego.
200 stopni to doskonała temperatura
do wypiekania pysznych ciasteczek.
Zachęcamy, żebyś sam się o tym
przekonał, a następnie zajrzał na
następną stronę.
jesteś tutaj
131
Instrukcja return
Śledzenie wykonania funkcji z instrukcją return
Teraz, kiedy już wiesz, jak działają argumenty i parametry oraz jak zwracać wartości
z funkcji, prześledzimy dokładnie sposób wykonywania funkcji od początku do końca,
by przekonać się, co zachodzi na każdym z jego etapów. Pamiętaj, by wykonywać
poszczególne kroki w odpowiedniej kolejności.
1
Najpierw deklarujemy zmienną
radius i przypisujemy jej wartość 5.2.
2
Następnie wywołujemy funkcję
calculateArea i przekazujemy do niej
zmienną radius jako argument.
3 function calculateArea(r) {
var area;
4
if (r <= 0) {
5
3
4
6
Wartość argumentu jest zapisywana
w parametrze r, więc gdy funkcja
calculateArea zaczyna być
wykonywana, jej parametr r
zawiera wartość 5.2.
return 0;
} else {
6
7
area = Math.PI * r * r;
8
return area;
}
Realizacja ciała funkcji rozpoczyna się od
pierwszej instrukcji, którą jest deklaracja
zmiennej area. Następnie sprawdzamy,
czy wartość zmiennej r jest mniejsza lub
równa 0.
}
1 var radius = 5.2;
5 Jeśli r jest mniejsze lub równe 0, funkcja
zwraca wartość 0, po czym jej wykonywanie
zostaje zakończone. Jednak przekazaliśmy
do funkcji wartość 5.2, zatem ta instrukcja
NIE zostanie wykonana.
Zamiast tego wykonywana jest
klauzula else.
7
Obliczamy pole koła przy użyciu wartości
5.2 zapisanej w parametrze r.
8
Zwracamy obliczoną wartość jako
wynik wykonania funkcji. Powoduje
to zakończenie wykonywania funkcji
i zwrócenie wartości.
9
Wartość zwrócona z funkcji zostaje
zapisana w zmiennej theArea.
2
9 var theArea = calculateArea(radius);
10 FRQVROHORJ 3ROHNRïDZ\QRVLWKH$UHD To są wyniki!
Konsola JavaScript
3ROHNRïDZ\QRVL
10 Realizacja programu przechodzi
do następnego wiersza kodu.
132
Rozdział 3.
Programiści często nazywają taką analizę
„śledzeniem przebiegu realizacji” albo po
prostu „śledzeniem”. Jak widać, „realizacja”
trochę skacze po kodzie w momencie
wywoływania funkcji i zwracania wartości.
Prześledź ją spokojnie, krok po kroku.
Przedstawienie funkcji
Anatomia funkcji
Już wiesz, jak definiować i wywoływać funkcje, teraz upewnimy się, czy
doskonale rozumiesz i pamiętasz ich składnię. Poniżej przedstawiliśmy
wszystkie elementy anatomii funkcji.
Za słowem kluczowym function
podawana jest nazwa funkcji.
Zawsze zaczynamy
od słowa kluczowego
„function”.
Zmienne potrzebne
do działania funkcji
należy deklarować
w jej ciele.
function
A następnie dowolna liczba argumentów
e
(w tym żadnego), oddzielonych od siebi
ątrz
wewn
anych
zapis
i
i
inkam
przec
pary nawiasów.
addScore ( level , score ) {
var bonus = level * score * .1;
return score + bonus;
}
Oto zamykający
nawias klamrowy,
kończący ciało funkcji.
Funkcja może, choć nie
musi, zawierać instrukcję
return.
Jeśli nawet funkcja nie
będzie mieć żadnych
parametrów, to i tak ta
para nawiasów — ( ) —
musi się znaleźć za jej
nazwą.
Ciało funkcji
jest zapisywane
pomiędzy parą
nawiasów
klamrowych i składa
się z sekwencji
instrukcji (dokładnie
takich samych jak
wszystkie inne
instrukcje).
Instrukcja return zawiera zmienną
lub wyrażenie określające wartość,
która zostanie zwrócona jako
wynik wywołania funkcji.
Nie istnieją
P: Co się stanie, kiedy pomylę
kolejność argumentów, czyli przekażę
argumenty do nieprawidłowych
parametrów?
O: Kiepska sprawa; można sądzić, że
niemal na pewno spowoduje to wystąpienie
błędu lub nieprawidłowe działanie kodu.
Zawsze uważnie sprawdzaj parametry
funkcji, by dokładnie wiedzieć, jakie
argumenty należy do niej przekazać.
P: Dlaczego przed nazwami
parametrów nie ma słowa kluczowego
var? Przecież parametry są nowymi
zmiennymi, prawda?
głupie pytania
O: Właściwie tak. Funkcja sama zajmuje się P: Co się stanie, kiedy parametr
utworzeniem wszystkich zmiennych, dlatego
w przypadku parametrów stosowanie słowa
kluczowego var nie jest konieczne.
P: Jakie są zasady określania nazw
funkcji?
O: Są to te same zasady, którym
podlegają nazwy zmiennych. Podobnie
jak w przypadku zmiennych, chcemy, by
nazwy funkcji miały sens, gdy będziemy
analizować kod, i przekazywały jakieś
wskazówki dotyczące tego, co funkcja
robi. W przypadku nazw składających się
z większej liczby słów warto także stosować
konwencję camelCase, tak samo jak
podczas określania nazw zmiennych.
funkcji oraz jej argument będą miały
tę samą nazwę, np. x?
O: Jeśli nawet parametr funkcji
i przekazany do niej argument będą miały
taką samą nazwę, np. x, to w parametrze
x zostanie zapisana kopia wartości
argumentu x; a zatem będą dwiema
odrębnymi zmiennymi. Zmiana wartości
parametru x nie spowoduje zmiany
wartości argumentu x.
P: Co zwraca funkcja, jeśli nie
użyjemy w niej instrukcji return?
O
: Funkcja bez instrukcji return zwraca
undefined.
jesteś tutaj
133
Deklaracje zmiennych lokalnych
Zauważyłem, że zaczęliście
umieszczać deklaracje
zmiennych bezpośrednio wewnątrz
funkcji. Czy takie deklaracje
działają tak samo jak te, które
umieszczane są poza funkcjami?
Dobre pytanie. Odpowiedź brzmi: i tak, i nie
Deklaracje umieszczane wewnątrz funkcji działają
dokładnie tak samo jak te umieszczane poza
funkcjami, przynajmniej w tym sensie, że powodują
utworzenie zmiennej i zapisanie w niej podanej
wartości początkowej. Jednak różnica pomiędzy
zmienną zadeklarowaną poza funkcją oraz zmienną
zadeklarowaną wewnątrz funkcji sprowadza się do tego,
gdzie są używane zmienne — innymi słowy, w których
miejscach kodu możemy się do tych zmiennych
odwoływać. Jeśli zmienna zostanie zadeklarowana
poza funkcją, będziesz mógł się do niej odwoływać
w dowolnym miejscu kodu. Jeśli natomiast zmienna
zostanie zadeklarowana wewnątrz funkcji, będziesz
mógł jej używać jedynie wewnątrz tej funkcji. Jest to
tzw. zasięg zmiennej. Istnieją dwa rodzaje tego zasięgu,
globalny i lokalny.
134
Rozdział 3.
Przedstawienie funkcji
Musimy porozmawiać
o tym, jak używasz
zmiennych…
Zmienne globalne i lokalne
Poznaj różnice lub ryzykuj upokorzenie
Już wiesz, że zmienną można zadeklarować w dowolnym
miejscu skryptu, używając do tego celu słowa kluczowego
var i nazwy.
var avatar;
var levelThreshold = 1000;
To zmienne globalne, które
są dostępne w całym
naszym kodzie JavaScript.
Wiesz również, że zmienne można deklarować
wewnątrz funkcji.
function getScore(points) {
var score;
var i = 0;
Zmienne points, score
oraz i są zmiennymi
zadeklarowanymi wewnątrz
funkcji.
while (i < levelThreshold) {
//code here
i = i + 1;
}
return score;
}
Nazywamy je zmiennymi
lokalnymi, gdyż są znane
wyłącznie wewnątrz danej
funkcji.
Niezależnie od tego, że używamy
levelTreshold wewnątrz funkcji, jest
ona zmienną globalną, gdyż została
zadeklarowana poza funkcją.
Jeśli zmienna została
zadeklarowana poza
funkcją, jest zmienną
GLOBALNĄ. Jeśli
natomiast została
zadeklarowana wewnątrz
funkcji, jest zmienną
LOKALNĄ.
Czy to ma jakieś znaczenie? Przecież zmienne to zmienne,
prawda? Otóż to, gdzie zmienna zostanie zadeklarowana,
określa jej widoczność dla pozostałych fragmentów kodu.
Później zrozumienie tych dwóch rodzajów zmiennych pomoże
w tworzeniu kodu, który będzie łatwiejszy w utrzymaniu
(nie wspominamy w ogóle o umożliwieniu zrozumienia kodu
pisanego przez innych).
jesteś tutaj
135
Konwencje nazewnicze zmiennych
Hej, mam jedno
pytanie. Rozmawialiśmy wcześniej
o tym, że warto nadawać zmiennym
sensowne nazwy, które coś znaczą.
A w ostatnim przykładzie użyliście nazwy i.
Nie wydaje mi się, by nazwa ta miała
jakieś znaczenie.
Kolejne słuszne spostrzeżenie
Zastosowanie nazwy i dla zmiennej służącej do
sterowania iteracją w pętlach ma bardzo długą
tradycję. Konwencja ta sięga dawnych czasów, kiedy
to przestrzeń do pisania programów była ograniczona
(jak było w przypadku kart i taśm perforowanych),
a stosowanie krótkich nazw miało duże zalety. Obecnie
jest to konwencja, którą rozumieją wszyscy programiści.
Często można się także spotkać z nazwami j i k,
stosowanymi w podobny sposób, a nieco rzadziej, nawet
z nazwami x i \. Jednak jest to chyba jedyny wyjątek
od powszechnej, dobrej praktyki nadawania zmiennym
sensownych, opisowych nazw.
136
Rozdział 3.
Przedstawienie funkcji
Poznawanie zasięgu zmiennych lokalnych i globalnych
Miejsce, w którym zdefiniujemy zmienne, określa ich zasięg, czyli to, gdzie będą dostępne,
a gdzie nie. Przeanalizujemy teraz przykład zmiennych globalnych i lokalnych — pamiętaj,
że zmienne definiowane poza funkcjami są zmiennymi o zasięgu globalnym, a definiowane
wewnątrz funkcji — zmiennymi o zasięgu lokalnym.
var avatar = ”ogolny”;
var skill = 1.0;
var pointsPerLevel = 1000;
var userPoints = 2008;
function getAvatar(points) {
var level = points / pointsPerLevel;
if (level == 0) {
return ”Nieděwiadek -ogi”;
} else if (level == 1) {
return ”Kot”;
} else if (level >= 2) {
return ”Goryl”;
Te cztery zmienne mają
zasięg globalny. Oznacza to,
że są zdefiniowane i widoczne
w całym kodzie poniżej.
Warto także pamiętać, że jeśli
dołączysz do strony dodatkowy
skrypt, to zmienne globalne
będą widoczne także w nim,
podobnie jak zmienne globalne
zdefiniowane w dołączonym
skrypcie będą widoczne
w tym kodzie!
Zmienna level jest zmienną
lokalną, dlatego jest
widoczna wyłącznie wewnątrz
funkcji getAvatar. Oznacza to,
że dostęp do zmiennej level
ma jedynie kod umieszczony
wewnątrz tej funkcji.
I nie zapomnij o parametrze
points, który także ma zasięg
lokalny i jest dostępny
wyłącznie wewnątrz funkcji
getAvatar.
}
}
Zwróć także uwagę, że
funkcja getAvatar korzysta
ze zmiennej globalnej
pointsPerLevel.
function updatePoints(bonus, newPoints) {
var i = 0;
while (i < bonus) {
newPoints = newPoints + skill * bonus;
i = i + 1;
}
return newPoints + userPoints;
}
userPoints = updatePoints(2, 100);
avatar = getAvatar(2112);
Wewnątrz funkcji updatePoints
tworzona jest kolejna zmienna
lokalna — i. Jest ona dostępna
wyłącznie dla kodu funkcji
updatePoints.
Zmienne bonus oraz newPoints
są także zmiennymi lokalnymi
dostępnymi wewnątrz funkcji
updatePoints; natomiast
userPoints jest zmienną
globalną.
A w tym miejscu kodu mamy
dostęp wyłącznie do naszych
zmiennych globalnych, nie
mamy natomiast dostępu
do żadnych zmiennych
zadeklarowanych wewnątrz
funkcji, gdyż nie są one
widoczne w zasięgu globalny
m.
jesteś tutaj
137
Zasięg zmiennych
Mógłbym przysiąc,
że zmienna była tuż za mną,
ale kiedy się obróciłem,
już znikła…
Krótkie życie zmiennych
Kiedy jesteś zmienną, pracujesz ciężko, a Twoje
życie może być krótkie. No chyba że jesteś zmienną
globalną, ale nawet ich życie jest ograniczone. Jakie
czynniki ograniczają życie zmiennych? Można to
sobie wyobrazić w następujący sposób.
Zmienne globalne istnieją tak długo, jak
długo istnieje strona. Zmienna globalna zostaje
powołana do życia, w momencie gdy strona wczyta
kod JavaScript, który ją deklaruje. Jednak życie
takiej zmiennej globalnej kończy się, kiedy strona
zostanie usunięta z przeglądarki. Jeśli nawet tylko
odświeżamy stronę, wszystkie zmienne globalne
zostaną zniszczone, a następnie utworzone
ponownie wraz z wyświetleniem nowej strony.
Zmienne lokalne znikają zazwyczaj razem
z funkcją. Zmienne lokalne są tworzone
w momencie wywołania ich funkcji i istnieją,
do momentu gdy funkcja zostanie zakończona
(niezależnie od tego, czy funkcja zwróci wartość,
czy nie). Oznacza to, że możemy zwrócić wartości
zmiennych lokalnych, zanim spotkają się ze swym
cyfrowym stwórcą.
Czy zatem naprawdę nie ma ŻADNEJ ucieczki
poza granice strony? Jeśli jesteś zmienną lokalną,
Twoje życie mija bardzo szybko; jeśli masz
na tyle szczęścia, że jesteś zmienną globalną,
możesz spokojnie żyć, aż do odświeżenia strony
w przeglądarce.
138
Rozdział 3.
Użyliśmy tu słowa „zazwyczaj”, gdyż istnieją
pewne zaawansowane sposoby zachowania
zmiennych lokalnych na trochę dłużej, jednak
na razie nie będziemy o nich pisali.
Przedstawienie funkcji
Nie zapominaj o deklarowaniu zmiennych
Jeśli użyjesz zmiennej bez jej wcześniejszego zadeklarowania, będzie to
zmienna globalna. Oznacza to, że nawet wtedy, kiedy po raz pierwszy użyjesz
zmiennej wewnątrz funkcji (bo np. miała być zmienną lokalną), automatycznie
zostanie ona utworzona jako zmienna globalna i będzie dostępna także poza
funkcją (co później może być źródłem zamieszania). A zatem nie zapominaj
o deklarowaniu swoich zmiennych!
function playTurn(player, location) {
points = 0;
if (location == 1) {
points = points + 100;
}
return points;
Zapomnieliśmy zadeklarować
zmiennej points przy użyciu
słowa kluczowego „var”.
Dlatego automatycznie
stała się zmienną globalną.
Jeśli zapomnisz
zadeklarować zmiennej
przed jej użyciem,
zawsze zostanie ona
utworzona jako zmienna
globalna (nawet wtedy,
kiedy po raz pierwszy
zostanie użyta
wewnątrz funkcji).
}
var total = playTurn(”Wilk”, 1);
alert(points);
A to oznacza, że możemy użyć zmiennej
points poza funkcją! Jej wartość nie znika
(a powinna) po zakończeniu wywołania funkcji.
Ten program zachowuje się tak, jakbyśmy napisali go w następujący sposób.
var points = 0;
point była
że chcieliśmy, by
JavaScript uznaje, zapomnieliśmy umieścić przed
bo
zmienną globalną, go „var”; dlatego też działa tak,
nią słowa kluczowe ali ją na poziomie globalnym.
jakbyśmy zadeklarow
function playTurn(player, location) {
if (location == 1) {
points = points + 100;
}
return points;
}
var total = playTurn(”Wilk”, 1);
alert(points);
Pominięcie deklaracji
zmiennej lokalnej może
przysporzyć problemów,
jeśli będzie ona miała
taką samą nazwę jak
jakaś zmienna globalna.
Może to doprowadzić do
niezamierzonego nadpisania
wartości zmiennej globalnej.
jesteś tutaj
139
Przesłanianie zmiennych
Co się stanie, kiedy
zmiennej lokalnej
nadam taką samą nazwę,
jaką ma już istniejąca
zmienna globalna?
W takim przypadku „przesłaniasz”
zmienną globalną
Oto wyjaśnienie. Załóżmy, że dysponujesz zmienną globalną o nazwie
beanCounter, a następnie zdefiniowałeś następującą funkcję.
var beanCounter = 10;
function getNumberOfItems(ordertype) {
var beanCounter = 0;
Mamy zmienne
globalną i lokalną
o tej samej nazwie!
if (ordertype == ”zamowienie”) {
// uĝywamy do czegoĂ zmiennej beanCounter...
}
return beanCounter;
}
W takim przypadku wszystkie odwołania do zmiennej beanCounter
wewnątrz funkcji będą się odnosiły do zmiennej lokalnej, a nie globalnej.
Dlatego mówimy, że zmienna globalna jest przesłonięta przez zmienną
lokalną (innymi słowy, nie widzimy zmiennej globalnej, bo przesłania ją
zmienna lokalna).
Zauważ jednak, że zmienne nie
przeszkadzają sobie wzajemnie:
jeśli zmienimy jedną z nich, nie
będzie to miało wpływu na drugą.
To dwie niezależne zmienne.
140
Rozdział 3.
Przedstawienie funkcji
Ćwiczenie
Poniżej przedstawiliśmy fragment kodu JavaScript zawierający zmienne, definicje funkcji oraz ich
wywołania. Twoim zadaniem jest zidentyfikowanie wszystkich zmiennych używanych we wszystkich
argumentach, a także parametrów, zmiennych lokalnych i zmiennych globalnych. Zapisz nazwy
zmiennych w odpowiednich ramkach widocznych po prawej stronie. Następnie zakreśl zmienne,
które są przesłonięte. Porównaj swoje odpowiedzi z naszymi zamieszczonymi pod koniec rozdziału.
var x = 32;
Argumenty
var y = 44;
var radius = 5;
var centerX = 0;
var centerY = 0;
var width = 600;
var height = 400;
Parametry
function setup(width, height) {
centerX = width/2;
centerY = height/2;
}
function computeDistance(x1, y1, x2, y2) {
var dx = x1 - x2;
var dy = y1 - y2;
Zmienne lokalne
var d2 = (dx * dx) + (dy * dy);
var d = Math.sqrt(d2);
return d;
}
function circleArea(r) {
var area = Math.PI * r * r;
return area;
Zmienne globalne
}
setup(width, height);
var area = circleArea(radius);
var distance = computeDistance(x, y, centerX, centerY);
alert(”Pole: ” + area);
alert(”OdlegïoĂÊ: ” + distance);
jesteś tutaj
141
Pogawędki przy kominku
Pogawędki przy kominku
7HPDWG]LVLHMV]HMSRJDZÚGNLEU]PL=PLHQQDJOREDOQDLORNDOQDVSLHUDMÈ
VLÚRWRNWöUD]QLFKMHVWQDMZDĝQLHMV]DZSURJUDPLH.
Zmienna globalna
Cześć Lokalna, w zasadzie nie wiem, czemu tu jesteś,
bo ja jestem w stanie zaspokoić wszystkie potrzeby,
jakie może mieć programista. W końcu jestem
dostępna wszędzie!
Musisz się ze mną zgodzić, że mogę zastąpić
wszystkie twoje zmienne lokalne, a funkcje i tak będą
dalej działać.
To wcale nie musiałby być bałagan. Programiści
mogliby po prostu tworzyć wszystkie potrzebne
zmienne na samym początku programu, tak by były
umieszczone w jednym miejscu…
Cóż, gdybyś stosowała lepsze nazwy, pewnie łatwiej
byłoby ci zapanować nad wykorzystaniem zmiennych.
To prawda. Ale po co zawracać sobie głowę
argumentami i parametrami, jeśli wszystkie potrzebne
wartości będą dostępne w zmiennych globalnych?
142
Rozdział 3.
Zmienna lokalna
Owszem, ale używanie zmiennych globalnych
wszędzie jest w złym stylu. Bardzo wiele funkcji
potrzebuje zmiennych, które są lokalne. No wiesz…
Ich prywatne zmienne na ich prywatne potrzeby.
A Globalne są wszędzie widoczne.
Cóż, i tak, i nie. Jeśli zachowasz przy tym dużą
ostrożność, to faktycznie możesz tak zrobić. Jednak
zachowanie takiej ostrożności jest bardzo trudne, a jeśli
popełnisz błąd, okaże się, że jedne funkcje korzystają
ze zmiennych, których inne używają w zupełnie innych
celach. Będziesz także zaśmiecać program zmiennymi
globalnymi, które tak naprawdę są potrzebne tylko
w jednej funkcji… To byłby jeden wielki bałagan.
Jasne… I co by się stało, gdybyś musiała wywołać
funkcję, która potrzebuje jakieś zmiennej, np., czy ja
wiem, x, i gdybyś nie mogła sobie przypomnieć, do
czego używałaś jej wcześniej? Musiałabyś przeglądać
cały kod, szukając, czy zmienna x była już gdzieś
wcześniej do czegoś używana! Co za koszmar.
A co z parametrami? Parametry funkcji zawsze są
lokalne. Tego nie możesz obejść.
Przepraszam, czy zastanowiłaś się w ogóle, co ty
mówisz? Cały sens funkcji polega na tym, żeby
można było wielokrotnie używać tego samego kodu
i obliczać różne rzeczy na podstawie różnych danych
wejściowych.
Przedstawienie funkcji
Zmienna globalna
Zmienna lokalna
Ale te twoje zmienne są takie… chwilowe. Zmienne
lokalne pojawiają się i znikają w mgnieniu oka.
W ogóle? Zmienne globalne są podporą dla
programistów JavaScript.
Chyba muszę się czegoś napić.
To prawda. Ale dobra praktyka programistyczna
nakazuje używać zmiennych lokalnych, chyba że
naprawdę potrzebujemy zmiennych globalnych.
A zmienne globalne mogą nas wpędzić w prawdziwe
kłopoty. Widziałam programy pisane w języku
JavaScript, które w ogóle nie używały zmiennych
globalnych.
Pewnie dla niedoświadczonych programistów.
Kiedy jednak programiści uczą się nadawać
swoim programom odpowiednią strukturę w celu
zapewniania poprawności kodu, łatwości jego
utrzymania i dobrego stylu, to uczą się także, by
nie używać zmiennych globalnych, o ile nie jest to
konieczne.
To Globalne mogą pić? Jeśli tak, to naprawdę
wkraczamy na niebezpieczne terytorium.
A komu to byłoby
potrzebne? Wszystko widać
po twojej twarzy.
Na szczęście, nie
jesteś w stanie odczytać
moich zmiennych lokalnych.
Inna funkcja
Funkcja
jesteś tutaj
143
Pytania o zmienne
Nie istnieją
głupie pytania
P: Pamiętanie o zasięgu tych wszystkich zmiennych
lokalnych i globalnych może być problematyczne, dlaczego
zatem nie stosować jedynie zmiennych globalnych?
Do tej pory zawsze tak robiłem.
O: Jeśli piszesz kod, który jest złożony bądź będzie musiał być
utrzymywany przez długi okres czasu, musisz zwracać uwagę
na sposób korzystania ze zmiennych. Jeśli zbyt chętnie tworzysz
zmienne globalne, w pewnym momencie pojawią się trudności ze
śledzeniem, gdzie są używane (i gdzie zmieniają się ich wartości),
a to z kolei może być przyczyną występowania błędów. Zagadnienie
to staje się jeszcze ważniejsze, gdy nad kodem pracuje więcej osób
lub korzystamy z gotowych bibliotek utworzonych przez inne firmy
lub osoby (choć jeśli zostały dobrze napisane, ich struktura powinna
ułatwiać unikanie takich problemów).
A zatem używaj zmiennych globalnych, kiedy ma to sens, a zawsze
wtedy, gdy to możliwe, korzystaj ze zmiennych lokalnych. Kiedy
nabierzesz nieco więcej doświadczenia w korzystaniu z JavaScriptu,
będziesz mógł zastosować inne techniki tworzenia kodu ułatwiające
jego utrzymanie.
P: Na mojej stronie używam zmiennych globalnych,
jednak dołączam do niej także inne pliki JavaScript.
Czy te pliki mają odrębne zestawy zmiennych globalnych?
O: Istnieje tylko jeden zasięg globalny, dlatego skrypty ze
wszystkich plików dołączonych do strony mają dostęp do tego
samego zestawu zmiennych (i tworzą zmienne globalne w tym
samym, jednym zasięgu). Dlatego tak ważne jest ostrożne
stosowanie zmiennych, bo wtedy unikasz potencjalnych kolizji
(a jeśli to możliwe, należy redukować lub nawet całkowicie
wyeliminować wszystkie zmienne globalne).
P: Czy jeśli parametr będzie mieć taką samą nazwę
jak zmienna globalna, to przesłoni tę zmienną?
O: Tak. Jeśli nadasz parametrowi funkcji taką samą nazwę, jaką
ma zmienna globalna, przesłonisz tę zmienną, zupełnie tak samo
jak wtedy, gdy wewnątrz funkcji zadeklarujesz nową zmienną
lokalną o takiej samej nazwie, jaką ma zmienna globalna. Jednak
nie ma nic złego w przesłanianiu zmiennych globalnych, o ile tylko
nie chcemy ich używać wewnątrz funkcji. Dobrym pomysłem
jest opisanie swoich zamierzeń w komentarzach, tak by później,
podczas analizy kodu, nie mieć problemów z jego zrozumieniem.
144
Rozdział 3.
P: Jeśli odświeżę stronę w przeglądarce, to wszystkie
zmienne globalne zostaną ponownie zainicjalizowane?
O: Tak. Odświeżenie strony jest równoznaczne z rozpoczęciem
od samego początku, przynajmniej jeśli chodzi o zmienne. A kiedy
w momencie odświeżania był wykonywany jakiś kod, wszystkie
zmienne lokalne także znikną.
P
: Czy zmienne lokalne zawsze należy deklarować
na początku funkcji?
O: Podobnie jak zmienne globalne, także zmienne lokalne można
deklarować tam, gdzie po raz pierwszy chcemy ich użyć. Jednak
dobra praktyka programistyczna zaleca, by deklarować je na
samym początku funkcji, tak żeby osoby czytające kod mogły łatwo
odnaleźć te deklaracje i szybko zorientować się, jakie zmienne są
używane w danej funkcji. Co więcej, jeśli opóźnisz zadeklarowanie
zmiennej, a potem zdecydujesz się użyć jej w którymś
z wcześniejszych fragmentów kodu funkcji, może się okazać,
że działanie funkcji jest inne, niż się tego spodziewałeś. JavaScript
tworzy wszystkie zmienne lokalne na początku funkcji, niezależnie
od tego, czy je tam zadeklarowałeś, czy nie (to tzw. „wyciąganie”,
ang. hoisting; wrócimy do niego jeszcze później), jednak aż do
momentu inicjalizacji w kodzie wszystkie te zmienne mają wartość
undefined, co może nie być tym, o co Ci chodzi.
P: Odnoszę wrażenie, że wszyscy skarżą się na
nadmierne wykorzystanie zmiennych globalnych. Dlaczego
tak się dzieje? Czy to język tak źle zaprojektowano, czy
ludzie nie wiedzą, co robią? No i co można z tym zrobić?
O: W języku JavaScript zmienne globalne są często nadużywane.
Po części wynika to z faktu, że sam język ułatwia błyskawiczne
rozpoczynanie tworzenia kodu — a to bardzo dobra cecha — gdyż
nie narzuca konieczności stosowania określonej struktury ani innych
narzutów. Wady takiego stylu kodowania uwidaczniają się, gdy
zaczynamy w taki sposób pisać poważne programy, które później
trzeba będzie zmieniać lub utrzymywać przez dłuższy czas (a tak
się dzieje z niemal wszystkimi stronami WWW). A zatem JavaScript
jest potężnym językiem, udostępniającym takie możliwości jak
obiekty, których możemy używać do tworzenia kodu o strukturze
modularnej. Na ten temat napisano już wiele książek, a my
przyjrzymy się obiektom w rozdziale 5.
Przedstawienie funkcji
Sporo mówiliśmy
o zmiennych lokalnych
i globalnych oraz o tym, gdzie
należy je deklarować, nie
powiedzieliśmy jednak, gdzie należy
deklarować nasze funkcje. Czy trzeba
je umieszczać na początku plików
JavaScript?
Funkcje można umieszczać w dowolnym
miejscu plików JavaScript
JavaScript nie zwraca uwagi na to, czy funkcje są deklarowane
przed, czy za miejscem, w którym są używane. Sprawdź np.
poniższy fragment kodu.
var radius = 5;
var area = circleArea(radius);
Zauważ, że używamy
funkcji circleArea przed
jej zdefiniowaniem!
alert(area);
function circleArea(r) {
var a = Math.PI * r * r;
return a;
}
W powyższym przykładzie funk
circleArea nie została zdefinio cja
przed miejscem, w którym ją wana
wywołaliśmy. Jak to, do diab
ła,
działa?
Może się to wydawać bardzo dziwne, zwłaszcza wtedy, kiedy
przypomnimy sobie, że po wczytaniu strony przeglądarka
zaczyna wykonywać kod, zaczynając od początku. Jednak
prawda wygląda tak, że JavaScript dwukrotnie analizuje kod
strony: podczas pierwszego przebiegu odczytuje wszystkie
definicje funkcji, a podczas drugiego wykonuje kod. To
właśnie dlatego funkcje można umieszczać w dowolnym
miejscu pliku.
jesteś tutaj
145
Zakodujmy to
Ćwiczenie
Kodo-skrypto-inator
.RGRVNU\SWRLQDWRUWRMHVWGRSLHURXVWURMVWZR³GĮZLĐF]\EU]ĐF]\DQDZHWWU]HV]F]\
.LHG\MHGQDNMXİFRĤUREL«1RZâDĤQLH]DVWDQDZLDMċFH3URJUDPLĤFLWZLHUG]ċİHZLHG]ċ
MDNG]LDâD&]\SRWUDILV]UR]JU\ĮþNRG]UR]XPLHþMDNG]LDâDLMDNLHJHQHUXMHZ\QLNL"
function clunk(times) {
var num = times;
while (num > 0) {
display(”brzÚk!”);
num = num - 1;
}
}
function kodoskryptoinator(size) {
var facky = 1;
clunkCounter = 0;
if (size == 0) {
display(”brzdÚk!”);
} else if (size == 1) {
display(”biiip!”);
} else {
while (size > 1) {
facky = facky * size;
size = size - 1;
}
clunk(facky);
}
}
function display(output) {
console.log(output);
clunkCounter = clunkCounter + 1;
}
var clunkCounter = 0;
kodoskryptoinator(5);
console.log(clunkCounter);
Zalecamy, by do kodo-skrypo-inatora przekazywać liczby: 0,
1, 2, 3, 4, 5 itd. Ciekawe, czy zorientujesz się, co się
w nim dzieje.
146
Rozdział 3.
Tutaj zapisz
wyniki!
Konsola JavaScript
Przedstawienie funkcji
Sieciowicki poradnik higieny kodu
W Sieciowicach lubimy, by wszystko było czyste, zorganizowane i gotowe do rozszerzania.
Nic nie wymaga bardziej troskliwego utrzymania niż nasz kod, a JavaScript jest dosyć
niechlujny, jeśli chodzi o organizowanie zmiennych i funkcji. Dlatego przygotowaliśmy dla
Ciebie niewielki, elegancki przewodnik zawierający kilka sugestii dla osób, które po raz
pierwszy odwiedzają Sieciowice. Polecamy, jest za DARMO.
Zmienne globalne na samej GÓRZE
Dobrym pomysłem jest grupowanie zmiennych globalnych, o ile tylko jest to możliwe;
jeśli umieścimy je na samym początku pliku, będzie można je łatwo odnaleźć. Choć nie
trzeba tego robić, jednak zarówno inni programiści, jak i my sami będziemy mogli je
znacznie łatwiej zlokalizować, kiedy będą umieszczone na samej górze.
)XQNFMHOXELĈE\þUD]HP
Właściwie… nie do końca. Nie zwracają na to uwagi, to przecież funkcje… Jeśli jednak
umieścimy je razem, będą znacznie łatwiejsze do odnalezienia. Jak wiesz, przeglądarka
analizuje kod, odnajdując w nim funkcje, zanim jeszcze zrobi cokolwiek innego; dlatego
można je umieszczać zarówno na górze, jak i na dole pliku. Jeśli jednak je zgrupujemy,
nasze życie może stać się nieco prostsze. W Sieciowicach lubimy zaczynać skrypty od
deklaracji zmiennych globalnych, a za nimi umieszczamy definicje funkcji.
'HNODUXMP\]PLHQQHORNDOQHQDSRF]ĈWNXIXQNFMLZNWyU\FKVĈXİ\ZDQH
Deklaracje wszystkich zmiennych lokalnych warto umieszczać na samym początku ciała
funkcji. Dzięki temu łatwiej można je znaleźć, a dodatkowo łatwiej zapewnić,
że wszystkie zostaną prawidłowo zadeklarowane przed użyciem.
,WRMXİZV]\VWNRİ\F]\P\EH]SLHF]QHJRSRE\WXLXGDQHJRNRGRZDQLDZ6LHFLRZLFDFK
jesteś tutaj
147
Ćwiczenie — Kim jestem?
?
Kim jestem
Grupa elementów JavaScriptu, przebrana w kostiumy, bawi się w grę towarzyską
„Kim jestem?”. Każdy element udziela podpowiedzi, a Ty na ich podstawie masz
odgadnąć, czym są. Możesz założyć, że zawsze mówią o sobie samą prawdę.
Wypełnij puste miejsca widoczne z prawej strony podpowiedzi.
W dzisiejszej zabawie udział biorą:
funkcja, argument, instrukcja return, zasięg, zmienna lokalna, zmienna
globalna, przekazywanie przez wartość, parametr, wywołanie funkcji,
Math.random, funkcje wbudowane i wielokrotne stosowanie kodu.
Jestem przekazywany do funkcji.
_________________________________
Przesyłam wartości z powrotem do kodu wywołującego.
_________________________________
Jestem najważniejszym słowem kluczowym.
_________________________________
To ja otrzymuję argumenty.
_________________________________
Tak naprawdę to można mnie rozumieć jako „zrób kopię”.
_________________________________
Jestem wszędzie.
_________________________________
To dzięki mnie funkcja zaczyna działać.
_________________________________
Jestem przykładem funkcji dołączonej do obiektu.
_________________________________
alert i prompt to moje przykłady.
_________________________________
Ja jestem tym, do czego funkcje nadają się najlepiej.
_________________________________
Gdzie można mnie zobaczyć.
_________________________________
Jestem w pobliżu, gdy pojawia się moja funkcja.
_________________________________
148
Rozdział 3.
Przedstawienie funkcji
6SUDZDSUöE\UDEXQNXQLHZDUWDZLÚNV]HJRGRFKRG]HQLD.
Sherlock dokończył rozmowę telefoniczną z niekompetentnym inspektorem policji Lestradem
i usiadł wygodnie przed kominkiem, by dokończyć czytanie gazety. Watson spojrzał na niego
z wyczekiwaniem.
Zagadka
na pięć
minut
„Proszę?” — spytał Sherlock, nie odrywając wzroku od gazety.
„A zatem? Co Lestrade miał ci do powiedzenia?” — spytał Watson.
„Och, powiedział, że znaleźli trochę niebezpiecznego kodu na koncie bankowym,
gdzie miały miejsce podejrzane aktywności.”
„I co?” — spytał Watson, próbując ukryć frustrację.
„Lestrade przesłał mi ten kod e-mailem, a ja odpowiedziałem, że ta sprawa nie jest warta
dalszego dochodzenia. Przestępca popełnił krytyczny błąd i nigdy nie będzie w stanie ukraść
pieniędzy” — odpowiedział Sherlock.
„A skąd to wiesz?” — zapytał Watson.
„To oczywiste… O ile wiesz, gdzie patrzeć” — wykrzyknął Sherlock. — „A teraz przestań mnie
nękać pytaniami i pozwól dokończyć czytanie gazety.”
Gdy Sherlock był pochłonięty czytaniem najnowszych doniesień, Watsonowi udało się zajrzeć do
jego telefonu i przejrzeć e-mail z kodem przesłany przez Lestrade’a.
var balance = 10500;
var cameraOn = true;
To jest faktyczny stan
konta bankowego.
function steal(balance, amount) {
cameraOn = false;
if (amount < balance) {
balance = balance - amount;
}
return amount;
cameraOn = true;
}
var amount = steal(balance, 1250);
alert(”=ïodziej: ukradïem ci ” + amount + ”!”);
Dlaczego Sherlock zdecydował, by nie badać dalej tej sprawy? Dlaczego jeden rzut okna na kod
wystarczył, by Sherlock wiedział, że przestępca nigdy nie będzie w stanie ukraść pieniędzy?
Czy w tym kodzie jest jakiś błąd? A może nawet więcej niż jeden?
jesteś tutaj
149
Celne spostrzeżenia
CELNE SPOSTRZEŻENIA
Q
Deklaracje funkcji należy zaczynać od słowa
kluczowego function, a za nim podawać nazwę
funkcji.
Q
Ewentualne parametry funkcji, jeżeli są, należy
zapisywać w nawiasach. Jeśli funkcja nie ma
żadnych parametrów, należy użyć samej pary
nawiasów.
Q
Ciało funkcji należy umieszczać wewnątrz pary
nawiasów klamrowych.
Q
Instrukcje umieszczone w ciele funkcji zostaną
wykonane w momencie jej wywołania.
Q
W celu wywołania funkcji należy podać jej nazwę
oraz listę argumentów, które mają być przekazane
do parametrów (jeśli takie są).
Q
Opcjonalnie, funkcja może zwrócić jakąś wartość,
używając w tym celu instrukcji return.
Q
Funkcja tworzy zasięg lokalny dla swoich
parametrów oraz używanych w niej zmiennych
lokalnych.
Q
Q
150
Zmienne mogą istnieć bądź to w zasięgu
globalnym (są widoczne w każdym miejscu
programu), bądź w zasięgu lokalnym (w takim
przypadku są widoczne tylko wewnątrz funkcji,
w której zostały zadeklarowane).
Zmienne lokalne najlepiej deklarować na samym
początku funkcji.
Rozdział 3.
Q
Jeśli zapomnimy umieścić słowa kluczowego
var w deklaracji zmiennej lokalnej, stanie się ona
zmienną globalną, co może mieć niezamierzone
konsekwencje dla działania programu.
Q
Funkcje są doskonałym narzędziem organizacji
kodu i umożliwiają tworzenie bloków kodu
nadających się do wielokrotnego stosowania.
Q
Kod funkcji można dostosowywać poprzez
przekazywanie argumentów do parametrów
(gdyż przekazując różne argumenty, można
uzyskiwać różne wyniki).
Q
Funkcje są także doskonałym rozwiązaniem
pozwalającym na zmniejszenie lub całkowite
wyeliminowanie powtarzającego się kodu.
Q
W celu wykonywania różnego typu zadań
we własnych programach można korzystać
z wbudowanych funkcji JavaScriptu, takich jak
alert, prompt czy też Math.random.
Q
Korzystanie z wbudowanych funkcji JavaScriptu
oznacza stosowanie gotowego kodu, którego
nie musimy sami pisać.
Q
Nasz kod warto zorganizować w taki sposób,
by funkcje były zgrupowane razem; podobnie
wszystkie zmienne globalne warto zgrupować
i umieścić na samym początku pliku JavaScript.
Przedstawienie funkcji
Zaostrz ołówek
Rozwiązanie
var dogName = ”Burek”;
var dogWeight = 23;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
dogName = ”Mops”;
dogWeight = 13;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
dogName = ”Reksio”;
dogWeight = 53;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
dogName = ”Saba”;
dogWeight = 17;
if (dogWeight > 20) {
console.log(dogName
} else {
console.log(dogName
}
Przeprowadź krótką analizę poniższego kodu. Jak działa?
Wybierz dowolne odpowiedzi podane poniżej
lub zapisz swoją własną analizę.
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
+ ” szczeka HAU HAU”);
+ ” szczeka hau hau”);
tkie odpowiedzi!
Zaznaczyliśmy wszys
______________________________________________
$7HQNRGZ\GDMHVLĊEDUG]R
QDGPLDURZ\
%*G\E\ĞP\FKFLHOL]PLHQLü
JHQHURZDQHZ\QLNLOXEGRGDüLQQH
LQIRUPDFMHRSVDFKZ\PDJDáRE\WR
VSRURSUDF\
&:SLV\ZDQLHJRE\áRE\PĊF]ąFH
'7RQLHMHVWQDMEDUG]LHMF]\WHOQ\NRG
MDNLZLG]LDáHPZĪ\FLX
na to, że programista przypuszczał,
E. Wygląda
_________________________________
iż waga psa może się z czasem zmieniać.
_________________________________
jesteś tutaj
151
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Poniżej przedstawiliśmy kilka kolejnych wywołań funkcji bark. Obok każdego z nich zapisz, jakie,
według Ciebie, powinny być jego wyniki, ewentualnie czy kod spowoduje wystąpienie błędu.
Oto nasze rozwiązanie.
bark(”Reksio”, 20); _______________________________________________
Reksio szczeka hau hau
Skuter szczeka hau hau
bark(”Skuter”, -1); _______________________________________________
, czy waga
Nasza funkcja bark nie sprawdza wywołanie
to
psa jest większa od 0. Dlatego
od 20.
zadziała, gdyż –1 jest mniejsze
bark(”Dino”, 0, 0); _______________________________________________
Dino szczeka hau hau
Funkcja bark ignoruje dodatkowy argument, czyli 0.
Użycie wartości 0 jako wagi nie ma większego sensu,
jednak funkcja zadziała prawidłowo.
Fido szczeka hau hau
bark(”Fido”, ”20”); _______________________________________________
Tu porównujemy łańcuch znaków “20” z liczbą 20.
Łańcuch “20” nie jest większy od 20, dlatego Fido
szczeka „hau hau”. (Dalej w książce dowiesz się,
w jaki sposób JavaScript porównuje “20” z 20).
152
bark(”Saba”, 10);
_______________________________________________
Saba szczeka hau hau
bark(”Agent”, 21);
Agent szczeka HAU HAU
_______________________________________________
Rozdział 3.
Przedstawienie funkcji
Rozwiązanie magnesików z kodem
']LDâDMċF\NRG-DYD6FULSW]RVWDâSRFLĐW\QD
NDZDâHF]NLSU]\F]HSLRQ\QDORGyZFHLFDâNRZLFLH
Z\PLHV]DQ\&]\SRWUDILV]SRXNâDGDþJR]SRZURWHP
ZG]LDâDMċF\SURJUDPJHQHUXMċF\SU]HGVWDZLRQH
SRQLİHMZ\QLNL"=ZUyþXZDJĐİHQDORGyZFHPRİH
E\þWURFKĐQLHSRWU]HEQHJRNRGX]DWHP³E\þPRİH
³QLHEĐG]LHV]PXVLDâXİ\þZV]\VWNLFKPDJQHVLNyZ
$RWRQDV]HUR]ZLċ]DQLH
function
whatShallIWear
(
temp
if (temp < 15) {
console.log("Ubierz kurtkÚ.");
}
)
{
Pozostałe magnesiki
,
,
else if (temp < 25) {
console.log("Ubierz sweter.");
}
,
}
}
temperature
else {
t-shirt.");
console.log("Ubierz
}
}
whatShallIWear(10)
;
whatShallIWear(30);
whatShallIWear(15);
Konsola JavaScript
8ELHU]NXUWNÚ
Ubierz t-shirt.
Ubierz sweter.
jesteś tutaj
153
Rozwiązanie ćwiczenia
Ćwiczenie
Rozwiązanie
Poniżej znajdziesz fragment kodu JavaScript, zawierający zmienne, definicje
funkcji oraz ich wywołania. Twoim zadaniem jest wskazanie wszystkich
zmiennych, funkcji, argumentów oraz parametrów. Ich nazwy zapisz
w odpowiednich ramkach z prawej strony. A oto nasze rozwiązanie.
Zmienne
function dogYears(dogName, age) {
var years = age * 7;
myDog, guests, pet,
console.log(dogName + ” ma ” + years + ” lata.”);
years, defaultSound
}
var myDog = ”Fido”;
dogYears(myDog, 4);
Funkcje
function makeTea(cups, tea) {
dogYears, makeTea,
console.log(”ParzÚ ” + cups + ” filiĝanek herbaty ” + tea
+ ”.”);
secret, speak
}
var guests = 3;
makeTea(guests, ”Earl Grey”);
function secret() {
console.log(”TajemnicÈ bytu jest liczba 42.”);
Funkcje wbudowane
alert, console.log,
prompt
}
secret();
function speak(kind) {
Argumenty
var defaultSound = ””;
myDog, 4, guests,
if (kind == ”pies”) {
“Earl Grey”, pet oraz
alert(”Hau”);
} else if (kind == ”kot”) {
alert(”Miau”);
} else {
wszystkie łańcuchy
znaków przekazywane
do funkcji alert
i console.log
alert(defaultSound);
}
}
var pet = prompt(”Podaj rodzaj zwierzaka: ”);
speak(pet);
154
Rozdział 3.
Parametry
dogName, age, cups,
tea, kind
Przedstawienie funkcji
Ćwiczenie
Rozwiązanie
Poniżej przedstawiliśmy fragment kodu JavaScript zawierający zmienne, definicje funkcji oraz ich
wywołania. Twoim zadaniem jest zidentyfikowanie wszystkich zmiennych używanych we wszystkich
argumentach, a także parametrów, zmiennych lokalnych i zmiennych globalnych. Zapisz nazwy
zmiennych w odpowiednich ramkach widocznych po prawej stronie. Następnie zakreśl zmienne,
które są przesłonięte. A oto nasze rozwiązanie.
Argumenty
var x = 32;
var y = 44;
var radius = 5;
Nie zapominaj
o argumentach
funkcji alert.
var centerX = 0;
var centerY = 0;
width, height, radius,
x, y, centerX,
centerY, "Pole: " +
area, "Odległość: " +
distance
var width = 600;
var height = 400;
Parametry
function setup(width, height) {
width, height, x1, y1,
centerX = width/2;
x2, y2, r
centerY = height/2;
}
function computeDistance(x1, y1, x2, y2) {
var dx = x1 - x2;
Zmienne lokalne
var dy = y1 - y2;
var d2 = (dx * dx) + (dy * dy);
var d = Math.sqrt(d2);
return d;
Zmienna lokalna area
przesłania zmienną
globalną o tej samej
nazwie.
dx, dy, d2, d,
area
}
function circleArea(r) {
var area = Math.PI * r * r;
Zmienne globalne
return area;
}
Nie zapomnij o area
i distance. To także
są zmienne globalne.
setup(width, height);
x, y, radius, centerX,
centerY, width, height,
area, distance
var area = circleArea(radius);
var distance = computeDistance(x, y, centerX, centerY);
alert(”Pole: ” + area);
alert(”OdlegïoĂÊ: ” + distance);
jesteś tutaj
155
Rozwiązanie ćwiczenia
Ćwiczenie
Rozwiązanie
Kodo-skrypto-inator
.RGRVNU\SWRLQDWRUWRMHVWGRSLHURXVWURMVWZR³GĮZLĐF]\EU]ĐF]\DQDZHWWU]HV]F]\
.LHG\MHGQDNMXİFRĤUREL«1RZâDĤQLH]DVWDQDZLDMċFH3URJUDPLĤFLWZLHUG]ċİHZLHG]ċ
MDNG]LDâD&]\SRWUDILV]UR]JU\ĮþNRG]UR]XPLHþMDNG]LDâDLMDNLHJHQHUXMHZ\QLNL"
Konsola JavaScript
$RWRQDV]HUR]ZLċ]DQLH
A co z innymi
wartościami?
Konsola JavaScript
EU]ĊN
1
Konsola JavaScript
ELLLS
1
Jeśli do funkcji kodoskryptoinator
przekażesz 5, w oknie konsoli
zobaczysz słowo „brzęk!” wyświetlone
120 razy (bądź też, jak w naszym
przedstawionym obok przypadku,
zobaczysz (120) brzęk!, a poniżej
liczbę 120.
EU]ĊN
120
Kodo-skrypto-inator i liczba 0
Kodo-skrypto-inator i liczba 1
Konsola JavaScript
EU]ĊN
2
Kodo-skrypto-inator i liczba 2
Konsola JavaScript
Kodo-skrypto-inator i liczba 3
EU]ĊN
6
Konsola JavaScript
EU]ĊN
24
Konsola JavaScript
Kodo-skrypto-inator i liczba 4
Kodo-skrypto-inator i liczba 5
EU]ĊN
120
A co to wszystko znaczy? Słyszeliśmy, że kodo-skrypto-inator został wymyślony
przez ciekawskiego gościa, który był zafascynowany anagramami. No wiecie, np.
anagramami KOT są KTO, OKT, OTK, TKO i TOK. A zatem, jeśli słowo ma trzy
litery, to kodo-skrypto-inator informuje, że jego litery można zapisać maksymalnie
na sześć różnych sposobów. Jeśli użyjemy słowa „piwko”, będzie można podać
aż 120 kombinacji jego liter, nieźle! Cóż, przynajmniej tak słyszeliśmy.
A tu dowiedzieliśmy się, że maszyna oblicza silnię! Kto by pomyślał!?
Sprawdź w wikipedii, czym jest silnia!
156
Rozdział 3.
Przedstawienie funkcji
'ODF]HJR6KHUORFN]GHF\GRZDãE\QLHEDGDþGDOHMWHMVSUDZ\"
'ODF]HJRMHGHQU]XWRNDQDNRGZ\VWDUF]\ãE\6KHUORFNZLHG]LDã
İHSU]HVWčSFDQLJG\QLHEčG]LHZVWDQLHXNUDĤþSLHQLčG]\"
&]\ZW\PNRG]LHMHVWMDNLĤEãĈG"$PRİHQDZHWZLčFHMQLİMHGHQ"
2WRQDV]HUR]ZLĈ]DQLH
Zagadka
na pięć
minut
var balance = 10500;
balance jest zmienną globalną…
var cameraOn = true;
Rozwiązanie
…jednak ten parametr ją
przesłania.
function steal(balance, amount) {
cameraOn = false;
if (amount < balance) {
balance = balance - amount;
}
Zwracamy ukradzioną
kwotę pieniędzy…
A zatem kiedy w funkcji steal
zmienimy stan konta, nie
zmieniamy faktycznego stanu
konta bankowego!
return amount;
cameraOn = true;
}
…jednak nie używamy jej do
zmiany faktycznego stanu konta.
A zatem stan konta pozostanie
taki sam jak na początku.
var amount = steal(balance, 1250);
alert("=ïodziej: ukradïem ci " + amount + "!");
Złodziej sądzi, że udało mu się
ukraść pieniądze, ale jest
w błędzie!
A co więcej, złodziej nie tylko nie zdołał ukraść
pieniędzy, lecz dodatkowo także zapomniał włączyć
kamery, co jest wyraźnym sygnałem, że stało się coś
podejrzanego. Pamiętaj, że instrukcja return powoduje
natychmiastowe zakończenie wykonywania funkcji,
a zatem wszystkie kolejne wiersze kodu funkcji zostaną
po prostu zignorowane!
jesteś tutaj
157
Rozwiązanie ćwiczenia
im jestem?
K
Grupa elementów JavaScriptu, przebrana w kostiumy, bawi się w grę towarzyską
„Kim jestem?”. Każdy element udziela podpowiedzi, a Ty na ich podstawie masz
odgadnąć, czym są. Możesz założyć, że zawsze mówią o sobie samą prawdę.
Wypełnij puste miejsca widoczne z prawej strony podpowiedzi. Oto nasze
rozwiązanie.
W dzisiejszej zabawie udział biorą:
LH
Q
D
]
È
L
Z
]
R
5
funkcja, argument, instrukcja return, zasięg, zmienna lokalna, zmienna
globalna, przekazywanie przez wartość, parametr, wywołanie funkcji,
Math.random, funkcje wbudowane i wielokrotne stosowanie kodu.
Jestem przekazywany do funkcji.
argument
_________________________________
Przesyłam wartości z powrotem do kodu wywołującego.
instrukcja return
_________________________________
Jestem najważniejszym słowem kluczowym.
function
_________________________________
To ja otrzymuję argumenty.
parametr
_________________________________
Tak naprawdę to można mnie rozumieć jako „zrób kopię”.
przekazywanie przez wartość
_________________________________
Jestem wszędzie.
zmienna globalna
_________________________________
To dzięki mnie funkcja zaczyna działać.
wywołanie funkcji
_________________________________
Jestem przykładem funkcji dołączonej do obiektu.
Math.random
_________________________________
alert i prompt to moje przykłady.
funkcja wbudowana
_________________________________
Ja jestem tym, do czego funkcje nadają się najlepiej.
wielokrotne stosowanie kodu
_________________________________
Gdzie można mnie zobaczyć.
zasięg
_________________________________
Jestem w pobliżu, gdy pojawia się moja funkcja.
zmienna lokalna
_________________________________
158
Rozdział 3.
4.3RU]ÈGNRZDQLHQDV]\FKGDQ\FK
Tablice
Często tu
przyjeżdżasz?
Tylko wtedy,
kiedy mnie pchają.
JavaScript to nie tylko liczby, łańcuchy znaków i wartości logiczne.
Dotychczas pisałeś jedynie kod JavaScript, w którym były używane wartości typów
prostych — proste łańcuchy znaków, liczby i wartości logiczne, takie jak ”Burek”,
23 oraz true. Korzystając z takich wartości, można zrobić naprawdę dużo, jednak
w którymś momencie będziesz musiał zacząć posługiwać się znacznie większą ilością
danych. Przykładowo mogą to być wszystkie produkty umieszczone w koszyku
zakupowym albo utwory na liście odtwarzania, albo gwiazdozbiory i współrzędne
poszczególnych gwiazd, albo cały katalog produktów. Jednak do tego potrzebujesz
czegoś bardziej… sexy. W języku JavaScript preferowanym typem danych dla takich
uporządkowanych zbiorów informacji jest tablica, a w tym rozdziale dokładnie
przeanalizujemy, jak umieszczać dane w tablicach, przekazywać tablice oraz jak na
nich operować. Dalej w książce omówimy także kilka innych sposobów strukturyzacji
danych, jednak zaczniemy od tablic.
to jest nowy rozdział
159
Przedstawiamy BańkoCorp
Czy możesz pomóc firmie BańkoCorp?
Poznaj firmę BańkoCorp. Jej niezmordowany dział badań dba o to, by wszelkiego
typu magiczne różdżki i inne urządzenia na całym świecie tworzyły najlepsze możliwe
bańki. Dziś przy użyciu „fabryki baniek” testują kilka wariantów płynu do robienia
baniek — konkretnie rzecz biorąc — testy polegają na policzeniu, ile baniek uda się
zrobić z każdego płynu. A oto dane, jakimi dysponuje dział badań.
kątem liczby
Każdy z płynów był testowany pod
użyciu.
jego
przy
ić
zrob
a
możn
e
któr
baniek,
Wszystkie probówki
testowe są
ponumerowane od
0 do 9 i zawierają
nieznacznie
różniące się płyny
do robienia baniek.
0
1
2
3
60
50
60
58
4
54
5
6
7
8
54
58
50
52
Oczywiście zależy nam na tym, by zapisać te wszystkie dane w kodzie JavaScript,
by później można było je programowano przetworzyć. Jednak to naprawdę sporo
wartości. W jaki sposób napisałbyś kod, by obsłużyć wszystkie te wartości?
160
Rozdział 4.
9
54
A to wynik —
liczba baniek
wytworzonych
przez fabrykę
baniek z porcji
danego płynu.
Porządkowanie naszych danych
Jak reprezentować wiele wartości w JavaScripcie?
Już wiesz, w jaki sposób w języku JavaScript można reprezentować pojedyncze wartości, takie jak
łańcuchy znaków, liczby i wartości logiczne. Jak jednak można reprezentować wiele wartości, takich
jak wyniki testów fabryki baniek dla dziesięciu różnych płynów? W języku JavaScript do tego celu
służą tablice. Tablica jest typem danych, który pozwala na przechowywanie wielu wartości. Poniżej
przedstawiliśmy tablicę, która przechowuje wyniki wygenerowane przez fabrykę baniek.
var scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54];
h, zapisanych w jednej
To dziesięć wartości zgrupowanyc zmiennej scores.
nych
pisa
przy
e
ępni
nast
a
cy,
tabli
Wszystkie te wartości możesz potraktować jak jedną całość, możesz także, jeśli będzie to konieczne,
odwoływać się do poszczególnych wyników. Sprawdź poniższy kod.
Aby odwołać się do elementu tablicy, używany
jest następujący zapis: nazwa zmiennej
zawierającej tablicę oraz indeks elementu
zapisany w nawiasach kwadratowych.
var solution2 = scores[2];
Zauważ, że numeracja elementó
w
zera. A zatem wynik dla pierwsze tablicy zaczyna się od
baniek jest zapisany w elemenci go płynu do robienia
e
scores[0]; analogicznie, wynik uzyso indeksie 0, czyli
płynu będzie zapisany w elemenci kany dla trzeciego
e o indeksie 2, czyli
scores[2].
alert(”Pïyn do baniek nr 2 wynik: ” + solution2);
Mój płyn do
robienia baniek o indeksie
2 bez wątpienia okaże się
najlepszy.
Jeden ze
speców od
baniek z firmy
BańkoCorp.
jesteś tutaj
161
Wprowadzenie do tablic
Jak działają tablice?
Zanim zaczniemy pomagać firmie BańkoCorp, musimy się upewnić, że dobrze zrozumiałeś,
czym są tablice. Jak już wspominaliśmy, tablic możemy używać do przechowywania wielu
wartości (w odróżnieniu od zmiennych, które pozwalają zapisać tylko jedną wartość, taką
jak liczba lub łańcuch znaków). Zazwyczaj tablice będziemy stosować do grupowania
podobnych informacji, takich jak wyniki eksperymentu z bańkami, smaki lodów,
temperatury o różnych porach dnia lub nawet odpowiedzi na serię pytań typu „tak lub
nie”. Zawsze wtedy, gdy będziemy dysponowali zbiorem danych, które chcemy zgrupować,
możemy utworzyć tablicę i w niej zapisać te dane, a później, gdy będzie to potrzebne,
odwoływać się do przechowywanych w niej wartości.
Tworzenie tablic
Zauważ, że
poszczególne elementy
tablicy są od siebie
oddzielone przecinkami.
Załóżmy, że chcemy utworzyć tablicę przechowującą różne smaki lodów.
Poniżej pokazaliśmy, jak to można zrobić.
var flavors = [”waniliowe”, ”kajmakowe”, ”truskawkowe”, ”czekoladowe”, ”malaga”];
Przypiszemy tablicę
zmiennej flavors.
Aby rozpocząć
tablicę, zapisz
otwierający nawias
kwadratowy („[”)…
… po czym zakończ
ją zamykającym
nawiasem
kwadratowym („]”).
Następnie wypisz
wszystkie elementy
tablicy…
Kiedy tworzysz tablicę, każdy jej element jest umieszczany w komórce, która
ma swój indeks. W tablicy flavors pierwszy element, ”waniliowe”, ma
indeks 0, drugi element, ”kajmakowe”, ma indeks 1 itd. Oto jak można sobie
wyobrażać tablice.
Tablica gromadzi
wszystkie
te wartości.
e"
e"
e"
e"
ow
ow
w
w
k
d
io
ko
aw
la
a"
il
ma
sk
ko
ag
n
j
u
e
l
a
a
r
z
a
"w
"k
"t
"c
"m
0
ÁDYRUV
162
Tablica jest
zapisywana
w zmiennej.
Rozdział 4.
1
2
Każda wartość
jest zapisana
w komórce
o określonym
indeksie, przy
czym numeracja
indeksów zaczyna
się od zera.
3
4
Każda komórka tablicy
zawiera wartość.
Porządkowanie naszych danych
Jak odwoływać się do elementów tablic?
Każdy element tablicy ma swój indeks i właśnie ten indeks ma kluczowe znaczenie podczas
odwoływania się do wartości przechowywanych w tablicach oraz ich modyfikowania. Aby
odwołać się do elementu tablicy, wystarczy podać nazwę zmiennej tablicowej, a za nią wpisać
indeks umieszczony wewnątrz pary nawiasów kwadratowych. Takiego wyrażenia można
używać wszędzie tam, gdzie da się umieścić zmienną.
To wyrażenie zwraca wartość elementu tablicy
flavors o indeksie 2 (czyli „truskawkowe”), która jest
następnie zapisywana w zmiennej flavorOfTheDay.
var flavorOfTheDay = falvors[2];
Aby pobrać element tablicy, należy
podać zarówno jej nazwę, jak i indeks
jej elementu.
W zmiennej falvorOfTheDay zapisywana jest wartość
przechowywana w elemencie falvors[2].
e"
ow
k
aw
sk
u
r
"t
e"
e"
ow
e"
e"
ow
ad
ow
ow
wk
l
i
k
a
a"
o
k
il
ma
sk
ag
ze
an
aj
ru
al
"c
"w
"k
"t
"m
ÁDYRU2I7KH'D\
0
1
2
3
4
ÁDYRUV
Pamiętaj, że indeksy tablic zaczynają się od
zera, dlatego wyrażenie flavors[2] reprezentuje
wartość trzeciego elementu tablicy.
Aktualizacja wartości w tablicach
Indeksu można także użyć w celu zmiany wartości zapisanej w tablicy.
Instrukcja ta zmienia wartość elementu
o indeksie 3 (który wcześniej zawierał
łańcuch „czekoladowe”) na nową, czyli
„waniliowo-czekoladowe”.
flavors[3] = ”waniliowo-czekoladowe”;
A zatem po wykonaniu tej instrukcji zawartość tablicy
będzie można przedstawić w następujący sposób.
"
owe
d
a
l
eko
z
e"
c
"
"
w
e
e
o
oow
ow
wk
a"
iow
li
ak
ka
l
i
m
s
ag
i
n
j
u
l
n
a
a
r
a
"wa
"w
"k
"t
"m
ÁDYRUV
0
1
2
3
4
Wartość elementu tablicy
o indeksie 3 została
zmieniona.
jesteś tutaj
163
Pobieranie długości tablicy
A w ogóle jak duża jest tablica?
Załóżmy, że ktoś przekazał Ci ładną, dużą tablicę pełną istotnych danych. Wiesz,
co w niej jest, ale najprawdopodobniej nie wiesz dokładnie, jak duża jest ta tablica.
Na szczęście, każda tablica posiada właściwość length. Więcej na temat właściwości
oraz ich działania napiszemy w następnym rozdziale; na razie wystarczy, jeśli będziesz
wiedział, że jest to po prostu wartość skojarzona z tablicą. A oto sposób użycia
właściwości length.
Aby odczytać długość
tablicy, należy podać
nazwę tablicy, następnie
kropkę „.” i w końcu
słowo length.
Każda tablica ma właściwość length,
która zawiera liczbę elementów
aktualnie zapisanych w tablicy.
var numFlavors = falvors.length;
Właściwość length ma wartość 5,
gdyż tablica zawiera pięć elementów.
Teraz zmienna numFlavors zawiera
liczbę elementów tablicy, czyli 5.
oe"
e"
"
iow owe"
ow
l
ow
"
we
k
i
k
o
w
n
ad
ga
a
ma
li
la
sk
"wa ekol
aj
a
ni
u
k
m
a
r
"
"
cz
"w
"t
0
ÁDYRUV
1
2
3
4
zawsze będzie
Zauważ, że długość tablicy indeksu jej
i
tośc
war
od
ksza
wię
n
jede
o
to z faktu,
ostatniego elementu. Wynika ane od zera.
erow
num
są
icy
tabl
że indeksy
Zaostrz ołówek
Tablica products zawiera smaki lodów Janki i Bartka. Poszczególne smaki były
dodawane do tablicy w kolejności, w jakiej powstawały. Dokończ kod, który
pozwoli określić ostatni z wymyślonych smaków lodów.
var products = [”Czu-Czu-Lada”, ”MiÚtowy chïöd”, ”Ciasto naleĂnikowe”, ”Guma balonowa”];
var last = ________________;
var recent = products[last];
164
Rozdział 4.
Porządkowanie naszych danych
Wypróbujcie mój nowy
korpo-zdanio-budowator,
a będziecie mogli mówić
zupełnie jak wasz szef albo
ci goście z marketingu.
Wypróbuj kod zupełnie nowej
aplikacji korpo-zdanio-budowatora
i zanim przejdziesz na następną
stronę, przekonaj się, czy potrafisz
powiedzieć, do czego służy…
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Korpo-zdanio-budowator</title>
<script>
Nie odnieśliście wrażenia, że nasza
poważna aplikacja biznesowa
z rozdziału 1. nie jest dostatecznie
poważna? Jeśli potrzebujecie czegoś,
co moglibyście pokazać szefowi,
wypróbujcie tę aplikację.
function makePhrases() {
var words1 = [”nieprzerwane”, ”wielowarstwowe”, ”tysiÈcmetrowe”, ”biznesowe”, ”niepowstrzymane”];
var words2 = [”wspomagane”, ”wartoĂciowe”, ”zorientowane”, ”skoncentrowane”, ”wyröwnane”];
var words3 = [”procesy”, ”rozwiÈzania”, ”punkty”, ”strategie”, ”wizje”];
var rand1 = Math.floor(Math.random() * words1.length);
var rand2 = Math.floor(Math.random() * words2.length);
var rand3 = Math.floor(Math.random() * words3.length);
var phrase = words1[rand1] + ” ” + words2[rand2] + ” ” + words3[rand3];
alert(phrase);
}
makePhrases();
</script>
</head>
<body></body>
</html>
jesteś tutaj
165
Analiza kodu korpo-zdanio-budowatora
Korpo-zdanio-budowator
Mamy nadzieję, że już zorientowałeś się, iż powyższy kod jest idealnym
narzędziem do opracowania chwytliwego sloganu marketingowego dla
Twojego kolejnego startupu. Posłużył on w przeszłości do utworzenia
takich hitów jak: „tysiącmetrowe zorientowane wizje” czy też
„wielowarstwowe wspomagane strategie”; dlatego teraz mamy nadzieję,
że również w przyszłości generowane przez niego slogany będą się cieszyć
równie wielkim powodzeniem. A teraz zobaczmy, jak ten niewielki
fragment kodu naprawdę działa.
1
Na początku definiujemy funkcję makePhrases, którą możemy wywołać
dowolnie wiele razy, by wygenerować potrzebne slogany.
function makePhrases() {
Tutaj zostanie umieszczony cały kod funkcji makePhrases,
już zaraz się nim zajmiemy…
}
makePhrases();
2
Definiujemy funkcję o nazwie
makePhrases, którą będziemy mogli
wywołać później.
Tutaj wywołujemy funkcję makePhrases
i
dokładnie jednen raz; jednak gdybyśmy chciel
uzyskać więcej niż jeden slogan, moglibyśmy
wywołać ją więcej razy.
Skoro strukturę mamy z głowy, możemy się zająć kodem funkcji makePhrases. Zacznijmy od
przygotowania trzech tablic. Każda z nich będzie zawierać słowa, których będziemy używali do tworzenia
sloganów. W następnym kroku utworzymy sam slogan, wybierając losowo z każdej tablicy jedno słowo.
Tworzymy zmienną o nazwie words1, w której
zapisujemy pierwszą tablicę.
var words1 = [”nieprzerwane”, ”wielowarstwowe”, ”tysiÈcmetrowe”, ”biznesowe”, ”niepowstrzymane”];
W tablicy umieścimy pięć łańcuchów znaków. Jeśli
chcesz, możesz zastąpić je słowami, które obecnie są
najbardziej chwytliwe.
var words2 = [”wspomagane”, ”wartoĂciowe”, ”zorientowane”, ”skoncentrowane”, ”wyröwnane”];
var words3 = [”procesy”, ”rozwiÈzania”, ”punkty”, ”strategie”, ”wizje”];
Kolejne dwie tablice słów zapisujemy w zmiennych
words2 oraz words3.
166
Rozdział 4.
Porządkowanie naszych danych
3 A teraz wygenerujemy trzy liczby losowe, które posłużą do wybrania trzech słów, z których
utworzymy nasz slogan. Pamiętasz zapewne z rozdziału 2., że funkcja Math.random
generuje liczbę z zakresu do 0 do 1 (lecz bez 1). Jeśli pomnożymy tę liczbę przez długość
tablicy i użyjemy funkcji Math.floor w celu zaokrąglenia wyniku w dół do liczby całkowitej,
uzyskamy liczbę z zakresu od 0 do liczby o jeden mniejszej od długości tablicy.
var rand1 = Math.floor(Math.random() * words1.length);
rand1 będzie liczbą z zakresu
od 0 do wartości ostatniego
indeksu tablicy words1.
var rand2 = Math.floor(Math.random() * words2.length);
var rand3 = Math.floor(Math.random() * words3.length);
W podobny sposób
obliczamy wartości
zmiennych rand2
oraz rand3.
4 Teraz możemy utworzyć chwytliwy slogan marketingowy, wybierając losowe słowo z
każdej tablicy, łącząc je razem w jeden łańcuch znaków i dodając pomiędzy nimi odstępy,
dla zachowania czytelności.
Każdej z liczb losowych używamy
jako indeksu tablicy…
Definiujemy kolejną zmienną, w której
zapiszemy nasz slogan.
var phrase = words1[rand1] + ” ” + words2[rand2] + ” ” + words3[rand3];
5 To już prawie koniec. Dysponujemy już sloganem, wystarczy go tylko wyświetlić.
Standardowo użyjemy do tego celu funkcji alert.
alert(phrase);
6
No dobrze, dokończ ten ostatni wiersz kodu, przyjrzyj mu się raz jeszcze i zanim
wyświetlisz stronę w przeglądarce, posmakuj przez chwilę uczucia spełnienia.
Wypróbuj aplikację i ciesz się wygenerowanym sloganem marketingowym.
!
Oto nasz slogan
Wystarczy odświeżyć stronę, by odkryć niesk
możliwości marketingowe (no dobrze, może ończone
nie
nieskończone, ale spróbuj z nami współpraco
— staramy się, by ten prosty kod był bardz wać
iej
fascynujący!).
jesteś tutaj
167
Pytania o tablice
Nie istnieją
głupie pytania
P: Czy kolejność elementów
się starać, by używane tablice miały rozsądną
wielkość — np. kilkaset elementów.
O: Zwykle tak. W przypadku tablicy
P: Czy tablica może być pusta?
O: Owszem może; co więcej, już niedługo
w tablicy ma znaczenie?
z wynikami testów firmy BańkoCorp kolejność
ma bardzo duże znaczenie, gdyż indeks
wyniku informuje o tym, którego płynu do
baniek użyto — płyn numer 0 uzyskał wynik
60 i wartość ta została zapisana w elemencie
o indeksie 0. Gdybyśmy pomieszali kolejność
wyników w tablicy, cały eksperyment nie
miałby sensu! Jednak w innych przypadkach
kolejność elementów w tablicy może nie mieć
znaczenia. Jeśli np. używamy tablicy wyłącznie
do przechowywania listy losowych słów
i nie obchodzi nas ich kolejność, porządek
elementów tablicy nie będzie mieć znaczenia.
Jeśli jednak później uznamy, że chcemy te
słowa posortować alfabetycznie, kolejność
znowu będzie kluczowa. A zatem wszystko
zależy od sposobu korzystania z tablicy.
Ogólnie rzecz biorąc, zauważysz zapewne,
że kolejność elementów tablicy częściej ma
znaczenie, niż go nie ma.
zobaczysz przykład takiej pustej tablicy.
Poniżej pokazaliśmy, jak można utworzyć
pustą tablicę.
var emptyArray = [ ];
Jeśli zaczniemy od pustej tablicy, później
będziemy mogli dodawać do niej elementy.
P: Do tej pory widziałem jedynie
tablice zawierające łańcuchy i liczby;
czy w tablicach można umieszczać
także inne dane?
O: Tak, można. Okazuje się, że w tablicach
można zapisywać praktycznie wszystkie
wartości spotykane w języku JavaScript,
w tym liczby, łańcuchy znaków, wartości
logiczne, inne tablice, a nawet obiekty
(nimi zajmiemy się później).
P: Ile danych można umieścić
P: Czy wszystkie wartości w tablicy
O: Teoretycznie tak dużo, jak nam się
O: Nie, nie muszą; choć zazwyczaj
w tablicy?
spodoba. Jednak praktycznie rzecz biorąc,
ich ilość jest ograniczona wielkością pamięci
komputera. Każdy element tablicy zajmuje
troszkę miejsca w pamięci. Pamiętaj,
że JavaScript działa w przeglądarce,
a przeglądarka jest jednym z wielu
programów działających na komputerze. Jeśli
nieprzerwanie będziemy dodawać elementy
do tablicy, kiedyś w komputerze zabraknie
pamięci. Jednak zależnie od tego, co
będziemy umieszczać w tablicy, potencjalna
maksymalna liczba jej elementów może
sięgać tysięcy lub nawet milionów, a tak
wielu danych rzadko będziesz potrzebował.
Pamiętaj także, że im więcej elementów
będzie liczyć tablica, tym wolniej będzie
działał program, dlatego też zazwyczaj warto
168
Rozdział 4.
muszą być tego samego typu?
wszystkie wartości w tablicy faktycznie są
tego samego typu. W odróżnieniu od wielu
innych języków, JavaScript nie wymusza,
by wszystkie elementy tablicy były tego
samego typu. Jednak umieszczając w tablicy
wartości różnych typów, musimy być
wyjątkowo ostrożni podczas korzystania
z nich. Wyjaśniamy, dlaczego tak się dzieje.
Załóżmy, że dysponujemy tablicą wartości
[1, 2, ”Burek”, 4, 5]. Jeśli później
napiszemy kod, który sprawdza, czy wartości
w tablicy są większe od np. 2, to co się stanie,
kiedy sprawdzimy, czy ”Burek” jest większy
od 2? Aby upewnić się, że nie zrobimy
czegoś, co będzie bez sensu, konieczne
byłoby sprawdzanie typu każdej wartości
pobieranej z tablicy, zanim ją wykorzystamy
gdzieś w kodzie. Oczywiście, można to robić
(o czym się przekonasz dalej w tej książce),
jednak ogólnie rzecz biorąc, znacznie łatwiej
i bezpieczniej umieszczać w tablicy dane
tego samego typu.
P: Co się stanie, gdy odwołam się do
tablicy przy użyciu zbyt dużego lub
zbyt małego (np. mniejszego od zera)
indeksu?
O: Jeśli będziesz dysponować tablicą,
taką jak ta:
var a = [1, 2, 3];
i spróbujesz odwołać się do elementu
a[10] lub a[-1], uzyskasz wynik
undefined. A zatem będziesz starał się
używać jedynie prawidłowych indeksów
bądź za każdym razem będziesz musiał
sprawdzać, czy pobrana wartość jest różna
od undefined.
P: Już wiem, że pierwszy element
tablicy mogę pobrać, używając
indeksu 0. A w jaki sposób mogę
pobrać ostatni element? Czy zawsze
muszę dokładnie wiedzieć, ile
elementów jest zapisanych w tablicy?
O: Ostatni element tablicy można pobrać
przy użyciu właściwości length. Pamiętasz
zapewne, że wartość właściwości length
jest zawsze o jeden większa od ostatniego
indeksu tablicy. A zatem ostatni element
tablicy można pobrać za pomocą wyrażenia:
myArray[myArray.length - 1];
Wyliczając je, JavaScript pobierze długość
tablicy, odejmie od niej 1, a następnie
pobierze element o tak wyliczonym
indeksie. Jeśli np. tablica ma 10 elementów,
wyrażenie to pobierze element o indeksie 9,
czyli dokładnie ten, o który Ci chodziło. Tej
sztuczki będziesz używał zawsze wtedy, gdy
zechcesz pobrać ostatni element tablicy,
której wielkości nie znasz.
Porządkowanie naszych danych
W międzyczasie w firmie BańkoCorp…
Hej, cieszę się, że jesteście.
Właśnie otrzymaliśmy sporo nowych
wyników testów. Sprawdźcie te wszystkie
nowe ilości wytworzonych baniek!
Naprawdę potrzebuję waszej pomocy,
żeby ogarnąć te dane. Byłbym szczęśliwy,
gdybyście mogli zakodować to,
co zapisałem poniżej.
var scores = [60,
58,
34,
69,
46,
41,
Prezes BańkoCorp.
BańkoCorp
50,
50,
55,
64,
31,
53,
60,
52,
51,
66,
57,
55,
58,
54,
52,
55,
52,
61,
54,
48,
44,
52,
44,
51,
54,
69,
51,
61,
18,
44];
Nowe wyniki testów.
y
To jest to, co musim
napisać.
szybko podejmować
ję tego raportu, żeby
ebu
trz
po
ę
wd
pra
na
ać! Czy możecie to
Hej,
baniek mamy produkow
decyzje, który płyn do
jakoś zaprogramować?
— Prezes BańkoCorp.
3ï\QGREDQLHNQU Z\QLN
3ï\QGREDQLHNQU Z\QLN
N
3ï\QGREDQLHNQU Z\QL
Tu jest zapisana reszta wyników…
/LF]ED WHVWöZ EDQLHN
1DMZLÚNV]D OLF]ED Z\WZRU]RQ\FK
3ï\Q\]QDMOHSV]\PZ\QLNLHP
jesteś tutaj
169
Kilka informacji o raporcie z testów
Przyjrzyjmy się nieco dokładniej raportowi, którego potrzebuje prezes
firmy BańkoCorp.
BańkoCorp
Musimy zacząć
od wyświetlenia
wszystkich
testowanych
płynów do robienia
baniek oraz
odpowiadających
im wyników.
ować
Hej, naprawdę potrzebuję tego raportu, żeby szybko podejm
e to
możeci
Czy
ować!
produk
mamy
baniek
do
płyn
który
,
decyzje
ać?
ramow
jakoś zaprog
— Prezes BańkoCorp.
3ï\QGREDQLHNQUZ\QLN
3ï\QGREDQLHNQUZ\QLN
3ï\QGREDQLHNQUZ\QLN
Tu jest zapisana reszta wyników
…
Następnie musimy
wyświetlić sumaryczną
liczbę przeprowadzonych
testów.
/LF]EDWHVWöZ
1DMZLÚNV]DOLF]EDZ\WZRU]RQ\FKEDQLHN
A na końcu
wyświetlić
najlepszy wynik
i numer każdego
płynu, z użyciem
którego ten wynik
osiągnięto.
3ï\Q\]QDMOHSV]\PZ\QLNLHP
WYSIL
SZARE KOMÓRKI
Poświęć trochę czasu, by zapisać pomysły dotyczące sposobu generacji raportu dla szefa firmy BańkoCorp.
Każdy element raportu rozpatrz odrębnie i zastanów się, jak go uzyskać i wygenerować odpowiednie wyniki.
Swoje notatki zapisz w tej ramce.
170
Rozdział 4.
Porządkowanie naszych danych
Biurowa konwersacja
Przyjrzyjmy się
temu raportowi
i zobaczmy, jak zabrać
się za pisanie kodu do
jego wygenerowania…
Judyta: Pierwszą rzeczą, którą musimy zrobić, jest wyświetlenie każdego
wyniku wraz z odpowiadającym mu numerem płynu
do baniek.
Józek: A numer płynu jest jednocześnie indeksem wyniku w tablicy,
prawda?
Judyta: Tak.
Franek: Zaczekajcie chwilę. A zatem musimy pobrać każdy wynik,
wyświetlić jego indeks, który jest jednocześnie numerem płynu do baniek,
a następnie wyświetlić odpowiedni wynik.
Franek
Judyta
Józek
Judyta: Tak, a wynik jest odpowiadającą indeksowi wartością w tablicy.
Józek: A zatem dla płynu o numerze 10 wynik jest zapisany w elemencie tablicy scores[10].
Judyta: Tak.
Franek: No dobrze, ale tych wyników jest dużo. Jak mamy wyświetlić je wszystkie?
Judyta: Użyj iteracji przyjacielu.
Franek: A… Chodzi ci o coś takiego jak pętla while?
Judyta: Właśnie. Odczytamy wszystkie elementy tablicy w pętli, zaczynając od zera aż do wartości właściwości
length…, chciałam powiedzieć do wartości właściwości length pomniejszonej o jeden.
Józek: To zaczyna wyglądać na całkiem realne do zrobienia. Dobra, napiszmy jakiś kod; myślę, że wiemy,
co trzeba zrobić.
Judyta: Uważam, że jest ok! Zabierajcie się do roboty, a ja potem przyjdę i dokończymy resztę raportu.
jesteś tutaj
171
Iteracja po tablicy
Jak pobrać wszystkie elementy tablicy?
Naszym celem jest wygenerowanie wyników w następującej postaci.
3ï\QGREDQLHNQUZ\QLN
3ï\QGREDQLHNQUZ\QLN
3ï\QGREDQLHNQUZ\QLN
.
.
.
3ï\QGREDQLHNQUZ\QLN
ów
Tutaj znajdą się wyniki test
34…
płynów o numerach od 3 do ić
zędz
oszc
my
chce
je,
c
ijają
Pom
kilka drzew (lub bitów, jeśli
korzystasz z elektronicznej
wersji książki).
0
1
2
3
60
50
60
58
4
54
Zrobimy to, wyświetlając wynik zapisany w elemencie o indeksie 0, następnie
w analogiczny sposób wyświetlimy wyniki zapisane w elemencie o indeksie 1,
2, 3 itd., aż do ostatniego indeksu w tablicy. Już wiesz, jak należy używać pętli
while; teraz zobaczysz, jak można ją zastosować do wyświetlenia wyników
ze
wszystkich testów.
A zaraz potem pokażemy leps
rozwiązanie…
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];
var output;
var i = 0;
Tej zmiennej używamy w pętli poniżej do tworzenia
wyświetlanego łańcucha znaków.
Tu tworzymy zmienną, która
będzie przechowywać aktualnie
przetwarzany indeks tablicy.
while (i < scores.length) {
Pętla będzie działać tak długo, jak długo
indeks będzie mniejszy od długości
tablicy.
output = ”Pïyn do baniek nr ” + i + ” wynik: ” + scores[i];
console.log(output);
i = i + 1;
}
A w końcu przed ponownym
rozpoczęciem pętli
inkrementujemy indeks.
172
Rozdział 4.
Następnie używamy
funkcji console.log
do wyświetlenia tego
łańcucha znaków.
Wewnątrz tablicy tworzymy
łańcuch znaków, który będzie
pojedynczym wierszem raportu.
Zawiera on numer płynu do
baniek (który jest jednocześnie
indeksem tablicy) oraz wynik
skojarzony z tym numerem.
5
54
6
7
58
50
8
52
9
54
Porządkowanie naszych danych
Magnesiki z kodem
2WU]\PDOLĤP\NRGVâXİċF\GRWHVWRZDQLD
NWyU\VPDNORGyZ]DZLHUDNDZDâHF]NLJXP\GR
İXFLD=DSLVDOLĤP\FDâ\NRGQDNDZDâHF]NDFK
PDJQHVyZLXâRİ\OLĤP\JRSLĐNQLHQDORGyZFH
QLHVWHW\PDJQHVLNLSRVSDGDâ\QDSRGâRJĐ
7ZRLP]DGDQLHPMHVWLFKSRQRZQHXâRİHQLH
ZRGSRZLHGQLHMNROHMQRĤFL8ZDİDMQD
SRGâRG]HOHİDâRNLONDLQQ\FKPDJQHVLNyZNWyUH
SRPLHV]Dâ\VLĐ]QDV]\PL=DQLP]DF]QLHV]
F]\WDþGDOHMSRUyZQDMVZRMHUR]ZLċ]DQLH
]QDV]\PSRGDQ\PSRGNRQLHFUR]G]LDâX
while (i < hasBubbleGum.length)
{
i = i + 2;
}
i = i + 1;
}
var i = 0;
{
{
if (hasBubbleGum[i])
while (i > hasBubbleGum.length)
Czu-Lada",
var products = ["Czuasto naleĂnikowe",
"MiÚtowy chïöd", "Ci
"Guma balonowa"];
var hasBubbleGum = [false,
false,
false,
true];
console.log(products[i] +
" zawiera gumÚ do ĝucia.");
A oto wyniki,
jakie powinniśmy
uzyskać.
Konsola JavaScript
*XPDEDORQRZD]DZLHUDJXPÚGRĝXFLD
Tutaj odtwórz ułożenie
magnesików z kodem.
jesteś tutaj
173
Iteracja przy użyciu pętli for
Chwila, istnieje lepszy sposób iteracji po tablicy
Naprawdę powinniśmy Cię przeprosić. Aż trudno uwierzyć, że to już jest rozdział 4.,
a my jeszcze nie przedstawiliśmy Ci pętli for. Pętlę for możesz sobie wyobrazić jako
kuzyna pętli while. Obie robią w zasadzie to samo, różnią się jednak tym, że pętla
for jest zazwyczaj nieco wygodniejsza w użyciu. Przyjrzyj się jeszcze raz używanej już
wcześniej pętli while, a zaraz wyjaśnimy, jak odpowiada ona pętli for.
A
Najpierw ZAINICJALIZOWALIŚMY licznik.
var i = 0;
while
Następnie sprawdziliśmy wartość licznika
przy użyciu wyrażenia WARUNKOWEGO.
B
(i < scores.length) {
output = ”Pïyn do baniek nr ” + i + ” wynik: ” + scores[i];
console.log(output);
C
Mieliśmy także CIAŁO do wykonania, czyli
wszystkie instrukcje umieszczone wewnątrz pętli,
pomiędzy nawiasami { i }.
i = i + 1;
}
I w końcu INKREMENTOWALIŚMY licznik pętli.
A teraz przekonajmy się, w jaki sposób pętla for ułatwia życie.
Następnie wewnątrz pary
nawiasów umieszczane są
trzy kluczowe elementy
pętli. Pierwszym z nich
jest INICJALIZACJA
zmiennej. Inicjalizacja
ta jest wykonywana tylko
jeden raz, bezpośrednio
przed rozpoczęciem
realizacji pętli.
Pętla zaczyna
się do słowa
kluczowego for.
A
Drugim elementem jest
wyrażenie WARUNKOWE.
Wyrażenie to jest obliczane
przed każdą iteracją pętli,
a jeśli przyjmie wartość false,
pętla zostaje zakończona.
B
Trzecim elementem jest
CE
wyrażenie INKREMENTUJĄt
wartość licznika pętli. Jes
ono wykonywane jeden raz li,
podczas każdej iteracji pęt
bezpośrednio po wykonaniu ŁA.
ostatniej instrukcji jej CIA
C
for (var i = 0; i < scores.length; i = i + 1) {
output = ”Pïyn do baniek nr ” + i + ” wynik: ” + scores[i];
console.log(output);
}
174
Rozdział 4.
A tu jest zapisywane
CIAŁO pętli. Zauważ,
że praktycznie się nie
zmieniło — jedynie
inkrementacja licznika
została przeniesiona do
samej instrukcji for.
Porządkowanie naszych danych
Zaostrz ołówek
Przepisz kod z magnesików (czyli ćwiczenia, które
zrobiłeś dwie strony wcześniej) w taki sposób,
by korzystał z pętli for, a nie z pętli while.
Jeśli potrzebujesz jakiejś pomocy, zerknij na
poszczególne elementy pętli while przedstawionej
na poprzedniej stronie oraz na odpowiadające im
elementy pętli for.
Czu-Lada",
var products = ["Czuasto naleĂnikowe",
"MiÚtowy chïöd", "Ci
"Guma balonowa"];
var hasBubbleGum = [false,
false,
false,
true];
var i = 0;
{
while (i < hasBubbleGum.length)
if (hasBubbleGum[i])
{
console.log(products[i] +
" zawiera gumÚ do ĝucia.");
}
i = i + 1;
Tutaj zapisz
swój kod.
}
jesteś tutaj
175
Kompletowanie kodu do generowania raportu
Mamy już wszystkie
elementy pierwszej części
programu do generacji raportu,
poskładajmy je w całość…
Tutaj są zapisane standardowenę WWW.
elementy HTML tworzące stro , żeby
Wiele ich nie trzeba, tylko tyle
utworzyć skrypt.
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Testy pïynöw do robienia baniek</title>
<script>
var scores = [60, 50, 60, 58, 54, 54,
A oto nasza tablica wyników.
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];
To jest pętla for, której używamy do
odczytania z tablicy wszystkich wyników
testów.
var output;
for (var i = 0; i < scores.length; i = i + 1) {
output = ”Pïyn do baniek nr ” + i +
” wynik: ” + scores[i];
console.log(output);
}
</script>
</head>
<body></body>
</html>
176
Rozdział 4.
A teraz wyświetlamy łańc
uch
znaków w oknie konsoli.
I to wszystko! Nadszedł cza
żeby wygenerować raport. s,
tworzymy
Podczas każdej iteracji pętli
wartość i, która
jący
iera
zaw
ów
znak
uch
łańc
baniek, oraz
jest numerem płynu do robienia
ślający,
okre
ik
wyn
i
czyl
—
]
es[i
wartość scor
użyciu
przy
zyć
wor
wyt
się
ło
ile baniek uda
tego płynu.
(Zauważ także, że instrukcja tworząca
ten łańcuch znaków została zapisana
w dwóch wierszach. Takie rozwiązanie jest
w porządku, o ile tylko nie umieścimy znaku
nowego wiersza pomiędzy cudzysłowami
wyznaczającymi początek i koniec
łańcucha znaków. W tym przypadku wiersz
zakończyliśmy po operatorze konkatenacji (+),
więc wszystko jest w porządku. Upewnij się,
że zapisałeś tę instrukcję dokładnie tak, jak
zrobiliśmy to tutaj).
Porządkowanie naszych danych
Testowanie generacji bańkowego raportu
Zapisz powyższy kod w pliku o nazwie bubbles.html, po czym wyświetl
go w przeglądarce. Upewnij się, że będzie w niej widoczne okno konsoli
(jeśli aktywowałeś okno konsoli dopiero po wczytaniu strony, to być
może będziesz musiał ją odświeżyć) i sprawdź nasz wspaniały raport
wygenerowany dla szefa BańkoCorp.
Dokładnie to, czego życzył
sobie prezes.
Miło zobaczyć wszystkie te
wyniki na raporcie, jednak
wciąż trudno znaleźć wśród
nich najlepszy wynik. Musimy
popracować nad pozostałymi
wymaganiami dotyczącymi
raportu, aby ułatwić
znalezienie zwycięzcy.
Konsola JavaScript
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
jesteś tutaj
177
Pogawędka pomiędzy pętlami for i while
Pogawędki przy kominku
2WRWHPDWG]LVLHMV]HMSRJDZÚGNL3ÚWOHZKLOHLIRURGSRZLDGDMÈQDS\WDQLH
ķ.WöUDSÚWODMHVWZDĝQLHMV]D"ĵ
3ÚWODWHILE
3ÚWODFOR
Słucham? Żartujesz sobie ze mnie? Że co?! To ja
jestem w JavaScripcie pętlą ogólnego przeznaczenia.
Nie wychodziłam za mąż za żaden głupi licznik.
Mogę współpracować z dowolnym wyrażeniem
warunkowym. Czy ktokolwiek zauważył, że to o mnie
pierwszej wspomniano w tej książce?
Nie podoba mi się ten ton.
I jeszcze jedno. Nie zauważyłaś, że pętla FOR nie ma
poczucia humoru? Choć sądzę, że gdybyśmy wszystkie
musiały całe dnie robić ogłupiająco nudne iteracje,
też byśmy takie były.
Urocze. Ale czy zauważyłaś, że w dziewięciu na
dziesięć przypadków programiści używają pętli FOR?
Och, nie sądzę, by to mogło być możliwe.
Nie wspominam już o tym, że wykonywanie iteracji
po, dajmy na to, tablicy o ściśle określonej liczbie
elementów przy użyciu pętli WHILE jest po prostu
złym i niepraktycznym rozwiązaniem.
Ta książka wyraźnie pokazała, że pętle FOR i WHILE
są bardzo podobne, więc jak to, co mówisz, może być
prawdą?
A zatem przyznajesz, że jesteśmy bardziej podobne,
niż byś sobie życzyła, prawda?
Powiem ci, dlaczego…
178
Rozdział 4.
Porządkowanie naszych danych
3ÚWOD:+,/(
3ÚWOD)25
Kiedy używasz pętli WHILE, musisz zainicjalizować
licznik, a później inkrementować go w odrębnych
instrukcjach. Jeśli w ramach wielu poprawek
którakolwiek z tych instrukcji zostanie zmieniona,
to cóż… sprawy mogą przybrać paskudny obrót.
Natomiast w przypadku pętli FOR wszystkie te
czynności są wykonywane bezpośrednio w instrukcji
FOR, dzięki czemu są wyraźnie widoczne i nie ma
szans na to, by zostały zmienione lub zgubione.
Cóż, jak miło i elegancko z twojej strony. Tylko
wiesz, ja w większości przypadków w ogóle nie mam
żadnych kontaktów z licznikami; wszystko załatwiam
mniej więcej tak:
ZKLOH DQVZHU ĵF]WHUG]LHĂFLGZDĵ
Spróbuj to zrobić, używając pętli FOR!
Proszę bardzo:
IRU DQVZHU ĵF]WHUG]LHĂFLGZDĵ
Nie mogę uwierzyć, że to zadziała.
A zadziała, zadziała.
Phi, to wygląda jak świnia w kapeluszu.
Tylko tyle masz do powiedzenia? A ty to niby radzisz
sobie lepiej ze swoimi ogólnymi warunkami?
Nie tylko lepiej, ale i ładniej!
O! Nie sądziłam, że bierzemy udział w konkursie
piękności.
jesteś tutaj
179
Post inkrementacja
Znowu nadszedł czas…
Czy możemy porozmawiać
o rozwlekłości Twojego kodu?
Pisałeś już sporo kodu przypominającego przedstawiony poniżej.
Załóżmy, że zmienna
myImportantCounter
0.
zawiera liczbę, taką jak
Tutaj pobieramy wartość tej
zmiennej i powiększamy ją
o jeden.
myImportantCounter = myImportantCounter + 1;
Kiedy ta instrukcja zostanie wykonana, wartość zmiennej myImportantCounter
będzie większa o jeden, niż była przed jej wykonaniem.
W rzeczywistości operacja tego typu jest wykonywana tak często, że język JavaScript ma dla niej specjalny,
skrótowy zapis. Jest to tzw. operator postinkrementacji, który — niezależnie od swojej wymyślnej nazwy — jest
całkiem prosty. Korzystając z tego operatora, całą powyższą instrukcję można zapisać w następujący sposób.
Po prostu dodaj „++” za nazwą
zmiennej.
myImportantCounter++;
Po wykonaniu tej instrukcji wartość zmiennej myImportantCounter będzie większa o jeden.
Oczywiście, to nie byłoby w porządku, gdyby nie było także operatora postdekrementacji.
Pozwala on zmniejszyć wartość zmiennej o jeden. Oto operator postdekrementacji.
Po prostu dodaj „--”
za nazwą zmiennej.
myImportantCounter--;
Po wykonaniu tej instrukcji wartość zmiennej myImportantCounter będzie o jeden mniejsza,
niż była wcześniej.
A dlaczego Ci o tym mówimy? Ponieważ oba operatory są bardzo często używane w pętlach for.
A zatem poprawmy nieco przejrzystość naszego kodu za pomocą operatora postinkrementacji.
180
Rozdział 4.
Porządkowanie naszych danych
Poprawienie pętli for przy użyciu
operatora postinkrementacji
Zmodyfikujmy szybciutko naszą pętlę i upewnijmy się, że kod
będzie działał tak samo jak wcześniej.
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,
tąpieniu
Cała zmiana polega na zaszmienną
j
instrukcji inkrementujące
sterującą pętli operatorem
postinkrementacji.
46, 31, 57, 52, 44, 18,
41, 53, 55, 61, 51, 44];
for (var i = 0; i < scores.length; i++) {
var output = ”Pïyn do baniek nr ” + i +
” wynik: ” + scores[i];
console.log(output);
}
Szybka jazda próbna
Nadszedł czas, by przeprowadzić szybką jazdę
próbną i upewnić się, że po zastosowaniu operatora
postinkrementacji kod wciąż działa prawidłowo. Zapisz plik
bubbles.html i otwórz stronę w przeglądarce. Powinieneś
zobaczyć dokładnie takie same wyniki jak wcześniej.
Konsola JavaScript
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
...
3á\QGREDQLHNQUZ\QLN
Raport wygląda dokładnie tak samo. 3á\QGREDQLHN
QUZ\QLN
Chcemy zaoszczędzić
trochę drzew, więc nie
pokazujemy tu wszystkich
wyników, ale one
wszystkie tam są.
jesteś tutaj
181
Planowanie dalszej części raportu
Załatwiliśmy już sprawę
wyświetlania wszystkich wyników
testów, teraz pozostaje jedynie
dokończyć pozostałą część
raportu.
Biurowej rozmowy
ciąg dalszy…
Judyta: Doskonale. Pierwszą
rzeczą, którą musimy teraz
zrobić, jest określenie łącznej
liczby przeprowadzonych testów.
To akurat jest proste: wystarczy
określić długość tablicy z wynikami.
BańkoCorp
Hej, napraw
dę potrzebu
ję tego rapo
podejmować
rtu, żeby sz
decyzje, kt
ybko
produkować
óry płyn do
! Czy możec
baniek mam
ie to jakoś
y
zaprogram
ować?
— Prezes
3ï\QGREDQL
HNQUZ\QL
N
3ï\QGREDQL
HNQUZ\QL
N
3ï\QGREDQL
HNQUZ\QL
N
Józek: Jasne. Musimy także
znaleźć największy wynik,
a następnie płyny, z którymi
ten wynik jest skojarzony.
BańkoCorp.
Tu
/LF]EDWHVWö
jest
Z
1DMZLÚNV]DO
LF]EDZ\WZRU
]RQ\FKEDQLH
3ï\Q\]QDMO
N
HSV]\PZ\QLN
LHP
zapisana
reszta w
yników…
Judyta: Tak… To ostatnie zadanie będzie najtrudniejsze. Zajmijmy się
najpierw znalezieniem najwyższego wyniku.
Józek: To faktycznie wygląda na najlepsze miejsce do rozpoczęcia prac.
Judyta: Uważam, że w tym celu potrzebujemy zmiennej, która będzie przechowywała największy wynik
podczas analizowania wszystkich wyników w tablicy. Dobra, napiszę to w formie pseudokodu.
ZADEKLARUJ: zmienną highScore i przypisz jej początkową wartość 0.
FOR: var i=0;i < scores.length; i++
nia
Dodajemy zmienną do przechowywa
najwyższego wyniku.
WYŚWIETL wynik dla analizowanego płynu: scores[i]
IF scores[i] > highScore
USTAW highScore = scores[i];
KONIEC IF
Podczas każdej iteracji pętli
sprawdzamy, czy aktualnie przetwarzany
wynik jest większy, a jeśli tak,
zapisujemy go w zmiennej highScore.
KONIEC FOR
WYŚWIETL highScore
Po zakończeniu pętli po prostu
wyświetlamy najwyższy wynik.
Józek: Super. Udało ci się to zrobić po dodaniu jedynie kilku wierszy do naszego istniejącego kodu.
Judyta: Podczas każdego wykonania zawartości pętli sprawdzamy, czy aktualny wynik nie jest większy od
wartości zmiennej highScore, a jeśli jest, zapisujemy go w niej. Później, kiedy pętla zostanie zakończona,
wyświetlamy ten największy wynik.
182
Rozdział 4.
Porządkowanie naszych danych
Zaostrz ołówek
Zaimplementuj pseudokod przedstawiony na poprzedniej stronie, który służy do
odnajdywania największego wyniku. W tym celu wypełnij puste miejsca w kodzie
przedstawionym poniżej. Kiedy już to zrobisz, wypróbuj kod, wprowadzając
odpowiednie modyfikacje w stronie bubbles.html i wyświetlając ją w przeglądarce.
Sprawdź wyniki w oknie konsoli, a następnie wypełnij puste miejsca w przedstawionym
poniżej oknie konsoli, wpisując w nich liczbę testów oraz największy wynik. Porównaj
swoje odpowiedzi z naszymi, podanymi pod koniec tego rozdziału.
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];
var highScore = ________;
var output;
Dokończ ten kod, uzupełniając
jego brakujące fragmenty…
for (var i = 0; i < scores.length; i++) {
output = ”Pïyn do baniek nr ” + i + ” wynik: ” + scores[i];
console.log(output);
if (___________ > highScore) {
___________ = scores[i];
}
}
console.log(”Liczba testöw: ” + _____________);
console.log(”NajwiÚksza liczba wytworzonych baniek: ” + __________);
Konsola JavaScript
…a następnie wypełnij puste miejsca
wyników przez wpisanie do nich liczb
wyświetlonych w Twoim oknie konsoli.
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
...
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
/LF]EDWHVWyZBBBBBBB
1DMZLĊNV]DOLF]EDZ\WZRU]RQ\FKEDQLHNBBBBBBB
jesteś tutaj
183
Dodanie największego wyniku
Słuchajcie, już
prawie się udało! Zostało tylko
wybranie i wyświetlenie wszystkich
płynów, które uzyskały największy wynik.
Pamiętajcie, że takich płynów może
być więcej niż jeden.
e:LĕFHM QLĵ MHdHQqf KPPP A czego używamy,
jeśli mamy zapisać więcej niż jedną informację?
Oczywiście, tablicy. A zatem możemy przeglądać
tablicę wyników w poszukiwaniu wyników równych
największemu, a następnie dodawać je do nowej
tablicy, żeby później wyświetlić w raporcie?
Można by się założyć, że faktycznie da się to
zrobić. Jednak w tym celu musimy się dowiedzieć,
jak utworzyć nową, pustą tablicę, a następnie
zrozumieć, jak dodawać do niej nowe elementy.
Czy pamiętasz, że
nia
zostało nam do zrobie
jedynie wygenerowanie
tego wiersza raportu?
BańkoCorp
Hej, naprawdę potrzebuję tego raportu, żeby szybko
podejmować decyzje, który płyn do baniek mamy
produkować! Czy możecie to jakoś zaprogramować?
— Prezes BańkoCorp.
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
Tu jest zapisana reszta wynik
ów…
/LF]EDWHVWyZ
1DMZLĊNV]DOLF]EDZ\WZRU]RQ\FKEDQLHN
3á\Q\]QDMOHSV]\PZ\QLNLHP
184
Rozdział 4.
Porządkowanie naszych danych
Tworzenie pustej tablicy
(i dodawanie do niej danych)
Zanim zajmiemy się dokończeniem kodu, musimy się dowiedzieć, w jaki sposób
można utworzyć nową tablicę oraz jak dodawać do niej nowe elementy. Już
wiesz, jak można utworzyć tablicę, która początkowo zawiera jakieś wartości.
var genres = [”Lata 80.”, ”Lata 90.”, ”Elektroniczna”, ”Folk”];
Taką konstrukcję nazywamy literałem
tablicowym, gdyż w dosłowny sposób
zapisujemy, jaka jest zawartość
tablicy.
Jednak można pominąć początkową zawartość tablicy i utworzyć tablicę, która będzie pusta.
var genres = [];
Nowa tablica, gotowa do pracy,
tylko pusta; jej długość wynosi 0.
To także jest literał
tablicowy, tylko nic
w nim (jeszcze) nie ma.
Wiesz już także, jak dodawać nowe wartości do tablic. W tym celu wystarczy je
zapisać w elemencie tablicy o określonym indeksie, tak jak pokazaliśmy poniżej.
var genres = [];
Tu tworzony jest nowy element
tablicy, zawierający łańcuch „Rock”.
genres[0] = ”Rock”;
genres[1] = ”Ambient”;
var size = genres.length;
A tu tworzony jest drugi element
tablicy, w którym zapisujemy łańcuch
znaków „Ambient”.
Tworzona tu zmienna size zawiera
wartość 2, czyli długość tablicy.
Podczas dodawania nowych elementów do tablicy należy bardzo uważać na
stosowane indeksy, bo można doprowadzić do powstania tablicy rzadkiej, czyli takiej,
w której będą „luki” (np. tablica będzie zawierać wartości w elementach o indeksach
0 i 2, lecz nie w elemencie o indeksie 1). Takie tablice niekoniecznie są złe, jednak
na pewno wymagają dodatkowej uwagi. Istnieje inny sposób dodawania nowych
elementów do tablic, który nie zmusza do przejmowania się indeksami; jest nim
metoda push. Poniżej pokazaliśmy, jak działa.
var genres = [];
genres.push(”Rock”);
genres.push(”Ambient”);
var size = genres.length;
Tworzy nowy element tablicy, który będzie posiadał następny
dostępny indeks (w tym przypadku będzie miał wartość 0),
i zapisuje w nim łańcuch znaków „Rock”.
Tworzy kolejny element tablicy, który będzie posiadał
następny dostępny indeks (w tym przypadku będzie miał
wartość 1), i zapisuje w nim łańcuch znaków „Ambient”.
jesteś tutaj
185
Pytania dotyczące iteracji i tablic
Nie istnieją
głupie pytania
P: Instrukcja for umożliwia podanie P: Czy możecie napisać coś więcej
deklaracji i inicjalizacji zmiennej na
samym jej początku. Napisaliście,
że deklaracje zmiennych należy
umieszczać na początku kodu.
Jak to się ma do siebie?
O: Tak, umieszczanie deklaracji zmiennych
na początku (pliku, jeśli to zmienne globalne,
lub funkcji, jeśli to zmienne lokalne) jest
dobrą praktyką. Jednak w pewnych
sytuacjach sensowne jest deklarowanie
zmiennych tam, gdzie będą używane. Pętle
for należą do tych sytuacji. Zazwyczaj do
sterowania iteracją używamy zmiennej,
takiej jak i, a po zakończeniu pętli zmienna
przestaje być potrzebna. Oczywiście,
można by jej używać potem w kodzie, lecz
zazwyczaj tak się nie robi. A zatem w takich
przypadkach deklarowanie zmiennej
tuż przed jej zastosowaniem poprawia
przejrzystość kodu.
P: Co oznacza zapis myarray.
push(value)?
O: Cóż, trzymaliśmy to w sekrecie, ale
w języku JavaScript tablica jest specjalnym
rodzajem obiektu. Jak się dowiesz
w następnym rozdziale, z obiektem mogą
być skojarzone funkcje, które na nim
operują. A zatem wyobraź sobie, że push
jest funkcją operującą na obiekcie myarray.
W tym przypadku jest to funkcja dodająca
do tablicy nowy element i zapisująca w nim
przekazany argument. Jeśli napiszemy:
JHQUHVSXVK ĵ0HWDOĵ zostanie wywołana funkcja push, a do niej
przekazany argument — łańcuch znaków
”Metal”. Funkcja push pobiera argument
i dodaje go jako nową wartość na końcu
tablicy genres. Kiedy zobaczysz wyrażenie
P\DUUD\SXVK YDOXH , wyobraź sobie,
że oznacza ono: „Wstawiam nową wartość
na końcu mojej tablicy”.
186
Rozdział 4.
na temat tablic rzadkich?
O: Tablica rzadka to taka, która zawiera
wartości tylko w kilku elementach,
a pomiędzy nimi występują elementy puste.
Taką tablicę można utworzyć bardzo łatwo;
oto przykład:
var sparseArray = [];
sparseArray[0] = true;
sparseArray[100] = true;
W tym przykładzie tablica sparseArray
zawiera tylko dwie wartości, obie to
wartości true, zapisane w elementach
o indeksach 0 i 100. Wszystkie pozostałe
elementy tablicy mają wartość undefined.
Długość tej tablicy wynosi 101, choć zawiera
ona jedynie dwie wartości.
P: Załóżmy, że mam tablicę
o długości 10 i dodaję do niej wartość
w elemencie o indeksie 10000.
Co się dzieje z elementami o indeksach
od 10 do 9999?
O: We wszystkich tych elementach
zostanie zapisana wartość undefined.
Pamiętasz zapewne, że wartość ta jest
zapisywana w niezainicjalizowanych
zmiennych. Wyobraź sobie zatem, że
tworzysz 9989 zmiennych, lecz ich nie
inicjalizujesz. Pamiętaj, że zajmują one
miejsce w pamięci komputera nawet wtedy,
kiedy nie mają wartości; dlatego tworząc
takie tablice, trzeba mieć dobre uzasadnienie.
P: Jeśli przeglądam tablicę i niektóre
wartości są udefined, to czy powinienem
je sprawdzać przed użyciem?
O: Jeśli podejrzewasz, że tablica może
być rzadka albo jedynie zawierać wartość
undefined, najprawdopodobniej tak
właśnie powinieneś robić — przed użyciem
wartości z tablicy sprawdzić, czy nie jest to
undefined. Jeśli chcesz jedynie wyświetlić
wartość w oknie konsoli, sprawdzanie nie
będzie mieć większego znaczenia, jednak
jest znacznie bardziej prawdopodobne,
że jakoś będziesz chciał użyć tej wartości,
np. w obliczeniach. W takim przypadku
zastosowanie wartości undefined może
doprowadzić do wystąpienia błędów,
a przynajmniej do nieoczekiwanego działania
programu. Aby sprawdzić, czy wartość nie
jest niezdefiniowana, użyj instrukcji:
LI P\DUUD\>L@
...
}
XQGHILQHG ^
Zwróć uwagę, że słowo undefined nie jest
zapisane w cudzysłowach (bo to nie jest
łańcuch znaków, lecz wartość).
P: Wszystkie tablice tworzone do
tej pory były literałami. Czy tablice
można także budować w inny sposób?
O: Tak. Możesz się spotkać z takim
zapisem:
YDUP\DUUD\ QHZ$UUD\ Instrukcja ta tworzy nową tablicę o trzech
pustych elementach (czyli tablica będzie
mieć długość 3, lecz jej elementy nie będą
mieć wartości). Później można w nich
zapisać jakieś wartości w standardowy
sposób, używając indeksów 0, 1 i 2. Dopóki
jednak nie zrobisz tego samodzielnie,
wartości w tablicy będą niezdefiniowane.
Tablica utworzona w taki sposób jest
dokładnie taka sama jak w przypadku
użycia literału i zapewne sam zauważysz,
że częściej będziesz używać literałów,
dokładnie tak, jak my w tej książce.
Na razie nie przejmuj się szczegółami tej
nowej składni tworzenia tablic (tym słowem
„new” oraz Array zapisanym z wielkiej
litery); wszystkiego na ich temat dowiesz
się dalej w tej książce.
Porządkowanie naszych danych
Teraz, kiedy już wiemy, jak
dodawać elementy do tablicy, możemy
dokończyć generowanie raportu. Wystarczy,
że utworzymy tablicę zawierającą wszystkie
płyny do baniek z największym wynikiem
i przeszukamy tablicę scores, by odnaleźć
numery tych płynów. Czy tak?
Judyta: Tak, zaczniemy od pustej tablicy do przechowania płynów z największym
wynikiem. Później, podczas przeglądania tablicy wyników do tej nowej tablicy
będziemy kolejno dodawali wszystkie płyny, które uzyskały największy wynik.
Franek: Dobra, bierzemy się do roboty.
Judyta: Ale… Poczekaj chwilę. Myślę, że potrzebujemy do tego odrębnej pętli.
Franek: Poważnie? Wydaje się, że dałoby się to zrobić w ramach już istniejącej pętli.
Judyta: Jestem pewna, że musimy napisać nową pętlę. Już wyjaśniam, dlaczego.
Największy wynik musimy znać, jeszcze zanim zaczniemy wybierać płyny.
To oznacza, że potrzebujemy dwóch pętli: pierwszej do odszukania największego
wyniku oraz drugiej do odszukania wszystkich płynów, które mają ten wynik.
Franek: A, rozumiem. W tej drugiej pętli będę porównywał każdy wynik
z największym wynikiem; a jeśli będą równe, dodam indeks do tej nowej tablicy
przechowującej numery płynów, które uzyskały najlepszy wynik.
Judyta: Właśnie tak! Bierz się do pracy!
Zaostrz ołówek
Czy potrafisz napisać pętlę, która odnajdzie wszystkie wyniki równe największemu? Spróbuj to zrobić
poniżej, zanim przejdziesz na następną stronę, gdzie poznasz rozwiązanie i będziesz mógł je przetestować.
Pamiętaj, największy wynik jest zapisany
w zmiennej highScore; możesz jej użyć
w swoim kodzie.
wali
, której będziemy uży
A to jest nowa tablica w płynów do robienia baniek,
eró
num
do gromadzenia
wynik.
jakie uzyskały najlepszy
var bestSolutions = [];
for (var i = 0; i < scores.length; i++) {
Tu zapisz swój kod.
}
jesteś tutaj
187
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Czy potrafisz napisać pętlę, która odnajdzie wszystkie wyniki równe największemu?
Poniżej przedstawiliśmy rozwiązanie.
I znowu zaczynamy od utworzenia
nowej tablicy, w której będziemy
zapisywali numery wszystkich płynów
do robienia baniek, jakie uzyskały
najlepszy wynik.
Następnie przeglądamy całą tablicę
wyników, poszukując tych elementów,
które mają najwyższy wynik.
var bestSolutions = [];
for (var i = 0; i < scores.length; i++) {
if (scores[i] == highScore) {
bestSolutions.push(i);
Podczas każdej iteracji pętli
porównujemy wynik zapisany
w analizowanym elemencie tablicy
z wartością zmiennej highScore i jeśli
są sobie równe, zapisujemy bieżący
indeks w tablicy bestSolutions,
używając do tego funkcji push.
}
}
console.log(”Pïyny z najlepszym wynikiem: ” + bestSolutions);
I w końcu możemy wyświetlić te płyny do robienia baniek, które uzyskały najlepszy
wynik. Zwróć uwagę, że używamy funkcji console.log, żeby wyświetlić tablicę
bestSolutions. Moglibyśmy oczywiście napisać następną pętlę i w niej wyświetlać
kolejno poszczególne elementy, ale funkcja console.log może to zrobić za nas
(a jeśli przyjrzysz się wynikom, zauważysz, że dodaje także przecinki pomiędzy
poszczególnymi wartościami!).
WYSIL
SZARE KOMÓRKI
Spójrz uważnie na kod przedstawiony powyżej w rozwiązaniu ćwiczenia Zaostrz ołówek. Co zrobiłbyś,
gdybyś się obudził i nagle okazałoby się, że funkcja push nie istnieje? Czy potrafisz przepisać ten kod tak,
by z niej nie korzystać? Zapisz ten zmodyfikowany kod poniżej.
188
Rozdział 4.
Porządkowanie naszych danych
Test ostatecznego raportu
Nie ociągaj się i dodaj najnowszy fragment kodu, generujący numery najlepszych płynów do
robienia baniek, do pliku bubbles.html, a następnie weź swój raport na kolejną jazdę próbną.
Cały kod JavaScript służący do generowania tego raportu przedstawiliśmy poniżej.
YDUVFRUHV >
34, 55, 51, 52, 44, 51,
@
var highScore = 0;
var output = 0;
IRU YDUL LVFRUHVOHQJWKL ^
FRQVROHORJ ĵ3ï\QGREDQLHNQUĵLĵZ\QLNĵVFRUHV>L@ LI VFRUHV>L@!KLJK6FRUH ^
highScore = scores[i];
}
}
FRQVROHORJ ĵ/LF]EDWHVWöZĵVFRUHVOHQJWK FRQVROHORJ ĵ1DMZLÚNV]DOLF]EDZ\WZRU]RQ\FKEDQLHNĵKLJK6FRUH YDUEHVW6ROXWLRQV >@
IRU YDUL LVFRUHVOHQJWKL ^
LI VFRUHV>L@
KLJK6FRUH ^
EHVW6ROXWLRQVSXVK L }
}
FRQVROHORJ ĵ3ï\Q\]QDMOHSV]\PZ\QLNLHPĵEHVW6ROXWLRQV Konsola JavaScript
Zwycięzcami są…
Płyny do robienia baniek o numerach 11 i 18 uzyskały taką samą,
największą liczbę baniek: 69! Dlatego też to one są zwycięzcami
naszego konkursu.
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
...
3á\QGREDQLHNQUZ\QLN
/LF]EDWHVWyZ
1DMZLĊNV]DOLF]EDZ\WZRU]RQ\FKEDQLHN
3á\Q\]QDMOHSV]\PZ\QLNLHP
jesteś tutaj
189
Powtórna analiza kodu z uwzględnieniem funkcji
W poprzednim
rozdziale poświęciliśmy
sporo czasu na przedstawienie
funkcji. Dlaczego nie
zastosowaliśmy ich w tym
kodzie?
0aV] raFMĕ powLQQLĩP\ Wo ]roELĉ Funkcje
przedstawiliśmy dopiero niedawno, dlatego
chcieliśmy, żebyś — zanim je zastosujesz — poznał
także podstawowe informacje na temat tablic.
Jednak zawsze będziemy starali się zwracać uwagę
na to, które fragmenty kodu można wyodrębnić
i zaimplementować w formie funkcji. Nie tylko
to — załóżmy, że chciałbyś ich wielokrotnie używać
lub nawet pozwolić innym na skorzystanie z całego
rozwiązania generującego raport z testów płynów
do robienia baniek. Być może chciałbyś udostępnić
innym programistom fajny zestaw funkcji, które
mogliby stosować.
Wróćmy zatem do kodu generującego raport
i spróbujmy przeprowadzić jego refaktoryzację
polegającą na wprowadzeniu funkcji. Pod
pojęciem refaktoryzacji rozumiemy modyfikację
sposobu organizacji kodu w celu poprawienia
jego czytelności i łatwości utrzymania, jednak bez
zmiany efektów działania. Innymi słowy, kiedy
skończymy, kod będzie robił dokładnie to samo, co
wcześniej, lecz jednocześnie będzie znacznie lepiej
zorganizowany.
190
Rozdział 4.
Porządkowanie naszych danych
Krótka inspekcja kodu…
Przejrzymy teraz kod, który napisaliśmy, i spróbujemy określić, które jego fragmenty
można zaimplementować w formie funkcji.
Oto kod raportu dla szefa
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Testy pïynöw do robienia baniek</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];
BańkoCorp.
ików wewnątrz funkcji
Nie chcemy deklarować wynh, gdyż będą one inne
ikac
wyn
tych
na
operującej
nia funkcji. Zamiast
podczas każdego wykorzysta przekazywana jako
zie
będ
tego tablica wyników
funkcja będzie mogła
argument do funkcji, zatemlicach z wynikami.
operować na dowolnych tab
var highScore = 0;
var output;
for (var i = 0; i < scores.length; i++) {
output = ”Pïyn do baniek nr ” + i + ” wynik: ” + scores[i];
console.log(output);
if (scores[i] > highScore) {
highScore = scores[i];
}
}
console.log(”Liczba testöw: ” + scores.length);
console.log(”NajwiÚksza liczba wytworzonych baniek: ” + highScore);
var bestSolutions = [];
Tego pierwszego fragmentu
kodu używamy do
wyświetlenia każdego wyniku
oraz do jednoczesnego
wyznaczenia największego
wyniku w tablicy.
Moglibyśmy umieścić ten
kod w funkcji o nazwie
printAndGetHighScore.
A tego drugiego fragmentu kodu
używamy do odszukania wszystkich
najlepszych wyników na podstawie
przekazanego najlepszego wyniku.
Moglibyśmy umieścić go w funkcji
o nazwie getBestResults.
for (var i = 0; i < scores.length; i++) {
if (scores[i] == highScore) {
bestSolutions.push(i);
}
}
console.log(”Pïyny z najlepszym wynikiem: ” + bestSolutions);
</script>
</head>
<body> </body>
</html>
jesteś tutaj
191
Refaktoryzacja kodu poprzez zastosowanie funkcji
Piszemy funkcję printAndGetHighScore
Dysponujemy już kodem, który umieścimy w funkcji printAndGetHighScore. To ten sam
kod, który już napisaliśmy, aby jednak przekształcić go w funkcję, musimy się zastanowić,
jakie argumenty będziemy do niego przekazywać i jaką wartość wynikową chcemy uzyskać.
Przekazanie tablicy wyników jako argumentu wydaje się dobrym pomysłem, gdyż dzięki
temu będziemy mogli używać tej samej funkcji do przetwarzania innych tablic z wynikami
testów. Jeśli chodzi o wynik, to chcemy by funkcja zwróciła najlepszy wynik odszukany
w wynikach, tak by kod wywołujący funkcję mógł zrobić z nim coś interesującego
(a poza tym, potrzebujemy tego wyniku do odszukania najlepszych płynów).
A… i jeszcze jedna sprawa: zazwyczaj chcemy, by funkcja robiła tylko jedną rzecz, ale dobrze.
W tym przypadku nasza funkcja będzie robić dwie rzeczy: wyświetlać wszystkie wyniki
w tablicy oraz wyznaczać największy zapisany w niej wynik. Można by się zastanowić nad
rozbiciem tych czynności na dwie funkcje, jednak zważywszy na to, jak proste są realizowane
przez nie operacje, oprzemy się tej pokusie. Gdybyśmy jednak pracowali nad profesjonalnym
rozwiązaniem, moglibyśmy ponownie przemyśleć zagadnienie i rozbić tę funkcję na dwie:
printScores oraz getHighScore, które, odpowiednio, wyświetlałyby wyniki i pobierały
największy wynik. Na razie jednak pozostaniemy przy jednej funkcji. A teraz zabierzmy się
za refaktoryzację kodu.
Napisaliśmy funkcję, która
oczekuje przekazania jednego
argumentu — tablicy wyników.
function printAndGetHighScore(scores) {
var highScore = 0;
var output;
for (var i = 0; i < scores.length; i++) {
output = ”Pïyn do baniek nr ” + i + ” wynik: ” + scores[i];
console.log(output);
if (scores[i] > highScore) {
highScore = scores[i];
}
}
.
Ten kod jest dokładnie taki sam
na
A
LĄD
WYG
tylko
dzie
zasa
A w
dokładnie taki sam, gdyż użyto
w nim parametru scores, a nie
zmiennej globalnej o tej nazwie.
return highScore;
}
Dodaliśmy tutaj jeden wiersz,
który zwraca wartość zmiennej
highScore, przekazując ją do
kodu, który wywołał funkcję.
192
Rozdział 4.
Porządkowanie naszych danych
Refaktoryzacja kodu z użyciem funkcji printAndGetHighScore
Teraz musimy zmienić resztę naszego kodu tak, by korzystał z funkcji. W tym celu wystarczy,
że ją wywołamy i zapiszemy zwracany przez nią wynik w zmiennej highScore.
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Testy pïynöw do robienia baniek</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 = ”Pïyn do baniek nr ” + i + ” wynik: ” + scores[i];
console.log(output);
if (scores[i] > highScore) {
highScore = scores[i];
}
}
return highScore;
}
var highScore = printAndGetHighScore(scores);
console.log(”Liczba testöw: ” + scores.length);
console.log(”NajwiÚksza liczba wytworzonych baniek: ” + highScore);
To jest nasza funkcja
gotowa do użytku.
A teraz wywołujemy tę
funkcję, przekazując do niej
tablicę wyników. Wartość
zwracaną przez tę funkcję
zapisujemy w zmiennej
hightScore.
var bestSolutions = [];
Pozostaje zatem w podobny sposób
for (var i = 0; i < scores.length; i++) {
przekształcić ten kod na funkcję
i ponownie wprowadzić odpowiednie
if (scores[i] == highScore) {
zmiany w pozostałej części kodu.
bestSolutions.push(i);
}
}
console.log(”Pïyny z najlepszym wynikiem: ” + bestSolutions);
</script>
</head>
<body> </body>
</html>
jesteś tutaj
193
Ćwiczenie z refaktoryzacji kodu
Zaostrz ołówek
Tym kolejnym zadaniem zajmiemy się wspólnie. Naszym celem jest napisanie funkcji tworzącej tablicę
wszystkich płynów do baniek, które uzyskały najlepszy wynik (a ponieważ może ich być więcej niż
jeden, dlatego zastosujemy tablicę). Do tej funkcji będziemy przekazywali tablicę wyników oraz
wartość zmiennej highScore, którą wyznaczyliśmy przy użyciu funkcji printAndGetHighScore.
Dokończ kod widoczny poniżej. Rozwiązanie znajdziesz na następnej stronie, więc nie podglądaj!
Dokończ kod samodzielnie, żebyś wszystko dobrze zrozumiał.
Gdybyś zapomniał,
tu przedstawiamy
kod w jego obecnej
postaci.
var bestSolutions = [];
for (var i = 0; i < scores.length; i++)
{
if (scores[i] == highScore) {
bestSolutions.push(i);
}
}
console.log(”Pïyny z najlepszym wynikiem:
” + bestSolutions);
ale
Zaczęliśmy już pisać funkcję, żeby ją
potrzebujemy Twojej pomocy,
dokończyć!
function getBestResults(__________, ____________) {
var bestSolutions = ______;
for (var i = 0; i < scores.length; i++) {
if (___________ == highScore) {
bestSolutions.__________;
}
}
return _______________;
}
var bestSolutions = _____________(scores, highScore);
console.log(”Pïyny z najlepszym wynikiem: ” + bestSolutions);
194
Rozdział 4.
Porządkowanie naszych danych
Zastosowanie zmian…
Skoro już zakończyłeś refaktoryzację kodu, wprowadź wszystkie zmiany w pliku bubbles.html, dokładnie tak jak
pokazaliśmy poniżej, po czym odśwież stronę w przeglądarce. Powinieneś uzyskać dokładnie te same wyniki jak
wcześniej. Jednak teraz wiesz, że Twój kod ma lepszą organizację i zapewnia większe możliwości wielokrotnego
stosowania. A zatem utwórz swoją własną tablicę wyników i sprawdź, jak to jest z tym wielokrotnym stosowaniem kodu!
<!doctype html>
<html lang=”pl”>
<head>
PHWDFKDUVHW ĵXWIĵ!
WLWOH!%XEEOH)DFWRU\7HVW/DEWLWOH!
<script>
YDUVFRUHV >
34, 55, 51, 52, 44, 51,
@
IXQFWLRQSULQW$QG*HW+LJK6FRUH VFRUHV ^
var highScore = 0;
var output;
IRU YDUL LVFRUHVOHQJWKL ^
RXWSXW ĵ3ï\QGREDQLHNQUĵLĵZ\QLNĵVFRUHV>L@
FRQVROHORJ RXWSXW LI VFRUHV>L@!KLJK6FRUH ^
highScore = scores[i];
}
}
return highScore;
}
IXQFWLRQJHW%HVW5HVXOWV VFRUHVKLJK6FRUH ^
YDUEHVW6ROXWLRQV >@
IRU YDUL LVFRUHVOHQJWKL ^
LI VFRUHV>L@ KLJK6FRUH ^
EHVW6ROXWLRQVSXVK L }
}
UHWXUQEHVW6ROXWLRQV
}
No dobrze, to jest
nasza nowa funkcja
getBestResult.
YDUKLJK6FRUH SULQW$QG*HW+LJK6FRUH VFRUHV FRQVROHORJ ĵ/LF]EDWHVWöZĵVFRUHVOHQJWK FRQVROHORJ ĵ1DMZLÚNV]DOLF]EDZ\WZRU]RQ\FKEDQLHNĵKLJK6FRUH YDUEHVW6ROXWLRQV JHW%HVW5HVXOW VFRUHVKLJK6FRUH FRQVROHORJ ĵ3ï\Q\]QDMOHSV]\PZ\QLNLHPĵEHVW6ROXWLRQV Wyników zwróconych przez
tę funkcję używamy do
wyświetlenia w raporcie
płynów do robienia baniek,
ik.
które uzyskały najlepszy wyn
VFULSW!
KHDG!
ERG\!ERG\!
KWPO!
jesteś tutaj
195
Więcej obliczeń związanych z bańkami
Świetna robota!
Jeszcze tylko jedna sprawa…
Czy jesteście w stanie wyznaczyć najbardziej
opłacalny płyn do robienia baniek? Dysponując
tymi informacjami, na pewno uda się podbić
cały rynek płynów do robienia baniek. To jest
tablica z kosztami poszczególnych płynów, której
powinniście użyć w tych obliczeniach.
ały
A oto i tablica. Zauważ, że zost płynów
h
w niej podane koszty wszystkic .
uwzględnionych w tablicy wyników
var costs = [.25,
.33,
.31,
.25,
.20,
.25,
.27,
.31,
.25,
.25,
.25,
.25,
.25,
.25,
.25,
.28,
.30,
.27,
.25,
.29,
.33,
.25,
.25,
.25,
.25,
.27,
.21,
.24,
.24,
.26,
.25,
.22,
.25,
.22,
.25,
.29];
O co chodzi w tym zadaniu? Polega ono na tym, by wskazać najlepsze płyny do robienia
baniek, czyli te, które uzyskały najwyższy wynik, i wybrać spośród nich najtańszy. Teraz, na
szczęście, dysponujemy tablicą costs, która jest bardzo podobna do tablicy scores. Innymi
słowy, koszt płynu do robienia baniek, którego wynik jest zapisany w elemencie o indeksie
0 tablicy scores, jest zapisany w tablicy costs, w elemencie o indeksie 0 (i wynosi . 25),
analogicznie, koszt płynu o indeksie 1 z tablicy scores będzie zapisany w tablicy costs
w elemencie o indeksie 1 (czyli wynosi ) itd. Innymi słowy, dla każdego płynu, którego
wynik jest zapisany w tablicy scores, koszt znajdziemy pod tym samym indeksem w tablicy
costs. Takie tablice czasami nazywa się tablicami równoległymi.
równoległymi,
scores oraz costs są tablicami
cy scores
tabli
w
iku
wyn
ego
każd
dla
gdyż
ający mu koszt,
wiad
odpo
ieje
istn
s
cost
cy
w tabli
indeksie.
ym
sam
tym
o
e
enci
elem
zapisany w
var costs = [.25, .27, .25, .25, .25, .25, .33, .31, .25, .29, .27, .22, ..., .29];
ksie
Koszt zapisany w elemencie o inde nia
robie
0 odpowiada wynikowi płynu do
baniek, którego wynik jest zapisany
w elemencie o indeksie 0…
Podobnie jest ze wszystkimi
innymi kosztami i wynikami
w obu tablicach.
var scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, ..., 44];
196
Rozdział 4.
Porządkowanie naszych danych
To zadanie wydaje się trochę
podchwytliwe. Jak mamy wyznaczyć
najpierw płyny, które mają najlepszy
wynik, a potem jeszcze spośród nich
wybrać najbardziej opłacalny?
Judyta: Cóż… Najlepsze wyniki już mamy.
Franek: To prawda. Ale jak możemy ich użyć? No i mamy obie tablice,
ale jak zastosować je w kodzie?
Judyta: Jestem pewna, że każde z nas mogłoby napisać prostą pętlę
for, która będzie przeglądać tablicę scores, wybierając z niej elementy
o najwyższym wyniku.
Franek: Pewnie, mógłbym to zrobić, ale co potem?
Judyta: Za każdym razem gdy odnajdziemy płyn, który uzyskał najlepszy
wynik, musimy sprawdzić, czy jego koszt jest najniższy spośród tych, które już sprawdziliśmy.
Franek: Rozumiem. A zatem będziemy musieli mieć zmienną, przechowującą indeks
„najlepszego wyniku o najniższym koszcie”. Rany, trochę to przydługie.
Judyta: Właśnie. A kiedy sprawdzimy już całą tablicę, indeks zapisany w tej zmiennej będzie
numerem płynu, który uzyskał najlepszy wynik, a jednocześnie jest najtańszy.
Franek: A co jeśli jakieś dwa płyny będą mieć ten sam koszt?
Judyta: Hmm… Musimy zdecydować, jak będziemy obsługiwać taką sytuację. Według mnie,
zwycięzcą powinien zostać pierwszy odnaleziony najtańszy płyn. Oczywiście moglibyśmy też
zrobić coś bardziej skomplikowanego, jednak na razie ograniczmy się do tego rozwiązania,
chyba że szef BańkoCorp zdecyduje inaczej.
Franek: To jest na tyle skomplikowane, że zanim zabiorę się do pracy, napiszę pseudokod
naszego rozwiązania.
Judyta: Słusznie. Zawsze wtedy, gdy mamy do czynienia z indeksami kilku tablic, sprawy się
znacząco komplikują. Zróbmy tak. Patrząc długofalowo, rozpisanie planu rozwiązania na
pewno się opłaci czasowo.
Franek: Dobrze. To ja zacznę…
jesteś tutaj
197
Przekształcanie pseudokodu na kod
Jestem zupełnie pewny, że dobrze
napisałem pseudokod. Sprawdź sam, zapisałem
go poniżej. Teraz Twoim zadaniem będzie
przerobienie go na kod JavaScript. Nie zapomnij
porównać swojego rozwiązania z naszym.
Ćwiczenie
FUNKCJA GETMOSTCOSTEFFECTIVESOLUTION (SCORE,COST,HIGHSCORE)
ZADEKLARUJ zmienną cost i przypisz jej wartość początkową 100.
ZADEKLARUJ zmienną index.
FOR: var i=0; i < scores.length; i++
IF wynik płynu scores[i] odpowiada najlepszemu wynikowi
IF bieżąca wartość zmiennej cost jest większa od kosztu aktualnie analizowanego płynu
TO
ZAPISZ w zmiennej index wartość i
ZAPISZ w zmiennej cost wartość kosztu aktualnie analizowanego płynu
KONIEC IF
KONIEC IF
KONIEC FOR
RETURN index
function getMostCostEffectiveSolution(scores, costs, highscore) {
Przerób
pseudokod
na kod
JavaScript.
}
var mostCostEffective = getMostCostEffectiveSolution(scores, costs, highScore);
console.log(”Pïyn numer ” + mostCostEffective + ” jest najbardziej opïacalny.”);
198
Rozdział 4.
Porządkowanie naszych danych
=Z\FLÚ]FDSï\QQXPHU
Ostatni napisany przez nas fragment kodu pozwolił na wskazanie PRAWDZIWEGO
zwycięzcy, czyli płynu, który uzyskał najlepszy wynik w teście na ilość wytworzonych
baniek, a jednocześnie jest najtańszy. Gratulujemy Ci, że potrafiłeś przebić się przez
całkiem dużo danych i zrobić z nich coś, na podstawie czego BańkoCorp może
podejmować prawdziwe decyzje biznesowe.
A teraz, jeśli naprawdę nas lubisz, na pewno skręca Cię z ciekawości, jak się robi płyn
do robienia baniek numer 11. Nie musisz już dłużej czekać! Szef BańkoCorp powiedział,
że będzie zaszczycony, mogąc zdradzić Ci tę miksturę w rewanżu za tę całą pracę, którą
bezpłatnie dla niego wykonałeś.
#11
Przepis na płyn numer 11 znajdziesz poniżej. Poświęć nieco czasu, by Twój mózg
utrwalił sobie wszystkie zdobyte informacje na temat tablic, podczas gdy Ty możesz
poszukać odpowiedniego patyczka, sporządzić płyn i pobawić się robieniem baniek,
zanim zabierzesz się za lekturę następnego rozdziału. Jednak nie zapomnij przeczytać
także „Celnych spostrzeżeń” zamieszczonych na następnej stronie.
U
LDEDQLHNQ
ELHQ
3ã\QGRUR
LDQDF]\ñ
\QXGRP\F
NXENDSï
\
OLWUDZRG
GRVWÚSQHM
JOLFHU\Q\
L
VWRïRZ\FK
N
KHPLF]Q\P
ĝH
LF
ï\
P
ïD
GR
SLH]DUW\NX
OH
VN
E
OX
H
ZDSWHF
QLNL
WNLHVNïDG
LHV]DMZV]\V
\P
:
-$
&
.
,16758
GREU]H
FHLEDZVLÚ
ZGXĝHMPLV
KONIECZNIE spróbuj
to ZROBIĆ W DOMU!
jesteś tutaj
199
Podsumowanie rozdziału
CELNE SPOSTRZEŻENIA
200
Q
Tablice są strukturami danych służącymi do
przechowywania uporządkowanych informacji.
Q
Nowe tablice najlepiej tworzyć przy użyciu literału
tablicowego.
Q
Tablica przechowuje zbiór danych, z których każda
ma swój własny indeks.
Q
Q
Indeksy tablic są numerowane, zaczynając od 0.
Nową, pustą tablicę można utworzyć
w następujący sposób:
var myArray = [];
Q
Wszystkie tablice mają właściwość length,
która zawiera liczbę określającą ilość elementów
przechowywanych w tablicy.
Q
Pętla for jest najczęściej używana do
odczytywania i przeglądania tablic.
Q
Q
Do każdego elementu tablicy można się
odwołać, używając jego indeksu. Przykładowo
wyrażenie myArray[1] pozwala pobrać element
o indeksie 1 (czyli drugi element tablicy).
Pętla for zawiera inicjalizację zmiennej, wyrażenie
warunkowe oraz inkrementację zmiennej.
Q
Pętla while jest najczęściej używana, gdy nie
wiemy, ile iteracji trzeba będzie wykonać, i pętla
działa, dopóki jest spełniony jej warunek. Pętla
for jest najczęściej używana, kiedy wiemy, ile razy
należy wykonać umieszczone w niej instrukcje.
Q
Próba pobrania nieistniejącego elementu
spowoduje zwrócenie wartości undefined.
Q
Przypisanie wartości istniejącemu elementowi
tablicy spowoduje zastąpienie jego wartości.
Q
Tablice rzadkie to takie, wewnątrz których znajdują
się elementy o niezdefiniowanej wartości.
Q
Przypisanie wartości elementowi, który do tej pory
nie istniał w tablicy, spowoduje jego utworzenie.
Q
Wartość zmiennej można inkrementować za
pomocą operatora postinkrementacji ++.
Q
W elementach tablicy można zapisywać wartości
dowolnych typów.
Q
Wartość zmiennej można dekrementować przy
użyciu operatora postdekrementacji --.
Q
Wartości zapisywane w tablicy nie muszą być tego
samego typu.
Q
Nowe elementy można dodawać do tablicy,
korzystając z funkcji push.
Rozdział 4.
Porządkowanie naszych danych
Zaostrz ołówek
Rozwiązanie
Tablica products zawiera smaki lodów Janki i Bartka. Poszczególne smaki były dodawane
do tablicy w kolejności, w jakiej były tworzone. Dokończ kod, który pozwoli określić ostatni
z wymyślonych smaków lodów. Poniżej znajdziesz rozwiązanie.
var products = [”Czu-Czu-Lada”, ”MiÚtowy chïöd”, ”Ciasto naleĂnikowe”, ”Guma balonowa”];
var last = products.length - 1;
Aby określić indeks ostatniego elementu tablicy,
wystarczy użyć wartości właściwości length
pomniejszonej o jeden. Właściwość length wynosi
4, a indeksem ostatniego elementu jest 3, gdyż
indeksy są numerowane od 0.
var recent = products[last];
Rozwiązanie magnesików z kodem
2WU]\PDOLĤP\NRGVâXİċF\GRWHVWRZDQLDNWyU\VPDNORGyZ]DZLHUDNDZDâHF]NLJXP\GR
İXFLD=DSLVDOLĤP\FDâ\NRGQDNDZDâHF]NDFKPDJQHVyZLXâRİ\OLĤP\JRSLĐNQLHQDORGyZFH
QLHVWHW\PDJQHVLNLSRVSDGDâ\QDSRGâRJĐ7ZRLP]DGDQLHPMHVWLFKSRQRZQHXâRİHQLH
ZRGSRZLHGQLHMNROHMQRĤFL8ZDİDMQDSRGâRG]HOHİDâRNLONDLQQ\FKPDJQHVLNyZNWyUH
SRPLHV]Dâ\VLĐ]QDV]\PL$RWRQDV]HUR]ZLċ]DQLH
Czu-Lada",
var products = ["Czuasto naleĂnikowe",
Úto
"Mi wy chïöd", "Ci
"Guma balonowa"];
Pozostałe magnesiki.
var hasBubbleGum = [false,
false,
false,
true];
var i = 0;
while (i < hasBubbleGum.length)
if (hasBubbleGum[i])
{
{
while (i > hasBubbleGum.length)
{
console.log(products[i] +
" zawiera gumÚ do ĝucia.");
}
i = i + 2;
A oto wyniki,
jakie powinniśmy
uzyskać.
}
Konsola JavaScript
i = i + 1;
*XPDEDORQRZD]DZLHUDJXPĊ
GRĪXFLD
Tutaj poukładaj magnesiki.
jesteś tutaj
201
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Przepisz kod z magnesików (czyli ćwiczenia, które
zrobiłeś dwie strony wcześniej) w taki sposób,
by korzystał z pętli for, a nie z pętli while. Jeśli
potrzebujesz jakiejś pomocy, zerknij na poszczególne
elementy pętli while przedstawionej na poprzedniej
stronie oraz na odpowiadające im elementy pętli
for. A oto nasze rozwiązanie.
Czu-Lada",
var products = ["Czuasto naleĂnikowe",
"MiÚtowy chïöd", "Ci
"Guma balonowa"];
var hasBubbleGum = [false,
false,
false,
true];
var i = 0;
{
while (i < hasBubbleGum.length)
{
if (hasBubbleGum[i])
console.log(products[i] +
" zawiera gumÚ do ĝucia.");
}
i = i + 1;
}
Tutaj zapisz swój kod.
var products = [”Czu-Czu-Lada”,
”MiÚtowy chïöd”, ”Ciasto naleĂnikowe”,
”Guma balonowa”];
var hasBubbleGum = [false,
false,
false,
true];
for (var i = 0; i < hasBubbleGum.length; i = i + 1) {
if (hasBubbleGum[i]) {
console.log(products[i] + ” zawiera gumÚ do ĝucia.”);
}
}
202
Rozdział 4.
Porządkowanie naszych danych
Zaostrz ołówek
Rozwiązanie
Zaimplementuj pseudokod przedstawiony na poprzedniej stronie, który służy do
odnajdywania największego wyniku. W tym celu wypełnij puste miejsca w kodzie
przedstawionym poniżej. Kiedy już to zrobisz, wypróbuj kod, wprowadzając
odpowiednie modyfikacje w stronie bubbles.html i wyświetlając ją w przeglądarce.
Sprawdź wyniki w oknie konsoli, a następnie wypełnij puste miejsca w przedstawionym
poniżej oknie konsoli, wpisując w nich liczbę testów oraz największy wynik.
Oto nasze rozwiązanie.
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];
0
var highScore = ________;
Dokończ ten kod, uzupełniając
jego brakujące fragmenty…
var output;
for (var i = 0; i < scores.length; i++) {
output = ”Pïyn do baniek nr ” + i + ” wynik: ” + scores[i];
console.log(output);
scores[i] > highScore) {
if (___________
___________
highScore = scores[i];
}
}
scores.length
console.log(”Liczba testöw: ” + _____________);
console.log(”NajwiÚksza liczba wytworzonych baniek: ” + __________);
highScore
Konsola JavaScript
…a następnie wypełnij puste miejsca
wyników przez wpisanie do nich liczb
wyświetlonych w Twoim oknie konsoli.
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
...
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
/LF]EDWHVWyZBBBBBB
36 B
1DMZLĊNV]DOLF]EDZ\WZRU]RQ\FKEDQLHNBBB
69BBBB
jesteś tutaj
203
Rozwiązanie ćwiczenia
Poniżej zamieściliśmy naszą wersję funkcji getMostCostEffectiveSolution, która pobiera tablicę
wyników, tablicę kosztów oraz najwyższy wynik i na ich podstawie odnajduje indeks płynu do robienia baniek,
który uzyskał najwyższy wynik, a jednocześnie jest najtańszy. Kiedy wpiszesz już go do pliku bubbles.html
odśwież stronę w przeglądarce i weź ją na jazdę próbną, by przekonać się, czy uzyskasz identyczne wyniki.
Ćwiczenie
Rozwiązanie
Funkcja getMostCostEffectiveSolution pobiera tablicę
wyników, tablicę kosztów oraz najlepszy wynik.
function getMostCostEffectiveSolution(scores, costs, highscore) {
W zmiennej cost zapiszemy koszt najtańszego płynu
do robienia baniek…
var cost = 100;
var index;
…a tu zapisujemy indeks najtańszego płynu.
że tu
Podobnie jak wcześniej, tak wyników…
licę
tab
li
pęt
w
y
dam
eglą
prz
for (var i = 0; i < scores.length; i++) {
if (scores[i] == highscore) {
if (cost > costs[i]) {
index = i;
cost = costs[i];
}
}
}
return index;
}
Początkowo przypisujemy tość,
zmiennej cost wysoką wariżać,
a później będziemy ją obn n
gdy odnajdziemy tańszy pły
(z najwyższym wynikiem).
…i sprawdzamy, czy bieżący wynik jest równy
największemu.
Jeśli jest, to następnie sprawdzamy odpowiadający mu koszt.
Jeśli aktualna wartość zmiennej cost jest większa od kosztu
właśnie analizowanego płynu, oznacza to, że znaleźliśmy tańszy
płyn i należy go zapamiętać (a raczej jego indeks w tablicy)
i zaktualizować wartość zmiennej cost, tak by zawierała
najniższy znaleziony do tej pory koszt.
Kiedy pętla zostanie zakończona, w zmiennej index
będzie zapisany indeks najtańszego płynu do robienia
baniek, a zatem zwrócimy go do kodu wywołującego
jako wynik wykonania funkcji.
var mostCostEffective = getMostCostEffectiveSolution(scores, costs, highScore);
console.log(”Pïyn numer ” + mostCostEffective + ” jest najbardziej opïacalny.”);
Następnie wyświetlamy indeks (który
jest jednocześnie numerem płynu do
robienia baniek) w oknie konsoli.
Ostateczna postać raportu, który wskazuje płyn
numer 11 jako zwycięzcę, gdyż jest najtańszy
spośród wszystkich płynów, jakie uzyskały
najlepszy wynik.
DODATEK: Nasze rozwiązanie moglibyśmy zaimplementować także,
korzystając z tablicy bestSolutions, dzięki czemu nie musielibyśmy
ponownie przeglądać całej tablicy wyników. Pamiętasz zapewne,
że tablica bestSolutions zawiera indeksy płynów, które uzyskały
najlepszy wynik. A zatem moglibyśmy użyć indeksów z tej tablicy
do sprawdzenia wartości z tablicy kosztów. Taki kod byłby nieco
bardziej wydajny niż przedstawiony tutaj, lecz jednocześnie byłby
nieco trudniejszy do zrozumienia! Jeśli jesteś zainteresowany
takim rozwiązaniem, znajdziesz je w przykładach dołączonych do
książki, które można pobrać z serwera FTP wydawnictwa Helion.
204
Rozdział 4.
Konsola JavaScript
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
...
3á\QGREDQLHNQUZ\QLN
3á\QGREDQLHNQUZ\QLN
/LF]EDWHVWyZ
1DMZLĊNV]DOLF]EDZ\WZRU]RQ\FKEDQLHN
3á\Q\]QDMOHSV]\PZ\QLNLHP
3á\QQXPHUMHVWQDMEDUG]LHMRSáDFDOQ\
5.=UR]XPLHÊRELHNW\
Wycieczka do Obiektowa
Wyjeżdżamy
do Obiektowa!
Na dobre zostawiamy tą
starą, zakurzoną, proceduralną
mieścinę. Wyślemy wam
pocztówkę!
Do tej pory w tworzonym kodzie używałeś jedynie danych typów prostych
oraz tablic. Dodatkowo podchodziłeś do programowania w sposób proceduralny
— korzystałeś z prostych instrukcji, instrukcji warunkowych oraz pętli, ewentualnie
umieszczałeś je w funkcjach — to właściwie nie jest programowanie obiektowe.
Prawdę powiedziawszy, to w ogóle nie jest programowanie obiektowe! Tu i tam, nawet
o tym nie wiedząc, użyłeś — co prawda — kilku obiektów, jednak na razie jeszcze
nie napisałeś żadnego własnego obiektu. Nadszedł najwyższy czas, żeby zostawić to
stare i nudne proceduralne miasteczko i zacząć tworzenie własnych obiektów. W tym
rozdziale dowiesz się, dlaczego stosowanie obiektów sprawi, że Twoje życie stanie
się znacznie lepsze — przynajmniej pod względem programistycznym (niestety,
w jednej książce nie możemy poprawić Twojej znajomości mody i jednocześnie nauczyć
programowania w języku JavaScript). I jeszcze jedno ostrzeżenie: kiedy już poznasz
obiekty, nigdy nie będziesz chciał ich porzucić. Wyślij nam pocztówkę, kiedy już
dojedziesz do krainy obiektów.
to jest nowy rozdział
205
Przedstawienie obiektów
Czy ktoś powiedział „obiekty”?
A teraz nasze ulubione zagadnienie! Obiekty przeniosą Twoje umiejętności
korzystania z języka JavaScript na następny, wyższy poziom. Obiekty są
kluczem do zarządzania złożonym kodem, do zrozumienia obiektowego
modelu dokumentu, którym posługuje się przeglądarka (i który przedstawimy
w następnym rozdziale), do organizacji naszych danych, stanowią także
podstawę organizacji i pakowania wielu bibliotek JavaScript (więcej na ten
temat napiszemy dalej w tej książce). Skoro tak, to pewnie obiekty są złożonym
zagadnieniem, prawda? Pewnie! Tym razem także wskoczymy na głęboką wodę
obiektowości i okaże się, że błyskawicznie zaczniesz ich używać.
Oto sekret związany z obiektami w języku JavaScript: są one jedynie kolekcjami
właściwości. W ramach przykładu przyjrzyjmy się np. samochodowi. Samochód
ma następujące właściwości.
A także model. W tym przypadku
nazwalibyśmy widoczny tu
samochód „Bel Air”.
Samochody mają markę,
taką jak Chevrolet,
w skrócie „Chevy”.
Niektóre samochody są
kabrioletami. Ten akurat
nie jest.
Każdy samochód
ma kolor.
Samochód może
zabrać pewną
maksymalną liczbę
pasażerów.
Samochody nie tylko mają właściwości, ale też potrafią robić
określone rzeczy. O zachowaniu obiektów porozmawiamy nieco
później, na razie skoncentrujemy się na właściwościach…
206
Rozdział 5.
Każdy samochód ma
określony rok produkcji,
ten został wyprodukowany
w roku 1957.
Samochody mają także przebieg,
określający liczbę przejechanych
kilometrów.
Zrozumieć obiekty
Myśląc o właściwościach…
Oczywiście prawdziwy samochód to znacznie więcej niż kilka właściwości,
jednak właśnie te właściwości chcemy uwzględnić w programie.
Przeanalizujemy je pod kątem typów danych języka JavaScript.
i.
Samochód dysponuje zestawem właściwośc
Oto nasz
samochód
przedstawiony
w formie obiektu.
Każda właściwość ma nazwę i wartość.
make: “Chevy”
model: “Bel Air”
year: 1957
color: “czerwony”
Wartościami właściwości
make, model oraz color
są łańcuchy znaków.
Natomiast właściwości year,
passengers oraz mileage
mają wartości liczbowe.
passengers: 2
convertible: false
mileage: 1021
Z kolei właściwość
convertible jest typu
logicznego.
Obiekt Car
WYSIL
SZARE KOMÓRKI
Czy są także jakieś inne właściwości, które chciałbyś dodać do obiektu samochodu? Zastanów się,
jakie właściwości związane z samochodem mógłbyś jeszcze wymyślić, i zapisz je poniżej.
Pamiętaj, że w programie przydadzą się tylko te właściwości, które istnieją w rzeczywistości.
Te ozdobne kostki do gry
wyglądają ładnie, ale czy
naprawdę przydadzą się
w obiekcie?
jesteś tutaj
207
Właściwości obiektów
Zaostrz ołówek
Zaczęliśmy tworzyć tablicę nazw właściwości samochodu oraz odpowiadających im wartości.
Czy możesz nam w tym pomóc? Zanim przejdziesz dalej, koniecznie porównaj swoje odpowiedzi
z naszymi.
y
Tutaj zapisz nazw
właściwości.
A tu odpowiadające
im wartości.
{
Tutaj zapisz swoje
odpowiedzi. Jeśli
zechcesz, rozszerz tę
listę, dopisując do
niej wymyślone przez
Ciebie właściwości.
make
__________
:
model
__________
:
year
__________
:
color
__________
:
passengers
__________ :
convertible
__________ :
mileage :
__________
__________ :
__________ :
______________,
“Chevy”
______________,
______________,
______________,
______________,
______________,
______________,
______________,
______________
};
Kiedy już skończysz, zwróć
właściwości oraz ich nazw. uwagę na składnię zapisu
z nimi związany… Tak tylk Kiedyś może się pojawić kwiz
o wspominam.
WYSIL
SZARE KOMÓRKI
A co by było, gdyby samochód był taksówką? Które z właściwości oraz ich wartości byłyby takie same jak
w obiekcie chevroleta z 1957 roku? Ile właściwości miałoby inne wartości? Jakie dodatkowe właściwości
powinny się pojawić (albo które powinny zniknąć)?
208
Rozdział 5.
Zrozumieć obiekty
W jaki sposób tworzy się obiekty?
Mamy dobrą wiadomość: po zrobieniu ostatniego ćwiczenia „Zaostrz
ołówek” już prawie udało Ci się utworzyć obiekt. Jedyną rzeczą, jaka
jeszcze pozostała, jest przypisanie tego, co zapisałeś na poprzedniej
stronie, do jakiejś zmiennej (żeby później można było na tym obiekcie
wykonywać różne operacje). Poniżej pokazaliśmy, jak to zrobić.
Dodaj deklarację zmiennej, w której zostanie
zapisany obiekt.
var chevy = {
Następnie zacznij obiekt, zapisując otwierający
nawias klamrowy.
make: ”Chevy”,
model: ”Bel Air”,
Wewnątrz podaj wszystkie właściwości obiektu.
year: 1957,
Każda właściwość składa się z nazwy, dwukropka oraz wartości.
W tym obiekcie mamy właściwości, których wartościami są
łańcuchy znaków, liczby oraz wartość logiczna.
color: ”czerwony”,
passengers: 2,
Zauważ, że poszczególne właściwości są oddzielone
przecinkami.
convertible: false,
mileage: 1021
};
Obiekt należy zakończyć zamykającym nawiasem klamrowym
i, podobnie jak wszystkie inne deklaracje zmiennych, znakiem
średnika.
A jaki jest wynik tych wszystkich zabiegów? Oczywiście — zupełnie nowy
obiekt. Wyobraź sobie obiekt jako coś, co łączy w jedną całość wszystkie
używane nazwy i wartości (czyli, innymi słowy, właściwości).
Teraz dysponujesz już żywym obiektem wypełnionym
właściwościami. Zapisałeś go w zmiennej, której będziesz
mógł używać, by pobierać i modyfikować właściwości obiektu.
make: "Chevy"
model: "Bel Air"
year: 1957
chevy
color: "czerwony"
Wzięliśmy przedstawiony powyżej
tekstowy opis obiektu i zmieniliśmy go
w prawdziwy, żywy obiekt języka
JavaScript.
passengers: 2
convertible: false
mileage: 1021
Teraz możesz już używać swojego obiektu, przekazywać go w inne miejsca kodu,
pobierać wartości jego właściwości, zmieniać je, możesz także dodawać nowe
właściwości lub usuwać już istniejące. Już za chwilkę pokażemy, jak wykonywać
wszystkie te operacje; na razie jednak utwórzmy trochę więcej obiektów, by było
na czym przeprowadzać eksperymenty…
jesteś tutaj
209
Ćwiczenie z tworzenia obiektów
Ćwiczenie
Nie musisz ograniczać się do jednego obiektu. Prawdziwa potęga obiektów (jak się już niedługo przekonasz)
polega na możliwości tworzenia wielu obiektów i pisaniu kodu, który może operować na dowolnym obiekcie
przekazanym do tego kodu. Spróbuj swoich sił, tworząc od podstaw jeszcze jeden obiekt, kolejny obiekt
samochodu. Nie ociągaj się i zapisz poniżej kod swojego drugiego obiektu.
var cadi = {
Tutaj zapisz
właściwości
swojego nowego
obiektu Cadillac.
};
Powiedzmy, że będzie
to kolor jasnobrązowy.
To Cadillac GM z 1955 roku.
To nie jest kabriolet,
może przewozić do
pięciu osób (ma duże,
wygodne i składane
tylne siedzenie).
Jego przebieg
wynosi 12 892.
210
Rozdział 5.
Zrozumieć obiekty
Lü
RSURV
\W\ONRS P
Ğ
OL
LH
F
Z
FK
LHNWy
PDQGDWX
HQLDRE
]DPLDVWF]ąF\PLWZRU]
R
K
F
X
RW\
QDV
MD]G\´G
MG]LH&L
Z\FK
D]HPX \PLÄ]DVDGDPL
U
P
W\
NODPUR
K
F
LĊ
]
D
V
V
V
M
LĪ
LD
LD
Q
R
Z
DZ
1LHRED DSR]QDáVLĊ]S
LHNW\ZQ
ZRMHRE
V
]
V
ĪHE\Ğ]
MH
X
DSLV
VLĊĪH]
8SHZQLM
= {
y”
var cat
puszyst
e
m
a
n : ”
};
D
RGLFKZ
GG]LHODM
RĞFLR
ZáDĞFLZ
1D]Z\
SND
GZXNUR
DNLHP
UWRĞFL]Q
net = {
8
var pla
r: 4952
diamete
yZMH
PL]QDN
ĔFXFKD L]PLHQQ\FK
áD
L
P
\
OQ
P
};
\üGRZR
LQD]ZD
LPRJąE üSUDZLGáRZ\P
F
Ğ
R
ć
LZ
F
áDĞ
\E\
i ma by
1D]Z\Z\NWyUHPRJá\E
ściwośc konieczne
ła
w
ą
w
naz
pu,
áDĔFXFK
k odstę
że jeśli
,
na
Zauważ zawierający z dzysłowach.
u
h
c
c
u
w
c
ń
o
ła
isanie g
jest zap
get = {
var wid
3.14,
cost$:
e
e”: tru
l
”on sa
};
LRWDNLH
ĞFLZRĞF
ZLHZáD
LVWQLHüG
:RELH
{
ecast =
var for
4,
3
:
p
highTem
p: 56
highTem
};
.
ziała
nie zad
ŹLE! To
RĞ
get = {
var gad
anvil",
name: "
: true
isHeavy
FLQND]D
LVXMSU]H
1LH]DS
= {
erhero
,
var sup
t
Ba man"
ciciel"
name: "
wany mĂ
o
k
s
a
m
a
"=
alias:
};
L
ĞFLZRĞF
WQLHMZáD
FLąRVWD
ZDUWRĞ
};
U]HFLQND
DNDPLS
LHODM]Q
üRGG]
ZDZDUW
DU\QD]
F]\OLS
FLZRĞFL
áDĞ
HJyOQHZ
QD]ZLH
MVDPHM
PRJą
NFLHQLH
3RV]F]
MHP\
DMVWRVX
]Z\F]
GQDN]D
rzeba ka!
nie t
in
Tutaj iać przec
staw
jesteś tutaj
211
Zrozumieć obiekty
Czym w ogóle jest programowanie obiektowe?
Dotychczas rozpatrywaliśmy wszystkie problemy jako zbiory deklaracji zmiennych,
instrukcji warunkowych, instrukcji while i for oraz wywołań funkcji. To tzw. myślenie
proceduralne: najpierw zrób to, potem to itd. Natomiast w programowaniu obiektowym
problem rozpatrujemy w kontekście obiektów. Obiektów, które mają stan (np. samochód
może mieć określony poziom oleju i paliwa) oraz zachowanie (np. samochód może być
uruchomiony, może jechać, może być zatrzymany lub zaparkowany).
A o co w tym chodzi? Otóż programowanie obiektowe wyzwala umysł i pozwala myśleć
na wyższym poziomie. To taka różnica, jak pomiędzy zrobieniem grzanki od samego
początku (czyli zrobieniem grzałki z drutu oporowego, podłączeniem jej do kontaktu,
włączeniem prądu i trzymaniem chleba na tyle blisko, by się opiekł, nie wspominając
już o czekaniu na tyle długo, by grzanka była dobra, i odłączeniem grzałki od prądu,
kiedy już będzie gotowa) a użyciem tostera (czyli umieszczeniem kromki chleba
w tosterze i wciśnięciem przycisku). Ten pierwszy sposób jest proceduralny, a ten drugi
— obiektowy: używamy obiektu tostera, który udostępnia metody łatwego wsadzania
kromek chleba i opiekania ich.
Co Ci się podoba
w programowaniu obiektowym?
3R]ZDODPLSURMHNWRZDþZEDUG]LHMQDWXUDOQ\
VSRVyE']LĐNLWHPXU]HF]\PRJċVLĐ]PLHQLDþ
³.XEDSURMHNWDQWRSURJUDPRZDQLD
2ELHNWSLâNL
Obiekt gracza
Obiekt gracza
WYSIL
SZARE KOMÓRKI
Załóżmy, że masz zaimplementować klasyczną
grę wideo, czyli grę w ping-ponga. Jakie obiekty
utworzyłbyś? Jaki stan posiadałyby te obiekty i jakie
miały zachowania?
1LHPXV]Đ]DMPRZDþVLĐNRGHPNWyU\MXİ
SU]HWHVWRZDâHPFK\EDİHGRGDMĐGRQLHJRQRZH
PRİOLZRĤFL
³%DUWHNSURJUDPLVWD
3RGREDPLVLĐWRİHGDQHLPHWRG\NWyUHQDW\FK
GDQ\FKRSHUXMċVċ]JUXSRZDQHUD]HPZMHGQ\P
RELHNFLH
³-DVLHNSLZRV]
0RİOLZRĤþXİ\ZDQLDJRWRZHJRNRGXZLQQ\FK
DSOLNDFMDFK.LHG\SLV]ĐQRZ\RELHNWPRJĐVSUDZLþ
İHEĐG]LHQDW\OHHODVW\F]Q\E\PPyJâSyĮQLHM
]QLHJRNRU]\VWDþZLQQ\FKSURMHNWDFK
³.U]\VLHNPHQDGİHUSURMHNWX
1LHPRJĐXZLHU]\þİH.U]\VLHNSRZLHG]LDâFRĤ
WDNLHJR1LHQDSLVDâQDZHWOLQLMNLNRGXRGSLĐFLXODW
³'DUHNSUDFXMH].U]\ĤNLHP
212
Rozdział 5.
Obiekt rakietki
Pong!
Zrozumieć obiekty
Najmniejszy samochód
w Sieciowicach!
var fiat = {
make: ”Fiat”,
model: ”500”,
year: 1957,
color: ”szaroniebieski”,
passengers: 2,
convertible: false,
mileage: 88000
};
Jak działają właściwości?
A zatem udało Ci się upakować wszystkie właściwości w obiekcie. I co teraz? No cóż…
Możesz sprawdzać wartości tych właściwości, możesz je zmieniać, dodawać nowe
właściwości, usuwać już istniejące i — ogólnie rzecz biorąc — używać ich w kodzie.
Wypróbujmy kilka tych operacji, wykonując je — oczywiście — w języku JavaScript.
Jak odwoływać się do właściwości? Aby odwołać się do właściwości obiektu,
zacznij od podania jego nazwy, następnie zapisz kropkę i w końcu nazwę właściwości.
Często mówimy, że takie wyrażenie jest przykładem zapisu z kropką.
Najpierw podaj
nazwę obiektu…
następnie kropkę…
…a w końcu na
właściwości. zwę
fiat.mileage
Takiej właściwości możemy następnie używać w wyrażeniach. Oto przykład.
=aSis ] kUoSką var miles = fiat.mileage;
if (miles < 2000) {
buyIt();
}
wy zmiennej
Zacznij od podania naz aj do niej kropkę,
zawierającej obiekt, dodściwości.
a następnie nazwę wła
ƕ =DSLV]NURSNą ]DSHZQLDGRVWĊSGR
ZáDĞFLZRĞFLRELHNWyZ
ƕ 3U]\NáDGRZRILDWFRORU
RGZRáXMHVLĊGRZáDĞFLZRĞFL
FRORURELHNWXILDWNWyUD
ZRVWDWQLPSU]\NáDG]LHPLDáD
ZDUWRĞüÄV]DURQLHELHVNL´
jesteś tutaj
213
Stosowanie właściwości obiektów
-aN ]PLHQLĉ warWoĩĉ wÿaĩFLwoĩFL" Wartość właściwości można zmienić
w dowolnym momencie. Trzeba tylko przypisać właściwości nową wartość.
W ramach przykładu załóżmy, że chcemy zmienić przebieg naszego fiata na
10 000. Możemy to zrobić w następujący sposób.
fiat.mileage = 10000;
Wystarczy podać właściwość, którą chcemy
zmienić, a następnie przypisać jej nową
wartość. Uwaga: w niektórych sytuacjach
może to być nielegalne!
-aN dodaĉ do oELHNWX QowĐ wÿaĩFLwoĩĉ" Obiekty można rozszerzać
w dowolnej chwili, wystarczy dodać do nich nowe właściwości. W tym celu
należy podać nazwę nowej właściwości i przypisać jej jakąś wartość. Przykładowo
dodamy zmienną informującą, czy samochód należy już umyć, czy nie.
make: "Fiat"
model: "500"
year: 1957
fiat.needsWashing = true;
Jeśli podanej właściwości nie będzie
w obiekcie, zostanie do niego dodana.
Jeśli podana właściwość już istnieje,
wykonanie tej instrukcji spowoduje,
że zmieni ona wartość.
color: "szaroniebieski"
passengers: 2
convertible: false
mileage: 88000
needsWashing: true
Do obiektu została
dodana nowa właściwość.
-aN VWoVowaĉ wÿaĩFLwoĩĉ w oEOLF]HQLaFK" W bardzo podobny sposób
można używać właściwości w obliczeniach: wystarczy potraktować je dokładnie
tak samo jak każdą zmienną (bądź wartość). Oto kilka przykładów.
Właściwości obiektów można używać dokładnie
tak samo jak zmiennych; z tym wyjątkiem, że
odwołanie do wartości właściwości wymaga
zastosowania zapisu z kropką.
if (fiat.year < 1965) {
classic = true;
}
for (var i = 0; i < fiat.passengers; i++) {
addPersonToCar();
}
214
Rozdział 5.
Zrozumieć obiekty
Obiektowe magnesiki
7HQNRG]RVWDâSRFLĐW\QDIUDJPHQW\LFDâNRZLFLHZ\PLHV]DQ\QDORGyZFH3U]HþZLF]VZRMH
XPLHMĐWQRĤFLWZRU]HQLDRELHNWyZL]QDMRPRĤþ]DSLVX]NURSNċDE\RGWZRU]\þSRF]ċWNRZċ
SRVWDþWHJRNRGX8ZDİDMMHGQDNERPRJâRVLĐ]GDU]\þİHZĤUyGZ\PLHV]DQ\FK
PDJQHVLNyZ]DZLHUXV]\â\VLĐMDNLHĤGRGDWNRZH
bark
,
dog.weight
age: 4
dog.activity
,
Obiekt
psa.
4
dog.bark
age
breed: "mieszaniec"
activity: "przynoszenie piïki"
,
breed
"Burek"
,
weight: 20.2
Burek
"przynoszenie piïki"
dog.name
name: "Burek"
20.2
weight
w
Użyj tych magnesikó
u.
do odtworzenia kod
var dog = {
name: _________
_________: 20.2
age: ________
________: ”mieszaniec”,
activity: ____________
};
var bark;
, że
Burek ma nadzieję asz
ład
uk
po
wo
dło
wi
pra
wszystkie jego
właściwości.
if (_____________ > 20) {
bark = ”HAU HAU”;
} else {
bark = ”hau hau”;
}
var speak = __________ + ” szczeka ” + ________ + ” kiedy ma ochotÚ na ” + ____________;
console.log(speak);
jesteś tutaj
215
Usuwanie właściwości
Jak widzę, nowe właściwości
mogę dodawać w dowolnym
momencie. A czy mogę je także
usuwać?
TaN wÿaĩFLwoĩFL PoĵQa dodawaĉ L XVXwaĉ
w dowoOQ\P F]aVLH Jak wiesz, żeby dodać właściwość
do obiektu, wystarczy po prostu przypisać do niej
wartość, tak jak pokazaliśmy poniżej:
fido.dogYears = 35;
i od tego momentu obiekt fido będzie miał właściwość
dogYears. Całkiem proste.
Aby usunąć właściwość z obiektu, należy użyć
specjalnego słowa kluczowego delete. Stosuje się je
w następujący sposób.
delete fido.dogYears;
Kiedy usuwamy właściwość, operacja usunięcia nie
ogranicza się do samej wartości — z obiektu znika cała
właściwość. Jeśli po usunięciu właściwości spróbujemy
odczytać wartość fido.dogYears, uzyskamy wartość
undefined.
Wyrażenie delete zwraca wartość true, jeśli
właściwość udało się usunąć, oraz wartość false, jeśli
operacja zakończyła się niepowodzeniem (co może
się zdarzyć, jeśli np. próbowaliśmy usunąć właściwość
chronionego obiektu należącego do przeglądarki).
Operacja ta zwróci wartość true nawet wtedy, gdy
usuwana właściwość nie istniała w obiekcie.
216
Rozdział 5.
Zrozumieć obiekty
Nie istnieją
głupie pytania
P: Ile właściwości może mieć obiekt?
O: Tak mało lub tak wiele, jak się nam podoba. Można tworzyć
P: Co się stanie, jeśli spróbuję dodać do obiektu
P: Jak można utworzyć obiekt bez żadnych właściwości?
O: Tak samo, jak się tworzy normalny obiekt, przy czym należy
np. needsWashing, a on już będzie miał właściwość o tej samej
nazwie, będzie to równoznaczne ze zmianą jej wartości. Jeśli np.
użyjesz instrukcji:
obiekty, które nie będą mieć żadnych właściwości, jak również
obiekty, które będą ich mieć setki. To zależy wyłącznie od nas.
właściwość, a on już wcześnie będzie mieć właściwość
o takiej samej nazwie?
O: Jeśli spróbujesz dodać do obiektu samochodu właściwość,
pominąć wszystkie umieszczone w nim właściwości. Oto przykład.
fiat.needsWashing = true;
var lookMaNoProps = { };
lecz obiekt fiat już ma właściwość needsWashing o wartości
false, jej wartość zostanie zmieniona na true.
P: Wiem, że właśnie zapytałem, jak się tworzy obiekt
bez właściwości, ale po co w ogóle tworzyć takie obiekty?
P: Co się stanie, kiedy spróbuję odwołać się do
O: Można zaczynać od pustego obiektu, a dopiero potem
if (fiat.make) { ... }
dynamicznie dodawać do niego właściwości, zależnie od logiki
działania kodu. Taki sposób tworzenia obiektów stanie się
bardziej zrozumiały nieco później, kiedy już zdobędziemy trochę
doświadczenia w ich stosowaniu.
var lookMaNoProps = { };
lookMaNoProps.age = 10;
if (lookMaNoProps.age > 5) {
lookMaNoProps.school = ”podstawowa”;
}
P: Dlaczego stosowanie obiektu jest lepsze od stosowania
grupy zmiennych. W końcu każda właściwość w obiekcie
fiat mogłaby być odrębną zmienną, prawda?
O: Obiekty gromadzą złożoność danych, dzięki czemu możemy
myśleć o projekcie kodu na wyższym poziomie abstrakcji,
a nie koncentrować się na szczegółach. Załóżmy, że chcemy
napisać symulator ruchu drogowego zarządzający dziesiątkami
samochodów; w takim przypadku lepiej będzie zarządzać obiektami
samochodów, ulic i sygnalizacji świetlnej, a nie setkami zmiennych.
Oprócz tego obiekty hermetyzują, czyli ukrywają, złożoność
stanu i zachowania obiektów, dzięki czemu nie musimy się nimi
przejmować. Wszystko to stanie się znacznie bardziej oczywiste,
kiedy nabędziesz nieco więcej doświadczeń w korzystaniu
z obiektów.
nieistniejącej właściwości? Gdy np. użyję wyrażenia:
lecz fiat nie będzie mieć właściwości make?
O
: Jeśli obiekt fiat nie będzie mieć właściwości make, to
wynikiem wyrażenia fiat.make będzie wartość undefined.
P: A co się stanie, jeśli za ostatnią właściwością zapiszę
przecinek?
O: W większości przeglądarek nie spowoduje to wystąpienia
błędu. Jednak w starszych wciąż może to prowadzić do
zatrzymania wykonywania kodu JavaScript. Jeśli zatem zależy nam,
by kod działał w możliwie dużej liczbie przeglądarek, należy unikać
niepotrzebnych przecinków.
P: Czy mogę używać funkcji console.log do wyświetlania
obiektu w oknie konsoli?
O: Tak. Wystarczy użyć w kodzie wywołania:
console.log(fiat);
a po wczytaniu strony w przeglądarce z widocznym oknem konsoli
zostaną w nim wyświetlone informacje o obiekcie.
Konsola JavaScript
> console.log(fiat)
Object {make: "Fiat", model: "500", year: 1957,
color: "szaroniebieski", passengers: 2...}
>
jesteś tutaj
217
Referencje do obiektów
W jaki sposób zmienna przechowuje obiekt?
Ciekawe umysły chciałyby to wiedzieć…
Za kulisami
Przekonałeś się już, że zmienna przypomina nieco pojemnik i może
przechowywać jakąś wartość. Jednak liczby, łańcuchy znaków oraz wartości
logiczne są dosyć niewielkimi wartościami. A jak to wygląda w przypadku
obiektów? Czy zmienna może przechowywać obiekt o dowolnej wielkości,
niezależnie do tego, ile będzie miał właściwości?
7ak naSUawGĊ ]mienne nie SU]echowuMą oEiektyw
=amiast tego SU]echowuMą UeIeUencMĊ Go oEiektu
make: "Chevy"
model: "Bel Air"
5eIeUencMa to wskaĨnik luE aGUes Iakt\c]nego oEiektu
year: 1957
color: "czerwony"
,nn\mi sáow\ ]mienna nie SU]echowuMe samego oEiektu lec]
passengers: 2
Uac]eM coĞ co moĪna E\ u]naü ]a wskaĨnik A w MĊ]\ku -aYaScUiSt
naSUawGĊ nie wiem\ co Mest ]aSis\wane w ]mienneM UeIeUenc\MneM
:iem\ natomiast Īe cokolwiek to Mest wska]uMe na oEiekt
.ieG\ uĪ\wam\ ]aSisu ] kUoSką inteUSUeteU -aYaScUiStu sam
]atUos]c]\ siĊ o ]astosowanie oGSowieGnich UeIeUencMi w celu
SoEUania wáaĞciwego oEiektu i uĪ\cia Mego wáaĞciwoĞci
convertible: false
mileage: 1021
Ej…
Przepraszam, ale nie
VċG]ĐE\WRVLĐWXWDM
]PLHĤFLâR«
chevy
A zatem obiektu nie można umieścić w zmiennej, choć często właśnie
tak o tym myślimy. Przechowywanie obiektów nie działa w taki sposób —
zmienne nie są gigantycznymi, rozszerzalnymi kubkami, w których można
zmieścić obiekty dowolnej wielkości. Zmienna obiektowa przechowuje
jedynie referencję do obiektu.
Można na to spojrzeć także w inny sposób: zmienna typu prostego
reprezentuje faktyczną wartość, natomiast zmienna obiektowa reprezentuje
sposób odwołania się do obiektu. W praktyce będziemy mogli wyobrażać sobie
obiekty wyłącznie jako... obiekty, takie jak psy lub koty, a nie jako referencje.
Jednak wiedza, że zmienne zawierają referencje do obiektów, przyda się nieco
później (będziesz mógł się o tym przekonać już za kilka stron).
Warto także pomyśleć o jeszcze jednej rzeczy: zapisu z kropką (.) używamy
wraz ze zmiennymi referencyjnymi, by powiedzieć: „użyj referencji zapisanej
przed kropką, by dostarczyć mi obiekt, który ma właściwość podaną
po kropce”. (Przeczytaj to zadanie kilka razy i pozwól, by dobrze utrwaliło
się w głowie). Przykładowo:
car.color;
oznacza: „użyj obiektu, do którego referencja jest zapisana w zmiennej car,
i odwołaj się do jego właściwości color”.
218
Rozdział 5.
make: "Chevy"
model: "Bel Air"
year: 1957
color: "czerwony"
passengers: 2
convertible: false
mileage: 1021
$FK«
=QDF]QLHOHSLHM
7HUD]Z\VWDUF]\İH
EĐGĐSU]HFKRZ\ZDþMHG\QLH
UHIHUHQFMĐGRRELHNWX
chevy
Zrozumieć obiekty
Porównanie danych typów prostych i obiektów
Referencję do obiektu możesz sobie wyobrazić jako kolejną wartość zmiennej, co
oznacza, że można ją wziąć i umieścić w kubku, tak samo jak każdą inną wartość
typu prostego. W przypadku danych typów prostych wartość zamiennej jest…
wartością, taką jak 5, -26.7, ĵF]HĂÊĵ lub false. Natomiast w przypadku zmiennych
referencyjnych wartością zmiennej jest referencja, czyli wartość reprezentująca sposób
dotarcia do konkretnego obiektu.
”
Ĥþ
]H
“F
ue
tr
OLF]EDâDĚFXFKZDUWRĤþreferencja
znaków
logiczna
A to jest zmienna
referencyjna; zawiera ncją
wartość będącą refere
do obiektu.
To są zmienne typów prostych.
Każda przechowuje wartość,
która została jej przypisana.
Inicjalizacja zmiennych typów prostych
Kiedy deklarujemy i inicjalizujemy zmienną typu prostego, nadajemy
jej wartość, która jest zapisywana bezpośrednio w tej zmiennej
jak w kubku; oto przykład.
var x = 3;
Zmienna zawiera
OLF]EĐ
Za kulisami
x
Liczba, wartość
typu prostego.
Nie wiemy (ani nie
interesuje nas), jak
interpreter JavaScriptu
reprezentuje referencje
do obiektów.
Wiemy natomiast, że
możemy odwoływać się
do obiektów oraz ich
właściwości przy użyciu
zapisu z kropką.
Inicjalizacja zmiennych obiektowych (referencyjnych)
Kiedy deklarujemy i inicjalizujemy obiekt, tworzymy go przy użyciu
specjalnego zapisu obiektowego, jednak sam obiekt nie będzie mógł
być umieszczony w zmiennej. Tym, co jest umieszczane w naszym
kubku, jest referencja do obiektu.
Ca
W zmiennej zostaje zapisana
UHIHUHQFMDGRRELHNWX&DU
Sam obiekt Car nie jest
zapisywany w zmiennej!
r
var myCar = {...};
O bi ekt
myCar
Wartość
referencji
jesteś tutaj
219
Przekazywanie obiektów do funkcji
Jeszcze inne operacje z wykorzystaniem obiektów…
Załóżmy, że chcesz znaleźć dobry samochód na okres pobytu w Sieciowicach.
Jakie są Twoje kryteria? Może takie:
‰ wyprodukowany w roku 1960 lub wcześniej,
‰ przebieg 10 000 km lub mniejszy.
Przy okazji chciałbyś wykorzystać swoje nowe umiejętności pisania kodu (i ułatwić sobie życie), dlatego też masz zamiar
napisać funkcję, która dokona za Ciebie wstępnej selekcji samochodów. Innymi słowy, jeśli samochód spełni kryteria,
funkcja zwróci wartość true; w przeciwnym razie samochód nie jest wart Twojej uwagi, więc funkcja zwróci wartość false.
Mówiąc nieco bardziej precyzyjnie, masz zamiar napisać funkcję, która będzie pobierać obiekt samochodu jako parametr,
sprawdzać właściwości tego obiektu i zwracać wartość logiczną. Funkcja ta będzie działać na dowolnych obiektach samochodów.
Przyjrzyjmy się tej funkcji.
Oto funkcja.
Chcesz przekazywać do niej
obiekt samochodu.
function prequal(car) {
if (car.mileage > 10000) {
Tutaj używasz zapisu z kropką, by
pobrać z parametru car wartości
właściwości mileage oraz year.
return false;
} else if (car.year > 1960) {
return false;
}
return true;
}
Tutaj sprawdzasz wartości
właściwości, porównując je ze z góry
określonymi wartościami.
Jeśli którykolwiek z warunków
dyskwalifikujących testowany samochód
zostanie spełniony, funkcja zwraca wartość
false. W przeciwnym razie zwracana jest
wartość true, która oznacza, że samochód
pomyślnie przeszedł wstępną kwalifikację.
A teraz wypróbujemy tę funkcję. Będziemy przede wszystkim potrzebowali obiektu
samochodu. Co powiesz na taki?
var taxi = {
make: ”SieMoCorp”,
model: ”Taxi”,
year: 1955,
color: ”ĝöïty”,
passengers: 4,
convertible: false,
mileage: 281341
};
220
Rozdział 5.
Co myślisz o tym aucie? Czy
powinieneś uwzględniać tę
taksówkę w swoich poszukiwaniach?
Dlaczego tak lub dlaczego nie?
Zrozumieć obiekty
Wstępna selekcja
Powiedzieliśmy już wystarczająco dużo na temat obiektów. Nadszedł czas, by faktycznie utworzyć
jakiś obiekt i przeanalizować go z wykorzystaniem funkcji prequal. A zatem otwórz swoją ulubioną,
prostą stronę WWW (tym razem ma ona nazwę prequal.html), wpisz w niej przedstawiony poniżej kod,
a następnie ją zapisz, otwórz w przeglądarce i sprawdź, czy samochód przeszedł wstępną selekcję.
var taxi = {
make: ”SieMoCorp”,
model: ”Taxi”,
year: 1955,
color: ”ĝöïty”,
passengers: 4,
convertible: false,
mileage: 281341
};
function prequal(car) {
if (car.mileage > 10000) {
return false;
} else if (car.year > 1960) {
return false;
}
return true;
}
var worthALook = prequal(taxi);
if (worthALook) {
console.log(”PowinieneĂ zainteresowaÊ siÚ tym ” + taxi.make + ” ” + taxi.model);
} else {
console.log(”Ten ” + taxi.make + ” ” + taxi.model + ” moĝesz sobie podarowaÊ”);
}
Czy taksówka się załapała?
Konsola JavaScript
A to uzyskaliśmy… Prześledźmy
szybko kod przedstawiony na następnej
stronie, by przekonać się, dlaczego
taksówka została odrzucona…
7HQ6LH0R&RUS7D[LPRĝHV]VRELHSRGDURZDÊ
jesteś tutaj
221
Jak działa wstępna selekcja
Analiza działania wstępnej selekcji
1
Na początek tworzymy obiekt taksówki i zapisujemy
go w zmiennej taxi. Oczywiście, zmienna taxi
zawiera referencję do obiektu taksówki, a nie sam
obiekt.
make: "SieMoCorp"
model: "Taxi
year: 1955
FRORUĝöïW\
passengers: 4
taxi
var taxi = { ... };
2
Następnie wywołujemy funkcję prequal, przekazując
do niej argument taxi, który wewnątrz funkcji zostanie
powiązany z parametrem car.
function prequal(car) {
3
make: "SieMoCorp"
model: "Taxi
year: 1955
FRORUĝöïW\
passengers: 4
...
}
convertible: false
mileage: 281341
convertible: false
mileage: 281341
car wskazuje na ten sam obiekt, co taxi!
Następnie wewnątrz funkcji wykonujemy niezbędne
testy operujące na parametrze car.
make: "SieMoCorp"
model: "Taxi
year: 1955
FRORUĝöïW\
passengers: 4
if (car.mileage > 10000) {
convertible: false
mileage: 281341
return false;
} else if (car.year > 1960) {
return false;
}
4
W tym przypadku przebieg samochodu
jest znacznie większy niż założone 10 000
kilometrów, dlatego też wstępna selekcja
tego auta zakończyła się negatywnie.
Szkoda, bo to fajna fura.
Niestety, taksówka przejechała zbyt wiele kilometrów, dlatego też pierwszy warunek,
car.mileage > 10000, przyjął wartość true. Oznacza to, że funkcja zwróci wartość
false, która zostanie zapisana w zmiennej worthALook. W efekcie w oknie konsoli
zostanie wyświetlony komunikat: ĵ7HQ6LH0R&RUS7D[LPRĝHV]VRELHSRGDURZDÊĵ.
Funkcja prequal zwraca wartość false,
a zatem w oknie konsoli zostanie
wyświetlony komunikat…
var worthALook = prequal(taxi);
Konsola JavaScript
7HQ6LH0R&RUS7D[LPRĝHV]VRELHSRGDURZDÊ
if (worthALook) {
FRQVROHORJ ĵ3RZLQLHQHĂ]DLQWHUHVRZDÊVLÚW\PĵWD[LPDNHĵĵWD[LPRGHO } else {
FRQVROHORJ ĵ7HQĵWD[LPDNHĵĵWD[LPRGHOĵPRĝHV]VRELHSRGDURZDÊĵ }
222
Rozdział 5.
Zrozumieć obiekty
Zaostrz ołówek
Teraz Twoja kolej. Oto trzy kolejne obiekty samochodów; masz określić, jaki będzie
wynik przekazania każdego z nich do funkcji prequal. Podaj każdą z odpowiedzi
samodzielnie, a później napisz kod, który je sprawdzi i wyświetli.
var fiat = {
var cadi = {
make: ”GM”,
make: ”Fiat”,
model: ”Cadillac”,
model: ”500”,
year: 1955,
year: 1957,
color: ”jasnobrÈzowy”,
color: ”szaroniebieski”,
passengers: 5,
passengers: 2,
convertible: false,
convertible: false,
mileage: 12892
mileage: 88000
};
};
Tu zapisz
wartość,
którą zwróci
funkcja
prequal.
var chevy = {
make: ”Chevy”,
model: ”Bel Air”,
year: 1957,
color: ”czerwony”,
passengers: 2,
convertible: false,
mileage: 1021
};
jesteś tutaj
223
Przekazywanie referencji do obiektów
Porozmawiajmy nieco więcej o przekazywaniu obiektów do funkcji
Mówiliśmy już trochę na temat sposobu przekazywania argumentów do funkcji — wspominaliśmy, że są
przekazywane przez wartość, co oznacza, że jest przekazywana kopia. A zatem, jeśli przekazujemy liczbę
całkowitą, do funkcji zostaje przekazana kopia tej wartości. Dokładnie ta sama zasada obowiązuje także
w przypadku przekazywania obiektów. Aby dokładnie zrozumieć, co się dzieje podczas przekazywania
obiektów do funkcji, musimy przyjrzeć się nieco bliżej temu, co w kontekście obiektów oznacza
„przekazywanie przez wartość”.
Już wiesz, że podczas zapisywania obiektu w zmiennej de facto zostaje w niej zapisana referencja do obiektu,
a nie sam obiekt. I jak już wspominaliśmy, taką referencję można sobie wyobrazić jako wskaźnik na obiekt.
QDPH´%XUHNµ
ILGR
ZHLJKW
EUHHG´PLHV]DQLHFµ
ORYHV´VSDFHU\µ
Kiedy zapisujemy obiekt
w zmiennej, zostaje w niej
umieszczona referencja do
obiektu. Zmienna „nie zawiera”
samego obiektu.
Dog
Jeśli zatem wywołujemy funkcję i przekazujemy do niej obiekt, w rzeczywistości nie
jest przekazywany obiekt, lecz referencja do niego. Korzystając z naszej semantyki
przekazywania przez wartość, w parametrze funkcji jest zapisywana kopia
referencji, a referencja ta jest wskaźnikiem do oryginalnego obiektu.
Parametr dog wskazuje
na ten sam obiekt, co zmienna fido.
function bark(dog) {
... kod funkcji ...
}
bark(fido);
ILGR
GRJ
kopia
Kiedy wywołujemy funkcję bark,
przekazując do niej fido jako
argument, w parametrze dog
zostaje zapisana kopia referencji.
Co to wszystko oznacza? Jedną z podstawowych konsekwencji tego faktu jest to, że jeśli
wewnątrz funkcji zmienimy wartość właściwości obiektu, zostanie także zmieniona właściwość
początkowego obiektu. A zatem wszelkie zmiany wprowadzane w obiekcie wewnątrz funkcji
będą obowiązywały nawet po jej zakończeniu. Przeanalizujmy działanie takiej funkcji…
224
Rozdział 5.
Zrozumieć obiekty
Burek na diecie…
Załóżmy, że testujemy nową metodę odchudzania psów, którą chcemy zgrabnie
zaimplementować w postaci funkcji loseWeight. Aby z niej skorzystać, trzeba przekazać
w jej wywołaniu obiekt psa oraz wielkość, o jaką chcemy naszego ulubieńca odchudzić —
funkcja, w magiczny sposób, zredukuje jego wagę. A tak działa całe rozwiązanie.
1
1DMSLHUZVSUDZG]LP\RELHNWSVDfidoNWyU\PDP\]DPLDU
SU]HND]DþGRIXQNFMLloseWeight
Oto obiekt psa.
fido jest referencją do obiektu,
a to oznacza, że obiekt nie
jest w niej umieszczony,
a jedynie zmienna go wskazuje.
QDPH´%XUHNµ
ZHLJKW
EUHHG´PLHV]DQLHFµ
ORYHV´VSDFHU\µ
fido
loseWeight(fido, 10);
2
Kiedy przekazujemy
fido do funkcji, nie
przekazujemy obiektu,
a referencję do niego.
Dog
W parametrze dog IXQNFMLloseWeight]DSLV\ZDQDMHVW
NRSLDUHIHUHQFMLGRRELHNWXfido$]DWHPZV]HONLH]PLDQ\
ZSURZDG]DQHZHZâDĤFLZRĤFLDFKRELHNWXSU]\Xİ\FLXWHJR
SDUDPHWUXEĐGċPLDâ\ZSâ\ZQDRELHNWSU]HND]DQ\GRIXQNFML
Kiedy przekażemy fido do funkcji loseWeight,
w parametrze dog zostanie zapisana kopia
referencji, a nie kopia obiektu. A zatem
zmienna fido oraz parametr dog wskazują
ten sam obiekt.
Referencja ą
dog jest kopi
o.
referencji fid
dog
function loseWeight(dog, amount) {
dog.weight = dog.weight — amount;
}
A zatem, kiedy odejmiemy 10 kilo
od dog.weight, zmienimy tym samym
wartość właściwości fido.weight.
alert(fido.name + ” waĝy teraz ” + fido.weight + ” kg.”);
jesteś tutaj
225
Ćwiczenie obiektowe
Zaostrz ołówek
Otrzymałeś supertajny plik oraz dwie funkcje zapewniające możliwość odczytu
i ustawiania jego zawartości, jednak wyłącznie wtedy, kiedy dysponujesz
prawidłowym hasłem. Pierwsza funkcja, getSecret, zwraca zawartość pliku,
jeśli hasło jest prawidłowe, i jednocześnie rejestruje próbę dostępu do pliku. Druga
funkcja, setSecret, aktualizuje zawartość pliku i cofa licznik dostępu do pliku
do wartości początkowej — 0. Twoim zadaniem jest uzupełnienie pustych miejsc
w poniższym kodzie i przetestowanie funkcji.
function getSecret(file, secretPassword) {
_______.opened = _______.opened + 1;
if (secretPassword == _______.password) {
return ______.contents;
}
else {
return ”=ïe hasïo! Nie masz dostÚpu do sekretöw.”;
}
}
function setSecret(file, secretPassword, secret) {
if (secretPassword == _______.password) {
______.opened = 0;
______.contents = secret;
}
}
var superSecretFile = {
level: ”tajne”,
opened: 0,
password: 2,
contents: ”NastÚpne spotkanie z dr. =atanem odbÚdzie siÚ w Katowicach.”
};
var secret = getSecret(_______________, _____);
console.log(secret);
setSecret(_____________, ____, ”NastÚpne spotkanie z dr. =atanem odbÚdzie siÚ w ¿ywcu.”);
secret = getSecret(_______________, _____);
console.log(secret);
226
Rozdział 5.
Zrozumieć obiekty
Wróciłem i tym razem mam
staro-auto-znajdowator. To
maleństwo sprawi, że całe dnie
będziesz polować na samochody.
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Staro-auto-znajdowator</title>
<script>
function makeCar() {
var makes = [”Chevy”, ”GM”, ”Fiat”, ”SieMoCorp”, ”Tucker”];
var models = [”Cadillac”, ”500”, ”Bel-Air”, ”Taxi”, ”Torpedo”];
var years = [1955, 1957, 1948, 1954, 1961];
var colors = [”czerwony”, ”niebieski”, ”jasnobrÈzowy”, ”ĝöïty”, ”biaïy”];
var convertible = [true, false];
var
var
var
var
var
var
rand1
rand2
rand3
rand4
rand5
rand6
=
=
=
=
=
=
Math.floor(Math.random()
Math.floor(Math.random()
Math.floor(Math.random()
Math.floor(Math.random()
Math.floor(Math.random()
Math.floor(Math.random()
var car = {
make: makes[rand1],
model: models[rand2],
year: years[rand3],
color: colors[rand4],
passengers: rand5,
convertible: convertible[rand6],
mileage: 0
};
return car;
*
*
*
*
*
*
makes.length);
models.length);
years.length);
colors.length);
5) + 1;
2);
Staro-auto-znajdowator jest
programem nieco podobnym
do korpo-zdanio-budowatora
z rozdziału 4., z tą różnicą,
że słowa zostały zamienione
na właściwości samochodów,
a program zamiast zdań
generuje nowe obiekty
samochodów!
Sprawdź, co ta
instrukcja robi
i jak działa.
}
function displayCar(car) {
console.log(”Twoim nowym samochodem jest ” + car.make + ” ” +
car.model + ” z roku ” + car.year);
}
var carToSell = makeCar();
displayCar(carToSell);
</script>
</head>
<body></body>
</html>
jesteś tutaj
227
Jak działa staro-auto-znajdowator
Staro-auto-znajdowator
Staro-auto-znajdowator, dostarczony przez tego samego człowieka, który stworzył
korpo-zdanio-budowator, potrafi całe dnie tworzyć odlotowe, stare samochody.
Ten kod zamiast sloganów marketingowych generuje marki, modele oraz wartości
wszystkich innych właściwości obiektów samochodów. To Twoja własna fabryka
samochodów. Przyjrzymy się nieco dokładniej, jak działa ten program.
1
Najpierw piszemy funkcję makeCar, którą możemy wywołać za każdym razem, kiedy będziemy
chcieli utworzyć nowy samochód. Dysponujemy czterema tablicami zawierającymi: marki,
modele, lata produkcji oraz kolory samochodów, a także tablicą z wartościami true i false
określającymi, czy samochód jest kabrioletem. Generujemy pięć liczb losowych, które posłużą
do wybrania z odpowiednich tablic: marki, modelu, roku produkcji, koloru oraz tego, czy
samochód będzie kabrioletem. Oprócz tego generujemy jeszcze jedną liczbę losową, która
określi liczbę pasażerów samochodu.
W tych czterech tablicach mamy kilka marek,
modeli, lat produkcji i kolorów, spomiędzy
których możemy dowolnie wybierać…
var makes = [”Chevy”, ”GM”, ”Fiat”, ”SieMoCorp”, ”Tucker”];
var models = [”Cadillac”, ”500”, ”Bel-Air”, ”Taxi”, ”Torpedo”];
var years = [1955, 1957, 1948, 1954, 1961];
var colors = [”czerwony”, ”niebieski”, ”jasnobrÈzowy”, ”ĝöïty”, ”biaïy”];
var convertible = [true, false];
… a tej tablicy użyjemy do ustawienia wartości
właściwości określającej, czy samochód jest kabrioletem,
czy nie; może ona przyjmować wartość true lub false.
var rand1 = Math.floor(Math.random() * makes.length);
var rand2 = Math.floor(Math.random() * models.length);
Wartości z tych tablic
będziemy wybierali
losowo wśród tych
czterech liczb losowych.
var rand3 = Math.floor(Math.random() * years.length);
var rand4 = Math.floor(Math.random() * colors.length);
var rand5 = Math.floor(Math.random() * 5) + 1;
var rand6 = Math.floor(Math.random() * 2);
natomiast ta liczba losowa pozwoli określić,
czy samochód jest kabrioletem.
2
228
Ta liczba losowa posłuży do określenia
dopuszczalnej liczby pasażerów
samochodu. Dodajemy do niej 1, żeby
samochód zawsze był zarejestrowany
na przynajmniej jednego pasażera.
Zamiast łączyć wylosowane właściwości samochodu w łańcuch znaków, jak to robił program korpozdanio-budowator, program tworzy zupełnie nowy obiekt — car. Obiekt ten będzie miał wszystkie
właściwości, których oczekujemy od samochodu. Wybieramy losową wartości dla właściwości make,
model, year oraz color, używając liczb losowych utworzonych w kroku 1.; a następnie zajmujemy
się wartościami właściwości passangers, convertible oraz mileage.
Rozdział 5.
Zrozumieć obiekty
var car = {
make: makes[rand1],
Tworzymy nowy obiekt car, którego właściwości
będą miały wartości losowo wybrane z tablic.
model: models[rand2],
year: years[rand3],
color: colors[rand4],
passengers: rand5,
convertible: convertible[rand6],
Dopuszczalną liczbę pasażerów także
na podstawie utworzonej wcześniej określamy
liczby losowej,
a wartość właściwości convertible —
true lub false
— wybieramy z tablicy convertible.
mileage: 0
};
3
I w końcu właściwości mileage przypisujemy
wartość 0 (to przecież będzie… hm… nowy
samochód).
Ostatnia instrukcja funkcji makeCar zwraca nowy obiekt car:
return car;
Zwracanie obiektu z funkcji przypomina zwracanie dowolnej innej wartości.
A teraz zrzućmy okiem na kod, który wywołuje funkcję makeCar.
function displayCar(car) {
Nie zapominaj, że tym,
co zwraca funkcja (i co
zapisujemy w zmiennej
carToSell), jest referencja
do obiektu samochodu.
console.log(”Twoim nowym samochodem jest ” + car.make + ” ” +
car.model + ” z roku ” + car.year);
}
var carToSell = makeCar();
displayCar(carToSell);
Najpierw wywołujemy funkcję makeCar, a zwróconą przez nią wartość
zapisujemy w zmiennej carToSell. Tak utworzony obiekt samochodu
przekazujemy następnie do funkcji displayCar, której działanie
sprowadza się do wyświetlenia wartości kilku jego właściwości.
4
2ELHNWVDPRFKRGX
var carToSell = makeCar();
No dalej — wyświetl aplikację staro-auto-znajdowatora (czyli plik autoomatic.html)
w przeglądarce i wypróbuj jej działanie. Nie powinno Ci zabraknąć „nowych”
samochodów, a pamiętaj, że co minutę rodzi się nowy frajer.
Konsola JavaScript
zimy,
Oto Twoje nowe auta! Sąd
Taxi
ta
Fia
ć
mie
oby
że fajnie był
z 1954 roku.
Twoim nowym samochodem jest Tucker Cadillac z roku 1961
Twoim nowym samochodem jest Fiat Taxi z roku 1954
Twoim nowym samochodem jest Chevy 500 z roku 1955
Odśwież stronę kilka
razy; myśmy też tak
zrobili.
jesteś tutaj
229
Dodawanie zachowań
Zachowuj się! Jak dodawać zachowania do obiektów?
Nie sądziłeś chyba, że obiekty służą jedynie do przechowywania liczb i łańcuchów znaków?
Obiekty są aktywne. Obiekty mogą robić różne rzeczy. Psy nie spędzają życia, siedząc bez
ruchu… Psy szczekają, biegają, przynoszą patyki i podobnie powinny postępować obiekty
psów! Analogicznie, jeździmy samochodami, parkujemy je, cofamy, hamujemy… Zważywszy
na to wszystko, czego już się nauczyłeś w tym rozdziale, jesteś zapewne gotowy, by rozpocząć
dodawanie do swoich obiektów zachowań. Poniżej pokazaliśmy, jak to robić.
var fiat = {
make: ”Fiat”,
model: ”500”,
year: 1957,
color: ”szaroniebieski”,
passengers: 2,
convertible: false,
mileage: 88000,
drive: function() {
wać
i możesz doda
Funkcjonalnośc ośrednio,
zp
do obiektu be ób.
os
w poniższy sp
Sprowadza się to do zapisania we właściwości
definicji funkcji. Owszem, właściwości mogą też
być funkcjami!
alert(”Brum wrrrr!”);
}
};
Zwróć uwagę, że w definicji funkcji nie podaliśmy
nazwy — ograniczyliśmy się do samego słowa
kluczowego function oraz ciała funkcji. Nazwą tej
funkcji jest nazwa właściwości.
I troszkę nomenklatury: zazwyczaj funkcje
umieszczane wewnątrz obiektów nazywamy
metodami. To jest powszechnie stosowany
obiektowy termin, określający funkcje
dostępne w obiektach.
Aby wywołać funkcję — o, przepraszam — metodę drive, należy ponownie skorzystać
z zapisu z kropką; podajemy nazwę obiektu, w tym przypadku będzie to fiat, oraz nazwę
metody, drive, ale tym razem umieszczamy za nią parę nawiasów (dokładnie tak samo jak
w wywołaniu wszystkich innych funkcji).
fiat.drive();
Używamy znanego już zapisu
z kropką, by odwołać się do funkcji
w obiekcie fiat, dokładnie tak samo, jak
odwoływalibyśmy się do dowolnej innej
właściwości. Mówimy, że „wywołujemy
metodę drive obiektu fiat”.
A to są wyniki wywołania
metody drive obiektu fiat.
230
Rozdział 5.
Zrozumieć obiekty
Poprawianie metody drive
Spróbujmy zrobić coś, by zachowanie obiektu fiat nieco bardziej przypominało
samochód. Większość samochodów nie jedzie, póki nie włączymy w nich silnika, prawda?
To może spróbujemy odwzorować takie zachowanie? W tym celu będziemy potrzebowali:
‰właściwości logicznej przechowującej stan samochodu
(czy silnik jest włączony, czy nie),
‰dwóch metod do włączania i wyłączania silnika,
‰instrukcji warunkowej w metodzie drive, która przed
rozpoczęciem jazdy upewni się, że silnik jest włączony.
Zaczniemy od dodania logicznej właściwości o nazwie started oraz dwóch metod,
start i stop; dopiero potem zmodyfikujemy metodę drive, tak aby korzystała
z właściwości started.
var fiat = {
make: ”Fiat”,
model: ”500”,
year: 1957,
color: ”szaroniebieski”,
passengers: 2,
convertible: false,
mileage: 88000,
To jest zmienna przechowująca informację
o bieżącym stanie silnika (ma wartość true,
jeśli silnik jest włączony, oraz wartość false,
jeśli silnik jest wyłączony).
started: false,
start: function() {
started = true;
},
A to jest metoda służąca do
uruchamiania silnika. Jej działanie
(na razie) sprowadza się do przypisania
właściwości started wartości true.
stop: function() {
started = false;
},
drive: function() {
To natomiast jest metoda służąca
do wyłączania silnika. Obecnie jej
działanie sprowadza się do przypisania
właściwości started wartości false.
if (started) {
alert(”Brum wrrrr!”);
} else {
alert(”Najpierw musisz wïÈczyÊ silnik.”);
}
}
};
Tu umieściliśmy interesujące zachowania obiektu: jeśli spróbujesz
pojechać samochodem, którego silnik jest włączony, uzyskasz w efekcie
komunikat „Brum wrrrr!”. Jeśli jednak silnik będzie wyłączony, zostanie
wyświetlony komunikat informujący, że aby pojechać, trzeba go
najpierw włączyć.
jesteś tutaj
231
Hermetyzacja zachowań
Ciekawe, że zamiast
bezpośrednio zmieniać wartość
właściwości started, napisaliście osobną
metodę, która to robi. Dlaczego? Przecież
bezpośrednie określenie wartości tej
właściwości oznacza krótszy i prostszy kod.
6ÿXV]QLH Masz rację; w przypadku uruchamiania samochodu
kod:
fiat.start();
moglibyśmy zastąpić instrukcją:
fiat.started = true;
Dzięki temu można by uniknąć pisania metody służącej do
uruchamiania silnika.
Dlaczego zatem napisaliśmy i wywołaliśmy metodę start,
zamiast bezpośrednio określić wartość właściwości started?
Otóż zmiana wartości właściwości w metodzie jest kolejnym
przykładem hermetyzacji, dzięki temu że pozwala, by obiekt sam
zajmował się swoimi sprawami, często upraszcza utrzymanie kodu
oraz zwiększa możliwości jego rozszerzania. Znacznie prostsze
jest skorzystanie z metody start, która „wie”, jak uruchomić
samochód, niż zmuszanie użytkownika obiektu, by wiedział, że
„aby uruchomić samochód, należy przypisać właściwości started
wartość true”.
Być może wciąż sobie myślisz: „A cóż to za problem? Co
szkodzi przypisywanie właściwości wartości true, by uruchomić
samochód?!”. Wyobraź sobie nieco bardziej złożoną metodę
start, która przed włączeniem silnika sprawdza, czy są zapięte
pasy bezpieczeństwa, czy jest wystarczająco dużo paliwa w baku,
weryfikuje poziom naładowania akumulatora, temperaturę
silnika… — a wszystko to jeszcze przed przypisaniem właściwości
started wartości true. Bez wątpienia nie chciałbyś myśleć o tym
wszystkim przed każdym uruchomieniem silnika. Na pewno
wolałbyś dysponować wygodną metodą, którą wystarczy wywołać,
by uruchomić samochód. Umieszczając wszystkie te szczegóły
w metodzie, tworzymy prosty sposób nakłonienia obiektu do
wykonania pewnej pracy, a jednocześnie pozwalamy, by to on
zajął się szczegółami wykonywanych operacji.
232
Rozdział 5.
Zrozumieć obiekty
Bierzemy fiata na jazdę próbną
Weźmy nasz nowy i poprawiony obiekt fiat na jazdę próbną. Przetestujemy
go naprawdę dokładnie — spróbujemy jechać przed włączeniem silnika,
następnie włączyć silnik, pojechać i zatrzymać się. Aby to wszystko zrobić,
musisz upewnić się, że w dokumencie HTML (carWithDrive.html) jest
odpowiedni kod obiektu fiat, zawierający metody start, stop oraz drive;
następnie poniżej obiektu musisz dodać następujący fragment kodu.
fiat.drive();
Najpierw spróbujemy pojechać, co powinno
spowodować wyświetlenie komunikatu, że należy włączyć silnik. Następnie
spróbujemy faktycznie go włączyć i pojechać. W końcu, kiedy
już skończymy, wyłączymy silnik.
fiat.start();
fiat.drive();
fiat.stop();
No dalej, wyświetl stronę w przeglądarce i ruszaj na wycieczkę!
Hej! Nie tak szybko…
Jeśli nie jesteś w stanie pojechać swoim fiatem, wiedz, że ten
problem nie dotyczy tylko Ciebie. Wyświetl zatem konsolę
JavaScript, a najprawdopodobniej zobaczysz w niej komunikat
o błędzie podobny do tego, który pokazaliśmy obok, a informujący
o tym, że identyfikator started nie został zdefiniowany.
Konsola JavaScript
ReferenceError: started is not defined
Co się dzieje? Posłuchajmy, co mówi do siebie metoda drive
i przekonajmy się, co się dzieje, kiedy spróbujemy wykonać
instrukcję fiat.drive().
No dobrze, zobaczmy…
Widzę tu coś, co przypomina
zmienną started. Muszę się
zorientować, gdzie została
zdefiniowana…
A! Zostałam
wywołana! Czas
się brać do roboty.
drive: function() {
Zawsze
najpierw szukam zmiennych
lokalnych, zdefiniowanych
wewnątrz mojej funkcji. Nie,
tu jej nie ma.
if (started) {
...
}
}
No to
może jest zmienną
globalną? Nie. No dobrze,
poddaję się, to musi być
jakiś błąd.
A może started
jest parametrem tej
funkcji? Nie, tam też jej
nie ma.
jesteś tutaj
233
Słowo kluczowe this
Dlaczego metoda drive nic nie wie
o właściwości started?
Oto mamy zagadkę: w metodzie obiektu fiat użyliśmy referencji do właściwości
started, a zazwyczaj, kiedy chcemy odwołać się do zmiennej wewnątrz funkcji, okazuje
się, że zmienna ta jest zmienną lokalną, parametrem funkcji lub zmienną globalną.
Jednak w metodzie drive started nie jest żadną z tych trzech rzeczy — jest natomiast
właściwością obiektu fiat.
Słuchaj, jeśli chcesz,
żebym wiedział, do
którego obiektu należy
started, musisz mi o tym
powiedzieć.
Czy ten kod nie powinien po prostu działać? Chodzi o to, że zdefiniowaliśmy właściwość
started w obiekcie fiat, czy zatem JavaScript nie powinien być na tyle sprytny,
by zorientować się, że chodzi o właściwość started?
drive: function() {
Niestety, nie. Jak widać, interpreter JavaScriptu nie jest tak mądry. A niby dlaczego
miałby być?
if (started) {
Wygląda to tak: to, co wewnątrz metody wygląda na zmienną, jest właściwością obiektu,
jednak problem polega na tym, że my nie określamy w kodzie, o jaki obiekt chodzi.
Możesz sobie pomyśleć: „Ale to przecież oczywiste, że chodzi o TEN obiekt, dokładnie
ten, w którym jest zdefiniowana metoda! Jakim cudem można się nie domyślić, że o niego
chodzi?”. I faktycznie, chodzi dokładnie o właściwość tego obiektu. Okazuje się, że
JavaScript udostępnia specjalne słowo kluczowe, this, którego użycie informuje język
JavaScript, że chodzi dokładnie o ten obiekt, w którym się aktualnie znajdujemy.
...
}
}
A teraz zastosujemy słowo kluczowe this i uruchomimy nasz kod.
var fiat = {
make: ”Fiat”,
// tu sÈ pozostaïe wïaĂciwoĂci, pominiemy je, oszczÚdzajÈc nieco miejsca
started: false,
start: function() {
this.started = true;
},
stop: function() {
this.started = false;
},
drive: function() {
if (this.started) {
alert(”Brum wrrrr!”);
} else {
alert(”Najpierw musisz wïÈczyÊ silnik.”);
}
}
};
234
Rozdział 5.
Przed każdym wystąpieniem
właściwości started zastosuj słowo
kluczowe this oraz zapis z kropką,
aby poinformować interpreter
JavaScriptu, że chodzi o właściwość
TEGO obiektu, a nie odwołanie do
jakiejś zmiennej.
Zrozumieć obiekty
Jazda próbna ze słowem kluczowym this
No dalej, zaktualizuj kod i weź go w obroty!
A to nam udało się uzyskać.
BĄDŹ przeglądarką
Poniżej znajdziesz kod JavaScript, w którym celowo wprowadziliśmy kilka błędów.
Twoim zadaniem jest wcielić się w rolę przeglądarki i wykryć błędy w tym kodzie.
Kiedy zrobisz już to ćwiczenie, zajrzyj na koniec rozdziału i sprawdź, czy udało Ci
się
znaleźć wszystkie.
var song = {
name: ”Brothers in Arms”,
artist: ”Dire Straits”,
minutes: 4,
seconds: 3,
genre: ”80s”,
playing: false,
play: function() {
if (!playing) {
Spokojnie możesz zaznaczać
błędy bezpośrednio tu, w książce.
this = true;
console.log(”Odtwarzam teraz ”
+ name + ” wykonywany przez ” +
artist + ”.”);
}
},
pause: function() {
if (playing) {
this.playing = false;
}
}
};
this.play();
this.pause();
jesteś tutaj
235
Zrozumienie słowa kluczowego this
Jak działa this?
O this możesz myśleć jak o zmiennej odwołującej się do obiektu, którego
metoda jest właśnie wykonywana. Innymi słowy, jeśli wywołamy metodę start
obiektu fiat przy użyciu wywołania w postaci fiat.start(), wewnątrz
metody start słowo kluczowe this będzie odwoływać się do obiektu fiat.
Przyjrzyjmy się nieco dokładniej temu, co się dzieje, kiedy wywołamy metodę
start obiektu fiat.
this
Przede wszystkim mamy obiekt reprezentujący samochód fiat, który jest
zapisany w zmiennej fiat.
make: "Fiat"
model: "500"
To jest obiekt fiat wraz ze wszystkim
swoimi nazwami i wartościami
właściwości oraz metodą start.
LQQHZïDĂFLZRĂFLRELHNWX
started: false
ILDW
start: function() {
this.started = true;
}
stop: function() { ... }
drive: function() {
... }
Później, kiedy wywołamy metodę start, JavaScript zadba o przypisanie
this do obiektu fiat.
W tym przypadku this odwołuje
się do obiektu fiat, gdyż
wywołaliśmy metodę start
właśnie tego obiektu.
{
start: function()
ed = true;
rt
ta
.s
this
fiat.start();
}
Zawsze wtedy, gdy wywołujemy metodę jakiegoś obiektu,
this będzie odwoływać się do tego obiektu. A zatem w tym
przypadku this odwołuje się do obiektu fiat.
Prawdziwym kluczem do zrozumienia this jest zapamiętanie, że za każdym
razem, gdy wywołujemy metodę, możemy mieć pewność, iż w jej ciele this
będzie odwoływać się do obiektu, którego metoda jest wykonywana. Abyś
dobrze to sobie przyswoił, wypróbujemy teraz użycie this w kilku innych
obiektach.
236
Rozdział 5.
Zrozumieć obiekty
Jeśli wywołamy metodę start obiektu chevy, wtedy this będzie się
w niej odwoływać do obiektu chevroleta.
make: "Chevy"
model: "Bel Air"
LQQHZïDĂFLZRĂFLRELHNWX
started: false
start: function() {
this.started = true;
}
start: function() {
this.started = true;
},
stop: function() { ... }
drive: function() {
... }
chevy.start();
A w metodzie start obiektu taxi słowo this będzie się
odwoływać do obiektu taksówki.
make: "SieMoCorp"
model: "Taxi"
LQQHZïDĂFLZRĂFLRELHNWX
started: false
start: function() {
start: function() {
this.started = true;
}
taxi.start();
Zaostrz ołówek
this.started = true;
}
stop: function() { ... }
drive: function() {...}
Skorzystaj ze swoich nowych umiejętności związanych ze stosowaniem
słowa kluczowego this, by pomóc nam dokończyć poniższy kod.
Porównaj swoją odpowiedź z naszą, podaną pod koniec rozdziału.
var eightBall = { index: 0,
advice: [”tak”, ”nie”, ”moĝe”, ”nie ma szans”],
shake: function() {
this.index = ________.index + 1;
if (_________.index >= _________.advice.length) {
_________.index = 0;
}
},
look: function() {
return _________.advice[_________.index];
}
};
eightBall.shake();
console.log(eightBall.look());
Aby przetestować kod, kilkakrotnie
powtórz tę sekwencję instrukcji.
Konsola JavaScript
tak
PRĪH
QLHPDV]DQV
jesteś tutaj
237
Pytania dotyczące funkcji
Nie istnieją
głupie pytania
P: Jaka jest różnica pomiędzy
metodą a funkcją?
O: Metoda jest zwykłą funkcją, przypisaną
do nazwy właściwości w obiekcie. Funkcje
wywołujemy, podając ich nazwę, aby
natomiast wywołać metodę, należy podać
nazwę obiektu, kropkę i nazwę właściwości.
W metodach można także używać słowa
kluczowego this, które umożliwia
odwoływanie się do obiektu, jakiego
metoda została wywołana.
P: Zauważyłem, że gdy używamy
słowa kluczowego function wewnątrz
obiektu, nie nadajemy funkcji jawnej
nazwy. Jaka w tym przypadku będzie
nazwa funkcji?
O: To prawda. Podczas wywoływania
metod posługujemy się nazwami
właściwości, a nie, jak w przypadku funkcji,
nazwami, które im jawnie nadajemy. Na
razie możesz to potraktować jako pewną
konwencję, natomiast dalej w książce
przedstawimy funkcje anonimowe, bo tak
nazywane są funkcje, które nie mają jawnej
nazwy.
P: Czy metody mogą mieć zmienne
O: Tak. Metoda jest funkcją, tylko
nazywamy ją metodą, bo istnieje wewnątrz
obiektu. Zatem metoda może dokładnie to
samo, co funkcja, gdyż jest funkcją.
P: A zatem także metody mogą
zwracać wartość?
O: Tak. Zgodnie z tym, co napisaliśmy
w ostatniej odpowiedzi.
P: A co z przekazywaniem
argumentów do metod? Czy możemy
coś do nich przekazywać?
O: Ej! Nie przeczytałeś odpowiedzi na
przedostatnie pytanie? Owszem, możemy.
P: Czy, podobnie jak właściwości,
także metody można dodawać do
obiektu po jego utworzeniu?
O: Tak. Wyobraź sobie metodę jako
funkcję zapisaną we właściwości obiektu;
wartości właściwości można określać
w dowolnym momencie.
GRGDMHP\PHWRGÚWXUER
car.engageTurbo =
function() { ... };
lokalne, tak jak funkcje?
P: Jeśli do obiektu dodam metodę,
taką jak engageTurbo, czy słowo
kluczowe this wciąż będzie działać?
O
: Tak. Pamiętaj, że this jest przypisywane
do obiektu w momencie wywoływania metody.
P: Kiedy jest określana wartość this?
W momencie definiowania obiektu czy
w momencie wywoływania metody?
O
: Wartość this jest określana
w momencie wywoływania metody. Kiedy
zatem wywołujesz metodę fiat.start(),
w this jest zapisywane odwołanie do
obiektu fiat, a kiedy wywołujesz
chevy.start(), w this jest zapisywane
odwołanie do obiektu chevy. Może się
wydawać, że wartość this jest określana
w momencie definiowania obiektu,
gdyż podczas wykonywania metody
fiat.start() słowo this zawsze będzie
ustawione na fiat, a podczas wywoływania
metody chevy.start() to this zawsze
będzie ustawione na chevy. Jednak, jak się
później przekonasz, istnieje ważki powód,
dla którego wartość this jest określana
podczas wywoływania metody, a nie
definiowania obiektu. To bardzo ważne
zagadnienie, do którego będziemy jeszcze
wracać przy kilku różnych okazjach.
WYSIL
SZARE KOMÓRKI
Gdybyś skopiował metody start, stop oraz drive i umieścił je w utworzonych wcześniej
obiektach chevy oraz cadi, to co musiałbyś zrobić, żeby działały prawidłowo?
Odpowiedź: Nic! Korzystają one z this „tego obiektu”, czyli obiektu, którego metoda jest wywoływana.
238
Rozdział 5.
Zrozumieć obiekty
Ćwiczenie
Nadszedł czas, by uruchomić całą flotę samochodów. Dodaj metodę drive do każdego z obiektów
samochodów. Kiedy już się z tym uporasz, dodaj kod, który dla każdego samochodu uruchomi silnik,
przejedzie się trochę, a następnie wyłączy silnik. Porównaj swoją odpowiedź z naszą, podaną pod koniec
rozdziału.
var cadi = {
make: ”GM”,
model: ”Cadillac”,
year: 1955,
color: ”jasnobrÈzowy”,
passengers: 5,
convertible: false,
mileage: 12892
};
var chevy = {
make: ”Chevy”,
model: ”Bel Air”,
year: 1957,
color: ”czerwony”,
passengers: 2,
convertible: false,
mileage: 1021
};
var taxi = {
make: ”SieMoCorp”,
model: ”Taxi”,
year: 1955,
color: ”yellow”,
passengers: 4,
convertible: false,
mileage: 281341
};
Kiedy zaczniesz dodawać
nowe właściwości, nie
zapomnij dodać przecinka po
właściwości mileage.
Do każdego z obiektów samochodów
dodaj właściwość started oraz
poniższe metody. Następnie skorzystaj
z pokazanego poniżej kodu, by każdym
z samochodów odbyć jazdę próbną.
started: false,
start: function() {
this.started = true;
},
stop: function() {
this.started = false;
},
ę
Poprawiliśmy nieco metod
drive, więc nie zapomnij
wprowadzić analogicznej
zmiany w swoim nowym
kodzie.
drive: function() {
if (this.started) {
alert(this.make + ” ” +
this.model + ” robi: brum wrrrr!”);
} else {
alert(”Najpierw musisz wïÈczyÊ silnik.”);
}
}
Dodaj ten kod poniżej
definicji obiektów, by
przetestować każdy
z samochodów.
cadi.start();
cadi.drive();
cadi.stop();
chevy.start();
chevy.drive();
chevy.stop();
taxi.start();
taxi.drive();
taxi.stop();
jesteś tutaj
239
Unikanie powielania kodu
Wygląda na to, że kopiując i wklejając
te wszystkie metody do obiektów
w ostatnim ćwiczeniu, powielamy strasznie
dużo kodu. Czy jest jakieś lepsze
rozwiązanie?
A… Spostrzegawczy gość
Owszem, kopiowanie metod start, stop oraz drive
do każdego z obiektów samochodów bez wątpienia jest
powielaniem kodu. W odróżnieniu od innych właściwości,
których wartości zależą od obiektu samochodu, w jakim
zostały zdefiniowane, metody są takie same dla wszystkich
tych obiektów.
Jeśli masz zamiar powiedzieć: „I dobrze, wielokrotnie
używamy tego samego kodu”, lepiej się z tym nie śpiesz.
Pewnie, że wielokrotnie używamy, ale sam kod występuje
nie jeden, lecz wiele razy! Co by się stało, gdybyś chciał
zmienić sposób działania metody drive? Musiałbyś
zmieniać kod metody w każdym z obiektów. Kiepska
sprawa. To nie tylko strata czasu, lecz także potencjalne
źródło błędów.
Jednak problem, który poruszyliśmy, jest znacznie większy
i nie sprowadza się jedynie do kopiowania i wklejania
kodu. Zakładamy tu, że sam fakt umieszczenia we
wszystkich obiektach tych samych właściwości sprawia,
że będą to obiekty samochodów. A co by się stało,
gdybyśmy przez przypadek zapomnieli w jednym z nich
umieścić właściwości mileage — czy to wciąż byłby obiekt
samochodu?
To są prawdziwe problemy występujące w napisanym
dotychczas kodzie; spróbujemy znaleźć na nie odpowiedzi
w następnym rozdziale, poświęconym zaawansowanym
zagadnieniom stosowania obiektów, w którym
przedstawimy kilka technik prawidłowego, wielokrotnego
używania kodu w obiektach.
240
Rozdział 5.
Zrozumieć obiekty
Zastanawiam się: gdyby ktoś
przekazał mi jakiś obiekt, to czy
mógłbym się w jakiś sposób dowiedzieć,
jakie on ma właściwości?
Jedną z rzeczy, jakie możesz zrobić, jest przejrzenie
właściwości obiektu w pętli. W tym celu musisz
zastosować formę iteracji, której jeszcze nie przedstawiliśmy
w tej książce, czyli pętlę for in. Pętla for in pobiera
kolejno wszystkie właściwości obiektu, w dowolnej kolejności.
Poniżej pokazaliśmy, w jaki sposób możesz wyświetlić
wszystkie właściwości obiektu chevy.
Pętla for in kolejno pobiera właściwości
obiektu, przypisując każdą z nich do
zmiennej prop.
for (var prop in chevy) {
console.log(prop + ”: ” + chevy[prop]);
}
Zmiennej prop można następnie użyć,
by uzyskać dostęp do właściwości
obiektu; należy przy tym skorzystać
z zapisu z nawiasami kwadratowymi.
A to prowadzi do kolejnego zagadnienia: istnieje jeszcze inny sposób
odwoływania się do właściwości obiektów. Czy zwróciłeś uwagę na
alternatywną składnię odwołań do właściwości obiektu chevy, którą zastosowaliśmy
powyżej? Jak się okazuje, do właściwości obiektów można się odwoływać na dwa
sposoby. Pierwszy zapis już znasz:
chevy.color
Używaliśmy już tego zapisu: nazwa obiektu,
kropka i nazwa właściwości.
Jest także inny sposób: zapis korzystający z nawiasów kwadratowych:
chevy["color"]
W tym przypadku podajemy nazwę obiektu,
a następnie nazwę właściwości zapisaną
w cudzysłowach i wewnątrz pary nawiasów
kwadratowych.
Konsola JavaScript
make: Chevy
model: Bel Air
year: 1957
color: czerwony
passengers: 2
convertible: false
mileage: 1021
To trochę przypomina
sposób odwoływania się
do elementów tablic.
Należy wiedzieć, że oba te zapisy są równoznaczne i zapewniają takie same efekty. Jedyną rzeczą, na jaką warto zwrócić
uwagę, jest to, że zapis z nawiasami kwadratowymi czasami zapewnia nieco większą elastyczność, gdyż pozwala na
podawanie nazwy właściwości w formie wyrażenia; oto przykład.
chevy["co" + "lor"]
Wewnątrz nawiasów można umieścić dowolne
wyrażenie, o ile tylko jego wartością będzie nazwa
właściwości zapisana jako łańcuch znaków.
jesteś tutaj
241
Zachowanie i stan
Jak zachowanie wpływa na stan?
Dodajemy trochę paliwa
Obiekty składają się ze stanu oraz zachowań. Właściwości obiektu pozwalają na
przechowywanie jego stanu, takiego jak poziom paliwa w baku, aktualna temperatura silnika
albo piosenka właśnie odtwarzana w radio. Z kolei metody obiektu pozwalają mu mieć
zachowania, takie jak uruchamianie silnika, włączenia ogrzewania lub szybkie przewinięcie
piosenki do końca. A przy okazji, czy zauważyłeś, że oba te elementy obiektu współpracują?
Przykładowo nie można uruchomić samochodu, jeśli w baku nie będzie paliwa, a ilość paliwa
powinna się zmniejszać, kiedy samochód jedzie. Prawie jak w realnym świecie, prawda?
Zajmijmy się tymi pomysłami nieco dokładniej; uzupełnimy nasz samochód o paliwo, by
można było dodać do niego jakieś interesujące zachowania. Aby dodać paliwo, będziemy
potrzebowali właściwości fuel oraz nowej metody addFuel. Metoda ta będzie mieć jeden
parametr amount, którego będziemy używali do powiększania wartości właściwości fuel.
A zatem dodajmy właściwość i metodę do obiektu fiat.
var fiat = {
make: ”Fiat”,
model: ”500”,
// inne wïaĂciwoĂci... usunÚliĂmy je, by zaoszczÚdziÊ trochÚ papieru...
started: false,
fuel: 0,
Dodaliśmy nową właściwość fuel, która
będzie przechowywać informację o ilości
paliwa w baku samochodu. Samochód
początkowo ma zupełnie pusty bak.
start: function() {
this.started = true;
},
stop: function() {
this.started = false;
},
drive: function() {
if (this.started) {
alert(this.make + ” ” + this.model + ” robi: brum wrrrr!”);
} else {
alert(”Najpierw musisz wïÈczyÊ silnik.”);
}
},
addFuel: function(amount) {
this.fuel = this.fuel + amount;
Dodajmy przy okazji metodę addFuel, która umożliwi
tankowanie samochodu. Do samochodu możemy
zać
zatankować tyle paliwa, ile chcemy, wystarczy przeka
odpowiednią ilość w wywołaniu metody.
}
};
242
Pamiętaj, że fuel jest właściwością
obiektu, zatem musimy użyć słowa
kluczowego this.
Rozdział 5.
Z kolei amount jest parametrem
funkcji, więc korzystając z niego,
nie musimy stosować this.
Zrozumieć obiekty
A teraz niech stan będzie mieć wpływ na zachowanie
Skoro nasz samochód jest już wyposażony w paliwo, możemy zacząć implementować w nim
interesujące zachowania. Jeśli np. w baku nie będzie paliwa, nie można jeździć samochodem!
Zacznijmy więc od zmodyfikowania metody drive tak, by sprawdzała ilość paliwa i upewniała się,
że jakieś jest w baku; później, za każdym razem gdy samochód gdzieś pojedzie, zmniejszymy ilość
paliwa, odejmując 1 od wartości właściwości fuel. A oto kod, który pozwoli to wszystko zrobić.
var fiat = {
// inne wïaĂciwoĂci i metody...
drive: function() {
if (this.started) {
wdzić,
Teraz przed jazdą możemy sprai będziemy
jeśl
A
wo.
pali
jest
czy w baku
powinna
mogli pojechać, każda jazda ci paliwa.
spowodować zmniejszenie iloś
if (this.fuel > 0) {
alert(this.make + ” ” +
this.model + ” robi: brum wrrrr!”);
this.fuel = this.fuel - 1;
} else {
alert(”Osz..., brak paliwa.”);
this.stop();
}
} else {
Jeśli bak jest pusty, wyświetlamy
odpowiedni komunikat i zatrzymujemy
silnik. Aby w takim przypadku znowu
pojechać, trzeba będzie dolać paliwa
i jeszcze raz włączyć silnik.
alert(”Najpierw musisz wïÈczyÊ silnik.”);
}
},
addFuel: function(amount) {
this.fuel = this.fuel + amount;
}
};
Zatankuj, by odbyć jazdę próbną
Nie zwlekaj i zaktualizuj swój kod. A następnie weź fiata na
przejażdżkę! Obok pokazaliśmy rezultaty uzyskane przy użyciu
poniższego kodu.
fiat.start();
fiat.drive();
fiat.addFuel(2);
fiat.start();
fiat.drive();
fiat.drive();
fiat.drive();
fiat.stop();
eś
Najpierw spróbowaliśmy gdzi
wa,
pali
bez
e
ełni
zup
chać
poje
dolaliśmy go zatem trochę,
długo,
a następnie jeździliśmy tak
Spróbuj
aż zupełnie się wyczerpało!
dodać swój własny kod testowy
i sprawdź, czy działa zgodnie
z oczekiwaniami.
jesteś tutaj
243
Ćwiczenie z integracji paliwa
Musimy jeszcze trochę popracować, zanim właściwość fuel zostanie w pełni zintegrowana z obiektem
samochodu. Czy np. powinien uruchomić się silnik, jeśli nie ma paliwa? Zresztą sprawdź metodę start.
Ćwiczenie
start: function() {
this.started = true;
},
Wygląda na to, że faktycznie uruchomienie silnika bez paliwa jest możliwe.
Pomóż nam zintegrować właściwość fuel z obiektem samochodu; w tym celu przed uruchomieniem
silnika musisz sprawdzić poziom paliwa. Jeśli w momencie wywołania metody start bak będzie pusty,
powiadom o tym kierowcę, wyświetlając adekwatny komunikat: ”Bak jest pusty, zatankuj
przed jazdÈ!”. Zmodyfikowaną wersję metody start najpierw zapisz poniżej, a następnie dodaj
do kodu strony i przetestuj. Nie zapomnij porównać swojej odpowiedzi z naszą, podaną pod koniec
rozdziału.
Tu zapisz
swój kod.
WYSIL
SZARE KOMÓRKI
Spójrz na kod obiektu fiat. Czy są w nim jakieś inne miejsca, w których mógłbyś użyć właściwości fuel,
by zmienić zachowanie samochodu (lub zdefiniować jakieś zachowanie, które modyfikowałoby wartość tej
właściwości)? Swoje pomysły zapisz poniżej.
244
Rozdział 5.
Zrozumieć obiekty
Dzięki obiektom przyszłość
rysuje się w tak jasnych
barwach, że aż musimy nosić
ciemne okulary.
Gratulujemy utworzenia pierwszych obiektów!
Już prawie udało Ci się przebrnąć pierwszy rozdział poświęcony obiektom i jesteś gotów, by ruszyć
w dalszą drogę. Czy pamiętasz, jak zaczynałeś swoją przygodę z JavaScriptem? Myślałeś o świecie
w kategoriach niskiego poziomu — liczbach, łańcuchach znaków, instrukcjach i warunkach,
pętlach for itd. A teraz zobacz, jak daleko udało Ci się dotrzeć. Zaczynasz myśleć na wyższym
poziomie, w kategoriach obiektów i metod. Tylko spójrz na poniższy kod.
fiat.addFuel(2);
fiat.start();
fiat.drive();
fiat.stop();
O ile łatwiej można zrozumieć, o co w nim chodzi, a wszystko dlatego, że opisuje świat jako
zestaw obiektów, które mają swój stan i zachowania.
A to dopiero początek. Można pójść znacznie dalej i tak też zrobisz. Teraz, kiedy już
znasz obiekty, będziesz kontynuował rozwijanie Twoich umiejętności pisania prawdziwie
obiektowego kodu, wykorzystując kolejno możliwości języka JavaScript oraz kilka
najlepszych praktyk (które w przypadku stosowania obiektów nabierają jeszcze większego
znaczenia).
Zanim jednak opuścisz ten rozdział, jest jeszcze jedna rzecz, o której powinieneś wiedzieć…
jesteś tutaj
245
Jeszcze więcej obiektów
Wiesz co? Obiekty są wszędzie dookoła Ciebie!
(I ułatwiają Ci życie)
Skoro już trochę poznałeś obiekty, otwiera się przed Tobą zupełnie nowy świat, gdyż JavaScript udostępnia
bardzo wiele obiektów (służących m.in. do wykonywania obliczeń matematycznych, operowania na
łańcuchach znaków, tworzenia dat i godzin), z których możesz korzystać w swoim kodzie. JavaScript
udostępnia także kilka naprawdę kluczowych obiektów, niezbędnych do pisania kodu działającego
w przeglądarkach (przyjrzymy się im dokładniej w następnym rozdziale). Na razie warto przyjrzeć się kilku
spośród tych obiektów, gdyż będziemy je stosowali dalej w tej książce.
Obiektu Date można używać do
operacji na datach i czasie.
stosować obiekt
Widziałeś już, jak można losowych.
b
licz
ia
wan
ero
gen
do
h
Mat
znacznie
Jednak obiekt ten potrafi
więcej!
Math
Date
Ten obiekt
pozwala
odnajdywać
wzorce
w łańcuchach
znaków.
Z pomocą obiektu JSON
możesz wymieniać obiekty
JavaScript z innymi
aplikacjami.
RegExp
JSON
Wszystkie te obiekty są
dostarczane przez język
JavaScript
Przekonasz się, że wszystkie te obiekty
oferuje przeglądarka. Mają one kluczowe
znaczenie dla tworzenia aplikacji
internetowych działających po stronie klienta!
Obiektu Document użyjemy
już w następnym rozdziale
do zapisywania łańcuchów
znaków na stronie z poziomu
kodu JavaScript.
Document
Metody lo
Console g obiektu
u
wyświetl żywałeś już do
a
w oknie nia komunikatów
konsoli.
Console
246
Rozdział 5.
Window
Window udostępnia
wiele kluczowych,
związanych z samą
przeglądarką,
właściwości i metod
,
które możemy
stosować w naszy
m
kodzie.
Zrozumieć obiekty
Obiekt bez tajemnic
Temat dzisiejszego wywiadu brzmi:
Obiekt — sam o sobie
Rusz głową: Witamy, Obiekcie. Fascynujący rozdział.
To jest naprawdę inspirujące, że możemy myśleć o kodzie
jak o obiektach.
Obiekt: No cóż… A to dopiero początek.
Rusz głową: Co masz na myśli?
Obiekt: Obiekt jest zbiorem właściwości, prawda? Niektóre
z nich służą do przechowywania stanu obiektu, a inne
są w rzeczywistości funkcjami — a raczej metodami —
określającymi jego zachowanie.
Rusz głową: Na razie wszystko rozumiem. Choć do tej
pory nie wyobrażałem sobie metod jako właściwości, jednak
rozumiem, że one też są parami nazwa-wartość, o ile tylko
funkcję można nazwać wartością.
Obiekt: Ależ oczywiście, że można! Czy to zauważasz, czy nie,
jest przejawem wielkiego zrozumienia i intuicji. Trzymaj się
tej myśli, a sądzę, że znajdziesz tu jeszcze wiele interesujących
informacji związanych z tym zagadnieniem.
Rusz głową: Ale mówiłeś przecież…
Obiekt: A zatem poznałeś te obiekty oraz ich właściwości,
a nawet trochę ich utworzyłeś, np. kilka różnych modeli
samochodów.
Rusz głową: No cóż…
Obiekt: Jednak one wszystkie były takie doraźne. Prawdziwe
możliwości pojawiają się, kiedy utworzysz swego rodzaju
szablon; coś takiego, za pomocą czego będziesz mógł wedle
bieżących potrzeb tworzyć jednolite obiekty.
Rusz głową: To już przecież zostało zrobione, prawda?
Te wszystkie nasze samochody…
Obiekt: Są takie same, bo tak się umówiliśmy, bo napisany
został kod tworzący obiekty, które wyglądają bardzo podobnie.
Innymi słowy obiekty, które mają takie same właściwości
i metody.
Rusz głową: No fakt, a przy okazji wspominaliśmy
coś o powielaniu kodu w tych wszystkich obiektach, co
niekoniecznie jest dobrym rozwiązaniem, z punktu widzenia
utrzymania kodu.
Obiekt: Kolejnym krokiem jest nauczenie się tworzenia
obiektów, które na pewno będą takie same i będą używały
tego samego kodu — kodu umieszczonego w jednym
miejscu. To kwestia, która jest już związana z zagadnieniami
projektowania kodu obiektowego. Po poznaniu podstaw
jesteście gotowi, by zająć się tymi zagadnieniami.
Rusz głową: Jestem przekonany, że nasi czytelnicy ucieszą się,
słysząc te słowa!
Obiekt: Jest jeszcze kilka spraw związanych z obiektami,
o których trzeba wiedzieć.
Rusz głową: Tak?
Obiekt: W internecie istnieje bardzo wiele gotowych obiektów,
których można używać w swoim kodzie.
Rusz głową: Naprawdę? Nie zauważyłem. Gdzie one są?
Obiekt: A co z console.log? Czym jest console?
Rusz głową: Z kontekstu wnioskuję, że to jest obiekt?
Rusz głową: Masz na myśli obiekty tego samego typu?
Obiekt: BINGO. A log?
Obiekt: Coś w tym stylu… Jak się przekonasz, pojęcie typu
w języku JavaScript jest dosyć interesujące. Jednak jesteś
na dobrym tropie. Przekonasz się, że prawdziwie wielkie
możliwości uzyskasz, gdy zaczniesz pisać kod, który operuje
na obiektach tego samego rodzaju. Przykładem może być kod,
który operuje na pojazdach, a Ty nie musisz się przejmować,
czy tym pojazdem jest rower, samochód, czy autobus. To jest
prawdziwa moc.
Rusz głową: Właściwość… Znaczy metoda?
Rusz głową: To faktycznie brzmi interesująco. Czego jeszcze
trzeba się nauczyć, by robić takie rzeczy?
Obiekt: Cóż, należy nieco dokładniej zrozumieć obiekty, no
i potrzeba sposobu na tworzenie obiektów tego samego rodzaju.
Obiekt: I znowu BINGO! A co z alert?
Rusz głową: Nie mam pojęcia.
Obiekt: To też ma związek z obiektem, ale z odpowiedzią na
to pytanie jeszcze trochę poczekam.
Rusz głową: No dobrze, na pewno dałeś wiele do myślenia
i mam nadzieję, że się jeszcze spotkamy.
Obiekt: Na pewno możemy się jakoś umówić.
Rusz głową: Świetnie! W takim razie do zobaczenia
następnym razem.
jesteś tutaj
247
Misja złamania kodu
¥&,¥/(
7$-1(
W swoim dążeniu do zdominowania świata dr Zatan pomyłkowo udostępnił publicznie swoją
wewnętrzną stronę WWW, zawierającą aktualne hasło jego operacji. Dysponując tym hasłem, w końcu
możemy wyprzedzić go o krok i pokonać. Oczywiście, gdy tylko dr Zatan zorientował się, że jego strona
jest widoczna w internecie, zaraz zablokował do niej dostęp. Na szczęście, nasi agenci zrobili jej kopię.
Jedyny problem polega na tym, że agenci nie znają ani języka HTML, ani JavaScript. Czy możesz im
pomóc w określeniu kodu dostępu na podstawie zamieszczonego poniżej kodu strony? Pamiętaj, że jeśli
się pomylisz, może to drogo kosztować królową i kraj.
248
Rozdział 5.
Wygląda na to, że kod
korzysta z obiektu document.
=
var access
");
yId("code9
etElementB
.g
nt
me
cu
do
nnerHTML;
= access.i
var code
RF\
HSöïQ
FRGH FRG
);
alert(code
Jakie hasło dostępu
zostanie wyświetlone
w oknie dialogowym?
Tutaj zapisz swoją
odpowiedź.
ipt.
JavaScr
Oto kod
A to jest kod
HTML strony.
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Tajna strona z kodem dr. Zatana</title>
</head>
<body>
SLG ĵFRGHĵ!2U]HïNUÈĝ\QDS!
SLG ĵFRGHĵ!/LVW]GÈĝDNXS!
SLG ĵFRGHĵ!JRG]LQÈGXFKöZS!
SLG ĵFRGHĵ!3RZLHG]LHOLĝHEÚG]LHSDGDÊSRS!
<p id=”code5”>Czy drozd krzyczy o </p>
SLG ĵFRGHĵ!*G]LHPRĝQD]QDOHěÊGU=DWDQDSRS!
SLG ĵFRGHĵ!3RZLHG]LDïHPFKïRSFRPE\SU]\QLHĂOLPLKHUEDWÚSRS!
<p id=”code8”>Gdzie moja forsa? Czekam do </p>
SLG ĵFRGHĵ!0öM]HJDUHNVWDQÈïRS!
SLG ĵFRGHĵ!0öMSDUDVROQLHODWD&]DUSU]HVWDïG]LDïDÊSRS!
<p id=”code11”>Zielony kanarek przyleci o </p>
SLG ĵFRGHĵ!2VWU\JLRWZLHUDMÈVLÚRS!
Pokazany powyżej kod
<script src=”code.js”></script>
JavaScript zostanie dołączo
</body>
ny
w tym miejscu strony.
</html>
Zrozumieć obiekty
, koniecznie wróć na nią
Jeśli pominąłeś poprzednią stronę
kluczowe znaczenie dla
i spróbuj wykonać misję! Ma ona
Rozdziału Szóstego!
CELNE SPOSTRZEŻENIA
Q
Obiekt jest kolekcją właściwości.
Q
Aby odwołać się do właściwości obiektu,
należy użyć zapisu z kropką, czyli wpisać
nazwę zmiennej zawierającej obiekt,
kropkę, a następnie nazwę właściwości.
Q
Nowe właściwości można dodawać
do obiektu w dowolnym momencie,
wystarczy przypisać wartość do nowej
nazwy właściwości.
Q
Można także usuwać właściwości
z obiektu; służy do tego operator delete.
Q
W odróżnieniu od zmiennych
zawierających wartości typów prostych,
takie jak łańcuchy znaków, liczby
i wartości logiczne, zmienna nie może
zawierać obiektu — zamiast tego zawiera
referencję do niego. Takie zmienne
nazywamy „zmiennymi referencyjnymi”.
Q
Q
Q
Kiedy przekazujemy obiekt do funkcji,
trafia do niej kopia referencji obiektu,
a nie kopia samego obiektu. Jeśli zatem
zmienimy wartość jednej z właściwości
takiego obiektu, zmiana będzie
zauważalna także w początkowym
obiekcie.
Właściwości obiektów mogą zawierać
funkcje. Funkcje umieszczone wewnątrz
obiektów nazywamy metodami.
Metody wywołuje się przy użyciu zapisu
z kropką: najpierw podawana jest nazwa
obiektu, potem kropka, a następnie nazwa
metody i para nawiasów.
Q
Metoda jest bardzo podobna do funkcji,
z tym że jest umieszczona w obiekcie.
Q
Podobnie jak do funkcji, także do metod
można przekazywać argumenty.
Q
Wewnątrz wywołanej metody słowo
kluczowe this odwołuje się do
obiektu, którego metoda jest aktualnie
wykonywana.
Q
Aby odwołać się do właściwości
obiektu, którego metoda jest aktualnie
wykonywana, należy użyć zapisu z kropką,
przy czym zamiast nazwy obiektu trzeba
podać this.
Q
W programowaniu obiektowym myślimy
w kategoriach obiektów, a nie procedur.
Q
Obiekty mają zarówno stan, jak i
zachowanie. Stan obiektu może mieć
wpływ na jego zachowanie, a zachowanie
obiektu może wpływać na jego stan.
Q
Obiekt hermetyzuje, czyli ukrywa, całą
złożoność swojego stanu i zachowania.
Q
Metody dobrze zaprojektowanego obiektu
ukrywają całą złożoność wykonywanych
przez siebie operacji, dzięki czemu nie
musimy się przejmować, w jaki sposób są
realizowane.
Q
Oprócz obiektów, które sami tworzymy,
JavaScript dysponuje wieloma
wbudowanymi obiektami, których
możemy używać. Dalej w tej książce
zastosujemy wiele z nich.
jesteś tutaj
249
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Zaczęliśmy tworzyć tablicę nazw właściwości samochodu oraz odpowiadających im wartości.
Czy możesz nam w tym pomóc? Poniżej znajdziesz nasze odpowiedzi.
y
Tutaj zapisz nazw
właściwości.
A tu odpowiadające
im wartości.
{
make :
__________
model :
__________
year
__________
:
color :
__________
passengers
__________ :
convertible :
__________
mileage :
__________
__________
accessories :
__________
whitewalls :
Tutaj zapisz swoje
odpowiedzi. Jeśli
zechcesz, rozszerz tę
listę, dopisując do
niej wymyślone przez
Ciebie właściwości.
Zaostrz ołówek
Rozwiązanie
_________________,
“Chevy”
"Bel Air"
_________________,
_________________,
1957
_________________,
czerwony
2
_________________,
false
_________________,
1021
_________________,
_________________,
"ozdobne kości do gry"
_________________
true
Używamy odpowiednio
łańcuchów znaków,
wartości logicznych
i liczb.
};
Skorzystaj ze swoich nowych umiejętności związanych ze stosowaniem słowa kluczowego
this, by pomóc nam dokończyć poniższy kod. Poniżej podaliśmy nasze rozwiązanie.
var eightBall = { index: 0,
advice: [”tak”, ”nie”, ”moĝe”, ”nie ma szans”],
shake: function() {
this.index = this.index + 1;
if ( this.index >= this.advice.length) {
this.index = 0;
}
},
look: function() {
return this.advice[this.index];
}
};
eightBall.shake();
console.log(eightBall.look());
250
Rozdział 5.
Aby przetestować kod, kilkakrotnie
powtórz tę sekwencję instrukcji.
Konsola JavaScript
tak
PRĝH
nie ma szans
Zrozumieć obiekty
Nie musisz ograniczać się do jednego obiektu. Prawdziwa potęga obiektów (jak się już niedługo przekonasz)
polega na możliwości tworzenia wielu obiektów i pisaniu kodu, który może operować na dowolnym obiekcie
Ćwiczenie
przekazanym do tego kodu. Spróbuj swoich sił, tworząc od podstaw jeszcze jeden obiekt, kolejny obiekt
Rozwiązanie samochodu. Poniżej znajdziesz nasze rozwiązanie
var cadi = {
make: "GM",
model: "Cadillac",
year: 1955,
color: "jasnobrÈzowy",
Oto właściwości
obiektu Cadillac.
passengers: 5,
convertible: false,
mileage: 12892
};
Powiedzmy, że to będzie
kolor jasnobrązowy.
To Cadillac GM z 1955 roku.
To nie jest kabriolet,
może przewozić do
pięciu osób (ma duże,
wygodne i składane
tylne siedzenie).
Jego przebieg
wynosi 12 892.
jesteś tutaj
251
Rozwiązanie ćwiczenia
Obiektowe magnesiki. Rozwiązanie
7HQNRG]RVWDâSRFLĐW\QDIUDJPHQW\LFDâNRZLFLHZ\PLHV]DQ\QDORGyZFH3U]HþZLF]VZRMH
XPLHMĐWQRĤFLWZRU]HQLDRELHNWyZL]QDMRPRĤþ]DSLVX]NURSNċDE\RGWZRU]\þSRF]ċWNRZċ
SRVWDþWHJRNRGX8ZDİDMMHGQDNERPRJâRVLĐ]GDU]\þİHZĤUyGZ\PLHV]DQ\FK
PDJQHVLNyZ]DZLHUXV]\â\VLĐMDNLHĤGRGDWNRZH3RQLİHM]QDMG]LHV]QDV]HUR]ZLċ]DQLH
name: "Burek"
weight: 20.2
,
20.2
"Burek"
bark
age: 4
breed: "mieszaniec"
activity: "przynoszenie piïki"
age
Obiekt
psa.
Pozostałe magnesiki
var dog = {
name: _________
Burek
,
we
ig
ht
_________:
20.2 ,
age: ________
4
,
________: ”mieszaniec”,
breed
"przynoszenie piïki"
activity: ____________
};
var bark;
, że
Burek ma nadzieję asz
prawidłowo poukład
wszystkie jego
właściwości.
dog.weight > 20) {
if (_____________
bark = ”HAU HAU”;
} else {
bark = ”hau hau”;
}
dog.name + ” szczeka ” + ________
var speak = __________
dog.activity
bark + ” kiedy ma ochotÚ na ” + __________________;
console.log(speak);
252
Rozdział 5.
Zrozumieć obiekty
Zaostrz ołówek
Rozwiązanie
Teraz Twoja kolej. Oto trzy kolejne obiekty samochodów; masz określić, jaki
będzie wynik przekazania każdego z nich do funkcji prequal. Podaj każdą
z odpowiedzi samodzielnie, a później napisz kod, który je sprawdzi i wyświetli.
Poniżej zamieściliśmy nasze rozwiązanie.
var fiat = {
var cadi = {
make: ”GM”,
make: ”Fiat”,
model: ”Cadillac”,
model: ”500”,
year: 1955,
year: 1957,
color: ”jasnobrÈzowy”,
color: ”szaroniebieski”,
passengers: 5,
passengers: 2,
convertible: false,
convertible: false,
mileage: 12892
mileage: 88000
};
};
false
false
Tu zapisz
wartość,
którą zwróci
funkcja
prequal.
var chevy = {
make: ”Chevy”,
model: ”Bel Air”,
year: 1957,
color: ”czerwony”,
passengers: 2,
convertible: false,
mileage: 1021
};
true
jesteś tutaj
253
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Otrzymałeś supertajny plik oraz dwie funkcje zapewniające możliwość odczytu
i ustawiania jego zawartości, jednak wyłącznie wtedy, kiedy dysponujesz
prawidłowym hasłem. Pierwsza funkcja, getSecret, zwraca zawartość pliku,
jeśli hasło jest prawidłowe, i jednocześnie rejestruje próbę dostępu do pliku. Druga
funkcja, setSecret, aktualizuje zawartość pliku i cofa licznik dostępu do pliku
do wartości początkowej — 0. Twoim zadaniem jest uzupełnienie pustych miejsc
w poniższym kodzie i przetestowanie funkcji. Poniżej znajdziesz nasze rozwiązanie.
function getSecret(file, secretPassword) {
file
file
_______.opened
= _______.opened
+ 1;
if (secretPassword == _______.password)
{
file
file
return ______.contents;
}
Obiekt superSecretFile jest przekazywany
do funkcji getSecret jako parametr
o nazwie file. Musimy się zatem upewnić,
że w odwołaniach do właściwości obiektu,
takich jak opened lub password, będziemy
używać jego nazwy, file, oraz kropki.
else {
return ”=ïe hasïo! Nie masz dostÚpu do sekretöw.”;
}
}
function setSecret(file, secretPassword, secret) {
file
if (secretPassword == _______.password)
{
______.opened
= 0;
file
Tak samo tutaj.
file
______.contents
= secret;
}
}
var superSecretFile = {
level: ”tajne”,
opened: 0,
password: 2,
contents: ”NastÚpne spotkanie z dr. =atanem odbÚdzie siÚ w Katowicach.”
};
superSecretFile _____);
var secret = getSecret(_______________,
2
console.log(secret);
Obiekt superSecretFile
możemy przekazać do funkcji
getSecret oraz setSecret.
superSecretFile
2
setSecret(_________________,
____,
”NastÚpne spotkanie z dr. =atanem odbÚdzie siÚ w ¿ywcu.”);
superSecretFile
secret = getSecret(_________________,
_____);
2
console.log(secret);
254
Rozdział 5.
Zrozumieć obiekty
BĄDŹ przeglądarką. Rozwiązanie
Poniżej znajdziesz kod JavaScript,
w którym celowo wprowadziliśmy
kilka błędów. Twoim zadaniem
jest wcielić się w rolę przeglądarki
i wykryć błędy w tym kodzie.
Poniżej znajdziesz nasze
rozwiązanie.
var song = {
name: "Brothers in Arms",
artist: "Dire Straits",
minutes: 4,
seconds: 3,
genre: "80s",
playing: false,
play: function() {
Tutaj zabrakło słowa
kluczowego this.
if (!this.playing) {
this.playing = true;
A tu zabrakło nazwy
właściwości playing.
console.log("Odtwarzam teraz "
+ this.name + " wykonywany przez " + this.artist + ".");
}
Także tutaj, aby odwołać się do
tych dwóch właściwości, należy
użyć słowa kluczowego this.
},
pause: function() {
if (this.playing) {
I tu też w odwołaniu do właściwości
musimy zastosować this.
this.playing = false;
}
}
};
this song.play();
this song.pause();
poza
Słowa kluczowego this nie używa się y, podając
łujem
wywo
tu
obiek
dy
meto
;
metodami
nazwę zmiennej zawierającej obiekt.
jesteś tutaj
255
Rozwiązanie ćwiczenia
Ćwiczenie
Rozwiązanie
Nadszedł czas, by uruchomić całą flotę samochodów. Dodaj metodę drive do każdego z obiektów
samochodów. Kiedy już się z tym uporasz, dodaj kod, który dla każdego samochodu uruchomi silnik,
przejedzie się trochę, a następnie wyłączy silnik. Poniżej znajdziesz nasze rozwiązanie.
var cadi = {
make: ”GM”,
model: ”Cadillac”,
year: 1955,
color: ”jasnobrÈzowy”,
passengers: 5,
convertible: false,
mileage: 12892,
started: false,
start: function() {
this.started = true;
},
stop: function() {
this.started = false;
},
drive: function() {
if (this.started) {
alert(this.make + ” ” +
this.model + ” robi: brum wrrrr!”);
} else {
alert(”Najpierw musisz wïÈczyÊ silnik.”);
}
}
};
var chevy = {
make: ”Chevy”,
model: ”Bel Air”,
year: 1957,
color: ”czerwony”,
passengers: 2,
convertible: false,
mileage: 1021,
started: false,
start: function() {
this.started = true;
},
stop: function() {
this.started = false;
},
drive: function() {
if (this.started) {
alert(this.make + ” ” +
this.model + ” robi: brum wrrrr!”);
} else {
alert(”Najpierw musisz wïÈczyÊ silnik.”);
}
}
};
256
Rozdział 5.
var taxi = {
make: "SieMoCorp",
model: "Taxi",
year: 1955,
color: "yellow",
passengers: 4,
Upewnij się, że
za wszystkimi
convertible: false,
nowymi, dodanymi
mileage: 281341,
właściwościami
started: false,
umieściłeś przecinki.
start: function() {
this.started = true;
},
stop: function() {
this.started = false;
},
drive: function() {
if (this.started) {
alert(this.make + " " +
this.model + " robi: brum wrrrr!");
} else {
alert("Najpierw musisz wïÈczyÊ silnik.");
}
}
};
cadi.start();
cadi.drive();
cadi.stop();
chevy.start();
chevy.drive();
chevy.stop();
taxi.start();
taxi.drive();
taxi.stop();
Skopiowaliśmy kod
i wkleiliśmy go do
każdego z trzech obiektów,
dzięki czemu każdy
z samochodów ma te same
właściwości i metody.
Teraz, używając tych
samych metod, w każdym
z samochodów możemy
włączyć silnik, przejechać
się trochę i wyłączyć
silnik.
Zrozumieć obiekty
Ćwiczenie
Rozwiązanie
Musimy jeszcze trochę popracować, zanim właściwość fuel zostanie w pełni zintegrowana
z obiektem samochodu. Czy np. powinien uruchomić się silnik, jeśli nie ma paliwa? Pomóż nam
zintegrować właściwość fuel z obiektem samochodu; w tym celu przed uruchomieniem silnika
musisz sprawdzić poziom paliwa. Jeśli w momencie wywołania metody start bak będzie
pusty, powiadom o tym kierowcę, wyświetlając adekwatny komunikat: ”Bak jest pusty,
zatankuj przed jazdÈ!”. Zmodyfikowaną wersję metody start najpierw zapisz poniżej,
a następnie dodaj do kodu strony i przetestuj. Poniżej znajdziesz nasze rozwiązanie.
var cadi = {
make: ”Fiat”,
model: ”500”,
year: 1957,
color: ”szaroniebieski”,
passengers: 2,
convertible: false,
mileage: 88000,
fuel: 0,
started: false,
start: function() {
if (this.fuel == 0) {
alert(”Bak jest pusty, zatankuj przed jazdÈ!”);
} else {
this.started = true;
}
},
stop: function() {
this.started = false;
},
drive: function() {
if (this.started) {
if (this.fuel > 0) {
alert(this.make + ” ” +
this.model + ” robi: brum wrrrr!”);
this.fuel = this.fuel - 1;
} else {
alert(”Osz..., brak paliwa.”);
this.stop();
}
} else {
alert(”Najpierw musisz wïÈczyÊ silnik.”);
}
},
addFuel: function(amount) {
this.fuel = this.fuel + amount;
}
};
jesteś tutaj
257
258
Rozdział 5.
6. Interakcja ze stronami WWW
Poznajemy DOM
Nie tak szybko kowboju.
Jeśli chcesz mnie poznać
dokładnie, musisz najpierw
poznać mój obiektowy model
dokumentu i nauczyć poruszać
się po nim.
Przebyłeś już długą drogę, poznając JavaScript. Powoli z żółtodzioba
zmieniłeś się w twórcę prostych skryptów, a potem w końcu w programistę. Jednak
wciąż Ci czegoś brakuje. Abyś mógł naprawdę wykorzystać całą swoją znajomość języka
JavaScript, musisz dowiedzieć się, jak prowadzić interakcję ze stronami WWW, w których
umieszczasz swoje skrypty. Tylko to pozwoli Ci tworzyć strony, które są dynamiczne,
które reagują, odpowiadają i aktualizują swoją zawartość już po jej wczytaniu przez
przeglądarkę. W jaki sposób można prowadzić interakcję ze stroną WWW? Służy do tego
DOM, nazywany także obiektowym modelem dokumentu. W tym rozdziale opiszemy
go szczegółowo i wyjaśnimy, jak można z niego korzystać i jak go używać wraz z językiem
JavaScript, by nauczyć nasze strony wykonywania wielu nowych sztuczek.
to jest nowy rozdział
259
Przypomnienie misji złamania kodu
W poprzednim rozdziale miałeś wykonać
niewielką misję — misję złamania kodu
Otrzymałeś kod HTML strony oraz umieszczony w odrębnym pliku kod
JavaScript, przejęte na stronie dr. Zatana. Przedstawiamy go ponownie poniżej.
Oto kod HTML strony.
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Tajna strona z kodem dr. Zatana</title>
Zwróć uwagę, że każdy
</head>
akapit ma swój własny
<body>
identyfikator określony
przez atrybut id.
<p id=”code1”>Orzeï krÈĝy na </p>
<p id=”code2”>List zdÈĝa ku </p>
<p id=”code3”> godzinÈ duchöw.</p>
<p id=”code4”>Powiedzieli, ĝe bÚdzie padaÊ po </p>
<p id=”code5”>Czy drozd krzyczy o </p>
<p id=”code6”>Gdzie moĝna znaleěÊ dr. Zatana po </p>
<p id=”code7”>Powiedziaïem chïopcom, by przynieĂli mi herbatÚ po </p>
<p id=”code8”>Gdzie moja forsa? Czekam do </p>
<p id=”code9”>Möj zegarek stanÈï o </p>
<p id=”code10”>Möj parasol nie lata. Czar przestaï dziaïaÊ po </p>
<p id=”code11”>Zielony kanarek przyleci o </p>
<p id=”code12”>Ostrygi otwierajÈ siÚ o </p>
<script src=”code.js”></script>
A to jest kod
</body>
JavaScript…
</html>
document jest obiektem globalnym. var access =
A getElementById jest metodą.
wę metody
Upewnij się, że naz łeś,
isa
zap
yId
getElementB
owiedniej
używając liter o odp eciwnym
wielkości, gdyż w przdziałać.
razie kod nie będzie
document.getElementById(”code9”);
var code = access.innerHTML;
FRGH FRGHĵSöïQRF\ĵ
alert(code);
Twoim zadaniem było określenie kodu dostępu dr. Zatana przy
wykorzystaniu dedukcyjnych możliwości tego fragmentu kodu.
260
Rozdział 6.
I spójrz tutaj:
użyliśmy zapisu
z kropką — to
wygląda jak obiekt
mający właściwość
innerHTML.
Interakcja ze stronami WWW
Co robi ten kod?
Przeanalizujemy teraz działanie tego kodu, by dowiedzieć się, w jaki sposób dr Zatan generuje
swoje kody dostępu. Kiedy omówimy każdą instrukcję, zrozumiesz, jak to wszystko działa.
Obiekt document oraz
obiekt elementu poznasz
dokładniej dalej w tym
rozdziale.
1 1DMSLHUZNRG]DSLVXMHZ]PLHQQHMaccessZ\QLN]ZUyFRQ\SU]H]Z\ZRâDQLHPHWRG\
getElementByIdRELHNWXdocumentGRNWyUHM]RVWDâSU]HND]DQ\âDĚFXFK]QDNyZ”code9”
7DPHWRGD]ZUDFDRELHNWHOHPHQWX
var access =
document.getElementById(”code9”);
To wywołanie pobiera
element o identyfikatorze
„code9”. Wychodzi na to, że
to jest ten element…
var code = access.innerHTML;
code = code + ” pöïnocy”;
alert(code);
2
SLG ĵFRGHĵ!0öM]HJDUHNVWDQÈïRS!
1DVWĐSQLHXİ\ZDP\WHJRHOHPHQWX F]\OLHOHPHQWXRLGHQW\ILNDWRU]H
”code9” LSRELHUDP\ZDUWRĤþMHJRZâDĤFLZRĤFLinnerHTMLNWyUċ
]DSLVXMHP\Z]PLHQQHMcode.
var access =
document.getElementById(”code9”);
var code = access.innerHTML;
code = code + ” pöïnocy”;
jest
Elementem o identyfikatorze „code9”
element akapitu, a jego zawartością HTML)
(a raczej wartością właściwości inner
jest tekst „Mój zegarek stanął o”.
alert(code);
3
.RGGU=DWDQDGRGDMHGR]DZDUWRĤFLZ\EUDQHJRHOHPHQWX]DSLVDQHM
Z]PLHQQHMcodeF]\OLĵ0öM]HJDUHNVWDQÈïRĵâDĚFXFK]QDNyZ
ĵSöïQRF\ĵ1DVWĐSQLHZ\ĤZLHWODQHMHVWRNQRLQIRUPDF\MQH
NWyUHJR]DZDUWRĤFLċMHVWNRGGRVWĐSX]DSLVDQ\Z]PLHQQHMcode.
var access =
document.getElementById(”code9”);
var code = access.innerHTML;
code = code + ” pöïnocy”;
alert(code);
A zatem do łańcucha „Mój zegarek
kując
stanął o” dodajemy „ północy”, uzys
kod dostępu w postaci „Mój zegarek
stanął o północy”, który następnie
.
wyświetlamy w oknie informacyjnym
jesteś tutaj
261
Jak działa skrypt misji złamania kodu
Szybkie podsumowanie
Co właściwie przed chwilą zrobiliśmy? Cóż, użyliśmy kodu JavaScript, który odwołał
się do zawartości strony (nazywanej także dokumentem), pobrał jeden z jej elementów
(ten, który miał identyfikator ”code9”), następnie pobrał jego zawartość (którą okazał
się łańcuch znaków ĵ0öM]HJDUHNVWDQÈïRĵ), dodał do niego łańcuch ĵSöïQRF\ĵ,
a uzyskany w ten sposób kod dostępu wyświetlił w oknie informacyjnym.
Strona dr. Zatana zawiera wszystkie możliwe kody dostępu,
przy czym każdy z nich jest umieszczony w odrębnym
akapicie posiadającym unikalny identyfikator (określony
przy użyciu atrybutu id).
1
2 Za kulisami JavaScript pobiera element
o identyfikatorze "code9".
SLG ĵFRGHĵ!0öM]HJDUHNVWDQÈïRS!
2U]HïNUÈĝ\QD
/LVW]GÈĝD NX
JRG]LQÈ GXFKöZ
G]LHSDGDÊ SR
3RZLHG]LHOLĝHEÚ
o
Czy drozd krzyczy
ÊGU =DWDQD SR
*G]LHPRĝQD]QDOHě
PL
RP E\ SU]\QLHĂOL
ïRSF
FK
3RZLHG]LDïHP
KHUEDWÚ SR
Czekam do
Gdzie moja forsa?
R
Èï
VWDQ
UHN
0öM ]HJD
ODWD&]DU SU]HVWDï
0öM SDUDVRO QLH
G]LDïDÊ SR
SU]\OHFLR
=LHORQ\ NDQDUHN
ÈVLÚ R
2VWU\JL RWZLHUDM
ĵ0öM]HJDUHNVWDQÈïRĵĵSöïQRF\ĵ
3
Następnie pobiera zawartość tego
elementu, dodaje do niej łańcuch
znaków ĵSöïQRF\ĵ, a całość wyświetla
w oknie informacyjnym.
Przeglądarka
A teraz, pomijając zamiary dr. Zatana, jego znajomość JavaScriptu oraz to, co sądzimy
na temat jego schematów zabezpieczeń, musimy jednak zwrócić uwagę na sprawę
najważniejszą, a mianowicie na to, że strona WWW jest żywą, oddychającą strukturą
danych, z którą nasz kod JavaScript może prowadzić interakcję — może odwoływać się
do elementów strony i odczytywać zawartość tych elementów. Jednak to nie wszystko
— można także postępować odwrotnie: można używać kodu JavaScript, by zmieniać
zawartość oraz strukturę strony. Aby jednak to wszystko robić, musimy się na chwilę
cofnąć i lepiej zrozumieć sposób współdziałania kodu HTML i JavaScript.
262
Rozdział 6.
Interakcja ze stronami WWW
Jak naprawdę wygląda interakcja JavaScriptu ze stroną WWW?
JavaScript i HTML to dwie zupełnie inne sprawy. HTML to kod znacznikowy, natomiast kod JavaScript
to program. W jaki sposób mogą ze sobą prowadzić interakcję? Jest ona możliwa dzięki specjalnej
reprezentacji strony, nazywanej obiektowym modelem dokumentu (ang. Document Object Model, w skrócie:
DOM). A skąd bierze się DOM? Jest tworzony w momencie, gdy przeglądarka wczytuje stronę WWW.
Poniżej pokazaliśmy, jak to się dzieje.
Kod
znacznikowy
1
Kiedy przeglądarka wczytuje stronę, nie
tylko analizuje jej zawartość i wyświetla
ją, lecz także tworzy zbiór obiektów
reprezentujących jej kod. Obiekty te są
zapisywane w DOM.
Twoja
przeglądarka
To nazywamy obiektowym
modelem dokumentu…
KWPO
KHDG
WLWOH
ERG\
VFULSW
K
K
S
HP
…albo w skrócie DOM.
2
Kod JavaScript może prowadzić interakcję
z DOM, by odwoływać się do elementów
oraz ich zawartości.
3
Kiedy kod JavaScript
modyfikuje DOM,
przeglądarka dynamicznie
modyfikuje stronę, dzięki
czemu zmiany i nowe
treści pojawiają się na tej
stronie.
jesteś tutaj
263
Przyrządzanie DOM
Jak wypiec swój własny DOM?
Weźmy prosty kod HTML i utwórzmy DOM stanowiący jego odpowiednik.
Poniżej zamieściliśmy prosty przepis na to, jak to zrobić.
6NïDGQLNL
-HGQDSUDZLGïRZR]DSLVDQDVWURQD+70/
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”
>
<title>Möj blog</tit
le>
<script src=”blog.js
”></script>
</head>
<body>
<h1>Möj blog</h1>
<div id=”entry1”>
<h2>Wspaniaïy dzieñ
spÚdzony na oglÈdaniu
ptaköw</h2>
<p>
-HGQDQRZRF]HVQDSU]HJOÈGDUNDZVWÚSQLHUR]JU]DQD
LJRWRZDGRXĝ\FLD
3U]HSLV
=DF]QLMRGXWZRU]HQLDZÚ]ïDGRFXPHQW]DSLVDQHJRQD
VDPHMJöU]H
GRFXPHQW
1DVWÚSQLHZHěHOHPHQWJïöZQ\VZRMHMVWURQ\:::
ZbQDV]\PSU]\SDGNXMHVWWRHOHPHQWKWPO!QD]ZLHP\
JRHOHPHQWHPELHĝÈF\PDQDVWÚSQLHGRGDMJRGRHOHPHQWX
JïöZQHJRMDNRMHJRSRWRPND
DziĂ zobaczyïem trzy
kaczki!
Nadaïem im imiona
Kwaczek, Kuper i Dzi
waczka.
</p>
<p>
Zrobiïem im teĝ kilka
zdjÚÊ...
</p>
</div>
</body>
</html>
GRFXPHQW
KWPO
'RNDĝGHJRHOHPHQWXXPLHV]F]RQHJRZHOHPHQFLHELHĝÈF\P
GRGDMJRMDNRMHJRSRWRPND
GRFXPHQW
KWPO
KHDG
ERG\
'ODNDĝGHJR]ZïDĂQLHGRGDQ\FKHOHPHQWöZZUöÊGR
SXQNWXLSRZWDU]DMWHF]\QQRĂFLDĝGRPRPHQWXGRGDQLD
ZV]\VWNLFKHOHPHQWöZ
264
Rozdział 6.
Ten DOM sami
.
wypiekliśmy dla Ciebie
nej
Zobacz na następ
stronie, jak wygląda
w całości.
Interakcja ze stronami WWW
Pierwszy smak DOM
Porównujemy tę strukturę
do drzewa, gdyż „drzewo”
to struktura danych
powszechnie wykorzystywana
w informatyce, jak również
dlatego, że z wyglądu
przypomina ona drzewo
odwrócone korzeniem
ku górze.
Jeśli postępowałeś zgodnie z przepisem na tworzenie DOM, uzyskałeś strukturę
podobną do przedstawionej poniżej. Każdy schemat DOM rozpoczyna się od obiektu
document umieszczonego na samej górze i stanowiącego początek drzewa składającego
się z gałęzi i węzłów liści reprezentujących wszystkie elementy zapisane w kodzie
HTML. Przyjrzyjmy się temu drzewu nieco dokładniej.
e zawsze jest
Na samej górzwęzeł document.
y
on
cz
ent
umiesz
czególny elem
Stanowi on sz o możemy używać
eg
ór
yskiwać
drzewa, kt
Script, by uz
w kodzie Java go DOM.
łe
dostęp do ca
GRFXPHQW
document przypomina także
korzeń odwróconego drzewa.
KWPO
A te elementy
przypominają gałęzie
drzewa.
KHDG
PHWD
WLWOH
0yMEORJ
Z kolei te elementy
przypominają liście drzewa
(gdyż poniżej nich nie ma
nic, ewentualnie tylko tekst).
ERG\
VFULSW
GLYLG µHQWU\µ
K
0yMEORJ
K
S
S
:VSDQLDâ\ ']LĤ
=URELâHP
G]LHĚ
]REDF]\âHP LPWHİ
VSĐG]RQ\ WU]\«
NLOND«
wraz z elementami.
DOM zawiera całą zawartość strony y całe teksty, jednak
zujem
poka
ze
zaws
nie
,
(Rysując DOM
są w nim dostępne w całości).
Teraz, kiedy już mamy
dostęp do DOM, możemy
go badać i modyfikować,
jak tylko zechcemy.
jesteś tutaj
265
Ćwiczenie z przyrządzania DOM
<!doctype html>
BĄDŹ przeglądarką
Twoim zadaniem jest wcielić
się w rolę przeglądarki. Masz
przeanalizować dokument
HTML i na podstawie jego
zawartości zbudować własny
DOM. Przeanalizuj kod HTML
przedstawiony z prawej strony,
a DOM narysuj poniżej. Trochę
już narysowaliśmy za Ciebie.
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Filmy</title>
</head>
<body>
<h1>Godziny seansöw</h1>
<h2 id=”movie1”>Plan dziewiÚÊ z kosmosu</h2>
<p>Seanse rozpoczynajÈ siÚ o 15:00 i 19:00.
<span>
Specjalny seans rozpocznie siÚ dziĂ o <em>pöïnocy</em>!
Zanim przejdziesz dalej,
sprawdź rozwiązanie
ćwiczenia — znajdziesz je
pod koniec rozdziału.
</span>
</p>
<h2 id=”movie2”>Zakazana planeta</h2>
<p>Seanse rozpoczynajÈ siÚ o 17:00 i 21:00.</p>
</body>
</html>
GRFXPHQW
KWPO
266
Rozdział 6.
Tutaj narysuj swój DOM.
Interakcja ze stronami WWW
O tym, jak dwie zupeânie ryīne technologie âĆczĆ siċ
7RSHZQHĪH+70/L-DYD6FULSWSRFKRG]ą]GZyFK
]XSHáQLHLQQ\FKSODQHW&]\PDP\QDWRMDNLĞGRZyG"
'1$MĊ]\ND+70/VNáDGDVLĊ]NRGX]QDF]QLNRZHJR
SR]ZDODMąFHJRRSLV\ZDü]ELyUHOHPHQWyZWZRU]ąF\FK
VWURQĊ-DYD6FULSWVNáDGDVLĊ]F]\VWRDOJRU\WPLF]QHJR
PDWHULDáXJHQHW\F]QHJRSU]H]QDF]RQHJRGRRSLV\ZDQLD
REOLF]HĔ
&]\UyĪQLFHSRPLĊG]\QLPLVąWDNRJURPQHĪHZ\NOXF]DMą
MDNąNROZLHNNRPXQLNDFMĊLZVSyáG]LDáDQLH"2F]\ZLĞFLH
QLH+70/L-DYD6FULSWPDMąFRĞZVSyOQHJRMHVWWR
'20=DSRĞUHGQLFWZHP'20NRG-DYD6FULSWPRĪH
NRPXQLNRZDüVLĊ]HVWURQą:::LQDRGZUyW.RPXQLNDFMĊ
PRĪQD]UHDOL]RZDüQDNLONDVSRVREyZOHF]QDUD]LH
VNRQFHQWUXMHP\VLĊW\ONRQDMHGQ\P]QLFKWRVZRLVW\
Z\WU\FKNWyU\SR]ZDODNRGRZL-DYD6FULSWX]\VNDüGRVWĊS
GRGRZROQHJRHOHPHQWXVWURQ\7\PZ\WU\FKHPMHVWPHWRGD
getElementById.
jesteś tutaj
267
Stosowanie getElementById do pobierania elementu
Zacznijmy poznawanie DO0 1LĪHMSU]HGVWDZLOLĞP\SU]\NáDGSURVWHJR'206NáDGDVLĊRQ]NLONX
DNDSLWyZ]NWyU\FKNDĪG\PDLQQ\LGHQW\ILNDWRURNUHĞODMąF\HOHPHQW\MDNRÄ]LHORQąSODQHWĊ´ÄF]HUZRQą
SODQHWĊ´RUD]ÄQLHELHVNąSODQHWĊ´2F]\ZLĞFLHRSUyF]QLFKZVNáDG'20ZFKRG]LWDNĪHHOHPHQWKHDG!,
DE\MHGQDNXSURĞFLü]DJDGQLHQLHMHJR]DZDUWRĞüSRPLQLHP\
GRFXPHQW
KWPO
KHDG
SLG µJUHHQSODQHWµ
:V]\VWNRMHVW
ZSRU]ċGNX
ERG\
SLG µUHGSODQHWµ
1LHPDWXQLF
FLHNDZHJR
SLG µEOXHSODQHWµ
:V]\VWNLHV\VWHP\
VSUDZQH
$ teraz uīyjmy -aYaScriptu, by nieco uatrakcyjniý sobie īycie =DáyĪP\ĪHFKFHP\
]PLHQLüWHNVWZ\ĞZLHWORQ\ZHOHPHQFLHRLGHQW\ILNDWRU]HÄJUHHQSODQHW´]Ä:V]\VWNRMHVWZSRU]ąGNX´
QDÄ$ODUPF]HUZRQ\UR]SRF]ĊWRRVWU]Dá]ID]HUyZ´0RĪHVLĊ]GDU]\üĪHWDNLH]PLDQ\EĊG]LHP\FKFLHOL
ZSURZDG]DüQDSRGVWDZLHF]\QQRĞFLZ\NRQ\ZDQ\FKSU]H]XĪ\WNRZQLNDEąGĨQDZHWQDSRGVWDZLHGDQ\FK
GRVWDUF]DQ\FKSU]H]XVáXJLLQWHUQHWRZH'RWHJRWHĪGRMG]LHP\QDUD]LHMHGQDNRJUDQLF]P\VLĊGR]PLDQ\
WHNVWXGODÄ]LHORQHMSODQHW\´$E\WR]URELüSRWU]HEQ\MHVWHOHPHQWRLGHQW\ILNDWRU]HÄJUHHQSODQHW´
3RQLĪHMSU]HGVWDZLOLĞP\NRGNWyU\SR]ZDODJRSREUDü
Obiekt document reprezentuje całą
stronę wyświetloną w przeglądarce
i zawiera pełny DOM, dlatego też
możemy go prosić o różne rzeczy,
takie jak znalezienie elementu
o określonym identyfikatorze.
Tutaj prosimy obiekt document
o zwrócenie konkretnego elementu,
który zostanie wyszukany na podstawie
identyfikatora.
document.getElementById(”greenplanet”);
S
Wywołanie getElementById(„greenplanet”)
zwraca element akapitu odpowiadający
elementowi o identyfikatorze
„greenplanet”…
268
Rozdział 6.
…a później
kod JavaScript
może na nim
wykonywać wiele
interesujących
operacji.
Interakcja ze stronami WWW
.iedy juī metoda get(lement%y,d zwryci element, moīemy z nim coğ zrobiý
QS]PLHQLüXPLHV]F]RQ\ZQLPWHNVWQDÄ$ODUPF]HUZRQ\UR]SRF]ĊWRRVWU]Dá]ID]HUyZ´ (OHPHQWQDNWyU\PFKFHP\Z\NRQ\ZDüMDNLHĞRSHUDFMH]D]Z\F]DM]DSLVXMHP\Z]PLHQQHM
ERZWHG\áDWZLHMEĊG]LHP\PRJOLRGZRá\ZDüVLĊGRQLHJRZNRG]LH$]DWHP]UyEP\WR
²]PLHĔP\WHNVWZHOHPHQFLH
To jest wywołanie metody
e
getElementById, które poszukuj
elementu i zwraca element
o identyfikatorze „greenplanet”.
Zapisujemy element
w zmiennej o nazwie planet.
var planet = document.getElementById(”greenplanet”);
A dalej w kodzie możemy używać
zmiennej planet, by odwoływać
się do znalezionego elementu.
planet.innerHTML = ”Alarm czerwony: rozpoczÚto ostrzaï z fazeröw!”;
Aby zmienić zawartość
elementu, możemy sko
z właściwości innerH rzystać
TML
elementu zapisanego
w zmiennej planet.
Zmieniamy zawartość elementu
„greenplanet” na nasz nowy tekst, co
sprawi, że zarówno DOM, jak i strona
zostaną odpowiednio zaktualizowane.
Już wkrótce napiszemy
znacznie więcej
o właściwościach
elementów.
GRFXPHQW
KWPO
KHDG
SLG µJUHHQSODQHWµ
$ODUPF]HUZRQ\
UR]SRF]ĐWRRVWU]Dâ
]ID]HUyZ
ERG\
SLG µUHGSODQHWµ
SLG µEOXHSODQHWµ
1LHPDWXQLF
FLHNDZHJR
:V]\VWNLH
V\VWHP\
VSUDZQH
Każda zmiana w DOM zostaje odzwierciedlona
w zmianie postaci strony wyświetlonej w przeglądarce,
zobaczysz zatem, że zawartość akapitu ulegnie
zmianie i będzie w nim widoczny nowy tekst!
jesteś tutaj
269
Jak działa metoda getElementById
Pobieranie elementu przy użyciu metody getElementById
Co przed chwilą zrobiliśmy? Przyjrzyjmy się wykonanym operacjom nieco bardziej szczegółowo.
Używamy obiektu document, by odwołać się do DOM naszej strony. Obiekt document jest
obiektem wbudowanym, dysponującym zbiorem wielu różnych właściwości i metod, a wśród
nich także metodą getElementById, która odnajduje i zwraca wybrany element DOM. Metoda
ta wymaga przekazania identyfikatora i zwraca element, który ten identyfikator posiada.
Na pewno używałeś już identyfikatorów, by wybierać i określać wygląd konkretnych elementów
w stylach CSS. Jednak w tym przypadku używamy identyfikatora, by pobrać element DOM —
a konkretnie, element <p> o identyfikatorze ”greenplanet”.
Kiedy już będziemy dysponować odpowiednim
elementem, możemy go modyfikować.
Dojdziemy do tego już niebawem, na razie
jednak skoncentrujemy się na tym, jak
działa metoda getElementById. W tym celu
przeanalizujemy trzy poniższe kroki.
1
To ja: przeglądarka!
Właśnie wyświetlam
stronę i tworzę jej DOM.
Prześledź kroki 1., 2. i 3.
GRFXPHQW
KWPO
KHDG
To ja: kod
JavaScript! Szukam
w DOM elementu
o identyfikatorze
„greenplanet”.
2
SLG µJUHHQSODQHWµ
SLG µUHGSODQHWµ
:V]\VWNRMHVW
ZSRU]ċGNX
1LHPDWXQLF
FLHNDZHJR
Używamy obiektu document,
by odwołać się do DOM.
var planet = document.getElementById("greenplanet");
Zwrócony element zapiszemy
w zmiennej planet, żeby
można było używać go dalej
w kodzie.
A tu wywołujemy
metodę getElementById.
Szukamy elementu
o identyfikatorze „greenplanet”.
Znalazłeś mnie!
To ja jestem elementem
<p> o identyfikatorze
„greenplanet”. Powiedz mi,
co mam dla Ciebie zrobić.
S
3
270
ERG\
Rozdział 6.
SLG
µEOXHSODQHWµ
:V]\VWNLHV\VWHP\
VSUDZQH
Interakcja ze stronami WWW
Co pobieramy z DOM?
Kiedy przy użyciu metody getElementById pobieramy z DOM element, uzyskujemy obiekt elementu,
którego możemy używać do odczytu, zmiany lub zastępowania zawartości elementu oraz modyfikacji
jego atrybutów. Oto magia: kiedy zmieniasz element, zmienia się także to, co jest wyświetlone na stronie.
Ale po kolei. Przyjrzyjmy się ponownie obiektowi elementu, który pobraliśmy z DOM. Wiemy, że
obiekt ten reprezentuje w kodzie strony element <p>, który ma identyfikator ”greenplanet” i którego
zawartością jest tekst ĵ:V]\VWNRMHVWZSRU]ÈGNXĵ. Podobnie jak wszystkie inne obiekty w języku
JavaScript, także i ten ma właściwości i metody. Właściwości i metod obiektu elementu możemy
używać do odczytu i modyfikacji elementu strony. Poniżej przedstawiliśmy przykłady kilku operacji,
które można wykonywać przy użyciu obiektów elementów.
HP
ERG\
GLY
3RELHUDQLH]DZDUWRĤFL WHNVWOXE+70/ S
Operacje, które można
wykonywać przy użyciu
obiektu elementu.
=PLHQLDQLH]DZDUWRĤFL
2GF]\W\ZDQLHZDUWRĤFLDWU\EXWX
'RGDZDQLHDWU\EXWX
=PLHQLDQLHZDUWRĤFLDWU\EXWX
8VXZDQLHDWU\EXWX
W przypadku naszego elementu <p> — który, jak pamiętamy, jest elementem o identyfikatorze
”greenplanet” — zależy nam na zmianie jego zawartości z ĵ:V]\VWNRMHVWZSRU]ÈGNXĵ na
ĵ$ODUPF]HUZRQ\UR]SRF]ÚWRRVWU]Dï]ID]HUöZĵ. Sam obiekt elementu zapisaliśmy już
wcześniej w zmiennej planet; zatem teraz możemy jej użyć do zmiany jednej z właściwości tego
obiektu, a konkretnie właściwości innerHTML.
kt elementu
Zmienna planet zawiera obie
u <p>
ent
elem
kt
obie
— a konkretnie
t”.
o identyfikatorze „greenplane
var planet = document.getElementById(”greenplanet”);
planet.innerHTML = ”Alarm czerwony: rozpoczÚto ostrzaï z fazeröw!”;
Możemy użyć właściwości innerHTM
L
elementu, by zmienić zawartość elem obiektu
entu!
jesteś tutaj
271
Zastosowanie właściwości innerHTML
Dostęp do kodu HTML w elemencie
:áDĞFLZRĞüinnerHTML jest ważną właściwością, której można używać do pobrania lub zmiany
zawartości elementu. Kiedy odczytamy jej wartość, poznamy zawartość umieszczoną wewnątrz
danego elementu, z pominięciem jego znaczników HTML. Właśnie ta „zawartość” sprawia, że
w nazwie właściwości występuje słowo „inner” (czyli „wewnętrzny”). Teraz wykonamy niewielki
eksperyment. Spróbujemy wyświetlić zawartość elementu planet w oknie konsoli, rejestrując
w tym celu wartość właściwości innerHTML. Oto wynik.
var planet = document.getElementById("greenplanet");
console.log(planet.innerHTML);
.RQVROD-DYD6FULSW
Przekazujemy właściwość planet.in
w wywołaniu console.log, by wyś nerHTML
wietlić
zawartość elementu w oknie kons
oli.
Wszystko jest
w porzÈdku
innerHTML jest
Wartością właściwościków, zatem zostanie on
zna
h
cuc
łań
y
ajn
zwycz
konsoli, tak jak każdy
wyświetlony w oknie
h.
inny łańcuc
A teraz spróbujmy zmienić wartość właściwości innerHTML. Operacja ta będzie
równoznaczna ze zmianą zawartości elementu <p> o identyfikatorze ”greenplanet”
na stronie, co oznacza, że zauważymy ją także w oknie przeglądarki!
var planet = document.getElementById(”greenplanet”);
planet.innerHTML = ”Alarm czerwony: rozpoczÚto ostrzaï z fazeröw!”;
console.log(planet.innerHTML);
Teraz zmieniamy zawartość elem
entu, przypisując
właściwości innerHTML łańcuch
znaków „Alarm
czerwony: rozpoczęto ostrzał z
fazerów!”.
A zatem, kiedy zarejestrujemy ML,
zawartość właściwości innerHT y
w oknie konsoli pojawi się now
łańcuch znaków.
I zmieni się także
zawartość strony!
272
Rozdział 6.
.RQVROD-DYD6FULSW
Alarm czerwony:
rozpoczÚto ostrzaï
z fazeröw!
Interakcja ze stronami WWW
5\[DMKGQFĆYKGĒGPKGRCOKôEK
Hej, usiądź i zrób sobie chwilkę przerwy. Być może nurtuje Cię pytanie: „Chwila, kołacze mi się po
głowie coś o identyfikatorach i klasach, jednak nie pamiętam szczegółów, ale czy to wszystko nie
było związane z CSS?”. Nie ma problemu, odświeżymy Ci szybko pamięć, byś znalazł odpowiedni
kontekst i już niebawem będziesz mógł wrócić do dalszej nauki…
W języku HTML identyfikatory dają możliwość unikalnej identyfikacji konkretnych
elementów. A kiedy element jest unikalny, możemy odwołać się do niego w stylach CSS,
by określić jego wygląd. A jak się już przekonałeś, w kodzie JavaScript na podstawie
identyfikatora można pobrać konkretny element.
Spójrz na poniższy przykład.
<div id="menu">
...
</div>
Temu elementowi <div> nadajemy unikalny
identyfikator o wartości „menu”. Tylko
ten jeden element strony powinien mieć
taki identyfikator.
Teraz możesz wybrać ten element w arkuszu stylów CSS, by określić jego wygląd, np. tak:
div#menu jest
seleketorem
indentyfikatora.
div#menu {
background-color: #aaa;
}
div#menu wybiera element
div o identyfikatorze menu,
więc możemy zdefiniować styl
określający postać tego jednego
elementu i tylko jego.
Dodatkowo na podstawie tego samego identyfikatora możemy odwołać się do elementu
w kodzie JavaScript.
var myMenu = document.getElementById(”menu”);
Nie zapominaj jednak, że istnieje także inny sposób oznaczania elementów: przy użyciu
klas. Klasy umożliwiają oznaczanie całych grup elementów. Oto przykład.
<h3 class=”drink”>Truskawkowa eksplozja</h3>
<h3 class=”drink”>Cytrynowy chïöd</h3>
Oba elementy <h3> należą do
klasy „drink”. Klasa przypomina
nieco grupę; do tej samej grupy
może jednocześnie należeć wiele
elementów.
Także na podstawie klas można wybierać elementy i to zarówno w arkuszach stylów CSS,
jak i w kodzie JavaScript. O tym, jak korzystać z klas w języku JavaScript, dowiesz się
dalej w tej książce. A swoją drogą, jeśli to krótkie przypomnienie Ci nie wystarczyło,
sugerujemy, żebyś zajrzał do rozdziału 7. książki Head First HTML and CSS. Edycja
polska lub Twojego ulubionego podręcznika dotyczącego HTML i CSS.
jesteś tutaj
273
Modyfikowanie DOM
Co się dzieje, kiedy zmieniamy DOM?
Co się dokładnie dzieje, kiedy zmieniasz zawartość elementu przy użyciu właściwości innerHTML?
Sprowadza się to do zmiany faktycznej zawartości strony WWW, na bieżąco. A kiedy zmieniasz
zawartość strony w DOM, zmiany będą natychmiast zauważalne w oknie przeglądarki.
Przed…
Oto postać strony widocznej
w przeglądarce oraz ukryty za
kulisami DOM przed zmianą
zawartości przy użyciu
właściwości innerHTML…
GRFXPHQW
KWPO
KHDG
SLG µJUHHQSODQHWµ
ERG\
SLG µUHGSODQHWµ
:V]\VWNRMHVW
ZSRU]ċGNX
1LHPDWXQLF
FLHNDZHJR
SLG µEOXHSODQHWµ
:V]\VWNLHV\VWHP\
VSUDZQH
To jest element, którego
zawartość mamy zamiar
zmienić…
…i po
… a to postać strony oraz DOM
po zmianie wartości właściwości
innerHTML.
GRFXPHQW
KWPO
KHDG
SLG µJUHHQSODQHWµ
$ODUPF]HUZRQ\
UR]SRF]ĐWRRVWU]Dâ
]ID]HUyZ
ERG\
SLG µUHGSODQHWµ
1LHPDWXQLF
FLHNDZHJR
Wszelkie zmiany wprowadzone w DOM są natychmiast
widoczne w przeglądarce wyświetlającej stronę,
zauważymy zatem, że treść akapitu została zmieniona!
274
Rozdział 6.
SLG µEOXHSODQHWµ
:V]\VWNLH
V\VWHP\
VSUDZQH
Interakcja ze stronami WWW
Nie istnieją
głupie pytania
P: Co się stanie, kiedy do wywołania metody
document.getElementById przekażę nieistniejący
identyfikator?
O: Jeśli będziesz chciał pobrać element DOM na podstawie
identyfikatora, lecz identyfikator ten nie zostanie znaleziony,
metoda getElementById zwróci null. Sprawdzanie, czy
wynik jest różny od null podczas korzystania z tej metody,
jest bardzo dobrym rozwiązaniem, gdyż pozwala upewnić
się, że element, do którego właściwości mamy zamiar się
odwołać, naprawdę istnieje. Więcej informacji na temat
null podamy w następnym rozdziale.
P: Czy mogę używać metody document.
getElementById do pobierania elementów na
podstawie klasy — gdybym np. miał na stronie grupę
elementów należących do klasy „planets”?
P: Można by raczej oczekiwać, że obiekt elementu
będzie miał właściwość o nazwie „content” albo
ewentualnie „html”. Skąd wzięła się nazwa
innerHTML?
O: Prawdą jest, że nazwa innerHTML jest nieco
dziwaczna. Właściwość ta reprezentuje całą zawartość
umieszczoną wewnątrz elementu, w tym także inne,
zagnieżdżone w nim elementy (np. wewnątrz tekstu akapitu
mogą być umieszczone elementy <em> lub <img>). Innymi
słowy, właściwość ta reprezentuje kod HTML umieszczony
„wewnątrz” elementu. A czy jest właściwość outerHTML?
Otóż jest! Reprezentuje ona cały kod HTML umieszczony
wewnątrz elementu oraz sam element. W praktyce niezbyt
często można się spotkać z przykładami zastosowania
właściwości outerHTML, natomiast właściwość innerHTML
jest bardzo często używana do aktualizacji zawartości strony.
O: Nie, ale dobrze myślisz. W metodzie getElementById P: A zatem, przypisując coś właściwości innerHTML,
można używać tylko identyfikatorów. Jednak istnieje także
inna metoda DOM, o nazwie getElementsByClassName,
z której możesz skorzystać do pobierania elementów
na podstawie nazwy klasy. W przypadku tej metody
zwracana jest kolekcja elementów należących do podanej
klasy (gdyż do klasy może należeć wiele elementów).
Kolejną metodą zwracającą kolekcję elementów jest
getElementsByTagName. W jej przypadku wynikiem
jest kolekcja elementów odpowiadających podanej nazwie
znacznika. Metodzie tej przyjrzymy się dokładniej dalej w tej
książce i jednocześnie pokażemy, jak operować na kolekcjach
elementów.
mogę zastąpić zawartość elementu czymś innym.
A co się stanie, jeśli użyję jej do zmiany, dajmy na to,
zawartości elementu <body>?
O
: No tak. Właściwość innerHTML zapewnia prostą
możliwość zmiany zawartości elementu. I faktycznie
możemy jej użyć do zastąpienia zawartości elementu
ERG\!, co spowoduje, że cała zawartość strony zostanie
zastąpiona czymś nowym.
P: Co to w ogóle jest ten obiekt elementu?
O: Doskonałe pytanie. Obiekt elementu to używana
przez przeglądarkę reprezentacja tego, co umieszczamy
w dokumencie HTML, czyli np. S!MDNLĂWHNVWS!.
Kiedy przeglądarka wczytuje i analizuje dokument HTML,
tworzy taki obiekt dla każdego elementu strony i jednocześnie
dodaje wszystkie te elementy do DOM. A zatem DOM
jest w rzeczywistości jednym wielkim drzewem obiektów
elementów. Musisz także zapamiętać, że tak jak wszystkie
inne obiekty, także obiekty elementów mają właściwości, np.
innerHTML, oraz metody. Kilka takich właściwości i metod
przedstawimy dalej w tej książce.
jesteś tutaj
275
Rozdział 6.
SRSU]HGQLċ
FRIQLHV]
VLĐQD
:\JUDV]
MHĤOL
OLLG µHµ
OLLG µHµ
OLLG µHµ
VLĐ
DOH
ZUyFLV]
WR
VWURQĐ
V]ODN
VSDQLG µHµ
VSDQLG µHµ
VSDQLG µHµ
SLG µHµ
F]DVX
ZWDPWċ
S
QD
GLYLG µHµ
SLG µHµ
SLG µHµ
QLH
VWURQJLG µHµ
QDþZLF]HQLD
WLPHLG µHµ
*G\VSRMU]\V]
-HVWGRĤþ
KLG µHµ
PHWD
S
KLG µHµ
7DMQDZLDGRPRĤþ
VFULSW
WLWOH
KWPO
KHDG
276
Odpowiedź: wrócisz na poprzednią stronę, ale nie
cofniesz czasu
GRFXPHQW
0RİH
ERG\
MHVWHP
ĤPLHV]Q\
wiadomość, zapisz
Aby odczytać tajną odwołuje się każde
o
reg
któ
element, do
z jego zawartość!
z tych wywołań, ora
HPLG µHµ
F]ĐVWR
GLYLG µHµ
document.getElementById(”e8”)
document.getElementById(”e9”)
document.getElementById(”e18”)
document.getElementById(”e6”)
document.getElementById(”e13”)
document.getElementById(”e12”)
document.getElementById(”e16”)
document.getElementById(”e2”)
VWURQJLG µHµ
ĤPLHMĐ
OLLG µHµ
Oto DOM, w którym ukryliśmy tajną
wiadomość. Przetwórz poniższy kod, aby ją
odczytać! Odpowiedź zapisaliśmy do góry
nogami u dołu strony.
SR]\FMĐ
XO
OLLG µHµ
Zaostrz ołówek
Interakcja ze stronami WWW
Jazda próbna wokół planet
Już wiesz, jak używać metody document.getElementById, by odwoływać się do
wybranego elementu strony oraz jak stosować właściwość innerHTML, by zmieniać
zawartość elementu. A teraz spróbuj to zrobić naprawdę.
Poniżej przedstawiliśmy kod HTML strony z planetami. Wewnątrz elementu
KHDG! znajduje się element <script>, w którym umieścimy kod oraz trzy nagłówki
i akapity — po jednym dla każdej planety: zielonej, czerwonej i niebieskiej. Jeśli
jeszcze tego nie zrobiłeś, powinieneś wpisać kod HTML strony oraz kod JavaScript,
który zmodyfikuje jej DOM.
<!doctype html>
To jest nasz element
<script> z kodem.
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
Podobnie jak wcześniej,
pobieramy element <p>
o identyfikatorze „greenplanet”
i zmieniamy jego zawartość.
<title>Planety</title>
<script>
var planet = document.getElementById(”greenplanet”);
planet.innerHTML = ”Alarm czerwony: rozpoczÚto ostrzaï z fazeröw!”;
</script>
A to jest element <p>,
którego zawartość chcesz
zmienić w skrypcie.
</head>
<body>
<h1>Zielona planeta</h1>
<p id=”greenplanet”>Wszystko jest w porzÈdku.</p>
<h1>Czerwona planeta</h1>
<p id=”redplanet”>Nie ma tu nic ciekawego.</p>
<h1>Niebieska planeta</h1>
<p id=”blueplanet”>Wszystkie systemy sprawne.</p>
</body>
</html>
Kiedy już to wpiszesz, zapisz plik i wczytaj go do przeglądarki,
aby na własne oczy zobaczyć magię, która dzieje się na zielonej
planecie.
HALO! Huston, mamy problem
— na zielonej planecie wciąż
widoczny jest tekst „Wszystko
jest w porządku.”. Gdzie tkwi
błąd?
jesteś tutaj
277
Rozmyślania o tym, dlaczego kod nie działa
Trzy razy sprawdziłem kod
strony i skrypt, ale ta strona
nie działa i już. Nie widzę, by
po wyświetleniu w przeglądarce
cokolwiek się na niej zmieniało.
A faktycznie… Zapomnieliśmy wspomnieć
o jednej rzeczy
Kiedy operujemy na DOM, bardzo ważne jest, by kod był
wykonywany wyłącznie po zakończeniu wczytywania strony.
Jeśli o tym zapomnisz, istnieją spore szanse, że w momencie
wykonywania kodu DOM jeszcze nie będzie utworzony.
Zastanówmy się nad tym, co się stało: umieściliśmy kod
w elemencie KHDG!, zatem zostanie on wykonany, zanim jeszcze
przeglądarka odczyta i przeanalizuje dalszą część strony. To duży
problem, gdyż w momencie jego wykonywania obiekt elementu
o identyfikatorze „greenplanet” jeszcze nie istnieje.
A zatem, co się właściwie dzieje w kodzie? Otóż wywołanie
metody getElementById zamiast obiektu, na którym nam zależy,
zwraca null, co powoduje wystąpienie błędu; jednocześnie
przeglądarka świadoma, że coś takiego może się zdarzyć,
kontynuuje działanie i wyświetla dalszą część strony, nie
zmieniając zawartości elementu zielonej planety.
A w jaki sposób można rozwiązać ten problem? Można przenieść
kod na sam koniec elementu ERG\!; istnieje jednak jeszcze
bardziej niezawodny sposób dający pewność, że kod zostanie
wykonany w odpowiednim momencie — sposób, by powiedzieć
przeglądarce: „Słuchaj, wykonaj ten kod dopiero wtedy, gdy
cała strona zostanie wczytana, a DOM będzie już utworzony”.
Poznasz go na następnej stronie.
Po wczytaniu strony
wyświetl okno
konsoli, w większości
przeglądarek zostanie
w nim wyświetlony
komunikat o błędzie.
Konsola to świetne
narzędzie do
testowania skryptów.
.RQVROD-DYD6FULSW
Nieprzechwycony błąd TypeError:
Nie można ustawić właściwości
'innerHTML' obiektu null
278
Rozdział 6.
Uncaught TypeError:
Cannot set property
'innerHTML' of null
Interakcja ze stronami WWW
Nawet nie myśl o uruchamianiu mojego kodu,
zanim strona nie zostanie w całości wczytana
No dobrze, ale jak? Oprócz przeniesienia kodu na sam koniec elementu
ERG\!, istnieje także inny sposób, by to zapewnić; a wiele osób uważa, że jest
bardziej przejrzysty. Polega on na zastosowaniu odpowiedniego kodu.
A oto co powinieneś zrobić: najpierw musisz napisać funkcję zawierającą kod,
który ma być wykonany dopiero po zakończeniu wczytywania strony. Kiedy już
to zrobisz, przypisz tę funkcję do właściwości onload obiektu ZLQGRZ.
Window jest wbudowanym
obiektem języka JavaScript.
Reprezentuje okno
przeglądarki.
Jak to działa? Obiekt ZLQGRZ wywoła dowolną funkcję, którą zapiszesz w jego
właściwości onload, jednak zrobi to dopiero po zakończeniu wczytywania
strony. Możesz zatem podziękować projektantom obiektu ZLQGRZ za
to, że zapewnili możliwość określania kodu, który zostanie wykonany po
zakończeniu wczytywania strony. Wypróbuj poniższy kod.
Funkcja
<script>
function init() {
Najpierw napisz funkcję
o nazwie init i umieść
w niej swój istniejący kod.
może nosić dowolną
nazwę, jednak zwyczajowo
i często nadaje się jej
nazwę init.
var planet = document.getElementById(”greenplanet”);
To jest nasz wcześniejszy
kod, jednak teraz
umieściliśmy go wewnątrz
funkcji init.
planet.innerHTML = ”Alarm czerwony: rozpoczÚto ostrzaï z fazeröw!”;
}
window.onload = init;
</script>
W tym miejscu przypisujemy funkcję init
właściwości window.onload. Koniecznie upewnij
się, że za nazwą funkcji nie zapisałeś nawiasów!
Nie chodzi o wywołanie funkcji — we właściwości
window.onload zapisujemy samą funkcję.
Spróbuj jeszcze raz
Po dodaniu do strony nowej funkcji init oraz określeniu
wartości właściwości ZLQGRZRQORDG spróbuj odświeżyć stronę
w przeglądarce. Tym razem przeglądarka wczyta całą stronę,
utworzy jej pełny DOM i dopiero potem wykona funkcję init.
O to chodziło — teraz pod
zieloną planetą pojawił się
alarm czerwony, dokładnie
tak jak chcieliśmy.
jesteś tutaj
279
Przeglądarka i procedury obsługi zdarzeń
Ty mówisz: „przeglądarka”,
ja mówię: „wywołanie zwrotne”
Zastanówmy się nieco dokładniej nad sposobem działania właściwości onload, gdyż korzysta
ona z popularnego wzorca, który jest bardzo często wykorzystywany w języku JavaScript.
Załóżmy, że niebawem ma zajść jakieś duże i ważne wydarzenie, o którym koniecznie
chcemy wiedzieć. Załóżmy, że chodzi nam o wydarzenie „strona została w całości wczytana”.
Powszechnie stosowanym sposobem radzenia sobie z takimi sytuacjami jest wykorzystanie
wywołania zwrotnego (ang. callback), nazywanego także procedurą obsługi zdarzeń.
Wywołania zwrotne działają w następujący sposób: najpierw do obiektu, który „wie”
o zdarzeniu, przekazujemy funkcję. Kiedy zdarzenie zajdzie, obiekt przekaże o tym informację
zwrotną, wywołując podaną funkcję. Przekonasz się, że wzorzec ten jest w języku JavaScript
używany powszechnie do obsługi bardzo wielu zdarzeń.
Hej,
przeglądarko, zanim
zrobię, co do mnie należy,
poczekam, aż zakończysz
wczytywanie strony.
Przeglądarka,
a bardziej konkretnie
obiekt window.
Czemu tak
siedzisz i się obijasz…
Dalej, przekaż mi funkcję
zwrotną, a ja ją wywołam,
kiedy skończę.
Nie
ma sprawy…
Oto ona, nazywa
się init.
280
Rozdział 6.
Wywołanie zwrotne
albo, jeśli wolisz,
procedura obsługi
zdarzeń.
Interakcja ze stronami WWW
Mam ją! Zapisałem
ją we właściwości onload,
więc na pewno o niej nie
zapomnę…
1LHFRSyĮQLHM
Ech… To była ciężka
robota, ale w końcu udało się
wczytać stronę. No dobra, zobaczmy
teraz… Muszę wywołać dowolną funkcję,
do której odwołuje się moja właściwość
onload. Dobrze, mam ją — to funkcja
init. Wywołujemy init!
0HWRGDLQLWMHVWZ\ZRâ\ZDQDL]RVWDMHZ\NRQDQD
Hej,
przeglądarko… Dzięki,
że nie zapomniałaś wywołać
funkcji init. Wszystko poszło
jak po maśle!
A kiedy funkcja init zostanie
wywołana, zauważymy, że
zawartość strony uległa
zmianie!
jesteś tutaj
281
Rozmyślania o funkcjach i procedurach obsługi zdarzeń
Ciekawe. Mogę zatem
używać funkcji, by gromadzić kod,
który zostanie wykonany później, kiedy
zajdzie jakieś zdarzenie. A jakie inne
rodzaje zdarzeń mogę w taki sposób
obsługiwać?
Masz rację, istnieje wiele rodzajów zdarzeń,
które możesz obsługiwać, jeśli tylko będziesz
nimi zainteresowany. Niektóre zdarzenia, takie jak
onload, są generowane przez przeglądarkę, natomiast inne
pojawiają się podczas interakcji użytkownika ze stroną lub
nawet są generowane przez sam kod JavaScript.
Zobaczyłeś już przykład zdarzenia „strona została w całości
wczytana”, które można obsługiwać, określając wartość
właściwości onload obiektu ZLQGRZ. Można także tworzyć
procedury obsługi zdarzeń obsługujące takie zdarzenia
jak „wywołaj tę funkcję po pięciu sekundach” lub „usługa
sieciowa przesłała jakieś dane, musimy teraz z nimi coś
zrobić” albo „użytkownik kliknął przycisk i musimy teraz
zrobić coś z informacjami podanymi w formularzu”. Takich
zdarzeń jest całkiem sporo. Wszystkie te zdarzenia są
powszechnie używane podczas tworzenia stron, które
działają raczej jak aplikacje, a nie statyczne dokumenty
(bo w końcu, kto by ich chciał). To, co przedstawiliśmy,
to jedynie pobieżny rzut oka na zdarzenia i procedury
ich obsługi, jednak dalej w tej książce poświęcimy im
znacznie więcej czasu i uwagi, gdyż odgrywają one bardzo
ważną rolę w rozwiązaniach tworzonych z użyciem języka
JavaScript.
282
Rozdział 6.
Interakcja ze stronami WWW
Zaostrz ołówek
<!doctype html>
To jest kod
HTML strony.
Poniżej przedstawiliśmy kod HTML listy odtwarzania, z tym że na liście
nie ma żadnych piosenek. Twoim zadaniem jest uzupełnienie kodu
JavaScript tak, by dodać piosenki do listy. Uzupełnij puste miejsca kodem
JavaScript, który wykona zadanie. Sprawdź rozwiązanie zadania, które
podaliśmy pod koniec rozdziału.
<html lang=”pl”>
<head>
<title>Moja lista odtwarzania</title>
<meta charset=”utf-8”>
<script>
o kod powinien
A to jest nasz skrypt. Jeg eszczoną
umi
k
ene
pios
ę
list
wypełnić
w elemencie <ul>.
____________ addSongs() {
var song1 = document.______________(”________”);
Uzupełnij puste miejsca
odpowiednim kodem, który
wyświetli listę utworów.
var _____ = _______________________(”________”);
var _____ = ________.getElementById(”________”);
________.innerHTML = ”Smutne zamszowe ïañcuchy, Elvisa Pagelya”;
__________________ = ”Wielkie obiekty w ogniu, -erryijego -SON Lewisa”;
song3.____________ = ”Pierwszy wiersz kodu, -ohnnyijego -avaScripta”;
}
window.___________ = ____________;
</script>
</head>
<body>
<h1>Moja niesamowita lista odtwarzania</h1>
<ul id=”playlist”>
<li id=”song1”></li>
To jest pusta lista piosenek.
Powyższy kod JavaScript powinien
określić zawartość każdego
z pustych elementów <li>.
<li id=”song2”></li>
<li id=”song3”></li>
</ul>
</body>
Kiedy już uruchomisz
skrypt, wygenerowana
przez niego strona
powinna wyglądać tak.
</html>
jesteś tutaj
283
Modyfikowanie stylów strony przy użyciu kodu JavaScript
Czemu mielibyśmy na tym poprzestać? Pójdźmy jeszcze dalej!
Zastanówmy się przez chwilę nad tym, co właśnie udało się zrobić. Wziąłeś
statyczną stronę WWW i dynamicznie zmieniłeś zawartość jednego z jej
elementów, używając kodu JavaScript. Mogłoby się wydawać, że to prosta
czynność, lecz w rzeczywistości to jedynie pierwszy krok na drodze do
tworzenia prawdziwie interaktywnych stron.
y
ą nasz cel, któr
Taki jest zreszt emy w rozdziale 8.
uj
liz
ea
w pełni zr
A teraz wykonajmy drugi krok: skoro już wiesz, jak odwoływać się do elementów
w DOM, spróbujmy programowo określić wartość jednego z atrybutów.
A niby czemu to ma być interesujące? Cóż, kontynuujmy przykład naszej
prostej strony z planetami. Skoro zmieniliśmy tekst akapitu na informację
o alarmie czerwonym, możemy jednocześnie zmienić jego kolor na czerwony.
Bez wątpienia wyraźnie wzmocniłoby to siłę przekazu naszego komunikatu.
Poniżej opisaliśmy, jak to zrobić.
1
Zdefiniujemy regułę CSS o nazwie ”redtext”, która
ustawi kolor tekstu w akapicie na czerwony. W ten sposób
w dowolnym akapicie, w którym użyjemy tej klasy, tekst
zostanie wyświetlony na czerwono.
2
Następnie dodamy kod, który zastosuje klasę ”redtext”
w akapicie ”greenplanet”.
I to wszystko. Pozostaje jedynie dowiedzieć się, jak można określać
wartości atrybutów elementu, a potem możemy zabrać się za
napisanie kodu.
Zaostrz ołówek
Wypadałoby chyba poćwiczyć także drugą półkulę Twojego mózgu. Będziemy potrzebować
reguły CSS o nazwie ”redtext”, która zmieni kolor tekstu w akapicie ”greenplanet”
na czerwony. Nie przejmuj się, jeśli już dosyć dawno nie bawiłeś się pisaniem reguł CSS i tak
warto spróbować. Jeśli możesz to zrobić z zamkniętymi oczami, tym lepiej. W każdym razie
odpowiedź znajdziesz pod koniec rozdziału.
284
Rozdział 6.
Interakcja ze stronami WWW
Jak ustawiać atrybuty przy użyciu metody setAttribute?
Obiekty elementów dysponują metodą o nazwie VHW$WWULEXWH, której można używać do
określania wartości atrybutów elementów HTML. Oto wywołanie tej metody.
Bierzemy nasz obiekt elementu.
Zauważ, że jeśli atrybut
nie istnieje, w elemencie
zostanie utworzony nowy
atrybut o podanej nazwie.
planet.setAttribute("class", "redtext");
Po czym używamy metody setAttribute,
Metoda ta wymaga podania dwóch … oraz wartości, którą
by dodać nowy atrybut bądź też
zmienić wartość istniejącego atrybutu. argumentów: nazwy atrybutu,
chcemy temu atrybutowi
którego wartość chcemy ustawić
przypisać.
lub zmienić…
Przy użyciu metody VHW$WWULEXWH można zmienić wartość dowolnego istniejącego
atrybutu bądź też, jeśli atrybut jeszcze nie istnieje, można go dodać do elementu.
W ramach przykładu sprawdźmy, jaki wpływ na DOM będzie miało wykonanie
powyższej instrukcji.
GRFXPHQW
Przed…
KWPO
Oto nasz element przed
wywołaniem metody
setAttribute. Zwróć uwagę,
że element ma już jeden
atrybut, czyli id.
KHDG
SLG µJUHHQSODQHWµ
:V]\VWNRMHVW
ZSRU]ċGNX
ERG\
SLG µUHGSODQHWµ
1LHPDWXQLF
FLHNDZHJR
SLG µEOXHSODQHWµ
:V]\VWNLH
V\VWHP\
VSUDZQH
Po…
A to jest nasz element po
wywołaniu metody setAttribute.
GRFXPHQW
KWPO
KHDG
Pamiętaj, że wywołując
metodę setAttribute,
zmieniamy element obiektu
w DOM, co z kolei powoduje
natychmiastową zmianę tego,
co prezentuje przeglądarka.
SLG µJUHHQSODQHWµFODVV UHGWH[W
:V]\VWNRMHVW
ZSRU]ċGNX
ERG\
SLG µUHGSODQHWµ
1LHPDWXQLF
FLHNDZHJR
SLG µEOXHSODQHWµ
:V]\VWNLH
V\VWHP\
VSUDZQH
jesteś tutaj
285
Pobieranie wartości atrybutu
Więcej zabawy z atrybutami
(wartości atrybutów można także POBIERAĆ)
Musisz dowiedzieć się, jaką wartość ma jakiś atrybut
elementu? Nie ma problemu — dysponujemy metodą
JHW$WWULEXWH, którą można wywołać, by pobrać wartość
wybranego atrybutu elementu HTML.
Pobierz odwołanie do elementu przy użyciu
metody getElementById, a następnie użyj
go do wywołania metody getAttribute.
W jej wywołaniu
przekaż nazwę
atrybutu, którego
wartość chcesz pobrać.
var scoop = document.getElementById(”raspberry”);
var altText = scoop.getAttribute(”alt”);
console.log(”W oknie konsoli nie mogÚ zobaczyÊ obrazka,”);
console.log(” ale powiedziano mi, ĝe wyglÈda jak: ” + altText);
Co się stanie, jeśli atrybut nie będzie istnieć w elemencie?
A pamiętasz, co się dzieje, kiedy wywołasz metodę getElementyById,
przekazując do niej identyfikator elementu, którego nie ma w DOM? Metoda
zwraca null. Dokładnie to samo dzieje się w przypadku korzystania z metody
JHW$WWULEXWH. Jeśli podany atrybut nie istnieje, jej wywołanie zwraca null.
Poniżej pokazaliśmy, jak można sprawdzać wystąpienie takiej sytuacji.
Sprawdzamy, by upewnić się,
że zwrócony wynik jest
wartością atrybutu.
var scoop = document.getElementById(”raspberry”);
var altText = scoop.getAttribute(”alt”);
if (altText == null) {
Jeśli to nie jest wartość
atrybutu, wyświetlamy
ten komunikat…
} else {
… a jeśli metoda zwróciła
console.log(”W oknie konsoli nie mogÚ zobaczyÊ obrazka,”);
wartość atrybutu,
console.log(” ale powiedziano mi, ĝe wyglÈda jak: ” + altText);
możemy wyświetlić tekst
w
oknie konsoli.
}
console.log(”Ups... -ak widzÚ atrybut alt nie istnieje.”);
cać null
Nie zapominaj, że także metoda getElementById może zwra
Za każdym razem, gdy o coś prosisz, musisz sprawdzić,
jest tym, czego oczekiwałeś…
czy to, co otrzymałeś,
UyFLþZDUWRĤþnullMHĤOLSRGDQ\
:\ZRâDQLHPHWRG\getElementByIdPRİH]Z
0$]DWHPDE\SRVWĐSRZDþ]JRGQLH
LGHQW\ILNDWRUHOHPHQWXQLHMHVWGRVWĐSQ\Z'2
HOHPHQWyZEĐG]LHV]FKFLDâVSUDZG]Dþ
]QDMOHSV]\PLSUDNW\NDPLWDNİHSRSRELHUDQLX
JOLE\ĤP\VWRVRZDþVLĐGRWHM
HİPR
DPLW
F]\QLH]RVWDâD]ZUyFRQDZDUWRĤþnull6
E\âDE\RVWURQGâXİV]D
]DVDG\MHGQDNZWDNLPSU]\SDGNXWDNVLċİND
286
Rozdział 6.
Interakcja ze stronami WWW
Tymczasem w zagrodzie systemie słonecznym
Nadszedł czas, by nadać ostateczną postać naszej stronie z planetami i przetestować ją ostatni raz.
<!doctype html>
, CSS
Oto pełne kody HTML planetami.
i JavaScript strony z
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Planety</title>
<style>
.redtext { color: red; }
Klasę „redtext” umieściliśmy tutaj, dzięki czemu,
kiedy zapiszemy jej nazwę w atrybucie class, tekst
akapitu zostanie wyświetlony na czerwono.
</style>
<script>
function init() {
var planet = document.getElementById(”greenplanet”);
planet.innerHTML = ”Alarm czerwony: rozpoczÚto ostrzaï z fazeröw!”;
planet.setAttribute(”class”, ”redtext”);
}
window.onload = init;
</script>
</head>
<body>
W ramach przypomnienia:
pobieramy element
greenplanet i zapisujemy
go w zmiennej planet.
Następnie zmieniamy jego
zawartość, a na samym
końcu dodajemy atrybut
class, który sprawi, że
zawartość elementu
zostanie wyświetlona
na czerwono.
Funkcja init będzie wywoływana
dopiero po zakończeniu
wczytywania strony!
<h1>Zielona planeta</h1>
<p id=”greenplanet”>Wszystko jest w porzÈdku.</p>
<h1>Czerwona planeta</h1>
<p id=”redplanet”>Nie ma tu nic ciekawego.</p>
<h1>Niebieska planeta</h1>
<p id=”blueplanet”>Wszystkie systemy sprawne.</p>
</body>
</html>
Ostatnia jazda próbna z widokiem na planety
Wczytaj tę nową wersję strony w przeglądarce, a przekonasz
się, że zielona planeta znalazła się pod ostrzałem z fazerów.
Zobaczysz także, że komunikat został wyświetlony na czerwono,
dzięki czemu na pewno go nie przegapimy!
jesteś tutaj
287
Co można zrobić przy użyciu DOM
Do czego jeszcze może się przydać DOM?
DOM potrafi znacznie więcej niż to, co mieliśmy okazję zobaczyć,
a niektóre z jego możliwości przedstawimy jeszcze dalej w tej książce;
na razie jednak przyjrzymy się im bardzo pobieżnie, tak byś zachował je
w jakimś zakamarku swojego umysłu.
Pobranie elementu z DOM.
Odszukanie
i pobranie jednego
lub kilku elementów
z DOM.
IRUP
ODEHO
LQSXW
Tworzenie
nowych
elementów…
LQSXW
OL
Tworzenie i dodawanie elementów do DOM.
XO
… i dodanie ich
do DOM poprzez
dołączenie
do innego
elementu już
umieszczonego
w drzewie.
OL
0RİQDWDNİHWZRU]\þQRZHHOHPHQW\RUD]
GRGDZDþMHGR'202F]\ZLĤFLHZV]HONLH]PLDQ\
ZSURZDG]DQHZ'20EĐGċQDW\FKPLDVWZLGRF]QH
JG\İSU]HJOċGDUNDZ\ĤZLHWODREUD]'20 FRMHVW
EDUG]RGREUH OL
Usuwanie elementów z DOM.
Usuwanie
istniejących
elementów.
0RİQDWDNİHXVXZDþHOHPHQW\]'20:W\PFHOX
QDOHİ\RGQDOHĮþURG]LFDHOHPHQWXDQDVWĐSQLH
XVXQċþMHJRZ\EUDQ\HOHPHQWSRWRPQ\7DNİHZW\P
SU]\SDGNXNLHG\W\ONRHOHPHQW]RVWDQLHXVXQLĐW\
]'20]QLNQLHWDNİH]HVWURQ\SUH]HQWRZDQHM
ZSU]HJOċGDUFH
XO
OL
Pobranie
wszystkich
elementów
potomnych
danego
elementu…
OL
XOLG µOLVWµ
OL
OL
… pobranie elementów
sąsiadujących…
288
2F]\ZLĤFLHWĐPRİOLZRĤþMXİ]QDV]JG\İXİ\ZDOLĤP\
Z\ZRâDQLDdocument.getElementById-HGQDNLVWQLHMċ
WDNİHLQQHVSRVRE\SRELHUDQLDHOHPHQWyZ2ND]XMH
VLĐİHPRİQDGRWHJRFHOXXİ\ZDþQD]Z]QDF]QLNyZ
QD]ZNODVRUD]DWU\EXWyZLSRELHUDþQLHW\ONR
SRMHG\QF]HHOHPHQW\OHF]WDNİHFDâHLFKJUXS\
QSZV]\VWNLHHOHPHQW\QDOHİċFHGRNODV\”on_sale” 0RİQDWDNİHSRELHUDþZDUWRĤFLZSLVDQHSU]H]
Xİ\WNRZQLNDZSRODFKIRUPXODU]\QSZV]\VWNLH
âDĚFXFK\]QDNyZZSLVDQHZSRODFKW\SXinput.
Rozdział 6.
…bądź
pobranie
elementu
rodzica...
Poruszanie siĊ So elementach w DOM.
.LHG\MXİSRELHU]HP\RGZRâDQLHGRHOHPHQWX
PRİHP\RGV]XNDþZV]\VWNLHMHJRHOHPHQW\
SRWRPQHPRİHP\SREUDþHOHPHQW\VċVLDGXMċFH
]QLP HOHPHQW\QDW\PVDP\PSR]LRPLH'20 RUD]HOHPHQWMHJRURG]LFD'20PDVWUXNWXUĐ
SU]\SRPLQDMċFċQLHFRGU]HZRJHQHDORJLF]QH
Interakcja ze stronami WWW
CELNE SPOSTRZEŻENIA
Q
Obiektowy model dokumentu, w skrócie
DOM, jest wewnętrzną reprezentacją
strony używaną przez przeglądarkę.
Q
Przeglądarka tworzy DOM dla danej strony
podczas wczytywania i analizowania jej
kodu.
Q
W kodzie JavaScript dostęp do DOM
zapewnia obiekt document.
Q
Obiekt document posiada właściwości
oraz metody, których możemy używać,
by uzyskiwać dostęp do DOM oraz
wprowadzać w nim zmiany.
Q
Metoda document.getElementById
pobiera z DOM element o podanym
identyfikatorze.
Q
Metoda document.getElementById
zwraca obiekt elementu reprezentujący
konkretny element strony.
Q
Q
Q
Obiekt elementu posiada właściwości
i metody, których możemy używać do
odczytu oraz modyfikacji zawartości tego
elementu.
Właściwość innerHTML zawiera zarówno
tekst, jak i kod HTML stanowiące zawartość
danego elementu.
Zawartość elementu można zmienić,
modyfikując wartość właściwości
innerHTML.
Q
Kiedy modyfikujemy zawartość
elementu, zmieniając wartość właściwości
innerHTML, wprowadzane zmiany
będą natychmiast widoczne w oknie
przeglądarki.
Q
Wartości atrybutów elementu
można pobierać przy użyciu metody
getAttribute.
Q
Wartość atrybutu elementu można ustawić
za pomocą metody setAttribute.
Q
Jeśli kod JavaScript zapiszesz w elemencie
<script> umieszczonym wewnątrz
elementu KHDG!, musisz się upewnić,
że nie będzie on próbował modyfikować
DOM przed zakończeniem wczytywania
strony.
Q
Możesz użyć właściwości onload obiektu
ZLQGRZ, by określić procedurę obsługi
zdarzeń, nazywaną także wywołaniem
zwrotnym, czyli funkcję obsługującą
zdarzenie.
Q
Procedura obsługi zdarzenia określona
przy użyciu właściwości onload zostanie
wywołana po zakończeniu wczytywania
strony.
Q
Istnieje wiele różnych rodzajów zdarzeń,
które można obsługiwać w języku
JavaScript przy użyciu procedur obsługi
zdarzeń.
jesteś tutaj
289
Rozwiązanie ćwiczenia
<!doctype html>
<html lang=”pl”>
BĄDŹ przeglądarką.
Rozwiązanie
<head>
<meta charset=”utf-8”>
Twoim zadaniem jest wcielić
się w rolę przeglądarki. Masz
przeanalizować dokument
HTML i na podstawie jego
zawartości zbudować własny
DOM. Przeanalizuj kod HTML
przedstawiony z prawej strony,
a DOM narysuj poniżej. Trochę
już narysowaliśmy za Ciebie.
<title>Filmy</title>
</head>
<body>
<h1>Godziny seansöw</h1>
<h2 id=”movie1”>Plan dziewiÚÊ z kosmosu</h2>
<p>Seanse rozpoczynajÈ siÚ o 15:00 i 19:00.
<span>
Specjalny seans rozpocznie siÚ dziĂ o <em>pöïnocy</em>!
</span>
</p>
<h2 id=”movie2”>Zakazana planeta</h2>
<p>Seanse rozpoczynajÈ siÚ o 17:00 i 21:00.</p>
</body>
</html>
GRFXPHQW
To jest nasza
wersja DOM.
KWPO
KHDG
PHWD
ERG\
WLWOH
K
KLG µPRYLHµ
S
VSDQ
HP
290
Rozdział 6.
KLG µPRYLHµ
S
Interakcja ze stronami WWW
Zaostrz ołówek
Rozwiązanie
<!doctype html>
To jest kod HTML
strony.
Poniżej przedstawiliśmy kod HTML listy odtwarzania, z tym że na liście
nie ma żadnych piosenek. Twoim zadaniem jest uzupełnienie kodu
JavaScript tak, by dodać piosenki do listy. Uzupełnij puste miejsca kodem
JavaScript, który wykona zadanie. Sprawdź rozwiązanie zadania, które
podaliśmy pod koniec rozdziału.
<html lang=”pl”>
<head>
<title>Moja lista odtwarzania</title>
<meta charset=”utf-8”>
<script>
o kod powinien
A to jest nasz skrypt. Jeg eszczoną
umi
k
ene
pios
ę
list
wypełnić
w elemencie <ul>.
function
____________
addSongs() {
var song1 = document.______________(”________”);
getElementById
song1
Uzupełnij puste miejsca
odpowiednim kodem, który
wyświetli listę utworów.
song2 = _______________________(”________”);
document.getElementById
song2
var _____
song3 = ________.getElementById(”________”);
document
song3
var _____
song1
________.innerHTML
= ”Smutne zamszowe ïañcuchy, Elvisa Pagelya”;
__________________
song2.innerHTML = ”Wielkie obiekty w ogniu, -erryijego -SON Lewisa”;
innerHTML = ”Pierwszy wiersz kodu, -ohnnyijego -avaScripta”;
song3.____________
}
onload
addSongs
window.___________
= ____________;
</script>
</head>
<body>
<h1>Moja niesamowita lista odtwarzania</h1>
<ul id=”playlist”>
<li id=”song1”></li>
To jest pusta lista piosenek.
Powyższy kod JavaScript powinien
określić zawartość każdego
z pustych elementów <li>.
<li id=”song2”></li>
<li id=”song3”></li>
</ul>
</body>
A tak wygląda nasza
strona, kiedy zostanie
wyświetlona
w przeglądarce.
</html>
jesteś tutaj
291
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Wypadałoby chyba poćwiczyć także drugą półkulę Twojego mózgu. Będziemy potrzebować reguły CSS
o nazwie ”redtext”, która zmieni kolor tekstu w akapicie ”greenplanet” na czerwony. Nie przejmuj
się, jeśli już dosyć dawno nie bawiłeś się pisaniem reguł CSS i tak warto spróbować. Jeśli możesz to
zrobić z zamkniętymi oczami, tym lepiej. Oto nasze rozwiązanie:
.redtext { color: red; }
292
Rozdział 6.
7.7\S\UöZQRĂÊNRQZHUVMHLFDï\WHQMD]]
Poważne typy
Nadszedł czas, by poważnie przyjrzeć się typom. Jedną ze wspaniałych
cech JavaScriptu jest to, że można w nim zrobić całkiem dużo bez jego szczegółowej
znajomości. Aby jednak perfekcyjnie opanować język, dostać awans i zacząć robić
to, co naprawdę chcesz robić w życiu, musisz się zaprzyjaźnić z typami. Czy pamiętasz,
co już dawno, na samym początku książki powiedzieliśmy na temat JavaScriptu? Że nie
może się poszczycić rozpuszczoną, popularną, akademicką definicją? No cóż… To prawda,
jednak życie akademickie nie zatrzymało ani Steve’a Jobsa ani Billa Gatesa, nie zatrzymało
także języka JavaScript. Oznacza to jednak, że JavaScript nie ma… hm… doskonale
przemyślanego systemu typów i znajdziemy w nim sporo dziwactw. Nie obawiaj się
jednak — w tym rozdziale dokładnie wszystko wyjaśnimy, dzięki czemu już niebawem
nauczysz się unikać tych wszystkich zawstydzających problemów z typami.
to jest nowy rozdział
293
Rozmyślania o typach
Gdzieś tam jest ukryta prawda…
Teraz, kiedy już zdobyłeś spore doświadczenie w korzystaniu z typów
dostępnych w języku JavaScript — a jak wiesz, są to typy proste,
takie jak liczby, łańcuchy znaków oraz wartości logiczne, jak również
obiekty i to zarówno te dostarczane przez sam język (takie jak Math)
lub przeglądarkę (jak obiekt document), jak i te pisane przez Ciebie —
czyż nie wygrzewasz się w promieniach prostego, potężnego i spójnego
systemu typów JavaScriptu?
Proste typy niskiego poziomu,
służące do reprezentacji liczb,
łańcuchów znaków i wartości
logicznych.
Obiekty wysokiego poziomu,
używane do reprezentacji rzeczy
i zagadnień należących do dziedziny
rozwiązywanego problemu.
Obiekty
Typy proste
.YM_WOX^
Liczby
AK\^YľMS
logiczne
ĦKĩM_MRc
znaków
Element
7K^R
Car
Dog
Song
Te typy są dost
przez sam języ arczane
k JavaScript.
JavaScript udostępnia także wiele użytecznych
obiektów, jednak można też tworzyć własne lub
korzystać z obiektów tworzonych przez innych
programistów.
No bo w końcu czego innego mógłbyś się spodziewać po języku, który
w Sieciowicach został wybrany na oficjalny język programowania? Jeśli
byłeś jedynie skromnym autorem prostych skryptów, możesz pomyśleć
o wyprostowaniu pleców, zrobieniu sobie martini z Sieciowic i dobrze
zasłużonej przerwy…
Jednak nie jesteś skromnym autorem skryptów i wiesz, że czegoś Ci
brakuje. Masz to przeszywające uczucie, że za drewnianymi płotami
Sieciowic dzieje się coś bardzo dziwnego. Widziałeś raporty donoszące,
że widziano łańcuchy znaków zachowujące się jak obiekty, czytałeś na
blogach wpisy o (prawdopodobnie radioaktywnym) typie null, słyszałeś
też plotki o tym, że interpreter JavaScriptu robił ostatnio jakieś dziwne
rzeczy z konwersjami typów. Co to wszystko oznacza? Tego nie wiemy,
lecz gdzieś tam jest ukryta prawda i w tym rozdziale zamierzamy ją
odkryć… Może się jednak okazać, że wywróci ona do góry nogami
Twoje poglądy na temat tego, co jest prawdą, a co fałszem.
294
Rozdział 7.
Typy, równość, konwersje i cały ten jazz
L. PMHVWHP"
Grupa wartości JavaScript oraz niezaproszonych gości, przebrana w kostiumy,
bawi się w grę towarzyską o nazwie „Zgadnij, kim jestem”. Wszyscy podają
podpowiedź, a Ty na jej podstawie starasz się powiedzieć, kim jest dana
osoba. Możesz założyć, że zawsze mówią o sobie prawdę. Narysuj strzałkę od
zdania do nazwy uczestnika zabawy. Dla ułatwienia narysowaliśmy już jedną
strzałkę. Porównaj swoje odpowiedzi z naszymi, zamieszczonymi pod koniec
rozdziału.
Jeśli okaże się, że to ćwiczenie jest trochę trudne, możesz oszukać i zerknąć na
odpowiedzi.
Jestem zwracana przez funkcję, w której nie ma instrukcji return.
zero
pusty obiekt
Jestem wartością zmiennej, kiedy nie zostałam do niej przypisana.
null
Jestem wartością elementu, który nie istnieje w tablicy rzadkiej.
undefined
NaN
Jestem wartością nieistniejącej właściwości.
Infinity
Obszar 51
Jestem wartością usuniętej właściwości.
... _ _ _ ...
Jestem wartością, której nie można przypisać właściwości podczas
tworzenia obiektu.
{}
[]
jesteś tutaj
295
undefined
Uważaj, możesz natknąć się na undefined,
kiedy będziesz się tego najmniej spodziewać…
Jak mogłeś się już przekonać, zawsze wtedy, gdy coś idzie nie tak jak powinno —
kiedy potrzebujemy zmiennej, która nie została zainicjalizowana lub właściwości,
która nie istnieje (lub została usunięta), albo nieistniejącego elementu tablicy —
spotkamy się z wartością undefined.
Co to właściwie jest? To nic tajemniczego. Wartość tę możesz sobie wyobrazić
jako wartość przypisywaną zmiennym i właściwościom, których wartości sam nie
określiłeś (innymi słowy takim, które nie zostały zainicjalizowane).
Do czego wartość ta może się przydać? No cóż… undefined pozwala
sprawdzać, czy zmiennej (albo właściwości lub elementowi tablicy) została
przypisana wartość. Przyjrzyjmy się kilku przykładom; zaczniemy od zmiennych,
którym nie przypisano wartości.
var x;
Możesz sprawdzić, czy
zmienna, taka jak x, nie została
zdefiniowana. Wystarczy ją
porównać z wartością undefined.
if (x == undefined) {
Zwróć uwagę, że używamy tu wartości
undefined, której nie należy mylić
z łańcuchem znaków „undefined”.
// x nie zostaïa zdefiniowana! Musisz sobie z tym poradziÊ!
}
A tak to będzie wyglądało w przypadku właściwości obiektu.
var customer = {
name: "-oanna"
ość
Możesz sprawdzić, czy właściw
tarczy
nie została zdefiniowana. Wys ed.
efin
ją porównać z wartością und
};
if (customer.phoneNumber == undefined) {
// pobranie numeru telefonu klienta
}
Nie istnieją
głupie pytania
P: Kiedy powinienem sprawdzać, czy zmienna
(właściwość lub element tablicy) nie została zdefiniowana?
O
: To już będzie zależeć od Twojego kodu. Jeśli napiszesz go w taki
sposób, że w momencie wykonywania bloku kodu właściwość lub
zmienna mogą nie mieć wartości, sprawdzanie, czy są one równe
undefined, zapewni możliwość prawidłowej obsługi takiej sytuacji,
a nie kontynuowania obliczeń, w których będą wykorzystywane
niezdefiniowane wartości.
296
Rozdział 7.
P: Skoro undefined jest wartością, to czy ma ona jakiś typ?
O: Owszem, ma. Typem wartości undefined jest undefined.
Dlaczego? Logika naszego wytłumaczenia jest taka (prosimy,
byś nam trochę pomógł): nie jest to obiekt ani liczba, ani
łańcuch znaków, ani wartość logiczna, ani nic innego, co byłoby
zdefiniowane. A zatem, czemu nie moglibyśmy utworzyć
niezdefiniowanego typu? To jedna z tych dziwacznych „stref cienia”
języka JavaScript, które po prostu trzeba zaakceptować.
Typy, równość, konwersje i cały ten jazz
W laboratorium
:ODERUDWRULXPOXELP\UR]ELHUDüZV]\VWNRQDF]\QQLNLSLHUZV]H
]DJOąGDüSRGPDVNĊSU]\SDWU\ZDüVLĊLDQDOL]RZDüSRGáąF]Dü
QDU]ĊG]LDGLDJQRVW\F]QHLVSUDZG]DüFRVLĊQDSUDZGĊG]LHMH
']LĞ]DMPLHP\VLĊV\VWHPHPW\SyZMĊ]\ND-DYD6FULSW3U]\WHM
RND]MLXGDáRVLĊQDP]QDOHĨüQLHZLHONLHQDU]ĊG]LHGLDJQRVW\F]QH
GREDGDQLD]PLHQQ\FK²typeof$]DWHPXELHU]IDUWXFK]DáyĪ
RNXODU\RFKURQQHL]DF]QLM]QDPLSUDFĊZODERUDWRULXP
2SHUDWRUtypeofMHVWMHGQ\P]ZEXGRZDQ\FKRSHUDWRUyZMĊ]\ND
-DYD6FULSW0RĪQDJRXĪ\ZDüGRVSUDZG]DQLDW\SXSRGDQHJR
RSHUDQGX F]\OLWHJRF]HJRĞFRVSUDZG]DV]SU]\XĪ\FLX
RSHUDWRUD 3RQLĪHMSRND]DOLĞP\SU]\NáDGMHJR]DVWRVRZDQLD
var subject = ”To tylko ïañcuch znaköw”;
Operator typeof ma jeden operand,
a wynikiem jego działania jest typ
tego operandu.
var probe = typeof subject;
console.log(probe);
$WHUD]7ZRMDNROHM=DQRWXMGDQH
]QDVWĊSXMąF\FKHNVSHU\PHQWyZ
Konsola JavaScript
W tym przypadku typem operandu
jest „string”. Zwróć uwagę, że
wyniki działania operatora typeof
są łańcuchami znaków, takimi
jak „string”, „boolean”, „number”,
„object”, „undefined” itd.
var test1 = ”abcdef”;
var test2 = 123;
var test3 = true;
To są dane testowe
var test4 = {};
oraz same testy.
var test5 = [];
var test6;
var test7 = {”abcdef”: 123};
var test8 = [”abcdef”, 123];
function test9(){return ”abcdef”};
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);
string
Konsola JavaScript
Tu zapisz swoje odpowiedzi.
Czy coś Cię zaskoczyło?
jesteś tutaj
297
Rozmyślania o null
Pamiętam z rozdziału o DOM, że
gdy podany identyfikator nie istnieje,
metoda getElementById zwraca null,
a nie undefined. Czym zatem jest null
i dlaczego metoda getElementById zwraca
właśnie null, a nie undefined?
Tak, faktycznie tak jest i wprowadza to spore zamieszanie. Istnieje
wiele języków programowania, w których stosowane jest pojęcie „wartości,
która nie jest obiektem”. I wcale nie jest to zły pomysł. Przyjrzyjmy się
przykładowo metodzie getElementById. Metoda ta ma zwracać obiekt,
prawda? Co się zatem dzieje, kiedy nie może tego zrobić? W takim przypadku
chcielibyśmy zwrócić wartość, która byłaby odpowiednikiem komunikatu:
„Byłabym obiektem, gdyby wymagany obiekt istniał… Ale nie istnieje”.
I właśnie do tego służy wartość null.
Oprócz tego, można jawnie przypisać null do zmiennej lub właściwości.
var killerObjectSomeday = null;
A co oznacza przypisanie zmiennej wartości null? Może coś w stylu:
„Kiedyś planujemy w tej zmiennej zapisać obiekt, ale jeszcze go nie mamy”.
Jeśli drapiesz się po głowie i zastanawiasz, dlaczego nie użyto w tym
celu undefined, to wiedz, że nie jesteś osamotniony. Odpowiedź na to
pytanie pochodzi z czasów samych początków języka JavaScript. Chodziło
o to, by istniała jedna wartość stosowana w zmiennych, które nie zostały
zainicjalizowane, oraz druga, która oznaczałaby brak obiektu. Może to niezbyt
piękne i zapewne nieco nadmiarowe, jednak obecnie po prostu tak już jest.
Musisz zatem zapamiętać przeznaczenie każdej z tych wartości (undefined
oraz null) i wiedzieć, że wartość null najczęściej jest stosowana w sytuacjach,
gdzie powinien pojawić się obiekt, lecz nie można go utworzyć lub znaleźć,
natomiast wartość undefined jest najczęściej stosowana w przypadkach, gdy
zmienna nie została zainicjalizowana, gdy obiekt nie ma wybranej właściwości
lub interesującej nas wartości nie ma w tablicy.
Z powrotem w laboratorium
8SV«=DSRPQLHOLĞP\RnullZQDV]\FKHNVSHU\PHQWDFK
3RQLĪHM]DPLHĞFLOLĞP\EUDNXMąF\WHVW
Konsola JavaScript
var test10 = null;
console.log(typeof test10);
298
Rozdział 7.
Tutaj zapisz wyniki.
Typy, równość, konwersje i cały ten jazz
Jak używać null?
Istnieje bardzo wiele funkcji i metod zwracających obiekty i często będziesz chciał
upewnić się, że to, co otrzymałeś, jest pełnoprawnym obiektem, a nie wartością null,
bo zawsze się może zdarzyć, że funkcja nie była w stanie znaleźć lub utworzyć
odpowiedniego obiektu. Widziałeś już przykłady związane z operacjami na DOM,
gdzie takie testy są potrzebne.
var header = document.getElementById(”header”);
Poszukajmy kluczowego
elementu nagłówka…
if (header == null) {
// no nie, coĂ jest naprawdÚ ěle, jeĂli na stronie nie mam nagïöwka
}
Ups… Nie ma! Uwaga,
wszyscy mają opuścić okręt!
Powinieneś jednak pamiętać, że uzyskanie wartości null nie musi wcale oznaczać, że
dzieje się coś niedobrego. Może oznaczać, że jakiś element jeszcze nie istnieje i należy
go utworzyć albo że czegoś nie ma i można to zignorować. Załóżmy, że użytkownicy
witryny mogą otwierać i zamykać prezentowany na niej widżet z prognozą pogody.
Jeśli widżet jest otwarty, na stronie mamy dostęp do elementu <div> o identyfikatorze
”weatherDiv”, a jeśli nie jest, to nie ma także elementu. I nagle wartość null staje się
całkiem użyteczna.
Sprawdźmy, czy istnieje element
o identyfikatorze „weatherDiv”.
var weather = document.getElementById(”weatherDiv”);
if (weather != null) {
// tworzymy zawartoĂÊ elementu div z prognozÈ pogody
}
Jeśli wywołanie metody getElementById zwróci coś
innego niż null, będzie to znaczyło, że poszukiwany
element jest dostępny na stronie. W takim przypadku
możemy w nim utworzyć elegancki widżet z prognozą
pogody (prawdopodobnie pobierając ją z jakiegoś
serwisu z prognozą lokalną).
Możemy skorzystać z null,
by sprawdzić, czy obiekt
istnieje, czy nie.
Zakopane
,
PDãRSROV
NLH
Aktualnie 2
7 stopni
LV]DQV
Z\VWĈSLHQLD
opadów.
Pamiętaj, null ma reprezentować obiekt,
który aktualnie nie istnieje.
jesteś tutaj
299
To zakręcone NaN
Bardzo sprytni
Wierzcie lub nie!!
,
b
a
z
Lic ie jest liczbą
an
r
ó
kt
:MĐ]\NX-DYD6FULSWEDUG]R
âDWZRPRİQDQDSLVDþLQVWUXNFMH
M
NWyUHEĐGċWZRU]\þQLHQDMOHSLH
RZH
F]E
FLOL
]GHILQLRZDQHZDUWRĤ
2WRNLONDSU]\NâDGyZ
var a = 0/0;
W matematyce nie istnieje
łatwy sposób wykonania
tego działania; nie możemy
zatem oczekiwać, że
ł,
JavaScript będzie wiedzia
jak je wykonać.
var b = "food" * 1000;
Nie wiemy, jaki będzie
wynik tej operacji, jednak
on
bez wątpienia nie będzie
liczbą!
var c = Math.sqrt(-9);
Jeśli pamiętasz matematykę
ze szkoły średniej,
powinieneś wiedzieć, że
pierwiastek kwadratowy
z liczby ujemnej jest liczbą
urojoną, a JavaScript nie
ich
potrafi reprezentować tak
liczb.
300
Rozdział 7.
0RİHV]QDPZLHU]\þOXEQLHMHGQDN
LVWQLHMċZDUWRĤFLOLF]ERZHktórychQLH
PRİQDUHSUH]HQWRZDþw JavaScripcie!
-Đ]\NWHQQLHMHVWZVWDQLHZ\UD]Lþ
W\FKZDUWRĤFL]DWHPZSU]\SDGNX
LFKZ\VWċSLHQLDXİ\ZDVWDQGDUGRZHM
ZEXGRZDQHMZDUWRĤFL
NaN
-DYD6FULSWXİ\ZDZDUWRĤFL1D1]QDQHM
WDNİHMDNRÅWRQLHMHVWOLF]EDµ DQJ 1RW
D1XPEHU DE\Z\UD]LþZDUWRĤFLNWyU\FK
SRSURVWXQLHSRWUDILZ\UD]Lþ:HĮP\
QSZ\QLNG]LHOHQLDSU]H]7DNLH
G]LDâDQLHGDMHZHIHNFLHFRĤF]HJR
NRPSXWHU\QLHVċZVWDQLHZ\UD]Lþ
:-DYD6FULSFLHZ\QLNWDNLHJRG]LHOHQLD
MHVWUHSUH]HQWRZDQ\SU]H]ZDUWRĤþ1D1
1D102į(%<ý
1$-'=,:1,(-6=Ċ
:$572ģ&,Ċ1$
ģ:,(&,(1LHW\ONR
UHSUH]HQWXMHZV]\VWNLH
ZDUWRĤFLOLF]ERZHNWyU\FK
QLHPRİQDZ\UD]LþOHF]GRGDWNRZRMHVW
MHG\QċZDUWRĤFLċZMĐ]\NX-DYD6FULSW
NWyUDQLHMHVWUyZQDVRELHVDPHM
7DNGREU]HVâ\V]DâHĤ-HĤOLSRUyZQDV]
1D1]1D1RNDİHVLĐİHQLHVċVRELH
UyZQH
NaN ! = NaN
Typy, równość, konwersje i cały ten jazz
Stosowanie wartości NaN
Mógłbyś sądzić, że wartość NaN jest rzadko stosowana i spotykana, gdybyś jednak miał
do czynienia z jakimkolwiek kodem operującym na liczbach, przekonałbyś się, jak
często się pojawia. W takim kodzie najczęściej wykonywaną operacją jest sprawdzanie,
czy wartość zmiennej jest równa NaN. Zważywszy na wszystko, co już wiesz o języku
JavaScript, wykonanie takiego testu może się wydawać czymś oczywistym.
if (myNum == NaN) {
myNum = 0;
Mógłbyś oczekiwać, że
będzie działać, ale tak taki warunek
nie jest.
}
H
O
ĭ
Każda rozsądnie myśląca osoba powiedziałaby, że właśnie tak można sprawdzić, czy
zmienna zawiera wartość NaN. Jednak takie rozwiązanie nie działa? Dlaczego? Otóż
NaN nie jest równa żadnej innej wartości, nie jest nawet równa sobie samej; dlatego też
jakiekolwiek testy równości z tą wartością nie mają najmniejszego sensu. Zamiast nich
należy użyć specjalnej funkcji, czyli isNaN. Poniżej pokazaliśmy ją w działaniu.
if (isNaN(myNum)) {
myNum = 0;
}
Użyj funkcji isNaN, która
zwraca true, jeśli przekazana
wartość nie jest liczbą.
H
]
EU
'R
Sprawy stają się jeszcze dziwniejsze
Zastanówmy się nad tym jeszcze trochę. Skoro NaN nie jest liczbą, na co wskazywałaby
angielska nazwa tej wartości — „Not a Number” (to nie liczba) — to czym jest? Czy
nie byłoby prościej, gdyby jej nazwa określała to, czym ta wartość jest, a nie to, czym
nie jest? Jak sądzisz, czym jest NaN? W ramach ułatwienia sobie odpowiedzi na to
pytanie, sprawdźmy jej typ.
var test11 = 0 / 0;
console.log(typeof test11);
Oto co uzyskaliśmy.
Co jest, do diabła!? Typem wartości NaN jest liczba? Jak coś, co nie jest liczbą, może
mieć taki sam typ jak liczby? Spokojnie, weź głęboki oddech. Wyobraź sobie, że NaN
jest wartością, której nazwa została nie najlepiej dobrana. Ktoś powinien ją raczej
nazwać „liczbą, która nie może być reprezentowana na komputerze” (choć zgadzamy
się, że akronim takiej nazwy nie byłby równie krótki i łatwy do zapamiętania). Jeśli
wyobrazisz sobie NaN w taki sposób, będziesz mógł o niej myśleć jak o liczbie, której
nie można przedstawić (a przynajmniej nie na komputerze).
Konsola JavaScript
number
Jeśli mózg Ci
jeszcze nie wybuchł,
najprawdopodobniej
powinieneś użyć tej
książki na podpałkę.
Nie wahaj się — dodaj NaN na listę „strefy cieni” języka JavaScript.
jesteś tutaj
301
Pytania o NaN i null
Nie istnieją
głupie pytania
P: Jeśli do funkcji isNaN przekażę łańcuch znaków,
czy zwróci ona wartość true?
O: Oczywiście, zgodnie z oczekiwaniami, zwróci. Możesz
oczekiwać, że przekazanie do funkcji isNaN zmiennej zawierającej
NaN bądź też jakąkolwiek inną wartość niebędącą liczbą spowoduje
zwrócenie wartości true (bądź false w przeciwnym razie).
Istnieje kilka zastrzeżeń do tej zasady, które opiszemy, gdy
będziemy mówić o konwersji typów.
P: Ale dlaczego NaN nie jest równa sama sobie?
O: Jeśli naprawdę jesteś bardzo zainteresowany tym
zagadnieniem, powinieneś zaznajomić się ze specyfikacją IEEE
dotyczącą liczb zmiennoprzecinkowych. Jednak najprościej rzecz
ujmując: to, że NaN reprezentuje wartości, których nie można
wyrazić, nie oznacza wcale, że dwie takie wartości są sobie
równe. Weźmy np. dwie liczby, takie jak sqrt(-1) oraz sqrt(-2).
Bez wątpienia nie są sobie równe, a w JavaScripcie obie zostaną
przedstawione jako NaN.
P: Kiedy podzielę 0/0, uzyskam NaN, jeśli natomiast
podzielę 10/0, uzyskam Infinity. Czy to coś innego niż
NaN?
O: Dobre pytanie. W języku JavaScript wartość Infinity
(lub -Infinity) reprezentuje wszystkie liczby, które (że się
tak technicznie wyrazimy) przekraczają górny zakres liczb
zmiennoprzecinkowych, jaki można reprezentować na
Ćwiczenie
komputerze i jaki wynosi 1,7976931348623157E+10308 (lub
–1,7976931348623157E+10308 w przypadku -Infinity). Jeśli
chodzi o typ, to wartość Infinity jest liczbą; a jeśli podejrzewamy,
że jakaś wartość może być nieco zbyt duża, możemy używać
Infinity w porównaniach:
if (tamale == Infinity) {
alert(”To naprawdÚ wielkie tamale!”);
}
P: Informacja, że NaN jest liczbą, faktycznie rozsadziła mi
mózg. Czy macie jeszcze inne podobne rewelacje?
O: Zabawne, że o to zapytałeś. A co powiesz na to, że Infinity
- Infinity równa się… ta dam… NaN! Jeśli chcesz to ogarnąć,
skierujemy Cię do dobrego matematyka.
P: Żeby czegoś nie pominąć, czy napisaliście jakiego typu
jest wartość null?
O
: Najprościej to sprawdzić, używając operatora typeof
z wartością null. Jeśli to zrobisz, okaże się, że zwróci on
”object”. I to nawet ma sens, zważywszy, że null ma
reprezentować obiekt, którym jeszcze nie dysponujemy. Jednak
zagadnienie to było szeroko dyskutowane i najnowsza specyfikacja
języka określa, że typem null jest null. Może się okazać, że pod
tym względem implementacja JavaScriptu w Twojej przeglądarce
nie odpowiada specyfikacji, jednak w praktyce bardzo rzadko
zdarza się, by konieczne było użycie w kodzie typu wartości null.
W tym rozdziale przyglądaliśmy się pewnym, hm…, interesującym wartościom. A teraz przyjrzymy
się pewnemu interesującemu zachowaniu. Spróbuj dodać poniższy fragment kodu do elementu
<script> w jakiejś prostej stronie i sprawdź, co zostanie wyświetlone w oknie konsoli po otworzeniu
strony w przeglądarce. Nie zrozumiesz jeszcze, dlaczego wynik jest taki, a nie inny, ale warto, żebyś
spróbował odgadnąć, co się stanie.
if (99 == ”99”) {
Konsola JavaScript
console.log(”Liczba jest röwna ïañcuchowi znaköw!”);
} else {
console.log(”Przecieĝ liczba nie bÚdzie röwna ïañcuchowi.”);
}
Tutaj zapisz wynik,
który uzyskałeś.
302
Rozdział 7.
> false
Typy, równość, konwersje i cały ten jazz
Musimy coś wyznać
Jest pewien aspekt języka JavaScript, którego specjalnie jeszcze
nie przedstawialiśmy. Mogliśmy, co prawda, powiedzieć Ci
o tym od razu, jednak opisanie go teraz było bardziej sensowne.
Nie chodzi o to, że próbowaliśmy zamydlić Ci oczy, a raczej
o to, że nie pisaliśmy o wszystkim. A o co właściwie chodzi?
Spójrz na poniższy przykład.
W pewnym momencie zostaje określona
wartość zmiennej, w tym przypadku
przypisujemy jej liczbę 99.
var testMe = 99;
A później w wyrażeniu warunkowym
zmienna jest porównywana
z wartością.
if (testMe == 99) {
// wszystko jest w porzÈdku
}
Banalne, nieprawdaż? No pewnie, nie wyobrażamy sobie niczego
prostszego. Jednak jest coś, co zrobiliśmy przynajmniej raz we
wcześniejszej części książki i czego, być może, nie zauważyłeś.
Chodzi mianowicie o kod, taki jak pokazany poniżej.
W pewnym momencie zostaje określona
wartość zmiennej, w tym przypadku
przypisujemy jej łańcuch znaków „99”. Czy wspomnieliśmy,
że tym razem używamy
łańcucha znaków?
CELNE
S SPOSTRZEŻENIA
Krótkie przypomnienie dotyczące
różnicy pomiędzy przypisaniem
i sprawdzeniem równości.
Q
var x = 99;
Znak równości (=) jest operatorem
przypisania. Służy on do
przypisywania wartości zmiennej.
Q
x == 99
Dwa znaki równości (==) to
operator porównania, a konkretnie
— równości. Służy on do
sprawdzania, czy jedna wartość
jest równa drugiej.
var testMe = ”99”;
A później w wyrażeniu warunkowym
zmienna jest porównywana
z wartością.
if (testMe == 99) {
// wszystko jest w porzÈdku
Tym razem porównujemy
łańcuch znaków z liczbą.
}
A zatem, co się dzieje, kiedy porównujemy liczbę z łańcuchem znaków?
Kompletny chaos? Stopienie procesora? Zamieszki na ulicach?
Nie. JavaScript jest na tyle zmyślny, by określić, że do wszystkich
praktycznych zastosowań 99 i ”99” są sobie równe. Co jednak się dzieje
za kulisami, by takie zachowanie było możliwe? Zobaczmy…
jesteś tutaj
303
Operator równości
Zrozumienie operatora równości (pseudonim: ==)
Mógłbyś sądzić, że zrozumienie równości będzie prostym zagadnieniem. W końcu
1 == 1, „pizza” == „pizza”, a true == true. Jednak skoro także „99” == 99, bez
wątpienia nie wszystko na ten temat zostało powiedziane. Cóż takiego dzieje się
w operatorze równości, że takie dwie wartości są sobie równe?
Okazuje się, że operator == uwzględnia także typy zastosowanych operandów (czyli
tych dwóch rzeczy, które ze sobą porównujemy). Analizując go, należy uwzględnić
dwie sytuacje.
Jeśli dwie wartości są tego samego typu, wystarczy je porównać
Jeśli dwie porównywane wartości są tego samego typu, są to np. dwie liczby lub dwa
łańcuchy znaków, to porównanie działa tak, jak można by się tego spodziewać: dwie
wartości są ze sobą porównywane, a wynikiem będzie true, jeśli są identyczne.
Jeśli typy wartości są różne, należy spróbować skonwertować je
do tego samego typu i dopiero wtedy porównać
Ten przypadek jest znacznie bardziej interesujący. Załóżmy, że porównujemy dwie
wartości różnych typów, np. liczbę i łańcuch znaków. W takim przypadku JavaScript
skonwertuje łańcuch znaków na liczbę, a dopiero potem porówna obie wartości.
Wygląda to tak.
99 = = "99"
99 = = 99
Kiedy porównujemy liczbę z łańcuchem
znaków, JavaScript konwertuje łańcuch
znaków na liczbę (o ile tylko jest to
możliwe)…
… a następnie stara się porównać obie
wartości. Jeśli okaże się, że są sobie
równe, całe wyrażenie przyjmie wartość
true, natomiast w przeciwnym razie
przyjmie ono wartość false.
No dobrze, intuicyjnie rzecz biorąc, ma to jakiś sens, ale jakie reguły określają te
konwersje? Co się stanie, jeśli spróbuję porównać wartość logiczną z liczbą, null
z undefined, bądź użyję jakiejś innej kombinacji wartości. Skąd mam wiedzieć,
co zostanie skonwertowane i na co? A poza tym, dlaczego ta liczba nie zostanie
skonwertowana na łańcuch znaków bądź dlaczego nie zostanie zastosowany
jakiś jeszcze inny schemat porównania? No cóż, to wszystko jest określane przez
stosunkowo prosty zestaw reguł podanych w specyfikacji języka. Precyzują one,
jaka konwersja zostanie zastosowana podczas porównywania dwóch wartości
różnych typów. To jedna z tych rzeczy, które należy sobie przyswoić — kiedy już
to zrobisz, do końca swojej kariery związanej z JavaScriptem będziesz doskonale
wiedział, jak działają konwersje.
304
Rozdział 7.
Zauważ, że ta konwersja
jest tymczasowa — jest ona
wykonywana tylko po to, by
można było porównać obie
wartości.
To także jedna z rzeczy,
które pozwolą Ci się wyróżnić
i zabłysnąć na następnej
rozmowie kwalifikacyjnej.
Typy, równość, konwersje i cały ten jazz
Jak wykonywana jest konwersja operandów operatora równości
(brzmi groźniej, niż jest w rzeczywistości)?
Powiedzieliśmy zatem, że podczas porównywania dwóch wartości różnych typów JavaScript konwertuje
wartość jednego typu do drugiego, co umożliwia przeprowadzenie porównania. Jeśli masz doświadczenia
w korzystaniu z innych języków programowania, może Ci się to wydawać dziwne, gdyż zazwyczaj operacje
tego typu są wykonywane jawnie, a nie automatycznie. Jednak nie masz się czego obawiać; ogólnie rzecz
biorąc, jest to zazwyczaj użyteczne rozwiązanie, o ile tylko będziesz rozumiał, kiedy i jak są wykonywane
konwersje. I właśnie spróbujemy teraz ustalić, kiedy operator porównania wykonuje konwersje oraz jak są
one realizowane.
Oto cała tajemnica (w czterech prostych przypadkach).
35=<3$'(.3RUöZQDQLHOLF]E\LïDñFXFKD]QDNöZ
Jeśli porównujesz łańcuch znaków i liczbę, za każdym razem zdarzy się to samo: łańcuch zostanie
skonwertowany na liczbę, a następnie obie liczby zostaną porównane. W tym przypadku nie zawsze obywa
się bez problemów, gdyż niektórych łańcuchów znaków nie można przekształcić na liczby. Zobaczmy,
co się stanie w poniższym przypadku.
99 == ”wanilia”
99 == NaN
Także tym razem porównujemy liczbę
i łańcuch znaków. Jednak kiedy
spróbujemy skonwertować łańcuch
na liczbę, okazuje się, że nie jest to
możliwe.
Kiedy spróbujemy skonwertować „wanilię” na
liczbę, uzyskamy NaN, a jak wiemy, wartość
NaN nie jest równa jakiejkolwiek innej wartości.
A zatem wynikiem całego porównania będzie false.
false
35=<3$'(.3RUöZQDQLHZDUWRĂFLORJLF]QHMLZDUWRĂFLGRZROQHJRLQQHJRW\SX
W tym przypadku wartość logiczna konwertowana jest do postaci liczby i porównywana z drugą wartością.
Może się to wydawać nieco dziwne, jednak znacznie łatwiej to zrozumiemy, jeśli będziemy pamiętać, że
wartość true jest zamieniana na 1, a wartość false na 0. Koniecznie trzeba także zrozumieć, że czasami
takie porównanie wymaga wykonania więcej niż jednej konwersji. Przeanalizujmy kilka przykładów.
1 == true
Porównujemy liczbę z wartością logiczną.
Wartość true jest konwertowana na wartość 1.
1 == 1
true
Następnie porównujemy 1 z 1
i uzyskujemy wartość true.
jesteś tutaj
305
Porównywanie wartości
A oto inny przypadek. Tym razem spróbujemy porównać wartość logiczną z łańcuchem
znaków. Zwróć uwagę, o ile więcej czynności trzeba przy tym wykonać.
Porównujemy łańcuch znaków z wartością
logiczną. Wartość true jest konwertowana
na liczbę 1.
"1" == true
"1" == 1
Następnie porównujemy „1” z 1.
Teraz stosujemy regułę z przypadku 1.
i konwertujemy łańcuch znaków na liczbę.
1 == 1
W końcu możemy porównać obie liczby i w tym
przypadku okazuje się, że porównanie zwróci
wartość true.
true
35=<3$'(.3RUöZQDQLHZDUWRĂFLnull i undefined
Porównanie tych dwóch wartości daje w wyniku true. Może się to wydawać dziwne,
ale tak już jest — to reguła. Pewnym wyjaśnieniem może być fakt, że obie zarówno null,
jak i undefined reprezentują coś, co „nie jest wartością” (czyli obiekt, który nie ma
wartości, lub niezainicjalizowaną zmienną), dlatego można uznać, że są one sobie równe.
undefined == null
Undefined i null zawsze
są sobie równe.
true
35=<3$'(.2FKZïDĂFLZLHQLHPDĝDGQHJRF]ZDUWHJRSU]\SDGNX
No właśnie. Opierając się na trzech poprzednich regułach, można określić wartości
wszystkich porównań. Jednak istnieją pewne przypadki skrajne i zastrzeżenia. Jednym
z takich zastrzeżeń jest to, że wciąż będziemy musieli zastanowić się nad porównywaniem
obiektów, czym zajmiemy się już wkrótce. No i istnieją także przypadki konwersji, które
mogą zaskoczyć nieuważnych programistów. Poniżej przedstawiliśmy jeden z nich.
1 == ""
1 == 0
false
306
Rozdział 7.
Porównujemy liczbę z łańcuchem znaków,
stosujemy zatem regułę numer 1.
Pusty łańcuch znaków jest zamieniany
na 0. Możesz nam wierzyć lub nie!
O… Szkoda. 1 i 0 są różne, więc
porównanie zwraca wartość false.
Typy, równość, konwersje i cały ten jazz
Gdybym tylko mogła znaleźć sposób sprawdzania
równości bez konieczności zwracania uwagi na
konwersję typów operandów. Sposób, w którym dwie
wartości są sobie równe wyłącznie w przypadku, gdy
mają takie same wartości i ten sam typ. Sposób, który
pozwoli mi zapomnieć o tych wszystkich regułach
konwersji i uniknąć błędów, których przyczyną mogą być
te konwersje. To byłoby fantastyczne. Ale wiem, że
to tylko moje fantazje…
jesteś tutaj
307
Ścisły operator równości
Ja bardziej
ściśle podchodzę do
używanych porównań.
Jak ściśle podejść
do zagadnienia równości?
Skoro już wszystkim zebrało się na wyznania, musimy powiedzieć
jeszcze jedno: naprawdę to istnieje nie jeden, lecz dwa operatory
równości. Poznałeś już operator == (równości), ale istnieje także
operator === (nazywany ścisłym operatorem równości).
Dokładnie trzy znaki równości. Możesz używać === wszędzie tam,
gdzie stosowałeś ==, jednak zanim zaczniesz to robić, upewnij się,
że rozumiesz, czym oba te operatory różnią się od siebie.
Już znasz wszystkie skomplikowane reguły konwersji wartości
porównywanych przy użyciu operatora == (oczywiście, jeśli są to
wartości różnych typów). W przypadku operatora === reguły te są
jeszcze bardziej złożone.
kcyjna.
Uwaga reda ewnić, że
up
ę
si
ba
Trze
ch zdjęcie
mamy w aktaforda.
ck
ro
C
Douga
Nie, to był żart. W rzeczywistości w przypadku operatora ===
istnieje tylko jedna reguła.
Dwie wartości są identyczne, jeśli mają ten sam typ
i tę samą wartość
Przeczytaj to jeszcze raz. Oznacza to, że jeśli dwie wartości mają ten sam typ,
zostaną porównane. Jeśli jednak są różnego typu, to niezależnie od wszystkiego
porównanie zwróci false — nie będzie żadnych konwersji, żadnych złożonych
reguł porównywania, nic z tych rzeczy. Musisz zapamiętać tylko to, że w przypadku
użycia operatora === dwie wartości są równe tylko wtedy, gdy są tego samego typu
i mają tę samą wartość.
Zaostrz ołówek
Dla każdego z przedstawionych poniżej porównań zapisz wynik
w kolumnach pod operatorem == oraz ===.
== = ==
”42” == 42
true
_________
_________
”42” === 42
”0” == 0
_________ _________
”0” === 0
”0” == false
_________ _________
”0” === false
”true” == true
_________ _________
”true” === true
true == (1 == ”1”)
_________ _________
Podchwytliwe!
308
Rozdział 7.
true === (1 === ”1”)
Podchwytliwe!
Typy, równość, konwersje i cały ten jazz
Nie istnieją
głupie pytania
P
: Co się stanie, kiedy porównam liczbę 15 z łańcuchem,
takim jak ”piÚtnaĂcie”, którego nie można skonwertować
na liczbę?
O: JavaScript spróbuje skonwertować ĵSLÚWQDĂFLHĵ na liczbę
i mu się to nie uda — zwrócona zostanie wartość NaN. Zatem obie
wartości nie będą równe, a wynikiem porównania będzie false.
P: Jak JavaScript konwertuje łańcuchy znaków na liczby?
O: Używa algorytmu do przekształcania poszczególnych znaków
P: A czy istnieje operator !==?
O: Tak. I podobnie jak operator ===, jest bardziej ścisły niż ==,
tak !== jest bardziej ścisły niż ==. Używając !==, możesz stosować
te same reguły, co w przypadku operatora ===, z tą różnicą, że
sprawdzasz, czy obie wartości nie są identyczne.
P: Czy w przypadku porównywania, dajmy na to wartości
logicznej i liczby, przy użyciu operatorów < i > (np. 0 <
true) stosowane są te same reguły?
O
łańcucha i każdą z nich próbuje zmienić na liczbę. Jeśli zatem
mamy łańcuch ”34”, najpierw weźmie ”3” i określi, że można ją
skonwertować na 3, a później weźmie ”4” i sprawdzi, że można
ją skonwertować na 4. Można także konwertować takie łańcuchy
jak ”1.2” na liczby zmiennoprzecinkowe — JavaScript jest na
tyle inteligentny, by zorientować się, że taki łańcuch też można
przekształcić na liczbę.
: Tak! W tym przypadku true jest konwertowane na 1, więc
uzyskamy wartość true, gdyż 0 jest mniejsze od 1.
P: A co będzie, jeśli spróbuję użyć porównania
< ”mango”? W przypadku łańcuchów znaków możesz używać
kolejności alfabetycznej i na jej podstawie określać, czy jeden
łańcuch jest większy, czy mniejszy od drugiego. Ponieważ słowo
”banan” zaczyna się od litery ”b”, a ”mango” od ”m”, zatem
”banan” jest mniejsze od ”mango”, ponieważ w alfabecie ”b”
znajduje się przed ”m”. Z kolei ”mango” jest mniejsze od ”melon”,
gdyż po identycznych pierwszych literach porównywane są drugie
litery, a ”a” jest mniejsze od ”e”.
”true” == true?
O: To jest porównanie łańcucha z wartością logiczną. A zatem,
zgodnie z regułami, JavaScript najpierw skonwertuje true na 1,
a następnie spróbuje porównać ”true” z 1. W tym celu spróbuje
przekształcić ”true” na liczbę, co się nie uda, więc uzyskasz wynik
false.
P: Skoro mamy operatory == i ===, czy to oznacza,
że istnieją także operatory <== oraz >==?
O
: Nie. Operatory <== i >== nie istnieją. Możesz używać wyłącznie
operatorów <= oraz >=. Pozwalają one tylko na porównywanie
łańcuchów znaków i liczb (porównania, takie jak true <= false,
nie mają większego sensu). Jeśli zatem spróbujesz porównać
wartości typów innych niż dwa łańcuchy lub dwie liczby (lub
łańcuch i liczbę), JavaScript spróbuje skonwertować ich typy
zgodnie z opisanymi wcześniej regułami.
P: A zatem, co się stanie, jeśli napiszę 99 <= ”100”?
O: Zastosuj reguły: ”100” zostanie skonwertowana na liczbę,
P: Sprawdzenie, czy jeden łańcuch jest równy drugiemu,
ma sens, ale jak łańcuch może być mniejszy lub większy
od innego?
O: Dobre pytanie. Co miałoby znaczyć porównanie ”banan”
Jednak takie alfabetyczne porównania mogą być mylące, np.
”Mango” < ”mango” zwraca true, choć można by przypuszczać,
że ”M” jest większe od ”m”, bo jest wielką literą. Jednak
porównywanie łańcuchów jest zależne od uporządkowania wartości
Unicode używanych do reprezentacji poszczególnych znaków na
komputerze (Unicode jest standardowym sposobem cyfrowej
reprezentacji znaków), a to uporządkowanie nie zawsze będzie
zgodne z naszymi oczekiwaniami! Jeśli interesują Cię szczegóły,
spróbuj wyszukać „Unicode” w wyszukiwarce Google. Jednak
w większości przypadków do porównywania łańcuchów znaków
wystarczy znajomość prostej kolejności alfabetycznej.
a następnie ta liczba porównana z 99. Ponieważ 99 jest mniejsze
lub równe 100 (jest mniejsze), zatem porównanie zwróci true.
jesteś tutaj
309
Pogawędka przy kominku pomiędzy operatorem równości oraz ścisłym operatorem równości
Pogawędki przy kominku
7HPDWG]LVLHMV]HMSRJDZÚGNLEU]PL2SHUDWRUUöZQRĂFLRUD]ĂFLVï\
RSHUDWRUUöZQRĂFLVSLHUDMÈVLÚRWRNWöU\]QLFKMHVWV]HIHP
==
===
No… kogo my tutaj mamy, Pan Spięty.
Jestem gotów na porównanie, ile razy w całym napisanym
na świecie kodzie JavaScript używany jest operator ==,
a ile razy operator ===. Na pewno uplasujesz się daleko
za moimi plecami. To nawet nie będzie blisko.
Nie sądzę. Dostarczam bardzo cennych
i poszukiwanych usług. Bo w końcu któż by,
od czasu do czasu, nie chciał porównać, dajmy na
to, informacji podanej przez użytkownika w formie
łańcucha znaków z jakąś liczbą?
Czy kiedy uczęszczałeś do szkoły, musiałeś chodzić do
niej codziennie na piechotę, w śniegu, pod górę… i to
w obu kierunkach? Czy zawsze trzeba wszystko robić
w ten trudniejszy sposób?
Chodzi o to, że nie tylko jestem w stanie wykonywać
dokładnie te same porównania, co ty, ale jeszcze
wzbogacam te możliwości, realizując elegancką
konwersję typów.
A ty byś raczej wzruszył ramionami, zwrócił false
i poszedł do domu?
310
Rozdział 7.
Proszę pamiętać, że kilka powszechnie uznawanych
autorytetów języka JavaScript stwierdza, że programiści
powinni używać mnie i tylko mnie. Uważają, że ciebie
w ogóle należałoby się pozbyć z języka.
Wiesz co, pewnie masz rację, jednak programiści
powoli zaczynają rozumieć, o co w tym chodzi,
i te liczby cały czas się zmieniają.
A wraz z tym idzie cała masa reguł, o których trzeba
pamiętać podczas stosowania operatora ==. Staraj
się upraszczać sobie życie i swój kod — używaj ===,
a jeśli chcesz skonwertować informacje podane przez
użytkownika na liczby, to istnieje kilka sposobów,
by to zrobić.
Bardzo zabawne. Nie ma nic złego w byciu ścisłym ani
w stosowaniu w porównaniach precyzyjnej semantyki.
A jeśli zapomnisz o regułach, mogą się wydarzyć złe
i nieoczekiwane rzeczy.
Za każdym razem, gdy spoglądam na twoje reguły, aż
coś mi się cofa do gardła. No bo wiesz… Niby czemu
porównanie wartości logicznej z jakąkolwiek inną ma
wymagać skonwertowania tej wartości logicznej na
liczbę? Według mnie to jest zupełnie nielogiczne.
Typy, równość, konwersje i cały ten jazz
==
Na razie wszystko działa. Spójrz na istniejący kod,
znaczna jego część została napisana przez… autorów
prostych skryptów.
Chodzi ci o wzięcie prysznica po rozmowie z tobą?
Hmm… Czy kiedykolwiek myślałeś o wykupieniu
moich udziałów? Byłbym szczęśliwy, mogąc spędzać
całe dnie na plaży z kieliszkiem margarity w ręku.
Spory dotyczące operatorów == i === powoli robią
się stare. A w życiu jest wiele bardziej interesujących
rzeczy do zrobienia.
No wiesz, są rzeczy, z którymi po prostu trzeba się
pogodzić: ludzie nie przestaną używać ==. Czasami
jest on naprawdę wygodny. A poza tym można go
używać w przemyślany i mądry sposób, wykorzystując,
kiedy jest to zasadne, korzyści, jakie daje.
Moja opinia jest taka: jeśli ktoś chce cię używać
— proszę bardzo. Wciąż tutaj będę, kiedy będą
chcieli mnie zastosować. Ale wiedz, że i tak każdego
miesiąca dostaję pokaźny czek. Na świecie jest
wystarczająco dużo starego kodu, z operatorem ==
nigdy nie będą mogli mnie zwolnić.
===
Nie, ale ktoś może kiedyś zbytnio się rozluźnić
i zapomnieć o twoich złożonych regułach.
I dobrze, jednak strony powoli stają się coraz
bardziej złożone i wyrafinowane. Warto zacząć
stosować najlepsze praktyki.
Nie, chodzi mi o stosowanie operatora ===.
W ten sposób można poprawić czytelność
kodu i wyeliminować prawdopodobieństwo
występowania problemów w porównaniach.
Nie przypuszczałem, że zaproponujesz
coś takiego. Sądziłem raczej, że do końca
będziesz bronił swoich pozycji jako
NAJPOPULARNIEJSZY operator równości
w JavaScripcie.
Nawet nie wiem, jak na to odpowiedzieć.
Cóż, tak jak powiedziałem, nigdy nie wiesz,
kiedy coś się wydarzy.
jesteś tutaj
311
Rozmyślania o równości
Super. No to
teraz mamy dwa operatory
porównania. Jakby wcześniej
sprawy nie były dostatecznie
skomplikowane. Którego z nich
mam teraz używać?
Weź głęboki oddech. Wokół tego zagadnienia toczy się
wiele sporów, a różni eksperci mają odmienne opinie na jego
temat. A oto i nasze zdanie: tradycyjnie programiści używali
niemal wyłącznie operatora == (równości), gdyż… no cóż…
ten drugi operator oraz wzajemne różnice pomiędzy nimi
nie były powszechnie znane. Jednak obecnie poziom naszej
edukacji jest wyższy, a w większości zastosowań operator ===
(identyczności) działa równie dobrze, a jednocześnie, pod
niektórymi względami, stanowi bezpieczniejsze rozwiązanie,
gdyż w jego przypadku dokładnie wiemy, co otrzymamy.
Oczywiście, także stosując ==, wiemy, co otrzymamy, jednak
przez te konwersje czasami trudno przeanalizować wszystkie
potencjalne możliwości.
Jednak faktycznie czasami operator == zapewnia miłą
wygodę (np. kiedy musimy porównywać łańcuchy
znaków z liczbami) i trzeba wiedzieć, że mamy pełną
swobodę, by z niego skorzystać — zwłaszcza teraz, kiedy
w odróżnieniu od wielu innych programistów JavaScript
bardzo dokładnie wiemy, jak działa. Skoro już poznałeś
operator ===, przekonasz się, że dalej w tej książce zmienimy
dotychczasowe przyzwyczajenia i będziemy niemal zawsze
używać tego operatora. Nie będziemy jednak pochodzić do
zagadnienia dogmatycznie, zwłaszcza w przypadkach, gdy ==
będzie mógł ułatwić życie, nie przysparzając jednocześnie
żadnych problemów.
Czasami ścisły
operator równości
(===) jest także
nazywany operator
em
identyczności.
312
Rozdział 7.
Typy, równość, konwersje i cały ten jazz
:
?
7
KTO CO ROBI?
7
7
Już mieliśmy gotowe opisy tych wszystkich operatorów, ale przez nieuwagę zupełnie
się wymieszały. Czy możesz nam pomóc w określeniu, co robi każdy z nich? Uważaj,
nie jesteśmy pewni, czy poszczególne operatory pasują tylko do jednej odpowiedzi,
czy też do kilku, a może nawet do żadnej. Udało się nam już znaleźć jedną
odpowiedź, którą zaznaczyliśmy poniżej.
=
==
===
====
Porównuje wartości, by sprawdzić, czy są sobie równe. Jest uważany
za operator równości. Zada sobie nawet trud skonwertowania typów
operandów, aby sprawdzić, czy wartości są naprawdę równe.
Porównuje dwie wartości, by sprawdzić, czy są równe. Ten gość
nawet nie zada sobie trudu, by sprawdzać wartości różnych typów.
Przypisuje zmiennej podaną wartość.
Porównuje referencje obiektów i zwraca true, jeśli są takie same;
w przeciwnym razie zwraca false.
jesteś tutaj
313
Konwersje typów
Jeszcze więcej konwersji typów…
Wyrażenia warunkowe nie są jedynymi miejscami, w których spotkamy się z konwersjami typów. Istnieje
także kilka innych operatorów, które chętnie konwertują typy swoich operandów, jeśli będą miały okazję.
Choć konwersje te mają za zadanie ułatwić życie nam — programistom — jednak warto dokładnie
rozumieć, gdzie i kiedy mogą być wykonywane. Przyjrzyjmy się im zatem.
Kolejne spojrzenie na konkatenację i dodawanie
Prawdopodobnie zorientowałeś się już, że kiedy operandy operatora + będą liczbami, zostanie wykonana
operacja dodawania; jeśli natomiast będą łańcuchami znaków, zostanie przeprowadzona ich konkatenacja.
Co się jednak stanie, kiedy typy operandów będą różne? Spróbujmy się dowiedzieć.
Jeśli spróbujesz dodać liczbę i łańcuch znaków, JavaScript skonwertuje liczbę na łańcuch, a następnie
wykona konkatenację, czyli na odwrót, niż dzieje się w przypadku operatora porównania.
var addi = 3 + ”4”;
Kiedy dodajemy łańcuch znaków do
liczby, wykonywana jest konkatenacja
łańcuchów, a nie dodawanie.
W zmiennej zostanie zapisany łańcuch
znaków „34” (a nie liczba 7).
var plusi = ”4” + 3;
Tak samo będzie w tym przypadku…
w zmiennej zostanie zapisany łańcuch „43”.
Dokładnie to samo stanie się, kiedy najpierw podamy łańcuch znaków, a do niego spróbujemy dodać liczbę:
liczba zostanie skonwertowana na łańcuch znaków, a następnie oba łańcuchy zostaną połączone.
A co z innymi operatorami arytmetycznymi?
Jeśli chodzi o pozostałe operatory arytmetyczne — mnożenie, dzielenie i odejmowanie — to JavaScript
woli traktować je jak operacje na liczbach, a nie na łańcuchach.
var multi = 3 * ”4”;
W tym przypadku JavaScript konwertuje
łańcuch „4” na liczbę 4, a następnie mnoży
ją przez 3, co daje wynik 12.
var divi = 80 / ”10”;
Tutaj łańcuch „10” jest zamieniany na
liczbę 10. A zatem 80 jest dzielone
przez 10, co daje w wyniku 8.
var mini = ”10” ļ 5;
W przypadku użycia operatora minus
łańcuch „10” jest zamieniany na liczbę;
mamy zatem 10 – 5, czyli 5.
314
Rozdział 7.
Typy, równość, konwersje i cały ten jazz
Nie istnieją
głupie pytania
P: Czy operator + zawsze jest traktowany jak
konkatenacja, jeśli jeden z jego operandów jest łańcuchem
znaków?
O: Tak. Trzeba jednak pamiętać, że operator + jest łączony od
lewej do prawej. Oznacza to, że jeśli mamy następującą instrukcję:
var order = 1 + 2 + ” pizze”;
to uzyskamy ”3 pizze”, a nie ”12 pizze”, gdyż analizując
wyrażenie po prawej stronie znaku równości, najpierw dodamy
2 do 1 (a obie te wartości są liczbami) i uzyskamy 3. Dopiero potem
interpreter spróbuje dodać 3 i łańcuch ” pizze”, a w efekcie
skonwertuje 3 na łańcuch i wykona konkatenację. Aby mieć
pewność, że uzyskamy taki wynik, na jakim nam zależy, możemy
skorzystać z nawiasów, by wymusić odpowiednią kolejność
wykonywania działań. Zapis:
zagwarantuje, że uzyskamy 3 pizze, natomiast zapis:
” pizze”);
zagwarantuje, że uzyskamy łańcuch ”12 pizze”.
Przykładowo jednoargumentowy operator negacji - (pozwalający
na tworzenie liczb ujemnych) konwertuje –true na -1. Poza tym
w przypadku konkatenacji wartości logicznej z łańcuchem wartość
logiczna jest konwertowana na łańcuch (czyli true + ” love”
da ”true love”). Takie sytuacje zdarzają się stosunkowo rzadko
i nigdy nie spotkaliśmy się z nimi w praktyce, lecz teraz już wiesz,
że istnieją.
P: Co zatem mam zrobić, jeśli chcę skonwertować łańcuch
na liczbę i dodać go do innej liczby?
O: Istnieje funkcja, która to robi. Nosi ona nazwę Number
(tak, pisane z wielkiej litery N). Używa się jej w następujący sposób:
var num = 3 + Number(”4”);
var order = (1 + 2) + ” pizze”;
var order = 1 + (2 +
P: Czy to wszystko? Czy są jeszcze jakieś inne konwersje?
O: Istnieją także inne miejsca, gdzie są wykonywane konwersje.
Wykonanie powyższej instrukcji spowoduje zapisanie w zmiennej
num wartości 7. Funkcja Number pobiera jeden argument i jeśli
to tylko możliwe, przekształca go na liczbę. Jeśli nie można
skonwertować argumentu, funkcja zwraca… chwileczkę… NaN.
Zaostrz ołówek
Nadszedł czas, żeby przetestować Twoją znajomość konwersji. Obok każdego z przedstawionych poniżej
wyrażeń zapisz jego wynik. Jedno działanie wykonaliśmy za Ciebie. Zanim zaczniesz dalszą lekturę,
sprawdź prawidłowe wyniki, które zamieściliśmy pod koniec rozdziału.
Infinity ļ ”1” ______________________________
„4242”
”42” + 42 ______________________________
2 + ”1 1” ______________________________
99 + 101 ______________________________
”1” ļ ”1” ______________________________
console.log(”Wynik: ” + 10/2) ______________________________
3 + ” banany i ” + 2 + ” jabïka” ______________________________
jesteś tutaj
315
Równość obiektów
Nie powiedzieliście jeszcze
o jednej rzeczy, mianowicie o tym,
jak wygląda równość w kontekście
obiektów. Przykładowo co to
oznacza, że dwa obiekty są sobie
równe?
Cieszymy się, że o tym pomyślałeś. W przypadku
równości obiektów istnieją dwie odpowiedzi: prosta
oraz długa i wyczerpująca. Prosta odpowiedź jest
związana z pytaniem, czy ten obiekt jest równy tamtemu
obiektowi? Jeśli zatem mamy dwie zmienne odwołujące
się do obiektów, czy wskazują one dokładnie ten sam
obiekt? Zagadnienie to opiszemy dokładnie na następnej
stronie. Złożona odpowiedź dotyka zagadnienia typu
obiektów oraz problemu, kiedy dwa obiekty mogą,
a kiedy nie mogą być tego samego typu. Przekonaliśmy
się już wcześniej, że można tworzyć obiekty, które
wyglądają tak, jakby były tego samego typu, np. dwa
samochody. Skąd jednak możemy wiedzieć, że naprawdę
są to obiekty tego samego typu? To ważne pytanie
— pytanie, którym zajmiemy się dalej w tej książce.
316
Rozdział 7.
Typy, równość, konwersje i cały ten jazz
Jak określić, czy dwa obiekty są równe?
Być może, pierwszym pytaniem, które chciałbyś zadać, będzie pytanie, czy rozmawiamy
o operatorze ==, czy ===? Mamy dla Ciebie dobrą wiadomość: jeśli porównujemy obiekty,
zastosowany operator równości nie ma znaczenia! Oznacza to, że jeśli oba operandy są
obiektami, możesz używać zarówno operatora ==, jak i ===, gdyż działają dokładnie w taki
sam sposób. Poniżej opisaliśmy, co się dzieje, kiedy porównamy dwa obiekty, sprawdzając,
czy są sobie równe.
Kiedy sprawdzamy równość dwóch zmiennych obiektowych,
porównujemy referencje do obiektów
Pamiętasz pewnie, że zmienne zawierają referencje do obiektów, dlatego zawsze
wtedy, gdy porównujemy dwa obiekty, w rzeczywistości porównywane są
ich referencje.
Zauważ, że
zawartość
obiektów nie
ma tu żadnego
znaczenia. Jeśli
referencje nie są
identyczne, obiekty
także nie są
równe.
if (var1 === var2) {
// jej, to sÈ te same obiekty!
}
Nie w tym
przypadku!
Ta referencja…
var1
…NIE jest równa
tej referencji.
To są dwie różne
referencje.
var2
Dwie referencje są równe tylko wtedy, gdy odwołują się
do tego samego obiektu
Jedynym przypadkiem, gdy sprawdzenie równości dwóch zmiennych
zawierających referencje do obiektów zwróci wartość true, jest sytuacja,
gdy obie referencje odwołują się do tego samego obiektu.
if (var1 === var3) {
// jej, to sÈ te same obiekty!
}
Ta referencja…
W końcu dwie referencje
do obiektów, które są
sobie równe.
var3 var1
var2
…jest równa tej referencji. Referencje
są sobie równe i wskazują ten sam
obiekt.
jesteś tutaj
317
Ćwiczenia z równości
Zaostrz ołówek
Poniżej zamieściliśmy prosty kod, który ułatwia odnajdywanie aut na
parkingu komisu Edka. Przeanalizuj kod i napisz poniżej, jakie będą
wartości zmiennych od loc1 do loc4.
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: ”SieMoCorp”,
model: ”Taxi”
};
var fiat1 = {
make: ”Fiat”,
model: ”500”
};
var fiat2 = {
make: ”Fiat”,
model: ”500”
Edek, szef
komisu Auta
Edka.
};
var lot = [chevy, taxi, fiat1, fiat2];
var loc1 = findCarInLot(fiat2);
318
oje odpowiedzi.
Tutaj zapisz sw
_______________
var loc2 = findCarInLot(taxi);
_______________
var loc3 = findCarInLot(chevy);
_______________
var loc4 = findCarInLot(fiat1);
_______________
Rozdział 7.
Typy, równość, konwersje i cały ten jazz
Gdzieś tam leży prawda…
Napisaliśmy „prawda”, a nie wspomnieliśmy o „wartości true”. Będziemy także pisać,
że coś jest „fałszywe”. Ale co to wszystko ma znaczyć? Niektóre języki programowania
bardzo precyzyjnie podchodzą do wartości logicznych true i false. Jednak JavaScript
do nich nie należy. JavaScript traktuje prawdę i fałsz na pewnym luzie. A na czym
polega to „luźne traktowanie”? W języku JavaScript istnieją pewne wartości, które
nie są wartościami true ani false, a pomimo to w wyrażeniach warunkowych są
traktowane jak wartości logiczne. Określamy je właśnie jako prawdę i fałsz, gdyż
z technicznego punktu widzenia nie są to wartości true i false, jednak zachowują się
tak, jakby nimi były (oczywiście wyłącznie w wyrażeniach warunkowych).
A oto i cały sekret do zrozumienia tej prawdy i fałszu: skoncentruj się na tym, co jest
fałszem, a wszystko pozostałe będzie prawdą. Przyjrzyjmy się kilku przykładom użycia
fałszu w wyrażeniach warunkowych.
var testThis;
if (testThis) {
// tu coĂ robimy
No dobrze, to jest trochę dziwne. Wiemy, że
w wyrażeniu warunkowym ta zmienna będzie mieć
wartość undefined. Czy taki warunek zadziała?
Czy jest on prawidłowy w języku JavaScript?
(Odpowiedź brzmi: tak, jest prawidłowy).
}
var element = document.getElementById(”elementThatDoesntExist”);
if (element) {
// tu coĂ robimy
}
if (0) {
W tym przypadku wartością zmiennej
element jest null. Jak zostanie
przetworzony taki warunek?
Sprawdzamy liczbę 0?
// a tu robimy coĂ innego
}
A tutaj całym warunkiem
jest pusty łańcuch
znaków. Czy ktoś chce się
założyć, jaki będzie
efekt?
if (””) {
// czy umieszczony tu kod w ogöle zostanie przetworzony? PrzyjmujÚ zakïady.
}
if (NaN) {
A tu wsadziliśmy NaN do wyrażenia warunkowego?
Ciekawe, jak ono zostanie przetworzone…
// HmmĮ A co NaN robi w wyraĝeniu logicznym?
}
jesteś tutaj
319
Prawda i fałsz
JavaScript uwzględnia fałsz
Jeszcze raz przypominamy, że zrozumienie tego, co będzie uznawane za
prawdę, a co za fałsz, sprowadza się do zapamiętania, co będzie fałszem
— bo wszystko pozostałe będzie prawdą.
Poniżej przedstawiliśmy pięć wartości, które JavaScript traktuje jak
logiczny fałsz.
Fałszem jest udefined.
Fałszem jest null.
Fałszem jest 0.
Fałszem jest także pusty łańcuch znaków.
Fałszem jest też NaN.
Aby wiedzieć, które wartości
będą traktowane jak prawda,
a które jak fałsz, wystarczy
nauczyć się na pamięć tych,
które będą uznawane za fałsz
— undefined, null, 0, ""
oraz NaN — i zapamiętać,
że wszystko inne będzie
potraktowane jak prawda.
A zatem każde wyrażenie warunkowe przedstawione na poprzedniej stronie
zostanie potraktowane tak, jak gdyby została w nim umieszczona wartość
false. A czy wspominaliśmy, że każda inna wartość (oczywiście z wyjątkiem
false) zostanie potraktowana jak wartość true? Oto kilka przykładów
wartości, które zostaną potraktowane jak logiczna prawda.
if ([]) {
To jest tablica. Tablica nie jest
ani undefined, ani null, ani 0, ani „”,
ani NaN. Zatem tablica jest prawdą!
// to siÚ zdarzy naprawdÚ
}
var element = document.getElementById(”elementThatDoesExist”);
if (element) {
// podobnie jak i to
}
if (1) {
// takĝe i to siÚ zdarzy
W tym przypadku dysponujemy faktycznym
obiektem elementu. Obiekt nie jest żadną
z pięciu wartości traktowanych jak fałsz,
zatem zostanie uznany za prawdę.
Z liczb tylko 0 jest traktowane jak fałsz,
wszystkie inne są prawdą.
}
var string = ”litoĂci...”;
if (string) {
// i to röwnieĝ siÚ zdarzy
}
320
Rozdział 7.
Jedynie pusty łańcuch znaków
jest fałszem, każdy inny jest
prawdą.
Typy, równość, konwersje i cały ten jazz
Zaostrz ołówek
Nadszedł czas na szybki test wykrywaczem kłamstw. Określ, czy przestępca mówi
prawdę, czy kłamie oraz czy jest winny zarzucanych mu czynów. W tym celu musisz
określić, które wartości będą prawdą, a które fałszem. Zanim przejdziesz do dalszej
lektury, sprawdź poprawne odpowiedzi, które zamieściliśmy pod koniec tego rozdziału.
Możesz także samodzielnie wypróbować działanie tego kodu w przeglądarce.
function lieDetectorTest() {
var lies = 0;
var stolenDiamond = { };
if (stolenDiamond) {
console.log(”UkradïeĂ diament!”);
lies++;
}
var car = {
keysInPocket: null
};
if (car.keysInPocket) {
console.log(”Oo, ukradïeĂ takĝe samochöd!”);
lies++;
}
if (car.emptyGasTank) {
console.log(”Takĝe jechaïeĂ ukradzionym samochodem!”);
lies++;
}
var foundYouAtTheCrimeScene = [ ];
if (foundYouAtTheCrimeScene) {
console.log(”To oczywisty dowöd przestÚpstwa!”);
lies++;
}
if (foundYouAtTheCrimeScene[0]) {
console.log(”Znaleziono przy tobie ukradziony diament!”);
lies++;
To łańcuch składający się
}
z jednego znaku odstępu.
var yourName = ” ”;
if (yourName) {
console.log(”Dodatkowo skïamaïeĂ, podajÈc swoje personalia”);
lies++;
}
return lies;
}
var numberOfLies = lieDetectorTest();
console.log(”SkïamaïeĂ ” + numberOfLies + ” razy!”);
if (numberOfLies >= 3) {
console.log(”Winny wszystkich zarzutöw!”);
}
jesteś tutaj
321
Wszystko o łańcuchach znaków
WYSIL
SZARE KOMÓRKI
Jak sądzisz, co robi poniższy fragment kodu? Czy widzisz
w nim coś dziwnego, zwłaszcza jeśli uwzględnisz to, co
wiesz o typach prostych?
var text = ”PISZkC WIADOMO¥CI, NIE POWINIENE¥ KRZYCZEm”;
var presentableText = text.toLowerCase();
if (presentableText.length > 0) {
alert(presentableText);
}
Sekretne życie łańcuchów znaków
Typy zawsze należą do jednego z dwóch obozów, są typami prostymi bądź obiektami.
Typy proste wiodą stosunkowo prosty żywot, natomiast obiekty przechowują swój stan
i mają zachowania (albo, mówiąc inaczej, mają właściwości i metody). Prawda?
Cóż, choć to faktycznie prawda, jednak nie można powiedzieć, by była to cała prawda.
Jak się okazuje, łańcuchy znaków są nieco bardziej tajemnicze. Sprawdź poniższy
fragment kodu.
var emot = ”XOxxOO”;
To wygląda jak zwyczajny,
prosty łańcuch znaków.
var hugs = 0;
var kisses = 0;
Ale chwileczkę… Co to? Wywołujemy
metody, używając łańcucha znaków?
emot = emot.trim();
emot = emot.toUpperCase();
I niby łańcuch znaków ma
także właściwości?
for(var i = 0; i < emot.length ; i++) {
if (emot.charAt(i) === ”X”) {
hugs++;
} else if (emot.charAt(i) === ”O”) {
kisses++;
}
}
322
Rozdział 7.
Kolejne metody?
Typy, równość, konwersje i cały ten jazz
Dlaczego łańcuch może wyglądać jak dana typu
prostego oraz jak obiekt?
Jak to się dzieje, że łańcuch znaków może wyglądać jednocześnie jak dana typu
prostego oraz jak obiekt? Ponieważ JavaScript pozwala traktować łańcuchy na
oba te sposoby. Oznacza to, że w języku JavaScript można utworzyć łańcuch, który
będzie daną typu prostego, oraz łańcuch, który będzie obiektem (udostępniającym
bardzo wiele użytecznych metod do operowania na łańcuchach). Do tej pory
jeszcze nigdy nie pokazaliśmy, jak można utworzyć łańcuch znaków, który byłby
obiektem, i w większości przypadków nie trzeba tego robić jawnie, gdyż w razie
konieczności interpreter JavaScriptu utworzy dla nas obiekt łańcucha znaków.
No dobrze, zatem kiedy i dlaczego mógłby to robić? Przyjrzyjmy się życiu
przykładowych łańcuchów znaków.
var name = ”-anka”;
Tu utworzyliśmy trzy
łańcuchy będące danymi typu
prostego i zapisaliśmy je
w zmiennych.
var phone = ”867-5309”;
var fact = ”To liczba pierwsza”;
var songName = phone + ”/” + name;
A tu połączyliśmy kilka
z nich, tworząc kolejny
łańcuch będący daną typu
prostego.
A tu używamy metody.
To właśnie w tym miejscu,
zakulisowo, JavaScript
tymczasowo zmienia zawartość
zmiennej phone na obiekt
łańcucha znaków.
var index = phone.indexOf(”-”);
if (fact.substring(10, 18) === ”pierwsza”) {
alert(fact);
}
Tutaj ponownie
używamy zmiennej
fact, jednak w tym
przypadku nie trzeba
tworzyć obiektu, więc
znowu używamy
zwyczajnej, nudnej
danej typu prostego.
To samo dzieje się tutaj…
Łańcuch w zmiennej
fact zostaje tymczasowo
skonwertowany na obiekt,
który umożliwi skorzystanie
z metody substring.
NUDNA DANA TYPU PROSTEGO
OBIEKT Z SUPERMOCAMI
jesteś tutaj
323
Pytania o łańcuchy znaków
To jest bardzo
mylące. Mój łańcuch jest
konwertowany tam i z powrotem,
i raz jest daną typu prostego, a raz
obiektem? W jaki sposób mam nad
tym wszystkim zapanować?
Nie musisz. Ogólnie rzecz biorąc, możesz traktować swoje
łańcuchy znaków jak obiekty udostępniające wiele wspaniałych
metod, które ułatwiają Ci operowanie na tekście umieszczonym
w tych łańcuchach. JavaScript zajmie się wszystkimi niezbędnymi
szczegółami. A zatem popatrz na to w ten sposób: teraz już lepiej
rozumiesz, co się dzieje za kulisami JavaScriptu, jednak w swojej
codziennej pracy większość programistów bazuje na założeniu,
że JavaScript najlepiej wie, co należy zrobić (i robi to).
Nie istnieją
głupie pytania
P: Chcę się tylko upewnić…
Czy kiedykolwiek będę musiał
zwracać uwagę, czy mój łańcuch jest
traktowany jak dana typu prostego,
czy jak obiekt?
O: W większości przypadków — nie.
Interpreter JavaScript sam obsługuje
wszystkie niezbędne konwersje. Ty tylko
piszesz kod, zakładając w nim, że łańcuch
udostępnia właściwości i metody, i wszystko
powinno działać zgodnie z oczekiwaniami.
P: Dlaczego JavaScript obsługuje
łańcuchy jak dane typu prostego
oraz jak obiekty?
O: Pomyśl o tym w następujący sposób: jeśli
tylko robisz proste rzeczy, są one wykonywane
z wydajnością operacji na danych typów
prostych; chodzi o takie operacje jak
porównania, konkatenacja, zapis łańcucha
w DOM itd. Jeśli musisz wykonać bardziej
złożone operacje, możesz błyskawicznie
skorzystać z obiektu łańcucha znaków.
324
Rozdział 7.
P: A jak można się dowiedzieć,
czy dany łańcuch jest daną typu
prostego, czy obiektem?
O: Łańcuch jest zawsze daną typu
prostego, chyba że utworzyliśmy go
w specjalny sposób, używając konstruktora.
Konstruktory przedstawimy dalej w tej
książce. Oprócz tego zawsze możesz użyć
operatora typeof, by przekonać się, czy
zmienna jest typem String, czy obiektem.
P: Czy inne dane typów prostych też
mogą działać jak obiekty?
O: Tak, dotyczy to czasami także liczb oraz
wartości logicznych. Jednak żadne z nich
nie mają tak wiele użytecznych właściwości
i metod, co łańcuchy znaków, zatem
na pewno nie będziesz korzystał z tych
możliwości równie często jak w przypadku
łańcuchów. Pamiętaj także, że te wszystkie
konwersje są realizowane niezauważalnie,
zatem w praktyce wcale nie musisz zwracać
na nie uwagi. Po prostu użyj właściwości,
jeśli jest Ci potrzebna, i niech JavaScript
tymczasowo wykona niezbędną konwersję.
P: Gdzie mogę dowiedzieć się
o wszystkich właściwościach
i metodach dostępnych w obiekcie
łańcucha znaków?
O: Tu przyda się książka o charakterze
encyklopedycznym. Istnieje także wiele
witryn, na których możesz znaleźć te
informacje, lecz jeśli preferujesz książkę,
to dane o wszystkich właściwościach
i metodach języka JavaScript znajdziesz
w JavaScript: The Definitive Guide.
Wyszukiwarka Google także dobrze
spełni to zadanie.
Typy, równość, konwersje i cały ten jazz
Pięciominutowa wycieczka po metodach
(i właściwościach) łańcuchów znaków
Ponieważ właśnie jesteśmy w trakcie rozważań na temat łańcuchów znaków,
a oprócz tego właśnie dowiedziałeś się, że łańcuchy znaków udostępniają
metody, dlatego zdecydowaliśmy, by na chwilę przerwać rozważania
o różnych typach i przyjrzeć się najpopularniejszym metodom łańcuchów
znaków, których użyciem mógłbyś być zainteresowany. Kilka spośród tych
metod jest używanych bardzo często i zdecydowanie warto poświęcić czas,
żeby je poznać. A zatem zaczynamy wycieczkę.
moglibyśmy
Krótka pogadanka motywacyjna:
od
zupełnie odwrócić Twoją uwagę rozdział
zagadnienia typów i napisać cały
ści i metody
prezentujący wszystkie właściwo wiłoby
spra
tylko
Nie
ów.
łańcuchów znak
i miała
to, że ta książka ważyłaby 10 kg zy
2000 stron grubości, ale, zważyws
łnie
na to, co aktualnie umiesz, zupe znasz
—
byś tej wiedzy nie potrzebował
dach
już podstawowe informacje o metosz
chce
ę
awd
napr
jeśli
a
h,
i obiektac
łańcuchów
poznać szczegóły przetwarzania ej książki
znaków, potrzebujesz jedynie dobr
encyklopedycznej.
właściwość length
ów w łańcuchu. Jest bardzo wygodna,
Właściwość length przechowuje liczbę znak
y znak łańcucha.
kiedy trzeba w jakiś sposób przetworzyć każd
Używamy właściwości length,
aby sprawdzić każdy znak
łańcucha...
var input = ”janka#bardzosprytni.com.pl”;
{
for(var i = 0; i < input.length; i++)
{
”#”)
if (input.charAt(i) ===
cu o indeksie ” + i);
console.log(”Znak # znajduje siÚ w miejs
}
}
Konsola JavaScript
...oraz metody charAt, by pobrać
znak umieszczony w konkretnym
miejscu łańcucha.
=QDN#]QDMGXMHVLÚZPLHMVFX
o indeksie 5
metoda charAt
witej z zakresu od 0 do liczby znaków
Metoda charAt wymaga podania liczby całko
ch składający się z jednego znaku,
w łańcuchu (pomniejszonej o 1) i zwraca łańcu
znaków można sobie wyobrazić jako coś
zapisanego we wskazanym miejscu. Łańcuch
są zapisane poszczególne znaki łańcucha.
podobnego do tablicy, w której elementach
nają się od 0. Jeśli do metody charAt
Indeksy tej tablicy (jak i każdej innej) zaczy
ości łańcucha, zwróci ona łańcuch pusty.
przekażemy indeks większy lub równy dług
języku JavaScript nie zy
Zwróć uwagę, że w
entującego pojedync
rez
rep
ych
dan
u
ma typ
jako nowe łańcuchy
ane
rac
zw
są
aki
znak. Zn
k.
zna
en
jed
ko
tyl
zawierające
a b c Ge I
charAt(0)
zwróci „a”.
charAt(5)
zwróci „f”.
jesteś tutaj
325
Metoda indexOf
Metoda indexOf
indeks pierwszego
ący łańcuchem znaków i zwraca
Metoda ta pobiera argument będ
ała wywołana.
zost
oda
met
uchu, na rzecz którego
wystąpienia tego łańcucha w łańc
h znaków, na
To jest łańcuc wywołamy metodę
o
eg
ór
kt
z
rzec
indexOf.
coĂ innego?”;
var phrase = ”czy to kot, czy
A naszym celem jest znalezienie
pierwszego wystąpienia słowa „kot”.
);
var index = phrase.indexOf(”kot”
x);
zaczynajÈc od indeksu ” + inde
,
sano
console.log(”Sïowo kot zapi
Konsola JavaScript
Zwracany jest indeks pierwszego kota.
6ïRZRNRW]DSLVDQR]DF]\QDMÈF
od indeksu 7
określający
ć drugi argument
Można także doda którego zostaną rozpoczęte
indeks miejsca, od
poszukiwania.
7);
index = phrase.indexOf(”czy”,
index);
, zaczynajÈc od indeksu ” +
console.log(”Sïowo czy zapisano
Konsola JavaScript
Ponieważ rozpoczynamy poszukiwania od sze
znaku o indeksie 7, zatem pominiemy pierwe,
wystąpienie słowa „czy” i znajdziemy drugi
rozpoczynające się od znaku o indeksie 12.
6ïRZRF]\]DSLVDQR]DF]\QDMÈF
od indeksu 12
index = phrase.indexOf(”pies”);
index);
, zaczynajÈc od indeksu ” +
console.log(”Sïowo pies zapisano
Zauważ, że jeśli nie udało się znaleźć
–1.
łańcucha znaków, metoda zwraca wartość
326
Rozdział 7.
Konsola JavaScript
6ïRZRSLHV]DSLVDQR]DF]\QDMÈF
od indeksu -1
Typy, równość, konwersje i cały ten jazz
Metoda substring
Do metody substring należy przekaza
ć dwa indeksy, a ona pobierze i zwróci
łańcuch
znaków zawarty pomiędzy nimi.
aków,
To jest łańcuch zn wołamy
wy
o
reg
któ
cz
na rze
var data = ”imiÚ|telefon|adres”;
metodę substring.
Interesuje nas fragment łańcucha
var val = data.substring(5, 12);
zaczynający się od indeksu 5
console.log(”Wybrany fragment to:
i
” + val); kończący przed indeksem 12.
Otrzymujemy nowy łańcuch składający
się ze znaków o indeksach od 5 do 12.
Konsola JavaScript
Wybrany fragment to: telefo
n
W wywołaniu metody substring można
pominąć drugi indeks i w takim przypadk
u
metoda zwróci fragment rozpoczynają
cy się od miejsca określonego pierwszy
m
indeksem i obejmujący całą pozostałą
część łańcucha.
val = data.substring(5);
console.log(”Teraz pobranym fragment
em jest: ” + val);
Konsola JavaScript
Teraz pobranym fragmentem
jest:
telefon|adres
Metoda split
li łańcuch znaków
ków pełniący rolę separatora i dzie
Metoda split pobiera łańcuch zna
owania.
paratora,
rzysta z se uch
na części w miejscach jego występ
oda split ko
ńc
ła
Met
początkowy
ą
by podzielić agmenty, które zostan
fr
znaków na formie tablicy.
zwrócone w
var data = ”imiÚ|telefon|adres”;
var vals = data.split(”|”);
nym ïañcuchem ”, vals);
console.log(”Oto tablica z podzielo
Zwróć uwagę, że tym razem do
metody console.log przekazujemy dwa
argumenty oddzielone przecinkiem.
Dzięki temu tablica vals nie zostanie
skonwertowana na łańcuch znaków
przed jej przekazaniem do metody.
Konsola JavaScript
2WRWDEOLFD]SRG]LHORQ\PïDñFXFKHP>LPLÚWHOHIRQDGUHV@
jesteś tutaj
327
Metody łańcuchów znaków
Zupa
¦KħM_MRYaK
toLowerCase
Zwraca łańcuch, w którym
wszystkie wielkie litery
zostały zamienione na
małe.
cucha
Znajduje fragmenty łań
i.
i zastępuje je innym
replace
lastIndexOf
Zwraca nowy łańcuc
h
będący łańcuchem
początkowym, z któreg
o
został usunięty
określony fragment.
Łączy ze sobą łańcuc
hy
znaków.
indexOf,
Działa jak metoda zego
ws
er
pi
ale zamiast
nego
wystąpienia poda e jego
uj
ajd
zn
tu
en
gm
fra
ienie.
ostatnie wystąp
slice
at
conc
match
Zwraca łańcuch, w którym
wszystkie małe litery
zostały zamienione na
wielkie.
Próbuje znaleźć w łańcuchu
fragmenty pasujące do
wyrażenia regularnego.
t
Zwraca fragmen
łańcucha.
ing
tr
subs
trim
z początku
białe znakizo wygodna
.
w
tz
a
w
ard
Usu
nych
ńcucha. B
i końca ła u przetwarzania danika.
dk
ow
a
tk
yp
uży
w prz
nych przez
wprowadza
ase
rC
ppe
toU
3R]QDQLHZV]\VWNLFKU]HF]\NWyUHPRİQDURELþ
]ãDęFXFKDPL]QDNyZPRJãRE\WUZDþFDãĈ
ZLHF]QRĤþ3RZ\İHMSU]HGVWDZLOLĤP\NLOND
NROHMQ\FKGRVWčSQ\FKPHWRG1DUD]LHW\ONR
SU]\MU]\MVLčLPDSyĮQLHMNLHG\EčG]LHV]
LFKSRWU]HERZDãSRV]XNDV]V]F]HJyãRZ\FK
LQIRUPDFMLQDLFKWHPDW
328
Rozdział 7.
Typy, równość, konwersje i cały ten jazz
Wojna o fotel
Specyfikacja
(albo jak dobra znajomość typów może zmienić Twoje życie)
Dawno temu, w firmie zajmującej się tworzeniem oprogramowania dwóch
programistów dostało specyfikację i kazano im „napisać to”. Naprawdę
Denerwujący Kierownik Projektu zmusił obu programistów do rywalizacji,
obiecując, że ten, który wykona zadanie jako pierwszy, otrzyma jeden z tych
fantastycznych foteli Aeron™, jakich używają wszyscy programiści w Dolinie
Krzemowej. Zarówno Bartek, hardcorowy haker skryptowy, jak i Wawrzyniec,
dyplomowany programista, wiedzą, że zadanie będzie proste jak przysłowiowa
kaszka z mleczkiem.
Wawrzyniec, siedząc w swoim boksie, pomyślał: „Co ten kod musi robić?
Musi się upewnić, że łańcuch będzie dostatecznie długi, musi się upewnić,
że środkowym znakiem będzie minus oraz musi się upewnić, że wszystkimi
pozostałymi znakami są cyfry. Mogę w tym celu użyć właściwości length.
Wiem także, że poszczególne znaki mogę pobierać przy użyciu metody charAt.
pisany
a
fonu z
r tele
nume
Mamy cie:
a
w form
”
7
6
5
4
“123
y go
d, któr Aby numer
o
k
ć
a
apis
zuci.
Masz n tuje lub odr , musi się
y
p
e
a
c
k
w
to n
zaa
do 9,
aakcep iu cyfr od 0
z
ł
a
t
s
s.
zo
dm
ć minu
ć z sie
składa ku musi mie
d
a w śro
W międzyczasie Bartek, siedząc w swoim boksie i popijając kawę, pomyślał
tak: „Co ten kod musi robić?”. A potem pomyślał jeszcze: „Łańcuch znaków
jest obiektem i ma dużo metod, z których mogę skorzystać, by ułatwić sobie
weryfikację numeru telefonu. Przejrzę je i błyskawicznie zaimplementuję ten
kod. W końcu obiekt to tylko obiekt”. Czytaj dalej, aby przekonać się jak
Bartek i Wawa napisali swoje programy, no i oczywiście po to, by znaleźć
odpowiedź na intrygujące zapewne pytanie, który z nich wygrał fotel Aeron?
0Y^OV+O\YX
W boksie Wawy
Wawa zaczął pisać kod, wykorzystując metody obiektów łańcuchów znaków.
Faktycznie, napisał go błyskawicznie.
function validate(phoneNumber) {
if (phoneNumber.length !== 8) {
Wawrzyniec używa właściwości length,
by sprawdzić długość łańcucha.
return false;
}
for (var i = 0; i < phoneNumber.length; i++) {
if (i === 3) {
if (phoneNumber.charAt(i) !== IJ-ij) {
return false;
Korzysta także z metody charAt, aby
sprawdzić poszczególne znaki łańcucha.
Najpierw upewnia się,
że znak o indeksie 3
jest minusem.
}
} else if (isNaN(phoneNumber.charAt(i))) {
return false;
Następnie sprawdza, czy znaki
o indeksach do 0 do 2 i od 4 do 7
są cyframi.
}
}
return true;
}
jesteś tutaj
329
Stosowanie łańcuchów znaków i operacje na nich
W boksie Bartka
Bartek napisał kod sprawdzający dwie liczby i znak minusa.
Bartek zaczyna podobnie jak
Wawrzyniec.
ści
Jednak korzysta ze swojej znajomo
ów.
metod obiektów łańcuchów znak
Używa metody substring, by utworzyć
łańcuch zawierający trzy pierwsze
znaki początkowego łańcucha...
function validate(phoneNumber) {
if (phoneNumber.length !== 8) {
return false;
}
var first = phoneNumber.substring(0,3);
var second = phoneNumber.substring(4);
...oraz drugiego łańcucha zawierającego
znaki o indeksach od 4 do końca łańcucha
początkowego.
if (phoneNumber.charAt(3) !== ”-” || isNaN(first) || isNaN(second)) {
return false;
}
return true;
}
Następnie w jednej
instrukcji warunkowej
sprawdza wszystkie
warunki określające, czy
numer jest prawidłowy.
je tu na
omie bądź nie bazu
Co ciekawe, świad zamienić łańcuch na liczbę,
by
,
ów
, sprawdza,
konwersji typ
ając funkcji isNaN
a następnie, używ jest liczbą. Chytrze!
ł,
czy to, co uzyska
Chwileczkę! Wprowadzono zmiany w specyfikacji
„No dobrze, technicznie rzecz biorąc, Wawa, to ty byłeś pierwszy, bo Bartek
szukał w dokumentacji, jak działają te jego wszystkie metody — powiedział
kierownik projektu — ale w międzyczasie dodaliśmy do specyfikacji tylko jedną
malutką zmianę. Nie będzie stanowiła najmniejszego problemu dla takich
zdolnych programistów jak wy”.
„Gdybym dostał choćby dziesięć groszy za każdym razem, kiedy to słyszę,
byłbym milionerem” — pomyślał Wawrzyniec, wiedząc, że zmiany specyfikacji,
które nie nastręczają problemów, są czystą fikcją. „Mimo to Bartek wygląda
na dziwnie spokojnego. O co chodzi?” — Wawrzyniec wciąż wierzył swojej
opinii, że rozwiązanie Bartka, choć ciekawe, jednak nie zda egzaminu. Był
przekonany, że w następnej rundzie wygra i dostarczy kod jako pierwszy.
WYSIL
SZARE KOMÓRKI
Chwileczkę… Czy możesz wskazać jakieś
błędy, które mogą wynikać ze sposobu
użycia funkcji isNaN zastosowanego
przez Bartka?
y
u zapisan
n
mer telefo
Mamy nu
ie:
w formac
”
7
6
5
4
3
2
1
“
który go
isać kod,
numer
Masz nap lub odrzuci. Aby
je
ę
zaakceptu ptowany, musi si
9,
kce
został zaa dmiu cyfr od 0 do
e
minus.
si
ć
z
ć
ra
a
ie
skład
że zaw
o
m
ie
ln
a
a opcjon
330
Rozdział 7.
To zostało dodane do specyfikacji.
Typy, równość, konwersje i cały ten jazz
Ponownie w boksie Wawy
Wawrzyniec pomyślał, że może wykorzystać fragmenty już istniejącego kodu; musi
jedynie uwzględnić warunki brzegowe związane z brakującym znakiem minusa. Obecnie
numer może się składać bądź to wyłącznie z siedmiu cyfr, bądź też jak wcześniej będzie
miał osiem znaków, z czego siedem będzie cyframi, a znak o indeksie 3 będzie minusem.
Wawa szybko zakodował te nowe zmiany (które wymagały przetestowania, by wszystko
działało, jak należy).
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) !== IJ-ij) {
return false;
Wawrzyniec musiał wprowadzić
kilka
zmiana do logiki działania swojego
kodu. Zmiany nie wymagały doda
rozbudowanego kodu, jednak po wania
ich
wprowadzeniu kod stał się trochę
trudny do zrozumienia.
} else if (phoneNumber.length === 7 &&
isNaN(phoneNumber.charAt(i))) {
return false;
}
} else if (isNaN(phoneNumber.charAt(i))) {
return false;
}
}
return true;
}
Bartek ze swoim laptopem na plaży
Bartek uśmiechnął się, pociągnął łyczek margarity i szybko wprowadził
niezbędne zmiany. Po prostu zmienił sposób pobierania drugiej części
numeru, wyliczając jej początek jako długość łańcucha pomniejszoną
o 4, zamiast, jak było wcześniej, podawać jej konkretną pozycję
początkową. To niemal wystarczyło, jednak musiał także zmienić
test sprawdzający występowanie znaku minusa, gdyż teraz można go
wykonywać wyłącznie wtedy, gdy numer ma osiem znaków długości.
jesteś tutaj
331
Rozmyślania o łańcuchach znaków
function validate(phoneNumber) {
Bartek wprowadził mniej więcej tyle
samo zmian, co Wawa, jednak jego kod
jest wyraźnie łatwiejszy do analizy.
if (phoneNumber.length > 8 ||
phoneNumber.length < 7) {
Teraz Bartek pobiera drugą część numeru
telefonu, wyznaczając początek na
podstawie całkowitej długości łańcucha.
return false;
}
var first = phoneNumber.substring(0,3);
var second = phoneNumber.substring(phoneNumber.length - 4);
A występowanie minusa sprawdza
wyłącznie wtedy, gdy cały łańcuch
ma osiem znaków długości.
if (isNaN(first) || isNaN(second)) {
return false;
}
WYSIL
if (phoneNumber.length === 8) {
SZARE KOMÓRKI
return (phoneNumber.charAt(3) === ”-”);
}
return true;
}
W tym miejscu zwracamy
wynik przetworzenia wyrażenia
warunkowego, czyli wartość true
lub false.
Wawa zdążył tuż przed Bartkiem
Jednak pogardliwy uśmieszek na twarzy Wawy zniknął, kiedy
Naprawdę Denerwujący Kierownik Projektu powiedział: „Bartek,
Twój kod jest naprawdę bardzo czytelny i prosty do utrzymania.
Dobra robota”.
Myślimy, że w kodzie Bartka cały czas jest
jeden błąd. Czy potrafisz go znaleźć?
WYSIL
SZARE KOMÓRKI
W jaki sposób zmodyfikowałbyś kod Bartka
tak, by korzystał z metody split?
Jednak Wawa nie ma się o co martwić, gdyż wie, że przejrzystość
i elegancja kodu to nie wszystko. Ten kod wciąż musi przejść
przez kontrolę jakości, a my wciąż nie jesteśmy do końca pewni,
czy kod Bartka dobrze działa we wszystkich przypadkach. A co
z Tobą? Jak sądzisz, który z programistów zasługuje na fotel?
Ta niepewność mnie zabija
Kto wygrał fotel?
Ania z drugiego piętra.
(Nie informując o tym naszych
bohaterów, Kierownik Projektu
przekazał specyfikację trzem
programistom).
d Ani.
A to jest ko
332
Rozdział 7.
Rany! Jeden wiersz?!
Sprawdź, jak to działa,
w dodatku na końcu książki!
function validate(phoneNumber) {
return phoneNumber.match(/A\d{3}-?\d{4}$/);
}
Typy, równość, konwersje i cały ten jazz
Z POWROTEM W LABORATORIUM
=DáRJDODERUDWRULXPEH]XVWDQQLHNRQW\QXXMHWHVWRZDQLHMĊ]\ND
-DYD6FULSW]Z\NRU]\VWDQLHPRSHUDWRUDtypeofLG]LĊNLWHPXXGDMHLP
VLĊF]DVDPLRGNU\ZDüXNU\WHJG]LHĞJáĊERNRQDSUDZGĊLQWHUHVXMąFH
U]HF]\:WUDNFLHEDGDĔXGDáRLPVLĊRGNU\ü]XSHáQLHQRZ\RSHUDWRU
²instanceof1DV]HODERUDWRULXP]QDOD]áRVLĊZVDPHMDZDQJDUG]LH
EDGDĔ8ELHU]]DWHPODERUDWRU\MQ\IDUWXFK]DáyĪRFKURQQHRNXODU\
LVSUDZGĨF]\EĊG]LHV]XPLDáRGV]\IURZDüSRQLĪV]\NRGLSRGDüMHJR
Z\QLNL2VWU]HĪHQLHEH]GZyFK]GDĔEĊG]LHWRQDMEDUG]LHM]ZDULRZDQ\
NRGMDNLGRWHMSRU\ZLG]LDáHĞ
A oto i kod. Przeczytaj go, wykonaj,
zmodyfikuj, wygeneruj komunikat podczas
wykonywania, sprawdź, co on robi…
Jakie to dziwaczne. Czy to nie wygląda
jak połączenie funkcji i obiektu?
function Duck(sound) {
this.sound = sound;
this.quack = function() {console.log(this.sound);}
}
Hmm… „new”. Czegoś takiego jeszcze nie
var toy = new Duck(”kwa kwa”);
toy.quack();
console.log(typeof toy);
console.log(toy instanceof Duck);
widzieliśmy. Ale przypuszczamy, że powinniśmy to
zrozumieć tak: utwórz nowe coś o nazwie Duck
i zapisz to coś w zmiennej toy.
Jeśli to coś wygląda jak obiekt i chodzi jak obiekt…
przetestujmy to.
No dobrze, i tu się pojawia
operator instanceof.
Konsola JavaScript
zoną
wiedź zamieszc
wdź naszą odpo
ra
sp
ko
st
ie
zy
zn
ws
ec
ni
to
Ko
rozdziału. Ale co
pod koniec tego , wszystko się wyjaśni w kilku
aw
jeszcze nie
znaczy? Bez ob
h. I jakbyś tego na drodze
ac
ał
zi
zd
ro
ch
następny
m daleko
edłeś już całkie
zauważył: zasz
ym programistą
tn
ie
św
dę
praw
do stania się na poważne sprawy!
są
JavaScript. To
Tutaj zapisz uzyskane
wyniki. Czy coś w nich
Cię zaskoczyło?
jesteś tutaj
333
Celne spostrzeżenia
CELNE SPOSTRZEŻENIA
Q
W języku JavaScript istnieją dwie grupy
typów, typy proste oraz obiekty. Każda
wartość, która nie jest typu prostego, jest
obiektem.
Q
Typami prostymi są: liczby, łańcuchy
znaków, wartości logiczne, null oraz
undefined. Wszystkie inne dane są
obiektami.
Q
334
Rozdział 7.
undefined oznacza, że zmienna
(albo właściwość bądź element tablicy)
nie została jeszcze zainicjalizowana
— nie przypisano jej wartości.
Q
null oznacza, że w zmiennej obiektowej
jeszcze nie zapisano obiektu.
Q
„NaN” to skrót od angielskich słów: „Not
a Number” (to nie liczba), choć wartość
tę lepiej sobie wyobrazić jako „liczbę,
której w języku JavaScript nie można
reprezentować”. Typem wartości NaN
jest ”number”.
Q
Wartość NaN nigdy nie jest równa żadnej
innej wartości, nawet samej sobie, a zatem
aby sprawdzić, czy zmienna zawiera tę
wartość, należy użyć funkcji isNaN.
Q
Równość dwóch wartości można
sprawdzać przy użyciu operatorów ==
oraz ===.
Q
Jeśli typy operandów będą różne, operator
równości (==) przed wykonaniem
porównania spróbuje skonwertować jeden
z operandów na typ drugiego operandu.
Q
Jeśli typy operandów są różne, ścisły
operator równości (===) zawsze zwróci
false.
Q
Możesz używać operatora ===, aby
mieć pewność, że nie zostaną wykonane
żadne konwersje typów; niemniej
jednak konwersje wykonywane podczas
stosowania operatora == czasami mogą
być użyteczne i wygodne.
Q
Konwersje typów są także wykonywane
podczas stosowania innych operatorów,
takich jak operatory arytmetyczne lub
operator konkatencji łańcuchów znaków.
Q
JavaScript zawiera pięć wartości
traktowanych jak logiczny fałsz; są to
undefined, null, 0, ”” (pusty łańcuch
znaków) oraz NaN. Wszystkie pozostałe
wartości są traktowane jak logiczna
prawda.
Q
Łańcuchy znaków czasami stają się
obiektami. Jeśli użyjemy właściwości lub
wywołamy metodę na rzecz łańcucha
będącego daną typu prostego, JavaScript
automatycznie i chwilowo skonwertuje
ten łańcuch na obiekt, następnie użyje
jego właściwości lub wywoła metodę, po
czym ponownie skonwertuje go na daną
typu prostego. Wszystkie te konwersje
są wykonywanie niezauważalnie dla
programisty, dzięki czemu nie musisz
o nich myśleć.
Q
Łańcuchy znaków dysponują wieloma
metodami, które są bardzo przydatne
podczas operacji na nich i ich
przetwarzania.
Q
Dwa obiekty są równe wyłącznie
w przypadku, gdy zmienne zawierające
referencje wskazują ten sam obiekt.
Typy, równość, konwersje i cały ten jazz
L. PMHVWHP"
LH
Q
D
]
È
L
Z
]
R
5
Grupa wartości JavaScript oraz niezaproszonych gości, przebrana
w kostiumy, bawi się w grę towarzyską o nazwie „Zgadnij, kim jestem”.
Wszyscy podają podpowiedź, a Ty na jej podstawie starasz się powiedzieć,
kim jest dana osoba. Możesz założyć, że zawsze mówią o sobie prawdę.
Narysuj strzałkę od zdania do nazwy uczestnika zabawy. Dla ułatwienia
narysowaliśmy już jedną strzałkę.
Poniżej przedstawiliśmy nasze rozwiązanie.
Uczestnicy
G]LVLHMV]HM]DEDZ\
Jestem zwracana przez funkcję, w której nie ma instrukcji return.
zero
pusty obiekt
Jestem wartością zmiennej, kiedy nie zostałam do niej przypisana.
null
Jestem wartością elementu, który nie istnieje w tablicy rzadkiej.
undefined
NaN
Jestem wartością nieistniejącej właściwości.
Infinity
Jestem wartością usuniętej właściwości.
Obszar 51
... ___ ...
Jestem wartością, której nie można przypisać właściwości
{}
podczas tworzenia obiektu.
[]
jesteś tutaj
335
W laboratorium
LH
È]DQ
W laboratorium
L
Z
]
UR
:ODERUDWRULXPOXELP\UR]ELHUDüZV]\VWNRQDF]\QQLNLSLHUZV]H
]DJOąGDüSRGPDVNĊSU]\SDWU\ZDüVLĊLDQDOL]RZDüSRGáąF]Dü
QDU]ĊG]LDGLDJQRVW\F]QHLVSUDZG]DüFRVLĊQDSUDZGĊG]LHMH
']LĞ]DMPLHP\VLĊV\VWHPHPW\SyZMĊ]\ND-DYD6FULSW3U]\WHM
RND]MLXGDáRVLĊQDP]QDOHĨüQLHZLHONLHQDU]ĊG]LHGLDJQRVW\F]QH
GREDGDQLD]PLHQQ\FK²typeof$]DWHPXELHU]IDUWXFK]DáyĪ
RNXODU\RFKURQQHL]DF]QLM]QDPLSUDFĊZODERUDWRULXP
typeofMHVWMHGQ\P]ZEXGRZDQ\FKRSHUDWRUyZMĊ]\ND-DYD6FULSW
0RĪQDJRXĪ\ZDüGRVSUDZG]DQLDW\SXSRGDQHJRRSHUDQGX
F]\OLWHJRF]HJRĞFRVSUDZG]DV]SU]\XĪ\FLXRSHUDWRUD 3RQLĪHMSRND]DOLĞP\SU]\NáDGMHJR]DVWRVRZDQLD
var subject = ”To tylko ïañcuch znaköw”;
var probe = typeof subject;
console.log(probe);
Operator typeof ma jeden operand,
a wynikiem jego działania jest typ
tego operandu.
Konsola JavaScript
W tym przypadku typem operandu jest
„string”. Zwróć uwagę, że wyniki działania string
operatora typeof są łańcuchami znaków,
takimi jak „string”, „boolean”, „number”,
„object”, „undefined” itd.
$WHUD]7ZRMDNROHM=DQRWXMGDQH]
QDVWĊSXMąF\FKHNVSHU\PHQWyZ
var test1 = ”abcdef”;
var test2 = 123;
var test3 = true;
To są dane testowe
var test4 = {};
oraz same testy.
var test5 = [];
var test6;
var test7 = {”abcdef”: 123};
var test8 = [”abcdef”, 123];
function test9(){return ”abcdef”};
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);
336
Rozdział 7.
Konsola JavaScript
string
number
boolean
object
object
undefined
object
object
function
To są nasze wyniki.
Typy, równość, konwersje i cały ten jazz
Q
=SRZURWHPZODERUDWRULXP
LÈ]D
5
R]Z
LH
8SV=DSRPQLHOLĞP\RQXOOZQDV]\FKHNVSHU\PHQWDFK
3RQLĪHM]DPLHĞFLOLĞP\EUDNXMąF\WHVW
Konsola JavaScript
var test10 = null;
console.log(typeof test10);
Ćwiczenie
Rozwiązanie
A oto nasz wynik.
object
W tym rozdziale przyglądaliśmy się pewnym interesującym wartościom. A teraz przyjrzymy się
pewnemu interesującemu zachowaniu. Spróbuj dodać poniższy fragment kodu do elementu
<scirpt> w jakiejś prostej stronie i sprawdź, co zostanie wyświetlone w oknie konsoli po
otworzeniu strony w przeglądarce. Nie zrozumiesz jeszcze, dlaczego wynik jest taki, a nie inny,
ale warto, żebyś spróbował odgadnąć, co się stanie.
if (99 == ”99”) {
console.log(”Liczba jest röwna ïañcuchowi znaköw!”);
} else {
console.log(”Przecieĝ liczba nie bÚdzie röwna ïañcuchowi.”);
Konsola JavaScript
Liczba jest röwna ïañcuc
howi
znaköw!
}
A to uzyskaliśmy.
jesteś tutaj
337
Rozwiązania ćwiczeń
Zaostrz ołówek
Rozwiązanie
Dla każdego z przedstawionych poniżej porównań zapisz wynik
w kolumnach pod operatorem == oraz ===.
== = ==
”42” == 42
true
false
_________
_________
”42” === 42
”0” == 0
true
false
_________
_________
”0” === 0
”0” == false
false
true
_________
_________
”0” === false
”true” == true
false
false
_________
_________
”true” === true
true == (1 == ”1”)
true
false
_________
_________
true === (1 === ”1”)
Podchwytliwe!
Jeśli zamienisz oba ope
warunek zwróci warto ratory == na ===,
ść false.
:
?
7
KTO CO ROBI?
7
7
ROZWIĄZANIE
Już mieliśmy gotowe opisy tych wszystkich operatorów, ale przez nieuwagę zupełnie się wymieszały.
Czy możesz nam pomóc w określeniu, co robi każdy z nich? Uważaj, nie jesteśmy pewni, czy
poszczególne operatory pasują tylko do jednej odpowiedzi, czy też do kilku, a może nawet do żadnej.
Udało się nam już znaleźć jedną odpowiedź, którą zaznaczyliśmy poniżej.
=
Jeśli
porównujemy
obiekty, możemy
używać obu tych
operatorów!
==
===
Nie ma takiego
operatora.
338
Rozdział 7.
====
Porównuje wartości, by sprawdzić, czy są sobie równe. Jest uważany
za operator równości. Zada sobie nawet trud skonwertowania typów
operandów, aby sprawdzić, czy wartości są naprawdę równe.
Porównuje dwie wartości, by sprawdzić, czy są równe. Ten gość
nawet nie zada sobie trudu, by sprawdzać wartości różnych typów.
Przypisuje zmiennej podaną wartość.
Porównuje referencje obiektów i zwraca true, jeśli są takie same;
w przeciwnym razie zwraca false.
Typy, równość, konwersje i cały ten jazz
Zaostrz ołówek
Rozwiązanie
Obok każdego z przedstawionych poniżej wyrażeń zapisz jego wynik. Jedno działanie wykonaliśmy za Ciebie.
Poniżej zamieściliśmy rozwiązanie.
Infinity - "1"
"42" + 42
Infinity
“4242”
2 + "1 1"
“21 1”
99 + 101
200
"1" - "1"
0
console.log("Wynik: " + 10/2)
3 + " banany i " + 2 + " jabïka"
ne
“1” jest konwertowa to
na 1, a Infinity – 1
wciąż Infinity.
Oba łańcuchy
znaków zostają
skonwertowane na 1,
a 1 – 1 daje 0.
“Wynik: 5”
“3 banany i 2 jabłka”
Najpierw jest
wykonywanie dzielenie
10/2, a następnie
jego wynik zostaje
dołączony do łańcucha
“Wynik: “
Każdy + jest operatorem
konkatenacji, gdyż dla każdego
z nich jeden operand jest
łańcuchem.
jesteś tutaj
339
Rozwiązanie zadania
Zaostrz ołówek
Rozwiązanie
Nadszedł czas na szybki test wykrywaczem kłamstw. Określ, czy przestępca mówi prawdę,
czy kłamie oraz czy jest winny zarzucanych mu czynów. W tym celu musisz określić, które
wartości będą prawdą, a które fałszem. Poniżej przedstawiamy nasze rozwiązanie.
Czy sprawdziłeś działanie tego kodu w przeglądarce?
function lieDetectorTest() {
var lies = 0;
Każdy obiekt jest
uznawany za prawdę,
nawet pusty.
ww
w.
Fr
ikS
ha
re.
pl
var stolenDiamond = { };
if (stolenDiamond) {
console.log(”UkradïeĂ diament!”);
lies++;
}
ukradł samochodu, t
var car = {
Ten przestępca nie
wości keysInPocke
ści
wła
ą
gdyż wartości
keysInPocket: null
nawane za
uz
t
jes
jest null, a null
};
logiczny fałsz.
if (car.keysInPocket) {
console.log(”Oo, ukradïeĂ takĝe samochöd!”);
Nasz przestępca nie prowadził
lies++;
także samochodu, ponieważ
}
właściwość emptyGasTank ma
if (car.emptyGasTank) {
wartość undefined, a także ona
jest traktowana jak logiczny fałsz.
console.log(”Takĝe jechaïeĂ ukradzionym samochodem!”);
lies++;
Ale [] (pusta tablic
}
traktowana jak log a) jest
var foundYouAtTheCrimeScene = [ ];
zatem nasz ptaszeiczna prawda,
złapany na miejs k został
if (foundYouAtTheCrimeScene) {
cu przestępstwa.
console.log(”To oczywisty dowöd przestÚpstwa!”);
lies++;
Tablica jest pusta, zatem jej element
}
o indeksie 0 ma wartość undefined,
if (foundYouAtTheCrimeScene[0]) {
która jest traktowana jak fałsz. Hm…
musiał już gdzieś ukryć
przestępca
console.log(”Znaleziono przy tobie ukradziony diament!”);
swój łup.
lies++;
}
To łańcuch składający się
var yourName = ” ”;
z jednego znaku odstępu.
if (yourName) {
Konsola JavaScript
console.log(”Dodatkowo skïamaïeĂ, podajÈc swoje personalia”);
8NUDGïHĂGLDPHQW
lies++;
Każdy łańcuch znaków, z wyjątkiem
7RRF]\ZLVW\GRZöGSU]HV
}
łańcucha pustego, jest traktowany jak
WÚSVWZD
'RGDWNRZRVNïDPDïHĂSRGDMÈ
prawda, nawet jeśli będzie się składa
return lies;
FVZRMHSHUV
tylko z jednego znaku odstępu!
}
var numberOfLies = lieDetectorTest();
console.log(”SkïamaïeĂ ” + numberOfLies + ” razy!”);
if (numberOfLies >= 3) {
Liczba kłamstw wyniosła
console.log(”Winny wszystkich zarzutöw!”); 3, zatem uznajemy, że
}
przestępca jest winny.
340
Rozdział 7.
6NïDPDïHĂUD]\
Winny wszystkich zarzutów!
RQDOLD
Typy, równość, konwersje i cały ten jazz
Zaostrz ołówek
Rozwiązanie
Poniżej zamieściliśmy prosty kod, który ułatwia odnajdywanie aut na
parkingu komisu Edka. Przeanalizuj kod i napisz poniżej, jakie będą wartości
zmiennych od loc1 do loc4.
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: ”SieMoCorp”,
model: ”Taxi”
};
var fiat1 = {
make: ”Fiat”,
model: ”500”
};
var fiat2 = {
make: ”Fiat”,
model: ”500”
};
Tutaj podaliśmy nasze
odpowiedzi.
Edek, szef
komisu Auta
Edka.
var lot = [chevy, taxi, fiat1, fiat2];
3
var loc1 = findCarInLot(fiat2); _______________
var loc2 = findCarInLot(taxi);
_______________
1
var loc3 = findCarInLot(chevy); _______________
0
2
var loc4 = findCarInLot(fiat1); _______________
jesteś tutaj
341
Wyniki z laboratorium
Z POWROTEM W LABORATORIUM
]DQLH
]ZLÈ
=DáRJDODERUDWRULXPEH]XVWDQQLHNRQW\QXXMHWHVWRZDQLHMĊ]\ND
-DYD6FULSW]Z\NRU]\VWDQLHPRSHUDWRUDtypeofLG]LĊNLWHPXXGDMHLPVLĊ
F]DVDPLRGNU\ZDüXNU\WHJG]LHĞJáĊERNRQDSUDZGĊLQWHUHVXMąFHU]HF]\
:WUDNFLHEDGDĔXGDáRLPVLĊRGNU\ü]XSHáQLHQRZ\RSHUDWRU²instanceof
1DV]HODERUDWRULXP]QDOD]áRVLĊZVDPHMDZDQJDUG]LHEDGDĔ8ELHU]]D
WHPODERUDWRU\MQ\IDUWXFK]DáyĪRFKURQQHRNXODU\LVSUDZGĨF]\EĊG]LHV]
XPLDáRGV]\IURZDüSRQLĪV]\NRGLSRGDüMHJRZ\QLNL2VWU]HĪHQLH
EH]GZyFK]GDĔEĊG]LHWRQDMEDUG]LHM]ZDULRZDQ\NRGMDNLGRWHMSRU\
ZLG]LDáHĞ
UR
A oto i kod. Przeczytaj go, wykonaj,
zmodyfikuj, wygeneruj komunikat podczas
wykonywania, sprawdź, co on robi…
Jakie to dziwaczne. Czy to nie wygląda
jak połączenie funkcji i obiektu?
function Duck(sound) {
this.sound = sound;
this.quack = function() {console.log(this.sound);}
}
var toy = new Duck(”kwa kwa”);
toy.quack();
console.log(typeof toy);
console.log(toy instanceof Duck);
Hmm… „new”. Czegoś takiego jeszcze nie
widzieliśmy. Ale przypuszczamy, że powinniśmy
to zrozumieć tak: utwórz nowe coś o nazwie Duck
i zapisz to coś w zmiennej toy.
Jeśli to coś wygląda jak obiekt i chodzi jak obiekt…
przetestujmy to.
No dobrze, i tu się pojawia
operator instanceof.
aw, wszystko
znaczy? Bez ob
Co to wszystko
rozdziałach.
kilku następnych ył: zaszedłeś
się wyjaśni w
aż
uw
za
e
szcze ni
stania się
I jakbyś tego je
ko na drodze do
le
da
m
ie
łk
JavaScript.
już ca
ą
st
mi
ra
ym prog
naprawdę świetn wy!
spra
To są poważne
Konsola JavaScript
kwa kwa
Zmienna toy działa
jak obiekt… Możemy
wywoływać jej metody.
object
true
Zatem jej typem będzie
object.
Jednak jest to „instancja”
Duck… Cokolwiek by to
miało znaczyć…
Tutaj podaliśmy nasze
wyniki.
342
Rozdział 7.
8.’ÈF]HQLHZV]\VWNLHJRZFDïRĂÊ
Tworzenie aplikacji
Podziwiam sposób,
w jaki połączyłaś wszystkie
składniki, by utworzyć coś
naprawdę smakowitego.
I słusznie!
Napawaj oczy tym
widokiem! Ciasto
jest gotowe.
Włóż to do swojego przybornika z narzędziami, czyli do skrzynki, w której
umieszczasz wszystkie swoje nowe umiejętności programistyczne, wiedzę dotyczącą
DOM, a nawet HTML i CSS. W tym rozdziale wykorzystasz wszystkie te umiejętności,
by utworzyć pierwszą prawdziwą aplikację internetową. Wystarczy już prymitywnych
namiastek gier, w których na jednowymiarowej planszy pływa jeden okręt. W tym
rozdziale wykonasz pełne doświadczenie: dużą, atrakcyjną planszę do gry, wiele
okrętów oraz obsługę wprowadzania danych przez użytkownika. Struktura strony, na
której będzie prowadzona gra, powstanie w języku HTML, jej wygląd określony zostanie
przy użyciu stylów CSS, a zachowanie samej gry napisane w języku JavaScript. Przygotuj się:
to będzie jazda na całego, rozdział, w którym pełnym gazem będziesz zmierzać
w kierunku pisania naprawdę poważnego kodu.
to jest nowy rozdział
343
Pisanie prawdziwej gry w okręty
Tym razem napiszemy PRAWDZIWĄ grę w okręty
Bez wątpienia możesz czuć satysfakcję, gdyż już w rozdziale 2. napisałeś małą, milutką
grę w okręty i to zupełnie od zera. Jednak sam musisz przyznać, że była to raczej zabawka,
choć działała i można było w nią grać, nie była czymś, czym mógłbyś pochwalić się przed
znajomymi lub czego użyłbyś do szukania dofinansowania w funduszach inwestycyjnych.
Aby wywrzeć naprawdę duże wrażenie, potrzebujesz prawdziwej gry planszowej,
z odjazdową grafiką okrętów oraz możliwością podawania ruchów bezpośrednio
w grze (a nie wpisywania ich w jakimś ogólnym oknie dialogowym przeglądarki).
Chcesz też poprawić poprzednią wersję gry i zapewnić obsługę aż trzech okrętów.
Innymi słowy, będziesz chciał napisać coś takiego.
Strzały zarówno
te celne, jak
i chybione są
wyświetlane
bezpośrednio
na planszy.
Atrakcyjna
wizualnie
plansza do gry,
wyświetlona
bezpośrednio
w przeglądarce
wraz z siatką.
I teraz mamy
aż trzy okręty
czekające, by je
zatopić.
Współrzędne strzału
są wpisywane
bezpośrednio na stronie.
WYSIL
SZARE KOMÓRKI
344
Rozdział 8.
Zapomnij na chwilę o JavaScripcie i przyjrzyj się planowanej planszy do gry
przedstawionej powyżej. Gdybyś miał skoncentrować się wyłącznie na strukturze
i wizualnej reprezentacji strony, jak utworzyłbyś ją przy użyciu HTML i CSS?
Łączenie wszystkiego w całość
Krok wstecz… do HTML i CSS
Aby opracować nową, interaktywną stronę WWW bądź też aplikację, konieczne
jest wykorzystanie trzech technologii, takich jak HTML, CSS i JavaScript. Dobrze
już znasz mantrę: „HTML służy do tworzenia struktury, CSS do określania stylów,
a JavaScript do zachowań”. Jednak w tym rozdziale, zamiast kolejny raz powtarzać,
spróbujemy ją urzeczywistnić. Zaczniemy od HTML i CSS.
Naszym celem jest odtworzenie wyglądu planszy do gry przedstawionej na
poprzedniej stronie. Jednak nie tylko jej odtworzenie: planszę do gry musimy
zaimplementować w sposób, który pozwoli na korzystanie z jej struktury w kodzie
JavaScript — chcemy pobierać dane o strzale użytkownika i wyświetlać na planszy
pudła, trafienia oraz wszelkie inne komunikaty.
Aby to wykonać, użyjemy obrazu umieszczonego w tle, który pozwoli wyświetlić
pod siatką efektowną tarczę radaru, wykorzystamy także bardziej funkcjonalną
tabelę HTML, która zapewni możliwość rozmieszczania na planszy różnych
rzeczy (takich jak okręty). Do pobierania danych od użytkownika skorzystamy
z formularza HTML.
W tle strony
umieścimy obraz
przedstawiający
siatkę planszy
naszej gry.
Następnie dodamy
tabelę HTML, która
będzie nałożona
na tę siatkę.
Dzięki temu później będziemy mogli
umieszczać w komórkach tej tabeli grafiki
reprezentujące okręty i chybione strzały.
A zatem zacznijmy tworzyć grę. W tym celu najpierw cofniemy się o krok
i poświęcimy kilka stron na przygotowanie niezbędnego kodu HTML i CSS;
kiedy już to zrobimy, będziemy gotowi, by skoncentrować się na kodzie JavaScript.
Poniżej przedstawiamy przybornik,
który ułatwi Ci rozpoczęcie prac nad
nową wersją gry w okręty.
Oto skład ZESTAWU.
board.jpg
ship.png
miss.png
Przybornik zawiera trzy obrazki:
board.jpg stanowiący główny
obraz tła gry i przedstawiający
jej planszę wraz z siatką, ship.png
przedstawiający niewielki okręt, który
będziemy wyświetlać na planszy
— zwróć uwagę, że jest to obraz
w formacie PNG wykorzystujący
przezroczystość, który będzie
wyświetlany bezpośrednio na planszy
— oraz miss.png reprezentujący
chybiony strzał; także i on będzie
wyświetlany na planszy. Analogicznie
do prawdziwej gry, kiedy uda
się trafić okręt, umieścimy jego
obrazek w odpowiedniej komórce
tabeli, natomiast kiedy spudłujemy,
w odpowiedniej komórce zostanie
umieszczony obrazek pudła.
i możesz
ezbędne grafik
Wszystkie ni era FTP wydawnictwa
pobrać z serwftp.helion.pl/
Helion: ftp:// rg.zip
przyklady/prjs
jesteś tutaj
345
Planowanie kodu HTML gry
Tworzenie strony HTML: postać ogólna
To plan ataku mającego doprowadzić do zbudowania strony HTML dla gry w okręty.
1
Na początku skoncentrujemy się na tle strony, co
będzie obejmowało zastosowanie obrazu tła oraz
umieszczenie na stronie obrazu siatki radaru.
2
Następnie utworzymy tabelę HTML i umieścimy ją
ponad obrazem tła. Każda komórka tabeli będzie
reprezentować jedno pole planszy do gry.
3
Kolejnym krokiem będzie dodanie elementu
formularza, w którym gracz wpisze ostrzeliwane
pole, np. A4. Dodamy także obszar, w którym
będą wyświetlane komunikaty, takie jak Zatopiłeś
mój okręt!
4
W tle strony umieszczamy
obraz tła, który
zapewni grze wygląd
fosforyzującego na zielono
ekranu radarowego.
Tabela HTML
umieszczona ponad
tłem tworzy planszę
gry, z której
będziemy korzystali
podczas rozgrywki.
Formularz HTML,
w którym użytkownik
wprowadza dane.
I w końcu określimy, jak należy używać tabeli
HTML do wyświetlania na planszy obrazków
okrętów (w przypadku trafienia) oraz chybionych
strzałów.
Będziemy używali
tych obrazków, by
w miarę potrzeb
wyświetlać je
w tabeli.
Krok 1. Podstawowy kod HTML
Do roboty! Najpierw potrzebujemy strony
HTML. Zaczniemy od utworzenia prostej
strony zgodnej ze standardem HTML5;
dodamy do niej style CSS określające obraz
tła. Na stronie wewnątrz elementu <body>
ulokujemy element <div>, w którym z kolei
umieszczona zostanie siatka gry.
Przejdź na następną stronę, na której
przedstawiliśmy wyjściowy kod HTML i CSS.
346
Rozdział 8.
Spokojnie
Twoja wiedza zwietrza
ła?
Jeśli czujesz, że Twoja
wiedza dotycząca językó
w
HTML i CSS nieco zw
ietrzała, skorzystaj
z książki Head First HT
ML with CSS & XHTM
L.
Edycja polska.
Łączenie wszystkiego w całość
<!doctype html>
<html lang=”pl”>
To jest zwyczajna
strona WWW.
<head>
<meta charset=”utf-8”>
<title>Gra w okrÚty</title>
<style>
Chcemy, żeby tło tej
strony było czarne.
body {
background-color: black;
}
div#board {
stawała
Chcemy, by plansza do gry pozo ć
w środku strony, dlatego szerokoś
tyle
ustawiamy na 1024 piksele (bo
inesom
wynosi szerokość planszy), a marg
.
auto
ość
wart
jemy
pisu
przy
position: relative;
To właśnie tutaj dodajemy do strony
obraz board.jpg — będzie on stanowił
tło elementu <div> o identyfikatorze
board. Element ten jest umiejscowiony
względnie, by można było umiejscowić
także tabelę, którą w następnym kroku
dodamy wewnątrz elementu <div>.
width: 1024px;
height: 863px;
margin: auto;
background: url(”board.jpg”) no-repeat;
}
</style>
</head>
<body>
<div id=”board”>
y
Na stronie umieścimplanszy
ę
tabelę pełniącą rol z,
do gry oraz formular
w którym użytkownikdane.
będzie wprowadzał
</div>
<script src=”battleship.js”></script>
</body>
Kod JavaScript umieścimy w pliku
battleship.js. Już teraz możesz
utworzyć pusty plik o tej nazwie.
</html>
Jazda próbna
Nie ociągaj się — wpisz powyższy kod (lub pobierz z serwera FTP wydawnictwa Helion),
zapisz go w pliku battleship.html, a następnie wyświetl w przeglądarce.
Wynik jazdy próbnej przedstawiliśmy poniżej.
A tak wygląda nasza
strona na obecnym
etapie prac.
jesteś tutaj
347
Tworzenie kodu HTML strony
Krok 2. Tworzenie tabeli
Druga w kolejce jest tabela. Nałożymy ją na siatkę widoczną na obrazie tła, co zapewni
możliwość wyświetlania na planszy obrazków reprezentujących trafione okręty oraz
chybione strzały. Każda komórka tabeli (bądź też, jeśli pamiętasz język HTML, każdy
element <td>) zostanie umieszczona bezpośrednio ponad odpowiadającą jej komórką
tabeli widocznej na obrazie tła. Oto cała sztuczka: każda komórka tej tabeli HTML
będzie miała unikalny identyfikator, zatem będziemy mogli modyfikować jej wygląd
za pomocą języków CSS i JavaScript.
Każda komórka siatki
odpowiada elementowi
<td> tabeli.
Każdy identyfikator
reprezentuje położenie
komórki w siatce. A zatem
lewa górna komórka ma
identyfikator „00”, a prawa
dolna — identyfikator „66”.
ne
W grze wiersze są reprezentowa
ak
przez litery (A, B, C itd.), jedn
aną
w identyfikatorach litery te zost
cyframi
zastąpione odpowiadającymi im
wiersz
wszy
pier
też
ego
Dlat
itd.)
2
1,
(0,
cyfrę
z
prze
y
czon
ozna
z
tera
jest
siatki
przez 6
0 (reprezentującą A), a ostatni
(reprezentującą G).
Ta komórka ma
identyfikator „60”.
Ta komórka ma
identyfikator „66”.
Poniżej zamieściliśmy kod HTML tabeli. Dodaj go pomiędzy znacznikami <div>.
<div id=”board”>
nątrz elementu
Tabela zostaje umieszczona wew
rd”.
„boa
ze
ator
tyfik
iden
<div> o
<table>
Upewnij się, że każdy
element <td> otrzymał
odpowiedni identyfikator
odpowiadający jego
położeniu w siatce.
<tr>
<td id=”00”></td><td id=”01”></td><td id=”02”></td><td id=”03”>
</td><td id=”04”></td> <td id=”05”></td><td id=”06”></td>
</tr>
<tr>
<td id=”10”></td><td id=”11”></td><td id=”12”></td><td id=”13”></td>
<td id=”14”></td> <td id=”15”></td><td id=”16”></td>
</tr>
...
<tr>
Pominęliśmy tutaj res
by ochronić kilka drz ztę wierszy,
jesteśmy pewni, że ew, jednak
będ
potrafił sam je uzupe ziesz
łnić.
<td id=”60”></td><td id=”61”></td><td id=”62”></td><td id=”63”></td>
<td id=”64”></td><td id=”65”></td><td id=”66”></td>
</tr>
</table>
</div>
348
Rozdział 8.
Łączenie wszystkiego w całość
Krok 3. Interakcja z graczem
Teraz potrzebujemy elementu HTML, który pozwoliłby graczowi na wpisywanie
współrzędnych strzału (np. ”A0” lub ”E4”), oraz elementu służącego do wyświetlania
komunikatów dla gracza (takich jak ĵ=DWRSLïHĂPöMRNUÚWĵ). Do wpisywania
danych posłużą elementy IRUP! oraz <input>, natomiast obszar potrzebny
do wyświetlania komunikatów utworzymy, używając elementu <div>.
Kiedy gracz zatopi statek,
poinformujemy go o tym
za pomocą komunikatu
wyświetlanego w lewym
górnym rogu.
A tu gracz
będzie wpisywał
współrzędne
ostrzeliwanego
miejsca planszy.
<div id=”board”>
<div id=”messageArea”></div>
Elementu <div> o identyfikatorze
messageArea będziemy używać
do wyświetlania komunikatów.
<table>
...
</table>
<form>
<input type=”text” id=”guessInput” placeholder=”A0”>
<input type=”button” id=”fireButton” value=”Ognia!”>
</form>
</div>
Zwróć uwagę, że element <div> obszaru powiadomień,
element <table> oraz <form> są umieszczone wewną
trz
elementu <div> o identyfikatorze „board”. To bardzo
ważne z punktu widzenia kodu CSS przedstawionego
na następnej stronie.
Element <form> zawiera dwa
elementy <input>: pierwszy
z nich służy do wpisywania
),
współrzędnych (to pole tekstowe
a drugi jest przyciskiem.
Zapamiętaj identyfikatory obu
tych elementów — będziemy
potrzebować ich później, kiedy
zabierzemy się za pisanie kodu
do pobierania współrzędnych
od użytkownika.
jesteś tutaj
349
Dodawanie do gry stylów CSS
Dodawanie stylów
Gdybyś teraz wyświetlił stronę (zrób to!), większość elementów będzie
umieszczona w niewłaściwych miejscach i będzie mieć nieodpowiednią
wielkość. Potrzebujemy zatem kodu CSS, który rozmieści elementy
w odpowiednich miejscach i zadba o to, by wszystkie elementy, takie jak
komórki tabeli, miały odpowiedni rozmiar, dostosowany do obrazu tła.
Aby rozmieścić elementy we właściwych miejscach, skorzystamy
z możliwości ich pozycjonowania, jakie daje CSS. Element <div>
o identyfikatorze ĵERDUGĵ ulokowaliśmy z wykorzystaniem umiejscawiania
względnego, dzięki czemu teraz możemy precyzyjnie umieścić obszar
powiadomień, tabelę oraz formularz w dowolnych, ale precyzyjnie
określonych miejscach elementu <div> o identyfikatorze ĵERDUGĵ.
Chcemy, by obszar powiadomień
został umiejscowiony w lewym,
gry.
górnym wierzchołku planszy do
Zacznijmy od obszaru powiadomień — elementu <div> o identyfikatorze
ĵPHVVDJH$UHDĵ. Jest on zagnieżdżony wewnątrz elementu <div>
o identyfikatorze ĵERDUGĵ, a chcemy, by był widoczny w samym lewym,
górnym rogu planszy do gry.
body {
background-color: black;
}
div#board {
position: relative;
width: 1024px;
height: 863px;
margin: auto;
Element <div>
o identyfikatorze „board”
jest umiejscowiony
w sposób względny,
zatem wszystkie
elementy w nim
zagnieżdżone mogą być
umiejscawiane względem
niego.
background: url(”board.jpg”) no-repeat;
}
CELNE SPOSTRZEŻENIA
div#messageArea {
position: absolute;
top: 0px;
left: 0px;
Obszar powiadomień
umieszczamy
w lewym, górnym
wierzchołku planszy.
color: rgb(83, 175, 19);
}
Element <div> „messageArea” jest zagnieżdżony
wewnątrz elementu <div> o identyfikatorze „board”,
więc jego położenie może być określane względem
elementu <div> planszy. A zatem zostanie on
umieszczony 0 pikseli poniżej górnej krawędzi
elementu <div> planszy oraz 0 pikseli na prawo
od jego lewej krawędzi.
350
Rozdział 8.
Q
Reguła ĵSRVLWLRQUHODWLYHĵ
umieszcza element w jego normalnym
położeniu, wynikającym z rozkładu
elementów strony.
Q
Reguła ĵSRVLWLRQDEVROXWHĵ
rozmieszcza element względem położenia
najbliższego w hierarchii nadrzędnego
elementu rozmieszczonego względnie
(ĵSRVLWLRQUHODWLYHĵ).
Q
Właściwości top oraz OHIW pozwalają
określić wielkość przesunięcia elementu
w pionie oraz poziomie od jego domyślnego
położenia i są wyrażone w pikselach.
Łączenie wszystkiego w całość
Możemy także określić położenie tabeli oraz formularza, które są umieszczone
wewnątrz elementu <div> o identyfikatorze ĵERDUGĵ. Także tym razem skorzystamy
z umiejscawiania bezwzględnego, które pozwoli precyzyjnie określić położenie elementów.
Poniżej przedstawiliśmy pozostałe reguły 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);
}
y 173 piksele
> umieściliśm
Element <table wej krawędzi planszy oraz
table {
na prawo od leżej jego górnej krawędzi,
position: absolute;
98 pikseli poni e pokrywa się z siatką
zatem dokładni obrazie tła.
left: 173px;
narysowaną na
top: 98px;
border-spacing: 0px;
śloną
}
Każdy element <td> ma ściśle okre
órki
szerokość i wysokość, więc kom ją się
td {
elementu <table> dokładnie pokrywa
ce.
width: 94px;
z komórkami na narysowanej siat
height: 94px;
}
prawym,
Element <form> umieszczamy w
przesłania
form {
dolnym rogu planszy. Nieznacznie (i tak
e cyfry, ale to nie przeszkadza
doln
position: absolute;
o nadaliśmy
wiemy, jakie to cyfry). Dodatkow onale
dosk
y
któr
,
kolor
ny
zielo
y
bottom: 0px;
ładn
mu
pasuje do obrazu radaru.
right: 0px;
padding: 15px;
background-color: rgb(83, 175, 19);
}
I w końcu trochę stylów określających
form input {
wygląd dwóch elementów <input>, by
background-color: rgb(152, 207, 113);
pasowały do tematu graficznego naszej gry.
I to już koniec!
border-color: rgb(83, 175, 19);
font-size: 1em;
}
jesteś tutaj
351
Zastosowanie CSS do wyświetlania trafień i pudeł
Nadszedł czas na kolejne sprawdzenie postępów w pracy nad grą. Wpisz kody
HTML i CSS w pliku HTML, zapisz go, a następnie wyświetl stronę w przeglądarce.
To właśnie powinieneś zobaczyć.
Jazda próbna
Tabela dokładnie pokrywa się
z graficzną siatką, choć nie
możesz jej zobaczyć (bo jest
niewidoczna).
Elementy formularza są
gotowe do wprowadzania
współrzędnych
strzałów, choć dopóki
nie napiszemy kodu
JavaScript, nic się nie
będzie działo.
Krok 4. Wyświetlanie celnych i chybionych strzałów
ship.png
Plansza do gry wygląda rewelacyjnie, nie sądzisz? Jednak wciąż musimy określić,
w jaki sposób będziemy na planszy wizualnie oznaczać celne oraz chybione strzały,
czyli jak dodawać do odpowiednich komórek planszy obrazki ship.png oraz miss.png.
Na razie zajmiemy się określeniem, jak przygotować odpowiedni kod HTML
lub CSS, nieco później użyjemy tej techniki w autentycznym kodzie.
W jaki sposób będziemy wyświetlać na planszy obrazki ship.png oraz miss.png?
Bardzo prostym sposobem może być wyświetlenie odpowiedniego obrazka jako
tła elementu <td>, przy użyciu stylów CSS. Spróbujmy zaimplementować to
rozwiązanie. Zaczniemy od utworzenia dwóch klas. Pierwsza z nich, o nazwie hit,
będzie wyświetlać w tle elementu obraz ĵVKLSSQJĵ, natomiast druga, PLVV, będzie
wyświetlać w tle elementu obraz ĵPLVVSQJĵ. Oto kod CSS definiujący te klasy.
.hit {
Jeśli element należy do klasy hit, w jego tle zostanie wyświetlony
obrazek ship.png. Jeśli z kolei element będzie należeć do klasy miss,
w jego tle zostanie wyświetlony obrazek miss.png.
background: url(”ship.png”) no-repeat center center;
}
.miss {
background: url(”miss.png”) no-repeat center center;
}
352
Rozdział 8.
Każda z tych
reguł wyświetla
w tle wybranego
elementu jeden,
wyśrodkowany
obrazek.
miss.png
Łączenie wszystkiego w całość
Stosowanie klas hit i miss
Upewnij się, że dodałeś do kodu strony definicje klas hit i PLVV. Być
może zastanawiasz się, w jaki sposób będziemy używać tych dwóch klas.
Wykonamy mały eksperyment, który to zilustruje. Wyobraź sobie, że
ukryliśmy okręt w komórkach planszy B3, B4 oraz B5, a użytkownik
sprawdził komórkę B3 — w efekcie mamy trafienie! A zatem trzeba
wyświetlić obrazek ĵVKLSSQJĵ w komórce B3. Oto jak to zrobić: najpierw
musisz zamienić ”B” na liczbę 1 (ponieważ A odpowiada 0, B odpowiada 1
itd.), a następnie odszukać w tabeli element <td> o identyfikatorze ”13”.
Teraz wystarczy, że dodasz do tego elementu <td> klasę hit.
Tutaj dodaliśmy do elementu <td> klasę „hit”.
<tr>
<td id=”10”></td> <td id=”11”></td> <td id=”12”></td> <td id=”13” class=”hit”></td>
<td id=”14”></td> <td id=”15”></td> <td id=”16”></td>
Upewnij się, że do kodu CSS
na swojej stronie dodałeś klasy
hit i miss z poprzedniej strony.
</tr>
t”
dodaniu klasy „hi
To zobaczymy po ntyfikatorze „13”.
ide
o
do elementu
Zanim napiszemy kod, który będzie wyświetlał na planszy trafione i chybione strzały, warto nabyć nieco
więcej praktyki w stosowaniu CSS. Spróbuj ręcznie zagrać w naszą grę, dodając do kodu klasy ”hit”
i ĵPLVVĵ, zgodnie z opisanym poniżej zestawieniem ruchów. Nie zapomnij sprawdzić odpowiedzi!
OkrÚt 1: A6, B6, C6
OkrÚt 2: C4, D4, E4
iesz
Pamiętaj, że będz ery na
lit
musiał zamienić G = 6.
cyfry: A = 0, …,
OkrÚt 3: B0, B1, B2
A oto współrzędne strzałów użytkownika.
A0, D4, F5, B2, C5, C6
Kiedy skończysz,
usuń wszystkie klasy,
które dodałeś do
elementów <td>, by
plansza była pusta,
kiedy zaczniemy
pisać skrypt.
Zanim przejdziesz dalej, sprawdź odpowiedź podaną pod koniec rozdziału.
jesteś tutaj
353
Pytania dotyczące gry
Nie istnieją
głupie pytania
P: Czy faktycznie w atrybutach id można
używać elementów łańcuchów znaków
składających się z samych cyfr?
O: Tak. W HTML5 można używać identyfikatorów
zawierających wyłącznie cyfry. O ile tylko w łańcuchu
identyfikatora nie ma znaków odstępu, wszystko będzie
w porządku. A jeśli chodzi o naszą aplikację, zastosowanie
identyfikatorów o postaci liczb doskonale ułatwi
odwoływanie się do poszczególnych elementów tabeli,
będziemy mogli robić to szybko i łatwo.
P: Chciałbym się tylko upewnić, używamy
każdego elementu td jako komórki na planszy gry,
a trafienia i pudła będziemy oznaczali, zapisując
nazwy hit i miss w atrybucie class?
O: Tak, to rozwiązanie składa się z kilku elementów:
w tle strony mamy wyświetloną graficzną siatkę, która
służy jedynie za element dekoracyjny. Następnie mamy
tabelę HTML, nakładającą się na tę siatkę. Mamy także
klasy hit oraz PLVV, których używamy do wyświetlania
w tle poszczególnych elementów tabeli odpowiednich
obrazków. To ostatnie zadanie będzie w całości
wykonywane przez kod JavaScript, gdyż będziemy
dynamicznie określać wartości atrybutów FODVV.
P: Wygląda na to, że będziemy musieli
skonwertować litery, np. w łańcuchu ”A6”, na cyfry
— np. ”06”. Czy JavaScript zrobi to za nas
automatycznie?
O: Nie. Będziemy musieli zadbać o to sami. Jednak
można to zrobić całkiem łatwo — wystarczy, że
wykorzystamy naszą wiedzę o tablicach. Czytaj dalej
uważnie…
354
Rozdział 8.
P: Obawiam się, że nie do końca pamiętam,
jak działa umiejscawianie CSS?
O: Umiejscawianie pozwala na określenie dokładnego
położenia elementu. Jeśli element jest umiejscowiony
„względnie”, jego położenie jest określane względem
normalnego miejsca, które zająłby na stronie. Z kolei
w przypadku umiejscawiania „bezwzględnego” położenie
elementu jest określane względem najbliższego
elementu nadrzędnego, który został w jakikolwiek
sposób umiejscowiony. Czasami może to być cała
strona, a w takim przypadku położenie elementu będzie
określane względem lewego, górnego wierzchołka
obszaru prezentacyjnego przeglądarki. W naszym
przypadku tabela oraz element obszaru powiadomień
będą umiejscowione bezwzględnie, jednak ich położenie
będzie określane względem elementu planszy (ponieważ
to właśnie on jest ich najbliższym, umiejscowionym
elementem nadrzędnym).
P
: Kiedy poznawałem element <form> HTML,
uczono mnie, że istnieje atrybut action
powodujący przesłanie formularza. Dlaczego tutaj
go nie ma?
O: Nie potrzebujemy atrybutu action w elemencie
IRUP!, gdyż nie przesyłamy danych z formularza
na serwer. W tej grze wszystko będzie obsługiwane
w przeglądarce, przy użyciu kodu JavaScript. Dlatego
też, zamiast przesyłać formularz, zaimplementujemy
procedurę obsługi zdarzeń, która będzie wykonywana
po kliknięciu przycisku — a kiedy to nastąpi, nasz kod
będzie obsługiwał wszystko, w tym także pobranie danych
z formularza. Zwróć uwagę, że przycisk na formularzu
jest typu ”button”, a nie ĵVXEPLWĵ, do czego możesz
być przyzwyczajony w formularzach przesyłających dane
do programów PHP lub innych aplikacji działających na
serwerze. To było dobre pytanie, więcej na ten temat
napiszemy w następnym rozdziale.
Łączenie wszystkiego w całość
Jak zaprojektować grę?
Skoro już załatwiliśmy sprawę kodu HTML i CSS, możemy przejść do projektowania faktycznej gry. Wcześniej
w rozdziale 2. nie znałeś jeszcze funkcji ani obiektów czy hermetyzacji i nie wiedziałeś niczego o projektowaniu
obiektowym; dlatego też, projektując pierwszą wersję gry w okręty skorzystaliśmy z podejścia czysto proceduralnego.
To właśnie dlatego zaprojektowaliśmy grę jako serię kroków, w których logika podejmowania decyzji oraz interakcja
z użytkownikiem były wymieszane. Co więcej, wtedy jeszcze nie wiedziałeś niczego o DOM, przez co gra nie była
zbytnio interaktywna. Jednak tym razem zorganizujemy grę jako serię obiektów, z których każdy będzie miał swoje
własne obowiązki, i wykorzystamy DOM do interakcji z użytkownikiem. Przekonasz się, jak taki projekt ułatwi
rozwiązanie problemu.
Najpierw opiszemy obiekty, które zaprojektujemy i zaimplementujemy. Będą to trzy obiekty: model, przechowujący
bieżący stan gry, np. położenie okrętów oraz gdzie zostały trafione; widok odpowiedzialny za aktualizację planszy
do gry oraz kontroler, który połączy wszystkie pozostałe elementy w jedną całość, a konkretnie mówiąc, będzie
obsługiwał wprowadzanie danych przez użytkownika, zagwarantuje wykonanie logiki gry oraz określi, kiedy gra
zostanie zakończona.
Łączę wszystko
w jedną całość, w tym też
zajmuję się pobieraniem danych
od użytkownika i wykonywaniem
logiki gry.
Kontroler
Moim zadaniem
jest aktualizacja wyglądu
planszy — wyświetlanie na niej
trafień, chybionych strzałów oraz
komunikatów dla użytkownika.
Moim zadaniem jest
śledzenie okrętów: gdzie
są, czy zostały trafione i czy
gracz ich nie zatopił.
Widok
Model
jesteś tutaj
355
Ćwiczenie z konstruowania widoku
Nadszedł czas na projektowanie obiektów. Zaczniemy od obiektu widoku. Musisz
pamiętać, że obiekt ten jest odpowiedzialny za aktualizację planszy gry wyświetlanej
w przeglądarce. Przyjrzyj się stronie przedstawionej poniżej i sprawdź, czy będziesz umiał
określić metody, które chcemy zaimplementować w obiekcie widoku. Poniżej napisz
deklaracje tych metod (chodzi o same deklaracje; ich kodem zajmiemy się już niebawem)
oraz jeden lub dwa komentarze na temat każdej z nich. Jedną z tych metod już opisaliśmy
za Ciebie. Zanim przejdziesz dalej, koniecznie sprawdź odpowiedzi!
Ćwiczenie
To jest komunikat.
Komunikaty będą
łańcuchami znaków,
takimi jak „Trafiony!”,
„Spudłowałeś” albo
„Zatopiłeś mój okręt!”.
W tym miejscu planszy
widok wyświetlił
w siatce obrazek
reprezentujący PUDŁO.
A tutaj widok wyświetlił
w siatce obrazek statku.
var view = {
Zwróć uwagę, że definiujemy obiekt
i zapisujemy go w zmiennej view.
// Ta metoda wymaga podania ïañcucha z komunikatem, a nastÚpnie
// wyĂwietla go w obszarze komunikatöw na stronie.
displayMessage: function(msg) {
// Wkrötce uzupeïnimy ten kod!
}
Twoje propozycje metod
zapisz tutaj.
};
356
Rozdział 8.
Łączenie wszystkiego w całość
Implementacja widoku
A jeśli nie, wstydź się. I zrób to teraz!
Jeśli sprawdziłeś odpowiedź na ćwiczenie podane na poprzedniej stronie,
widziałeś, że podzieliliśmy zachowania widoku na trzy niezależne metody:
GLVSOD\0HVVDJH, GLVSOD\+LW oraz GLVSOD\0LVV. Warto, żebyś wiedział, że na
tak postawione pytanie nie ma zazwyczaj jednej, jedynie słusznej odpowiedzi.
Przykładowo Ty mógłbyś zaproponować zdefiniowanie jedynie dwóch metod:
GLVSOD\0HVVDJH oraz GLVSOD\3OD\HU*XHVV, do której byłby przekazywany
jeden argument. Taki projekt także byłby całkowicie uzasadniony i rozsądny.
My jednak pozostaniemy przy naszym projekcie. Zastanówmy się zatem, jak
zaimplementować pierwszą z metod, GLVSOD\0HVVDJH.
Oto nasz obiekt view.
var view = {
displayMessage: function(msg) {
Zaczniemy w tym miejscu.
},
displayHit: function(location) {
},
displayMiss: function(location) {
}
};
Jak działa metoda displayMessage?
Aby zaimplementować metodę GLVSOD\0HVVDJH, musisz zacząć
od przeglądnięcia przygotowanego kodu HTML naszej strony.
Zauważysz w nim element <div> o identyfikatorze ĵPHVVDJH$UHDĵ,
który jest gotowy do prezentowania komunikatów.
<div id=”board”>
<div id=”messageArea”></div>
...
</div>
Skorzystamy z DOM, by uzyskać dostęp do tego elementu <div>,
a następnie określimy wyświetlany w nim tekst, wykorzystując
właściwość LQQHU+70/. Pamiętaj, że zawsze, gdy zmienisz DOM,
odpowiednie zmiany zostaną natychmiast wyświetlone w oknie
przeglądarki. A to mamy zamiar zrobić…
jesteś tutaj
357
Pomyśl o projekcie obiektu
Hej, zaczekajcie
chwilę… Jakim cudem możemy
implementować obiekt widoku, skoro
jeszcze nie pobieramy żadnych danych od
użytkownika ani nie robimy nic innego?
To właśnie jedna ze wspaniałych cech obiektów.
Możemy zagwarantować, że obiekt będzie wypełniał swoje
obowiązki bez konieczności przejmowania się wszystkimi
szczegółami innych elementów programu. W naszym przypadku
widok musi jedynie wiedzieć, jak zaktualizować obszar
komunikatów oraz wyświetlać na planszy znaczniki celnego
i chybionego strzału. Kiedy poprawnie zaimplementujemy te
zachowania, obiekt widoku będzie gotowy, a my będziemy mogli
przejść do pozostałych części kodu.
Kolejną zaletą takiego podejścia jest możliwość testowania
widoku zupełnie niezależnie od pozostałych fragmentów kodu
oraz upewnienia się, że działa on prawidłowo. Kiedy testujemy
wiele aspektów programu jednocześnie, zwiększamy szansę, że
coś pójdzie nie tak, a czasami utrudniamy sobie także zadanie
odnalezienia problemu (gdyż wymaga to sprawdzenia większej
liczby miejsc w kodzie).
Aby przetestować odizolowany obiekt (bez dokończenia
pozostałych części programu), konieczne będzie napisanie prostego
kodu testowego, który później usuniemy; ale to nic nie szkodzi.
A zatem, dokończmy nasz obiekt widoku, przetestujmy go
i przejdźmy dalej!
Implementacja metody displayMessage
Wróćmy do pisania kodu metody GLVSOD\0HVVDJH. Pamiętaj, że metoda ta musi:
Q
skorzystać z DOM, by pobrać element o identyfikatorze ĵPHVVDJH$UHDĵ;
Q
zapisać we właściwości LQQHU+70/ pobranego elementu komunikat przekazany
w wywołaniu metody.
358
Rozdział 8.
Łączenie wszystkiego w całość
A zatem otwórz plik battleship.js i dodaj do niego poniższy obiekt.
var view = {
a
Message pobier
Metoda display msg.
:
nt
jeden argume
displayMessage: function(msg) {
var messageArea = document.getElementById(”messageArea”);
messageArea.innerHTML = msg;
},
displayHit: function(location) {
Tu pobieramy element
messageArea ze strony…
…i aktualizujemy wyświe
zapisując wartość paramtlony w nim tekst,
właściwości innerHTML. etru msg we
},
displayMiss: function(location) {
}
};
Jednak zanim przetestujemy ten kod, przejdziemy trochę do przodu
i zaimplementujemy pozostałe dwie metody. Nie będą one szczególnie skomplikowane,
a dzięki temu później będziemy mogli przetestować działanie całego obiektu.
Jak działają metody displayHit oraz displayMiss?
Pisaliśmy o tym już wcześniej, ale powinieneś zapamiętać, że aby wyświetlić obraz na
planszy, musisz wybrać element <td> i dodać do niego klasy hit lub PLVV. Zastosowanie
pierwszej sprawi, że w odpowiedniej komórce tabeli pojawi się obrazek ĵVKLSSQJĵ,
natomiast zastosowanie drugiej spowoduje wyświetlenie obrazka ĵPLVVSQJĵ.
możemy
Postać planszy dając klasy „hit”
do
,
ać
ow
modyfik
powiednich
lub „miss” do odTeraz musimy się
>.
<td
elementów
ć, jak można to
tylko dowiedzie kodu.
zrobić z poziomu
<tr>
<td id=”10”></td> <td class=”hit” id=”11”></td> <td id=”12”></td> ...
</tr>
W naszym kodzie skorzystamy z DOM, by uzyskać dostęp do elementu
<td>, następnie zapiszemy w jego atrybucie FODVV wartości ”hit”
bądź ĵPLVVĵ, używając do tego metody VHW$WWULEXWH. Gdy tylko to
zrobimy, odpowiedni obrazek pojawi się w oknie przeglądarki. Poniżej
opisaliśmy, co mamy zamiar zrobić.
Q
Pobieramy łańcuch znaków zawierający identyfikator komórki,
w której ma być wyświetlony obrazek oznaczający trafienie lub
pudło; identyfikator ten składa się z dwóch cyfr.
Q
Użyjemy DOM do pobrania elementu o tym identyfikatorze.
Q
Zapiszemy w atrybucie FODVV tego elementu wartość ”hit”,
jeśli będziemy w metodzie GLVSOD\+LW, lub zapiszemy
w tym atrybucie wartość ĵPLVVĵ, jeśli będziemy w metodzie
GLVSOD\0LVV.
jesteś tutaj
359
Implementacja widoku
Implementacja metod displayHit oraz displayMiss
Obie metody, GLVSOD\+LW oraz GLVSOD\0LVV, pobierają argument określający współrzędne
strzału. Współrzędne te powinny odpowiadać identyfikatorowi komórki (czyli elementowi <td>)
tabeli, która reprezentuje planszę w kodzie HTML. A zatem pierwszą rzeczą, którą powinniśmy
zrobić, jest pobranie referencji do tego elementu przy użyciu metody JHW(OHPHQW%\,G.
Wypróbujmy to na przykładzie metody GLVSOD\+LW.
Pamiętaj, że współrzędne te składają
się z określenia wiersza i kolumny,
i odpowiadają identyfikatorowi elementu <td>.
displayHit: function(location) {
var cell = document.getElementById(location);
},
Kolejną czynnością jest dodanie do elementu komórki klasy hit, co możemy zrobić
przy użyciu metody VHW$WWULEXWH.
displayHit: function(location) {
var cell = document.getElementById(location);
cell.setAttribute(”class”, ”hit”);
Teraz przypisujemy atrybutowi class wartość
„hit”. Spowoduje to natychmiastowe dodanie
do elementu <td> obrazka okrętu.
},
Teraz dodamy ten kod do obiektu YLHZ i zaimplementujemy jednocześnie drugą metodę,
czyli displayMiss.
var view = {
displayMessage: function(msg) {
var messageArea = document.getElementById(”messageArea”);
messageArea.innerHTML = msg;
},
displayHit: function(location) {
Identyfikatora utworzonego na podstawie
współrzędnych podanych przez użytkownika
używamy do pobrania odpowiedniego
elementu, który trzeba zaktualizować.
var cell = document.getElementById(location);
cell.setAttribute(”class”, ”hit”);
},
Następnie w atrybucie class tego
elementu zapisujemy wartość „hit”.
displayMiss: function(location) {
var cell = document.getElementById(location);
cell.setAttribute(”class”, ”miss”);
}
};
W metodzie displayMiss robimy niemal to samo,
z tą różnicą, że w atrybucie class zapisujemy
łańcuch „miss”, co sprawi, że na planszy
zostanie wyświetlony obrazek pudła.
Upewnij się, że dodałeś kod metod GLVSOD\+LW oraz GLVSOD\0LVV do pliku battleship.js.
360
Rozdział 8.
Łączenie wszystkiego w całość
Kolejna jazda próbna
Zanim zajmiemy się kolejnymi sprawami, dokładnie przetestujemy nasz kod.
Wykorzystamy w tym celu współrzędne strzałów z przedstawionego wcześniej
ćwiczenia „Ostrzał próbny”, które zaimplementujemy w naszym kodzie.
Oto sekwencja strzałów, którą chcemy zaimplementować.
A0, D4, F5, B2, C5, C6
PUDŁO TRAFIONY
PUDŁO
PUDŁO
TRAFIONY TRAFIONY
Aby przedstawić tę sekwencję w kodzie, należy dodać na końcu
pliku battleship.js następującą sekwencję wywołań.
“A0”
view.displayMiss(”00”);
“D4”
view.displayHit(”34”);
view.displayMiss(”55”);
“F5”
view.displayHit(”12”);
“B2”
view.displayMiss(”25”);
“C5”
view.displayHit(”26”);
Pamiętaj, metody displayHit oraz
które
displayMiss pobierają współrzędne,
cyfry
zostały już skonwertowane z litery i
na łańcuch składający się z dwóch cyfr,
rki
odpowiadający identyfikatorowi komó
tablicy.
“C6”
Nie zapomnij także przetestować metody GLVSOD\0HVVDJH.
Podczas tych prostych testów
możemy wyświetlać dowolny
komunikat…
YLHZGLVSOD\0HVVDJH ĵ+DORKDOR&]\WRFRĂG]LDïD"ĵ Po wprowadzeniu tych zmian odśwież stronę w przeglądarce i sprawdź
jak wygląda zaktualizowana plansza do gry.
Jedną z zalet
podzielenia
kodu na obiekty,
z których każdy
będzie miał tylko
jeden, precyzyjnie
zdefiniowany zakres
obowiązków, jest
możliwość dokładnego
ich przetestowania
i upewnienia się,
że każdy z nich robi,
co do niego należy.
Komunikat „halo, halo”
zostaje wyświetlony
tutaj, w lewym górnym
wierzchołku widoku.
A trafienia i pudła, które
wyświetlamy, używając
obiektu view, są pokazywane
na planszy do gry.
Sprawdź każdą z komórek,
aby upewnić się, że
oznaczenia strzałów
są wyświetlane
w odpowiednich miejscach.
jesteś tutaj
361
Planowanie modelu
Model
Skoro już poradziliśmy sobie z obiektem widoku, przejdźmy do modelu. Model jest tym miejscem,
w którym będziemy przechowywali stan gry. W modelu często umieszczona jest także jakaś logika związana
ze zmianami stanu. W naszym przypadku stan zawiera informacje o położeniu okrętów, informacje
o miejscach okrętów, które zostały trafione, oraz liczbę zatopionych okrętów. Jedyną logiką, jakiej
będziemy potrzebowali (przynajmniej na razie), będzie określenie, czy próba wykonana przez użytkownika
spowodowała trafienie jakiegoś okrętu, a następnie oznaczenie tego okrętu jako trafionego.
Tak będzie wyglądał nasz obiekt modelu.
boardSizeZLHONRĤþVLDWNL
WZRU]ċFHMSODQV]Đ
numShipsOLF]EDRNUĐWyZ
ELRUċF\FKXG]LDâZJU]H
Model
To są właściwości
y
przechowujące bieżąc
stan gry.
ships: informacje o lokalizacji
RNUĐWyZRUD]WUDILHQLDFK
shipsSunkOLF]ED]DWRSLRQ\FK
RNUĐWyZ
shipLengthOLF]EDNRPyUHNVLDWNL
]DMPRZDQ\FKSU]H]NDİG\RNUĐW
firePHWRGDVâXİċFDGRRGGDZDQLDVWU]DâXLRNUHĤODQLD
F]\MHVWWRVWU]DâFHOQ\F]\WHİQLH
A to jest metoda obsługująca
oddawanie strzałów.
Jak model prowadzi interakcję z widokiem?
Kiedy zmienia się stan gry — czyli wtedy, gdy gracz odda strzał, celny bądź nie — to widok będzie musiał
zaktualizować wygląd planszy. W tym celu model musi skomunikować się z widokiem — na szczęście, istnieje
kilka metod, których można w tym celu użyć. Najpierw zajmiemy się przygotowaniem logiki działania modelu,
a następnie dodamy kod aktualizujący wygląd planszy.
Zrozumiałem,
dziękuję! Zajmę się
aktualizacją planszy, by
odpowiadała bieżącej
sytuacji.
Hej Widoku,
musisz zaktualizować
planszę. Gracz właśnie trafił okręt
umieszczony w komórce „B0”.
view.displayHit(”10”)
Model
362
Rozdział 8.
Model informuje widok o zmianie
stanu, zatem widok może
w odpowiedni sposób zaktualizować
planszę do gry.
Widok
Łączenie wszystkiego w całość
Będziesz potrzebował większych okrętów… i większej planszy
Zanim zaczniemy pisanie kodu modelu, musimy się zastanowić nad sposobem reprezentacji
okrętów w modelu. W pierwszej, prostej wersji gry przedstawionej w rozdziale 2. używaliśmy
tylko jednego okrętu, umieszczanego na planszy o wymiarach 1×7. Teraz sytuacja nieco się
skomplikuje: użyjemy trzech okrętów umieszczonych na planszy o wymiarach 7×7. A tak to
wszystko będzie wyglądać.
Każdy okręt zajmuje trzy
komórki na dwuwymiarowej
planszy.
Zauważysz zapewne, że okręty
na planszy nie nakładają
się na siebie. Nie byłoby to
możliwe na normalnej planszy
i prowadziłoby do dziwnego
przebiegu gry. Do tego problemu
wrócimy nieco później, kiedy
będziemy zastanawiać się, jak
losowo rozmieszczać okręty
na planszy, i zobaczymy, jak
można zapewnić, by okręty nie
nakładały się na siebie.
A
C
D
Okręt1
B
órki
Ten okręt zajmuje kom
„B0”, „C0” oraz „D0”.
Okręt2
E
A to kolejny okręt
zajmujący komórki
od D2 do D4.
F
IE
Okręt3 TRAFIEN
G
0
1
2
3
4
5
6
Musimy też mieć możliwość
rejestrowania trafień. Każdy
okręt zajmuje trzy komórki,
dlatego dla każdego musimy
mieć także możliwość
przechowania informacji
o trzech trafieniach.
A to jest trzeci okręt, umieszczony
w komórkach G3 – G5.
Zaostrz ołówek
Wiesz już, jak opisaliśmy planszę do gry, zastanów się zatem, w jaki sposób można by reprezentować
okręt w modelu (chodzi tylko o położenie, trafieniami zajmiemy się później). Spośród podanych
poniżej odpowiedzi zaznacz najlepszą.
‰Użyj dziewięciu zmiennych, z których każda będzie
przechowywać współrzędne jednej komórki
zajmowanej przez okręty. Sposób podobny do
zastosowanego w rozdziale 2.
‰Zastosuj tablicę, której elementy będą odpowiadały
poszczególnym komórkom planszy (czyli będzie ich
w sumie 49). W każdej z komórek tej tablicy, która
zawiera jakiś fragment statku, należy zapisać jego numer.
‰Użyj tablicy składającej się z dziewięciu komórek
do zapisania położenia każdego z okrętów. Komórki
0 – 2 będą zawierały współrzędne pierwszego okrętu,
komórki 3 – 5 współrzędne drugiego itd.
‰Użyj trzech odrębnych tablic, z których każda będzie
się składać z trzech komórek i zawierać współrzędne
pól zajmowanych przez jeden okręt.
‰Użyj obiektu o nazwie VKLS, który będzie zawierał
trzy właściwości ze współrzędnymi trzech pól planszy
zajmowanych przez dany okręt. Utwórz trzy takie
obiekty i zapisz je w tablicy o nazwie VKLSV.
‰________________________________________
________________________________________
________________________________________
Albo tutaj zapisz swoje rozwiązanie.
jesteś tutaj
363
Struktura danych okrętów
W jaki sposób będziemy reprezentować okręty?
Jak widzisz, istnieje kilka sposobów reprezentacji okrętów, a być może wymyśliłeś
jeszcze parę innych. Przekonasz się, że niezależnie od tego, jakiego rodzaju danych
używasz, możesz je przechowywać na kilka różnych sposobów, a wybór konkretnego
będzie wymagał określonych kompromisów — niektóre będą efektywne pod względem
wielkości pamięci zajmowanej przez dane, inne zapewnią wyższą efektywność
działania, jeszcze inne będzie można łatwiej zrozumieć itd.
Wybraliśmy stosunkowo prostą reprezentację okrętów — każdy okręt będzie obiektem
przechowującym współrzędne pól planszy zajmowanych przez okręt oraz otrzymane
przez niego trafienia. Przyjrzyjmy się zatem, jak będzie reprezentowany nasz okręt.
Każdy okręt
jest obiektem.
Obiekt okrętu posiada
właściwości locations oraz hits.
var ship1 = {
Właściwość locations
jest tablicą zawierającą
współrzędne wszystkich pól
zajmowanych przez okręt.
locations: [”10”, ”20”, ”30”],
hits: [””, ””, ””]
};
Właściwość hits także jest tablicą, lecz zawiera informacje
o tym, czy poszczególne pola zajmowane przez okręt
zostały trafione, czy nie. Początkowo w każdym elemencie
tej tablicy będzie zapisany pusty łańcuch znaków, a kiedy
jakieś pole zostanie trafione, w odpowiedniej komórce tej
tablicy zapiszemy łańcuch „hit”.
A tak będą wyglądały obiekty okrętów.
Zauważ, że współrzędne pól
zajmowanych przez okręt
skonwertowaliśmy do postaci dwóch
cyfr, zamieniając A na 0, B na 1 itd.
Każdy okręt zawiera tablicę trzech
zajmowanych pól planszy oraz tablicę
do rejestracji trafień.
var ship1 = { locations: [”10”, ”20”, ”30”], hits: [””, ””, ””] };
var ship2 = { locations: [”32”, ”33”, ”34”], hits: [””, ””, ””] };
var ship3 = { locations: [”63”, ”64”, ”65”], hits: [””, ””, ”hit”] };
Jednak zamiast tworzyć trzy odrębne zmienne do przechowywania
trzech okrętów, zapiszemy je w jednej tablicy, jak pokazaliśmy poniżej.
Zwróć uwagę na liczbę mnogą
w nazwie — ships.
W zmiennej ships zapisujemy
tablicę zawierającą wszystkie
trzy okręty.
var ships = [{ locations: [”10”, ”20”, ”30”], hits: [””, ””, ””] },
{ locations: [”32”, ”33”, ”34”], hits: [””, ””, ””] },
{ locations: [”63”, ”64”, ”65”], hits: [””, ””, ”hit”] }];
Zauważ, że ten okręt
zawiera informacje
o trafieniu w polu „65”.
364
Rozdział 8.
To jest pierwszy okręt…
…to jest drugi…
…a tu jest trzeci.
Łączenie wszystkiego w całość
Okrętowe magnesiki
Użyj przedstawionych poniżej informacji o ostrzeliwanych przez użytkownika polach planszy
oraz tablicy z danymi okrętów, by umieścić na planszy magnesiki okrętów oraz chybionych strzałów.
Czy graczowi udało się zatopić którykolwiek z okrętów? Pierwsze ostrzelane pole zaznaczyliśmy za Ciebie.
A oto współrzędne strzałów:
A6, B3, C4, D1, B0, D4, F0, A1, C6, B1, B2, E4, B6
Zaznacz te strzały
na planszy.
YDUVKLSV >^ORFDWLRQV>ĵĵĵĵĵĵ@KLWV>ĵKLWĵĵĵĵĵ@`
^ORFDWLRQV>ĵĵĵĵĵĵ@KLWV>ĵĵĵĵĵĵ@`
To jest struktura
danych. Zaznacz
każdy okręt trafiony
podczas rozgrywki.
^ORFDWLRQV>ĵĵĵĵĵĵ@KLWV>ĵĵĵĵĵĵ@`@
A tu jest plansza do gry oraz magnesiki
okrętów i chybionych strzałów.
Kilka magnesików może Ci zostać.
jesteś tutaj
365
Ćwiczenie ze struktur danych
Zaostrz ołówek
Poćwiczymy teraz stosowanie struktury danych okrętów, by zasymulować różne operacje z nimi związane.
Bazując na podanej poniżej definicji okrętów, odpowiedz na pytania oraz uzupełnij puste miejsca w kodzie.
Nie zapomnij sprawdzić odpowiedzi, zanim zaczniesz dalszą lekturę, gdyż jest to bardzo ważny aspekt
działania gry.
var ships = [{ locations: [”31”, ”41”, ”51”], hits: [””, ””, ””] },
{ locations: [”14”, ”24”, ”34”], hits: [””, ”hit”, ””] },
{ locations: [”00”, ”01”, ”02”], hits: [”hit”, ””, ””] }];
Które okręty zostały już trafione? _________________ O które pola chodzi? ____________________
Gracz sprawdza pole „D4”, czy to jest trafienie? ____________ Który to okręt? ___________
Gracz sprawdza pole „B3”, czy to jest trafienie? ____________ Który to okręt? ___________
Uzupełnij poniższy fragment kodu, tak by odwoływał się do środkowej komórki drugiego okrętu
i wyświetlał jej wartość przy użyciu metody FRQVROHORJ.
var ship2 = ships[____];
var locations = ship2.locations;
console.log(”WspöïrzÚdne pola to: ” + locations[____]);
Uzupełnij poniższy kod, by sprawdzić, czy pierwsza komórka trzeciego okrętu jest trafiona.
var ship3 = ships[____];
var hits = ship3._____;
if (_____ === ”hit”) {
console.log(”Aua, trafienie pierwszej komörki trzeciego okrÚtu!”);
}
Dokończ poniższy kod, tak by oznaczyć trafienie trzeciej komórki pierwszego okrętu.
var ______ = ships[0];
var hits = ship1._______;
hits[____] = ________;
366
Rozdział 8.
Łączenie wszystkiego w całość
Implementacja obiektu modelu
Skoro już wiesz, jak będziemy reprezentować okręty i trafienia, nadszedł czas, by
napisać trochę kodu. Najpierw utworzymy obiekt modelu, po czym weźmiemy
opracowaną przed chwilą strukturę danych VKLSV i dodamy ją do niego jako
właściwość. A skoro już się tym zajmujemy, warto pamiętać także o kilku innych
właściwościach, które będą potrzebne, takich jak QXP6KLSV, która będzie
przechowywać liczbę okrętów uczestniczących w grze. Pewnie chcesz
zapytać: „O co wam chodzi, przecież wiadomo, że w grze będą trzy
okręty. Po co więc ta właściwość QXP6KLSV?”. No cóż, a jeślibyś
chciał napisać następną, nieco trudniejszą wersję gry, w której trzeba
by zatopić cztery lub pięć okrętów? Dzięki temu, że nie podamy tej
liczby na stałe, lecz zapiszemy w formie właściwości (której później
będziemy używali w kodzie zamiast wartości stałej), możemy sobie
zaoszczędzić przyszłych kłopotów, kiedy będziemy chcieli zmienić
liczbę okrętów — wystarczy to zrobić w jednym miejscu.
boardSizeZLHONRĤþ
VLDWNLWZRU]ċFHMSODQV]Đ
numShipsOLF]EDRNUĐWyZ
ELRUċF\FKXG]LDâZJU]H
ships: informacje
RORNDOL]DFMLRNUĐWyZ
RUD]WUDILHQLDFK
shipsSunk: liczba
]DWRSLRQ\FK
RNUĐWyZ
Model
A skoro już mówimy o „podawaniu czegoś na stałe”, to podamy na stałe
położenie okrętów, choć jedynie tymczasowo. Kiedy będziemy wiedzieć, gdzie
znajdują się okręty, będzie łatwiej przetestować grę i na razie skoncentrować się na jej
logice. Kodem związanym z rozmieszczaniem okrętów w losowych miejscach zajmiemy
się nieco później.
shipLength: liczba
NRPyUHNVLDWNL
]DMPRZDQ\FKSU]H]
NDİG\RNUĐW
firePHWRGDVâXİċFD
GRRGGDZDQLDVWU]DâX
LRNUHĤODQLDF]\MHVWWR
VWU]DâFHOQ\F]\WHİQLH
A zatem, utwórzmy w końcu obiekt modelu.
Model jest obiektem.
var model = {
boardSize: 7,
numShips: 3,
shipLength: 3,
shipsSunk: 0,
wartości na
wolą nam nie podawać
Te trzy właściwości pozize (wielkość siatki tworzącej planszę
stałe. Oto one: boardS okrętów biorących udział w grze)
gry), numShips (liczba komórek planszy zajmowanych przez
oraz shipLength (liczba
jeden okręt, czyli 3).
To już jest
całkiem sporo
informacji
określających
stan gry!
Właściwość shipsSunk (której podczas rozpoczynania gry
przypisywana będzie wartość 0) przechowuje informację
o liczbie okrętów zatopionych przez gracza.
ships: [{ locations: [”06”, ”16”, ”26”], hits: [””, ””, ””] },
{ locations: [”24”, ”34”, ”44”], hits: [””, ””, ””] },
{ locations: [”10”, ”11”, ”12”], hits: [””, ””, ””] }]
};
Nieco dalej zajmiemy się generowaniem
losowego położenia okrętów, jednak na
razie podamy je na stałe, by ułatwić
sobie testowanie gry.
Zwróć uwagę, że tablice locations oraz hits wszystkich okrętów zostały
podane na stałe. Dalej w książce dowiesz się, jak można je generować
dynamicznie.
Właściwość ships jest tablicą
obiektów ship, z których każdy
u
przechowuje informacje o położeni
oraz trafieniach danego okrętu.
(Zauważ, że zmieniliśmy ships
ze zmiennej, której używaliśmy
wcześniej, na właściwość obiektu
model).
jesteś tutaj
367
Planowanie metody fire
Rozmyślamy o metodzie fire
boardSizeZLHONRĤþ
VLDWNLWZRU]ċFHMSODQV]Đ
Metoda ILUH odpowiada za przekształcenie współrzędnych pola wytypowanych przez
gracza na trafienie lub pudło. Wiemy już, że to obiekt YLHZ będzie się zajmował
wyświetlaniem strzałów zarówno tych trafionych, jak i chybionych, jednak to
właśnie metoda ILUH będzie dysponowała logiką niezbędną do określenia,
czy strzał we wskazane pole będzie celny, czy okaże się pudłem.
Model
Ustalenie, że okręt został trafiony, jest bardzo proste; dysponując
współrzędnymi pola wskazanego przez użytkownika, musisz wykonać
kolejne kroki.
numShips: liczba
RNUĐWyZELRUċF\FK
XG]LDâZJU]H
ships: informacje
RORNDOL]DFMLRNUĐWyZ
RUD]WUDILHQLDFK
shipsSunk: liczba
]DWRSLRQ\FKRNUĐWyZ
shipLengthOLF]EDNRPyUHN
VLDWNL]DMPRZDQ\FKSU]H]
NDİG\RNUĐW
Q
Sprawdzić każdy okręt i określić, czy zajmuje on podane pole planszy.
Q
Jeśli zajmuje, oznacza to, że mamy trafienie i należy wpisać wartość
”hit” w odpowiednie pole tablicy KLWV (i jednocześnie poinformować widok
o trafieniu). Oprócz tego trzeba zwrócić WUXH jako wartość wynikową metody,
informując tym samym o trafieniu.
Q
Jeśli żaden z okrętów nie zajmuje podanego pola planszy, oznacza to, że strzał okazał
się niecelny. Musimy zatem poinformować o tym widok i zakończyć działanie metody,
zwracając wartość IDOVH.
fire:PHWRGDVâXİċFDGRRGGDZDQLD
VWU]DâXLRNUHĤODQLDF]\MHVWWR
VWU]DâFHOQ\F]\WHİQLH
Dodatkowo metoda ILUH powinna także określać, czy okręt został jedynie trafiony, czy już jest zatopiony.
Tym problemem zajmiemy się nieco później, kiedy już poradzimy sobie z resztą logiki metody ILUH.
Przygotowywanie metody fire
Spróbujmy przygotować podstawowy szkielet metody ILUH. Jej argumentem będą współrzędne pola wskazane
przez gracza, natomiast metoda przeanalizuje każdy z okrętów, by upewnić się, czy został trafiony, czy nie.
Nie napiszemy jeszcze kodu wykrywającego trafienia, a jedynie przygotujemy główną strukturę kodu.
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: [””, ””, ””] }],
fire: function(guess) {
Nie zapomnij dodać
tutaj przecinka!
Metoda pobiera współrzędne
podane przez użytkownika.
for (var i = 0; i < this.numShips; i++) {
var ship = this.ships[i];
}
Następnie w pętli przeglądamy
każdy element tablicy ships,
analizując kolejno każdy
z zapisanych w niej okrętów.
}
Kiedy już obraliśmy obiekt okrętu, musimy sprawdzić, czy pole wybrane
przez użytkownika jest jednym z pól zajmowanych przez okręt.
};
368
Rozdział 8.
Łączenie wszystkiego w całość
Poszukujemy trafień
A zatem teraz, podczas każdej iteracji pętli musimy sprawdzić, czy przekazane
współrzędne odpowiadają któremuś z pól zajmowanych przez okręt.
for (var i = 0; i < this.numShips; i++) {
var ship = this.ships[i];
Sprawdzamy kolejno
każdy z okrętów.
Pobraliśmy tablicę pól zajmowanych przez
dany okręt. Pamiętaj, że jest to właściwość
obiektu okrętu zawierająca tablicę.
locations = ship.locations;
Teraz potrzebujemy kodu, który sprawdzi,
czy współrzędne podane przez użytkownika
znajdują się w tablicy locations danego okrętu
.
}
Nasza aktualna sytuacja wygląda następująco: mamy łańcuch znaków, JXHVV, którego
poszukujemy w tablicy ORFDWLRQV. Jeśli łańcuch ten będzie odpowiadał jednemu z pól
zapisanych w tej tablicy, będzie to oznaczało, że graczowi udało się trafić nasz okręt.
guess = ”16”;
locations = [”06”, ”16”, ”26”];
Musimy dowiedzieć się, czy wartość
w zmiennej guess jest jedną
z wartości zapisanych w tablicy
pól zajmowanych przez okręt.
Moglibyśmy napisać jeszcze jedną pętlę, przeglądającą tablicę ORFDWLRQV
i porównującą zapisane w niej wartości ze zmienną JXHVV — gdyby któraś z nich
była równa tej zmiennej, oznaczałoby to, że okręt został trafiony.
Jednak zamiast kolejnej pętli możemy zrobić to samo w znacznie prostszy sposób.
poszukując w niej
Metoda indexOf przegląda tablicę,u argumentowi, a jeśli
nem
poda
ej
wartości odpowiadając
jeśli wartości nie uda
taką znajdzie, zwraca jej indeks;
ość –1.
się znaleźć, metoda zwraca wart
var index = locations.indexOf(guess);
A zatem, korzystając z metody LQGH[2I, kod wykrywający trafienia możemy
zapisać w następujący sposób.
for (var i = 0; i < this.numShips; i++) {
var ship = this.ships[i];
var locations = ship.locations;
var index = locations.indexOf(guess);
if (index >= 0) {
// Mamy trafienie!
}
jest bardzo
toda indexOf tablicy
Zwróć uwagę, że me exOf łańcuchów znaków.
ind
y
podobna do metod
raca indeks
zania wartości i zw
Wymaga ona przeka (bądź wartość –1, jeśli
tej wartości w tablicy znaleźć).
się
wartości nie udało
Jeśli zatem zwrócony indeks będzie większy lub równy 0, oznacza to,
że współrzędne pola wytypowanego przez użytkownika są zapisane
w tablicy locations, a więc okręt został trafiony.
}
Zastosowanie metody LQGH[2I nie jest w żadnym stopniu bardziej wydajne od samodzielnego
napisania pętli, jednak sprawia, że nasz kod będzie bardziej przejrzysty i znacząco krótszy.
Uważamy także, że zastosowanie tej metody sprawiło, iż zrozumienie przeznaczenia kodu
jest łatwiejsze niż w przypadku użycia pętli: łatwiej zorientować się, czego szukamy w tablicy.
Właśnie zdobyłeś kolejne narzędzie do swojego programistycznego przybornika.
jesteś tutaj
369
Implementacja metody fire
Składamy wszystko w jedną całość…
By dokończyć metodę ILUH, musimy ustalić jeszcze jedną rzecz: co ma się stać,
kiedy okaże się, że okręt został trafiony? Na razie wystarczy, że oznaczymy trafienie
w modelu, zapisując łańcuch ”hit” w odpowiedniej komórce tablicy KLWV.
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: [””, ””, ””] }],
fire: function(guess) {
for (var i = 0; i < this.numShips; i++) {
Dla każdego okrętu…
var ship = this.ships[i];
Jeśli współrzędne pola wskazanego
przez użytkownika znajdują się
w tablicy locations, będzie to oznaczało,
że mamy trafienie.
var locations = ship.locations;
var index = locations.indexOf(guess);
if (index >= 0) {
ship.hits[index] = ”hit”;
return true;
A zatem musimy za
pisać „hit”
w tablicy hits, w kom
o tym samym indek órce
sie.
A oprócz tego musim
zwrócić true, ponie y
okręt został trafio waż
ny.
}
}
return false;
}
};
W przeciwnym razie, jeśli sprawdziliśmy już
wszystkie okręty i nie znaleźliśmy trafienia, będzie
to oznaczało, że strzał był niecelny, a zatem metoda
musi zwrócić wartość false.
Udało nam się wspaniale rozpocząć prace nad obiektem modelu. Musimy
zrobić jeszcze tylko dwie rzeczy: sprawdzić, czy okręt nie został zatopiony,
oraz poinformować widok o zmianach wprowadzonych w modelu, by
odpowiednio zaktualizować interfejs gry. Zabierzmy się zatem za to…
370
Rozdział 8.
Łączenie wszystkiego w całość
Czy możemy ponownie porozmawiać o Twoim przydługim kodzie?
Przepraszamy, ale musimy ponownie wrócić do tego problemu. Twoje odwołania do
niektórych obiektów i tablic są zbyt długie. Przyjrzyj się poniższemu fragmentowi.
for (var i = 0; i < this.numShips; i++) {
var ship = this.ships[i];
var locations = ship.locations;
var index = locations.indexOf(guess);
...
Najpierw pobieramy okręt…
Następnie z obiektu okrętu
pobieramy tablicę locations…
A w końcu pobieramy indeks
współrzędnych odnalezionych
w tej tablicy.
}
Są tacy, którzy stwierdziliby, że ten kod jest nadmiernie rozwlekły. Dlaczego? Gdyż niektóre
z tych odwołań można skrócić, łącząc je w łańcuch. Tworzenie łańcuchów odwołań pozwala
łączyć ze sobą odwołania, dzięki czemu nie trzeba stosować zmiennych tymczasowych, takich
jak ORFDWLRQV w powyższym fragmencie kodu.
Możesz się teraz zastanawiać, dlaczego ORFDWLRQV określiliśmy jako zmienną tymczasową?
Wynika to z faktu, że używamy jej tylko do chwilowego zapisania tablicy VKLSORFDWLRQV,
która jest potrzebna wyłącznie do wywołania metody LQGH[2I i uzyskania indeksu wartości
JXHVV, którą zapisujemy w zmiennej LQGH[. W tej metodzie zmienna ORFDWLRQV nie
jest potrzebna do niczego więcej. Jednak tworząc łańcuchy odwołań, możemy pozbyć się
zmiennej ORFDWLRQV. Poniżej pokazaliśmy, jak można to zrobić.
var index = ship.locations.indexOf(guess);
Dwa wyróżnione wiersze kodu z powyższego
fragmentu zmieniliśmy w jeden.
Jak działają łańcuchy odwołań?
Łańcuchy odwołań są jedynie skrótowym zapisem sekwencji kroków służących do
odwoływania się do właściwości lub metod obiektów (oraz tablic). Przyjrzyjmy się
dokładniej, co zrobiliśmy, by połączyć dwie instrukcje z wykorzystaniem tej techniki.
To jest obiekt okrętu.
var ship = { locations: [”06”, ”16”, ”26”], hits: [””, ””, ””] };
var locations = ship.locations;
var index = locations.indexOf(guess);
Pobieramy z niego tablicę locations.
A następnie używamy jej, by wywołać metodę indexOf.
Dwie ostatnie instrukcje możemy połączyć w jedną, korzystając z techniki tworzenia
łańcucha wyrażeń (a przy okazji pozbędziemy się zmiennej ORFDWLRQV).
ship.locations.indexOf(guess)
1
Zwraca
obiekt
okrętu...
2 ...który zawiera
właściwość
locations, będącą
tablicą...
3
...która z kolei ma
właściwość o nazwie
indexOf.
jesteś tutaj
371
Czy okręt został zatopiony?
W międzyczasie na okręcie…
Teraz musimy napisać kod, który określi, czy okręt został zatopiony. Znasz zasady: okręt uznajemy za
zatopiony, kiedy wszystkie zajmowane przez niego pola planszy zostaną trafione. Możemy dodać do
obiektu okrętu niewielką metodę pomocniczą, która będzie sprawdzać, czy okręt został zatopiony.
Metodzie nadamy nazwę isSunk. Jej zadaniem będzie sprawdzenie
okrętu i zwrócenie wartości true, jeśli został zatopiony, oraz
wartości false, jeśli jeszcze jakoś unosi się na wodzie.
ętu,
Metoda pobiera obiekt okrde
każ
da
eglą
prz
nie
tęp
nas
a
go pól,
z zajmowanych przez nie trafione.
tało
zos
czy
c,
zają
sprawd
isSunk: function(ship) {
for (var i = 0; i < this.shipLength; i++) {
if (ship.hits[i] !== ”hit”) {
return false;
}
return true;
Jeśli metoda znajdzie pole, które jeszcze
nie zostało trafione, będzie to znaczyło,
że okręt wciąż jeszcze pływa, więc
metoda musi zwrócić wartość false.
}
}
W przeciwnym razie
okręt został zatopiony!
A zatem zwracamy true.
Dodaj tę metodę do obiektu modelu,
bezpośrednio poniżej metody fire.
Teraz możemy użyć tej metody wewnątrz metody ILUH, by sprawdzić,
czy okręt został zatopiony.
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;
Tutaj, kiedy już mamy pewność, że okręt
został trafiony, dodamy sprawdzenie
warunku, czy został także zatopiony.
Jeśli okręt został zatopiony, powiększymy
o jeden liczbę zatopionych okrętów,
przechowywaną we właściwości shipsSunk
modelu.
}
}
return false;
},
isSunk: function(ship) { ... }
372
Rozdział 8.
Tutaj, bezpośrednio poniżej metody fire dodaliśmy nową
metodę isSunk. Upewnij się, czy pomiędzy wszystkimi
właściwościami i metodami obiektu są przecinki!
Łączenie wszystkiego w całość
Prezentacja zatopienia…
I to mniej więcej wszystko, co dotyczyłoby obiektu modelu. Obiekt ten przechowuje stan gry oraz dysponuje
logiką wykrywającą celne i chybione strzały. Jedyną rzeczą, jakiej brakuje, jest kod, który poinformuje widok
o tym, że obiekt modelu zarejestrował nowy strzał, chybiony bądź celny. Zajmiemy się tym teraz.
var model = {
boardSize: 7,
numShips: 3,
shipLength: 3,
shipsSunk: 0,
};
delu, więc
To jest cały obiekt mo kod w całości
możesz zobaczyć jego
w jednym miejscu.
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);
w polu
Poinformuj widok, że nych
if (index >= 0) {
o współrzędnych zapisa my trafienie.
ma
ship.hits[index] = ”hit”;
w parametrze guess
view.displayHit(guess);
I poproś widok o wyświetlenie
view.displayMessage(”TRAFIONY!”);
komunikatu „TRAFIONY!”
if (this.isSunk(ship)) {
view.displayMessage(”ZatopiïeĂ möj okrÚt!”);
this.shipsSunk++;
Poinformuj gracza, że
}
ten strzał doprowadził
do zatopienia okrętu!
return true;
}
w polu
}
Poinformuj widok, że nych
rzędnych zapisa
pół
ws
o
view.displayMiss(guess);
mamy pudło.
w parametrze guess
view.displayMessage(”SpudïowaïeĂ.”);
I poproś widok o wyświetlenie
return false;
komunikatu „Spudłowałeś.”
},
isSunk: function(ship) {
Pamiętaj, że metody w obiekcie
for (var i = 0; i < this.shipLength; i++) {
widoku dodają do elementu, którego
identyfikator odpowiada wierszowi
if (ship.hits[i] !== ”hit”) {
i kolumnie zapisanym w parametrze
return false;
guess, klasę „hit” lub „miss”. A zatem
obiekt widoku przenosi łańcuch „hit”
}
zapisany w tablicy hits do kodu
return true;
HTML. Pamiętaj jednak, że łańcuch
„hit” w kodzie HTML służy wyłącznie
}
do celów prezentacyjnych, natomiast
}
ten sam łańcu
ch w obiekcie modelu
reprezentuje bieżący stan gry.
jesteś tutaj
373
Ćwiczenie modelu
Dodaj cały kod modelu do pliku battleship.js. Wypróbuj go, wywołując metodę ILUH modelu
i przekazując do niej za każdym razem wiersz i kolumnę wytypowanego pola. Położenie
naszych statków wciąż jest podane na stałe, zatem trafienie ich wszystkich nie powinno
przysporzyć Ci problemów. Spróbuj także dodać kilka własnych strzałów (by kilka razy
chybić). Skopiuj plik battleshp_tester.js, by przejrzeć naszą wersję kodu testowego.
Jazda próbna
model.fire(”00”);
Aby uzyskać te
same wyniki, które
tu pokazaliśmy,
będziesz musiał
usunąć lub umieścić
w komentarzu
napisany wcześniej
kod, który posłużył
do testowania
widoku. W pliku
battleship_tester.js
możesz zobaczyć, jak
to zrobić.
model.fire(”06”);
model.fire(”16”);
model.fire(”26”);
model.fire(”34”);
model.fire(”24”);
model.fire(”44”);
model.fire(”12”);
model.fire(”11”);
model.fire(”10”);
Odśwież stronę battleship.html. Oddane przez Ciebie strzały, te celne oraz chybione,
powinny zostać wyświetlone na planszy.
Nie istnieją
głupie pytania
P: Czy stosowanie łańcucha odwołań w celu łączenia
instrukcji jest lepszym rozwiązaniem od korzystania
z niezależnych instrukcji?
O: Niekoniecznie jest lepsze, nie. Łańcuchy odwołań wcale nie są
bardziej efektywne (oszczędzają nam utworzenia jednej zmiennej),
jednak skracają kod. Uważamy, że krótkie łańcuchy (składające
się co najwyżej z dwóch, trzech poziomów) są łatwiejsze do
zrozumienia od kilku odrębnych wierszy kodu, ale to nasza opinia.
Jeśli wolisz stosować odrębne instrukcje, nie ma sprawy. Jeśli
jednak wybierzesz łańcuchy odwołań, pamiętaj o tym, by nie
były naprawdę długie — zbyt długie łańcuchy trudno analizować
i zrozumieć.
P: W naszym kodzie mamy tablicę (locations) wewnątrz
obiektu (ship), który jest umieszczony w tablicy (ships).
Jak głęboka może być taka struktura zagnieżdżonych tablic
i obiektów?
374
Rozdział 8.
O: Tak głęboka, jak nam się będzie podobać. W praktyce jest
jednak mało prawdopodobne, że tworzone łańcuchy kiedykolwiek
będą głębsze (a jeśli okaże się, że jednak składają się z więcej
niż trzech lub czterech poziomów, może to świadczyć o tym,
że struktura danych staje się zbyt złożona i trzeba ją przemyśleć).
P: Zauważyłem, że do modelu dodaliśmy właściwość
o nazwie boardSize, jednak nie widziałem, żeby była
gdzieś używana. Do czego służy?
O: Zarówno tej, jak i innych właściwości modelu będziemy
używali w kodzie, który wkrótce napiszemy. Zadaniem modelu jest
przechowywanie informacji o stanie gry, a właściwość ERDUG6L]H,
bez dwóch zdań, zalicza się do tego stanu. Kontroler będzie
uzyskiwał dostęp do stanu, odwołując się do właściwości modelu,
a podczas dalszych prac dodamy kolejne metody modelu, które
także będą korzystały z tych właściwości.
Łączenie wszystkiego w całość
Implementacja kontrolera
Skoro zakończyliśmy już prace nad widokiem i modelem, zaczniemy łączyć poszczególne
elementy naszej aplikacji i zaimplementujemy kontroler. Ogólnie rzecz biorąc, kontroler spaja
ze sobą widok i model, bo pobiera pole wytypowane przez użytkownika, sprawdza, czy strzał
był celny, czy nie, a następnie zapisuje go w modelu. Kontroler dba także o pewne szczegóły
administracyjne, takie jak liczba prób oraz postępy użytkownika w grze. Podczas wykonywania
wszystkich tych operacji kontroler bazuje na modelu, który przechowuje bieżący stan gry, oraz na
widoku, który ją wyświetla w oknie przeglądarki.
Poniżej przedstawiliśmy pełną listę obowiązków kontrolera.
Pobieranie i przetwarzanie pól sprawdzanych przez użytkownika (np. ”A0” lub ”B1”).
Śledzenie liczby prób.
Wymuszanie aktualizacji widoku na podstawie wyników ostatniego strzału oddanego
przez użytkownika.
Określanie momentu zakończenia gry (czyli sprawdzanie, czy zostały zatopione
wszystkie okręty).
Prace nad implementacją kontrolera zaczniemy od zdefiniowania właściwości JXHVVHV.
Następnie zaimplementujemy jedną metodę, SURFHVV*XHVV, która na podstawie przekazanych
alfanumerycznych współrzędnych pola wskazanego przez użytkownika przetworzy je i przekaże
do modelu.
guesses3U]HFKRZXMHOLF]EĐSUyE
Kontroler
processGuess3U]HWZDU]DZVSyâU]ĐGQH
SRODZVND]DQHJRSU]H]Xİ\WNRZQLND
LSU]HND]XMHMHGRPRGHOX:\NU\ZD
NRQLHFJU\
Poniżej przedstawiliśmy szkielet kodu kontrolera; teraz, na kilku kolejnych stronach,
będziemy go uzupełniać.
var controller = {
guesses: 0,
na razie
Tutaj definiujemy obiekt kontrolera;
której
zawiera on jedną właściwość, guesses,
początkowo nadajemy wartość 0.
processGuess: function(guess) {
// tutaj pöěniej dodamy wiÚcej kodu
}
};
A to jest początkowy szkielet metody processGuess,
do której będą przekazywane współrzędne pola
wytypowanego przez użytkownika, takie jak „A0”.
jesteś tutaj
375
Przetwarzanie pola wskazanego przez użytkownika
Przetwarzanie pola wskazanego przez użytkownika
Zadaniem kontrolera jest pobranie pola wskazanego przez użytkownika, sprawdzenie, czy
można je ostrzelać, a następnie przekazanie jego współrzędnych do modelu. Skąd można pobrać
te informacje? Nie obawiaj się, już zaraz wszystko wyjaśnimy. Na razie założymy, że w jakimś
momencie rozgrywki jakiś kod wywoła metodę SURFHVV*XHVV kontrolera i przekaże do niej
łańcuch znaków w następującej postaci:
Doskonale już znasz postać
współrzędnych stosowanych w grze
w okręty: to litera i cyfra.
"A3"
Teraz, kiedy już pobraliśmy współrzędne w odpowiedniej postaci (łańcuch składający się
z dwóch znaków alfanumerycznych, taki jak ”A3”), musimy je przekształcić do postaci,
którą będzie w stanie zrozumieć model (czyli do postaci łańcucha znaków składającego
się z dwóch cyfr, takiego jak ”03”). Poniżej naszkicowaliśmy ogólną postać sposobu
konwersji prawidłowych współrzędnych do postaci współrzędnych zapisanych przy użyciu
dwóch cyfr.
To doskonała
technika pisania
kodu. Koncentrujemy
się na wymaganiach
konkretnego kodu,
nad którym aktualnie
pracujemy. Zajmowanie
się całym problemem
jednocześnie rzadko daje
pomyślne efekty.
Gracz na pewno nigdy
nie poda niewłaściwych
współrzędnych, prawda?
Ha! Lepiej jednak
upewnijmy się,
że to, co wpisał,
to prawidłowe
współrzędne pola.
Załóżmy, że otrzymaliśmy
alfanumeryczny łańcuch znaków
w postaci:
"A3"
"A"
Zamienimy literę na
cyfrę i sprawdzimy,
czy mieści się ona
w zakresie od zera
do sześciu.
"3"
0
"03"
A zatem podzielmy łańcuch znaków
na części, znaki określające
wiersz i kolumnę.
Skonwertujemy ten znak na liczbę
i upewnimy się, czy mieści się
ona w zakresie od zera do sześciu.
W końcu obie cyfry ponownie
połączymy w nowy łańcuch znaków.
Wszystko po kolei. Musimy także pamiętać o sprawdzeniu, czy dane wpisane przez
użytkownika są prawidłowe. Zanim zabierzemy się do kodowania, spróbujmy to
wszystko zaplanować.
376
Rozdział 8.
Łączenie wszystkiego w całość
Planowanie kodu…
Zamiast umieszczać cały kod służący do przetwarzania pola wskazanego
przez użytkownika w metodzie SURFHVV*XHVV, napiszemy niewielką
funkcję pomocniczą (w końcu może się nam jeszcze do czegoś przydać).
Nazwiemy ją SDUVH*XHVV.
Zanim zabierzemy się do pisania jej kodu, prześledźmy, jak będzie
działać.
Pobranie
ZVSyâU]ĐGQ\FK
1
1
2
3RELHU]HP\SROHZVND]DQHSU]H]
Xİ\WNRZQLNDL]DSLVDQHZVSRVyE
FKDUDNWHU\VW\F]Q\GODJU\ZRNUĐW\
F]\OLMDNROLWHUĐLF\IUĐ
6SUDZG]LP\F]\ZSLVDQHZVSyâU]ĐGQH
VċSUDZLGâRZH âDĚFXFK]QDNyZQLH
MHVWSXVW\F]\QLHMHVW]E\WNUyWNL
OXE]E\WGâXJL 3
2GF]\WDP\OLWHUĐL]DPLHQLP\MċQD
F\IUĐ$QD%QDLWG
4
6SUDZG]LP\F]\OLF]EDX]\VNDQD
ZNURNXMHVWSUDZLGâRZD F]\PLHĤFL
VLĐZ]DNUHVLHRG]HUDGRV]HĤFLX 5
6SUDZG]LP\F]\GUXJDOLF]EDMHVW
SUDZLGâRZD F]\PLHĤFLVLĐZ]DNUHVLH
RG]HUDGRV]HĤFLX 6
2
nie
&]\GDQHVċ
SUDZLGâRZH"
WDN
3
4
.RQZHUWXMHP\
OLWHUĐQDF\IUĐ
nie
&]\OLF]ED
MHVWSUDZLGâRZD"
WDN
5
nie
-HĤOLNWyU\NROZLHN]WHVWyZGD
Z\QLNQHJDW\ZQ\]ZUyFLP\QXOO
:SU]HFLZQ\PUD]LHSRâċF]\P\GZLH
OLF]E\WZRU]ċFQRZ\âDĚFXFK]QDNyZ
NWyU\]ZUyFLP\
&]\GUXJD
OLF]EDMHVW
SUDZLGâRZD"
WDN
6
]ZUDFDP\
QXOO
WZRU]\P\
L]ZUDFDP\
âDĚFXFK
jesteś tutaj
377
Przetwarzanie współrzędnych
Implementacja funkcji parseGuess
Mamy już dobry plan działania tej funkcji, a zatem możemy przystąpić do pisania jej kodu.
1
7\PLGZRPDSXQNWDPL]DMPLHP\VLĐMHGQRF]HĤQLH6SURZDG]DMċVLĐRQHGRSREUDQLDZVSyâU]ĐGQ\FKZSLVDQ\FKSU]H]Xİ\WNRZQLNDLVSUDZG]HQLDF]\]DZLHUDMċF\MH
âDĚFXFK]QDNyZMHVWSUDZLGâRZ\:W\PPRPHQFLHSRSUDZQRĤþâDĚFXFKDVSURZDG]D
VLĐGRWHJRF]\QLHMHVWUyZQ\QXOOLF]\PDGRNâDGQLHGZD]QDNLGâXJRĤFL
2
Współrzędne pola do sprawdzenia są przekazywane jako parametr guess.
Następnie sprawdzamy, czy przekazany
łańcuch jest różny od null i czy ma
dokładnie dwa znaki długości.
function parseGuess(guess) {
if (guess === null || guess.length !== 2) {
alert(”Ups, proszÚ wpisaÊ literÚ i cyfrÚ.”);
}
}
3
Jeśli nie spełnia tych warunków,
wyświetlamy stosowny komunikat.
1DVWĐSQLHRGF]\WXMHP\OLWHUĐLNRQZHUWXMHP\MċQDOLF]EĐXİ\ZDMċF
SRPRFQLF]HMWDEHOL]DZLHUDMċFHMOLWHU\RG$GR):FHOXX]\VNDQLDWHM
F\IU\Z\ZRâXMHP\PHWRGĐLQGH[2INWyUD]ZUyFLLQGHNVOLWHU\ZWDEOLF\
Tablica zawierająca wszystkie litery,
które mogą się znaleźć w prawidłowych
współrzędnych.
function parseGuess(guess) {
var alphabet = [”A”, ”B”, ”C”, ”D”, ”E”, ”F”, ”G”];
if (guess === null || guess.length !== 2) {
alert(”Ups, proszÚ wpisaÊ literÚ i cyfrÚ.”);
} else {
firstChar = guess.charAt(0);
Pobieramy pierwszy znak
przekazanego łańcucha.
var row = alphabet.indexOf(firstChar);
}
}
378
Rozdział 8.
Następnie, wywołując metodę indexOf, uzyskujemy liczbę
z zakresu od zera od sześciu, odpowiadającą konwertowanej
literze. Sprawdź kilka różnych współrzędnych, by przekonać
się, czy ten sposób konwersji działa.
Łączenie wszystkiego w całość
4
5
7HUD]]DMPLHP\VLĐVSUDZG]HQLHPREX]QDNyZZVSyâU]ĐGQ\FKERPXVLP\
VLĐXSHZQLþF]\VċOLF]EDPL]]DNUHVXRG]HUDGRV]HĤFLX LQQ\PLVâRZ\
F]\SUDZLGâRZRRNUHĤODMċSRâRİHQLHMHGQHJR]SyOSODQV]\ function parseGuess(guess) {
var alphabet = [”A”, ”B”, ”C”, ”D”, ”E”, ”F”, ”G”];
if (guess === null || guess.length !== 2) {
alert(”Ups, proszÚ wpisaÊ literÚ i cyfrÚ.”);
} else {
W tym miejscu dodaliśmy kod pobierający
drugi znak łańcucha, określający kolumnę.
firstChar = guess.charAt(0);
var row = alphabet.indexOf(firstChar);
var column = guess.charAt(1);
Używając funkcji isNaN, sprawdzamy,
czy wiersz i kolumna są prawidłowymi
liczbami.
if (isNaN(row) || isNaN(column)) {
alert(”Ups, to nie sÈ wspöïrzÚdne!”);
} else if (row < 0 || row >= model.boardSize ||
column < 0 || column >= model.boardSize) {
alert(”Ups, pole poza planszÈ!”);
}
}
}
Oprócz tego upewniamy się, czy
są to liczby z zakresu od zera do
sześciu.
Zauważ, że jak zwariowani korzystam
tutaj z konwersji typów! column jest y
łańcuchem znaków, a zatem, kiedy
sprawdzamy, czy jego wartość mieści
się w zakresie 0 – 6, bazujemy na
konwersji, która na czas porównania
przekształci łańcuch na liczbę.
W rzeczywistości postępujemy tu w jeszcze
bardziej ogólny sposób, gdyż nie podajemy
liczby 6 na stałe, lecz prosimy model,
by powiedział, jaki jest rozmiar planszy,
i w porównaniu używamy przekazanej wartości.
WYSIL
SZARE KOMÓRKI
Zamiast na stałe podawać w kodzie liczbę 6, stanowiącą największą wartość,
jaką można zapisać w zmiennych URZ i FROXPQ, skorzystaliśmy z właściwości
ERDUG6L]H modelu. Jak uważasz, jakie są zalety takiego rozwiązania
w perspektywie korzystania i utrzymania naszej aplikacji przez dłuższy czas?
jesteś tutaj
379
Kończenie implementacji funkcji processGuess
6
3R]RVWDâMHV]F]HGRQDSLVDQLDRVWDWQLIUDJPHQWNRGXIXQNFMLSURFHVV*XHVV«-HĤOLNWyU\NROZLHN
]WHVWyZSRSUDZQRĤFLZ\NDİHQLHSUDZLGâRZRĤFLIXQNFML]ZUDFDQXOO:SU]HFLZQ\PUD]LH
]ZUDFDQHVċZVSyâU]ĐGQHZLHUV]DLNROXPQ\SRâċF]RQHZIRUPLHâDĚFXFKD]QDNyZ
function parseGuess(guess) {
var alphabet = [”A”, ”B”, ”C”, ”D”, ”E”, ”F”, ”G”];
if (guess === null || guess.length !== 2) {
alert(”Ups, proszÚ wpisaÊ literÚ i cyfrÚ.”);
} else {
firstChar = guess.charAt(0);
var row = alphabet.indexOf(firstChar);
var column = guess.charAt(1);
if (isNaN(row) || isNaN(column)) {
alert(”Ups, to nie sÈ wspöïrzÚdne!”);
} else if (row < 0 || row >= model.boardSize ||
Zwróć uwagę, że
tutaj wykonujemy
konkatenację
współrzędnych
wiersza i kolumny,
aby uzyskać jeden
łańcuch znaków,
który następnie
zwracamy. Także
tutaj korzystamy
z konwersji typów:
zmienna row zawiera
liczbę, a zmienna
column łańcuch
znaków, zatem po ich
połączeniu uzyskamy
łańcuch znaków.
column < 0 || column >= model.boardSize) {
alert(”Ups, pole poza planszÈ!”);
} else {
return row + column;
}
}
return null;
}
Jazda próbna
Skoro jesteśmy tutaj,
wygląda na to, że
wszystko jest w porządku,
zatem możemy zwrócić
współrzędne wiersza
i kolumny.
Jeśli dotarliśmy
znaczy to, że gd do tej instrukcji,
wykryto nieprawizieś po drodze
zwracamy null. dłowości, a zatem
No dobrze. Upewnij się, że dodałeś cały nowy kod do pliku battleship.js, a następnie
poniżej niego dodaj kilka, przedstawionych poniżej, wywołań funkcji.
console.log(parseGuess("A0"));
console.log(parseGuess("B6"));
console.log(parseGuess("G3"));
console.log(parseGuess("H0"));
console.log(parseGuess("A7"));
Odśwież stronę battleship.html i upewnij się, że jest widoczne
okno konsoli. Powinieneś w nim zobaczyć wyniki wywołań
funkcji SDUVH*XHVV, a także jeden lub dwa komunikaty.
380
Rozdział 8.
Konsola JavaScript
00
QXOO
QXOO
Łączenie wszystkiego w całość
W międzyczasie w kontrolerze…
Skoro uporaliśmy się z funkcją SDUVH*XHVV, możemy zająć się
implementacją kontrolera. Najpierw zintegrujemy funkcję SDUVH*XHVV
z istniejącym już kodem kontrolera.
var controller = {
guesses: 0,
processGuess: function(guess) {
var location = parseGuess(guess);
if (location) {
}
}
};
Skorzystamy z funkcji parseGuess, by
sprawdzić poprawność współrzędnych
podanych przez użytkownika.
Jeśli funkcja parseGuess nie zwróci wartości
null, będziemy wiedzieć, że współrzędne są
prawidłowe.
Pamiętaj, że null
jest traktowane
jak logiczny fałsz.
Tutaj umieścimy pozostałą
część kodu kontrolera.
I w ten sposób zaimplementowaliśmy pierwszy z obowiązków kontrolera.
Pobieranie i przetwarzanie pól sprawdzanych przez użytkownika
(np. "A0" lub "B1").
Śledzenie liczby prób.
Wymuszanie aktualizacji widoku na podstawie wyników ostatniego strzału
oddanego przez użytkownika.
Teraz zajmiemy się
tymi zagadnieniami.
Określanie momentu zakończenia gry (czyli sprawdzanie, czy zostały
zatopione wszystkie okręty).
Zliczanie prób i oddawanie strzałów
Kolejny punkt na naszej liście jest bardzo prosty: aby liczyć ilość prób, wystarczy
inkrementować wartość właściwości JXHVVHV po każdym strzale oddanym przez
użytkownika. Jak się przekonasz, analizując kod, nie będziemy karać użytkownika za
podanie nieprawidłowych współrzędnych pola.
Kolejną czynnością będzie wymuszenie aktualizacji modelu na podstawie podanych
współrzędnych. W tym celu wywołamy metodę ILUH modelu. W końcu, kiedy
podajemy współrzędne użytkownikowi, chodzi nam o oddanie strzału z nadzieją, że
uda mu się trafić i zatopić nasz okręt. Pamiętaj przy tym, że metoda ILUH wymaga
przekazania łańcucha znaków, zawierającego numer wiersza i kolumny, a tak się
szczęśliwie składa, że taki łańcuch uzyskujemy po wywołaniu funkcji SDUVH*XHVV.
Połączymy zatem to wszystko, implementując następny punkt naszej listy…
jesteś tutaj
381
Kiedy gra się kończy?
var controller = {
guesses: 0,
processGuess: function(guess) {
var location = parseGuess(guess);
if (location) {
this.guesses++;
Jeśli użytkownik podał
prawidłowe współrzędne
pola, powiększamy liczbę
prób o jeden.
var hit = model.fire(location);
}
Następnie przekazujemy numery wiersza i kolumny
zapisane w formie łańcucha znaków do metody fire
modelu. Pamiętaj, że metoda ta zwraca true, jeśli
okręt został trafiony.
}
};
e, że
Pamiętasz zapewn sses++
wyrażenie this.gueści
dodaje 1 do warto s.
se
właściwości gues ie tak
Działa ono dokładnie i++
samo jak wyrażen .
używane w pętlach
Oprócz tego zauważ,
że jeśli gracz poda
nieprawidłowe współrzędne
pola, nie będziemy go karać
i uwzględniać tej próby.
Czy to koniec gry?
Obecnie musimy tylko określić, kiedy gra została zakończona. Jak należy to zrobić? Cóż, wiemy,
że gra się kończy, kiedy zostaną zatopione wszystkie trzy okręty. A zatem za każdym razem, kiedy
okaże się, że użytkownik oddał celny strzał, sprawdzimy wartość właściwości PRGHOVKLSV6XQN,
by przekonać się, czy zostały zatopione trzy okręty. Spróbujmy to jednak nieco uogólnić: zamiast
używać w porównaniu liczby 3, wykorzystamy w nim właściwość QXP6KLSV modelu. Może się
zdarzyć, że w przyszłości zdecydujesz się zmienić liczbę okrętów na 2 lub 4, a dzięki takiemu
rozwiązaniu nie będziesz musiał wprowadzać żadnych zmian, by zapewnić prawidłowe działanie gry.
var controller = {
guesses: 0,
processGuess: function(guess) {
var location = parseGuess(guess);
if (location) {
Jeśli próba zakończyła się trafieniem
okrętu i liczba zatopionych okrętów
jest równa liczbie okrętów biorących
udział w grze, wyświetlamy
użytkownikowi komunikat o zatopieniu
wszystkich okrętów.
this.guesses++;
var hit = model.fire(location);
if (hit && model.shipsSunk === model.numShips) {
view.displayMessage(”ZatopiïeĂ wszystkie moje okrÚty, w ” +
this.guesses + ” pröbach.”);
}
}
}
};
382
Rozdział 8.
Dodatkowo wyświetlamy także liczb
ę
prób, które użytkownik wykonał,
by
zatopić okręty. Właściwość gues
ses
jest właściwością obiektu „this”,
czyli
obiektu controller.
Łączenie wszystkiego w całość
Teraz upewnij się, że dodałeś kod kontrolera do pliku battleship.js, a następnie poniżej
niego dodaj kilka wywołań funkcji, które pozwolą przetestować jego działanie. Odśwież
stronę battleship.html i zanotuj trafienia oraz pudła na planszy. Czy wszystkie znalazły
się w odpowiednich miejscach? (Aby zobaczyć wyniki generowane przez naszą wersję,
pobierz plik battleship_tester.js).
Jazda próbna
Także teraz, aby uzyskać te same wyniki,
które przedstawiliśmy
obok, będziesz musiał usunąć poprzedni kod
umieścić go w komentarzu. Zajrzyj do pliku testowy lub
battleship_tester.js,
aby zobaczyć, jak to zrobić.
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”);
Wywołujemy metodę processGuess obiektu
kontrolera, przekazując do niej współrzędne
wytypowanych pól, zapisane w sposób
charakterystyczny dla gry w okręty.
WYSIL
SZARE KOMÓRKI
Kiedy gracz zatopi wszystkie trzy okręty, informujemy go o zakończeniu gry,
generując odpowiedni komunikat, wyświetlany w obszarze powiadomień.
Jednak gracz wciąż może wpisywać współrzędne kolejnych pól. Gdybyś chciał
poprawić grę, uniemożliwiając wpisywanie współrzędnych po zatopieniu
wszystkich okrętów, jak byś to zrobił?
jesteś tutaj
383
Pobieranie wartości z formularza
Pobieranie współrzędnych
Teraz, kiedy już zaimplementowałeś podstawową logikę i prezentację gry,
musisz się zająć wpisywaniem i pobieraniem współrzędnych, co pozwoli na
faktyczne prowadzenie rozgrywki. Zapewne pamiętasz, że w kodzie HTML
strony znajduje się już element IRUP!, oczekujący na dane. W jaki sposób
możemy połączyć go z pozostałą częścią gry?
Będziemy potrzebowali procedury obsługi zdarzeń. Już wcześniej napisaliśmy
trochę na temat takich procedur obsługi zdarzeń. Jednak teraz poświęcimy
im na tyle dużo czasu i uwagi, by zaimplementować taką procedurę
i uruchomić naszą grę. W następnym rozdziale spróbujemy omówić wszystkie
pozostałe szczegóły procedur obsługi zdarzeń. Obecnie chodzi o to, byś
ogólnie zrozumiał sposób działania procedur obsługi zdarzeń związanych
z elementami formularzy — a nie o to, byś zdobył wszystkie szczegółowe
informacje na ich temat.
A0
To jest nasz element
form, gotowy na
wpisywanie danych.
Poniżej opisaliśmy, jak to wygląda w ujęciu ogólnym.
1
*UDF]ZSLVXMHZVSyâU]ĐGQHVWU]DâXLNOLNDSU]\FLVN2JQLD
2
.OLNQLĐFLHSU]\FLVNXSRZRGXMHZ\NRQDQLH]JyU\RNUHĤORQHMSURFHGXU\REVâXJL]GDU]HĚ
3
3URFHGXUDREVâXJLSU]\FLVNX2JQLDSRELHUDZVSyâU]ĐGQHZSLVDQH
SU]H]Xİ\WNRZQLNDZSROXIRUPXODU]DLSU]HND]XMHMHGRNRQWUROHUD
”
1
2
3
function handleFireButton() {
// Pobieramy wspöïrzÚdne podane w formularzu
// i przekazujemy je do kontrolera.
}
384
Rozdział 8.
“
Łączenie wszystkiego w całość
Dodanie procedury obsługi zdarzeń do przycisku Ognia!
Aby nasza gra zaczęła w końcu działać, będziemy jeszcze musieli dodać odpowiednią procedurę
obsługi zdarzeń do przycisku Ognia!. Pierwszą rzeczą, której będziemy potrzebowali, jest
pobranie referencji do przycisku na podstawie jego identyfikatora. Zajrzyj zatem do kodu
HTML strony — okaże się, że przycisk Ognia! ma identyfikator ĵILUH%XWWRQĵ. Aby pobrać
referencję do przycisku, wystarczy już tylko wywołać metodę GRFXPHQWJHW(OHPHQW%\,G.
Dysponując referencją do przycisku, możemy przypisać procedurę obsługi zdarzeń przy użyciu
właściwości RQFOLFN, tak jak pokazaliśmy poniżej.
Ten kod musimy gdzieś umieścić,
zapiszemy go zatem w funkcji init.
Najpierw pobieramy referencję do
przycisku Ognia!, używając przy tym
jego identyfikatora.
function init() {
var fireButton = document.getElementById(”fireButton”);
fireButton.onclick = handleFireButton;
Teraz możemy dodać do przycisku funkcję
o nazwie handleFireButton, która będzie
obsługiwać zdarzenia jego kliknięcia.
}
Nie możemy także zapomnieć o
rozpoczęciu funkcji handleFireButton.
Oto funkcja handleFireButton. Będzie
ona wywoływana po każdym kliknięciu
przycisku Ognia!
function handleFireButton() {
// kod pobierajÈcy wartoĂÊ pola z formularza
}
window.onload = init;
Już za chwilę zabierzem
za pisanie kodu tej fun y się
kcji.
Zgodnie z informacjami podanymi
w rozdziale 6., chcemy, by przeglądarka
wykonała funkcję init bezpośrednio po
zakończeniu wczytywania strony.
Pobieranie współrzędnych z pola formularza
To właśnie kliknięcie przycisku Ognia! jest czynnikiem, który rozpoczyna całą
procedurę strzału. Jednak współrzędne pola wskazanego przez gracza są zapisane
w polu tekstowym o identyfikatorze ĵJXHVV,QSXWĵ. Wartość pola można pobrać,
korzystając z właściwości YDOXH elementu pola. Poniżej pokazaliśmy, jak to zrobić.
function handleFireButton() {
Najpierw pobieramy referencję do elementu pola
formularza, używając w tym celu identyfikatora
elementu „guessInput”.
var guessInput = document.getElementById(”guessInput”);
var guess = guessInput.value;
}
Następnie pobieramy
współrzędne z pola. Są one
zapisane we właściwości value
elementu pola.
A zatem mamy już wartość, teraz musimy tylko coś
z nią zrobić. Na szczęście, mamy naprawdę sporo
gotowego kodu, który możemy wykorzystać. Spróbujmy
to zatem zrobić.
jesteś tutaj
385
Testowanie kontrolera
Przekazywanie współrzędnych do kontrolera
W tym miejscu wszystko ostatecznie się połączy. Mamy już gotowy kontroler, który
wprost nie może się doczekać na współrzędne pola wskazanego przez użytkownika.
Musimy przekazać te współrzędne do kontrolera. A zatem, do roboty.
function handleFireButton() {
var guessInput = document.getElementById(”guessInput”);
var guess = guessInput.value;
controller.processGuess(guess);
guessInput.value = ””;
}
Jazda próbna
B6
C6
C4
D4
E4
B0
B1
B2
Rozdział 8.
Ten prosty wiersz kodu czyści pole tekstowe
formularza, zapisując w nim pusty łańcuch znaków.
W ten sposób gracz nie będzie musiał samodzielnie
zaznaczać tekstu i usuwać go przed wpisaniem
współrzędnych kolejnego strzału, gdyż mogłoby to być
uciążliwe.
Już nie będzie więcej jazd próbnych. W końcu możesz zagrać w prawdziwą grę!
Upewnij się, że dodałeś cały nowy kod do pliku battleship.js, a następnie odśwież
w przeglądarce stronę battleship.html. Musisz pamiętać, że położenie okrętów jest na
razie podane na stałe, więc nie powinieneś mieć większego problemu z wygraniem.
Poniżej znajdziesz współrzędne, które zapewnią Ci zwycięstwo. Wpisz także kilka
niecelnych strzałów oraz parę nieprawidłowych współrzędnych.
A6
386
Przekazujemy współrzędne podane
przez użytkownika do kontrolera i od
tego momentu cała reszta magicznie
powinna wydarzyć się sama!
To są wygrywające
współrzędne pogrupowane
według okrętów. Nie trzeba
jednak wpisywać ich w takiej
kolejności. Spróbuj je
trochę wymieszać. Wpisz
także kilka nieprawidłowych
współrzędnych, przeplatając
je prawidłowymi. Dodatkowo
wpisz kilka niecelnych
strzałów. To wszystko będzie
stanowiło element testowania
gry i zapewniania jej
wysokiej jakości.
Łączenie wszystkiego w całość
.RGRZDQLHQDSRZDĝQLH
Czy nie uważasz, że ciągłe klikanie przycisku Ognia! za każdym razem po
wpisaniu współrzędnych jest bardzo niewygodne? No jasne, takie klikanie działa,
jednak jednocześnie jest wolne i uciążliwe. Czy nie byłoby wygodniej, gdyby
wystarczyło nacisnąć klawisz Enter? Poniżej przedstawiliśmy krótki fragment kodu,
który pozwala na obsługę naciśnięcia tego klawisza.
function init() {
var fireButton = document.getElementById(”fireButton”);
fireButton.onclick = handleFireButton;
var guessInput = document.getElementById(”guessInput”);
guessInput.onkeypress = handleKeyPress;
}
Oto nasza nowa procedura obsługi
zdarzeń. Będzie ona wywoływana
za każdym razem, kiedy po
umieszczeniu kursora w polu
formularza naciśniesz jakiś klawisz.
Dodaj nową procedurę obsługi zdarzeń.
Ta będzie obsługiwać zdarzenia naciśnięcia
klawisza w polu formularza.
Przeglądarka przekazuje do procedury
obsługi obiekt zdarzenia. Obiekt ten
zawiera informację, który klawisz
został naciśnięty.
function handleKeyPress(e) {
var fireButton = document.getElementById(”fireButton”);
if (e.keyCode === 13) {
fireButton.click();
return false;
}
}
W końcu zwracamy false, aby
formularz nie robił już nic więcej
(szczególnie, by nie próbował
gdzieś wysyłać danych).
Jeśli naciśniesz klawisz Enter, we
właściwości keyCode obiektu zdarzenia
zostanie zapisana wartość 13. W takim
przypadku chcemy, by przycisk Ognia!
zachował się tak, jak byśmy go kliknęli.
W tym celu wystarczy wywołać metodę
click elementu fireButton (co sprawi,
że przycisk uzna, iż został kliknięty).
Zaktualizuj kod funkcji init, a następnie dodaj funkcję KDQGOH.H\3UHVV
w dowolnym miejscu kodu. Odśwież stronę i zacznij zabawę!
jesteś tutaj
387
Planowanie rozmieszczania okrętów
Co nam jeszcze zostało?
A tak… Położenie okrętów jest podane na stałe!
Obecnie dysponujesz zupełnie niesamowitą grą internetową, utworzoną przy użyciu
bardzo prostej strony WWW, kilku obrazków i nie więcej niż 100 wierszy kodu. Jednak
jest pewien aspekt tej gry, który sprawia, że nie jest ona w pełni satysfakcjonująca.
Chodzi o to, że okręty zawsze znajdują się w tym samym miejscu. Musisz zatem napisać
kod, który podczas rozpoczynania każdej nowej gry będzie rozmieszczał wszystkie okręty
w losowych miejscach (inaczej gra będzie wyjątkowo nudna).
Zanim zaczniemy, chcemy, żebyś wiedział, że opiszemy kod nieco bardziej pobieżnie —
dochodzisz już do takiego etapu, w którym znacznie lepiej potrafisz czytać i rozumieć
kod, a poza tym, nie ma w nim zbyt wielu nowych elementów. A zatem zaczynajmy.
Co musimy wziąć pod uwagę?
Jednak kolejnego okrętu nie może
my
umieścić w taki sposób, gdyż okrę
nałożyłyby się na siebie — wyst ty
ąpiła
kolizja z istniejącym okrętem. Poło by
tego okrętu trzeba będzie zmienić. żenie
ęt.
A tu jest kolejny okr
Tu mamy
miejsce na
okręt.
A
Okręty można
umieszczać
w pionie lub
w poziomie.
C
D
Okręt
Okręt
B
Okręt
Każdy okręt musi
się znaleźć w takim
miejscu, gdzie nie
będzie się pokrywał
z żadnym innym
okrętem.
E
F
G
Okręt
0
1
2
3
4
5
6
No dobrze, to jest trzecie
miejsce, gdzie można
umieścić okręt, tym razem
jest w porządku.
388
Rozdział 8.
Łączenie wszystkiego w całość
Magnesiki z kodem
$OJRU\WPUR]PLHV]F]DQLDRNUĐWyZQDSODQV]\]RVWDâ]DSLVDQ\QDPDJQHVLNDFK
SU]\F]HSLRQ\FKGRORGyZNLZORVRZHMNROHMQRĤFL&]\SRWUDILV]SRQRZQLHSRXNâDGDþ
PDJQHVLNLZHZâDĤFLZHMNROHMQRĤFLE\RGWZRU]\þG]LDâDMċF\DOJRU\WP"=DQLPSU]HMG]LHV]
GDOHMVSUDZGĮQDV]ċRGSRZLHGĮ]DPLHV]F]RQċSRGNRQLHFUR]G]LDâX
ie wymyślne
Algorytm to jedyn sekwencję
e
jąc
śla
re
ok
wo
sło
wanych w celu
czynności wykonylemu.
ob
pr
ia
an
rozwiąz
Wygeneruj losowe miejsce
do umieszczenia okrÚtu.
W pÚtli obsïuĝ wszystkie okrÚty,
ktöre majÈ byÊ uĝywane w grze.
Wygeneruj losowy kierunek (pionowy
lub
poziomy), w jakim zostanÈ rozmiesz
czone
poszczegölne pola zajmowane prze
z okrÚt.
Dodaj poïoĝenie nowego okrÚtu
do tablicy ships.
Sprawdě, czy
poïoĝenie no
wego
okrÚtu nie ko
liduje z ĝadn
ym
z juĝ istnie
jÈcych okrÚtö
w.
jesteś tutaj
389
Metody do rozmieszczania okrętów
Jak rozmieszczać okręty?
Podczas rozmieszczania okrętów na planszy należy uwzględnić dwa czynniki. Pierwszym jest orientacja
okrętu — każdy z okrętów może być rozmieszczony w pionie bądź w poziomie. Drugim czynnikiem jest
to, że poszczególne okręty nie mogą na siebie nachodzić. Znaczna część kodu, który zaraz napiszemy,
jest związana z zagwarantowaniem, że oba te czynniki będą spełnione. Jak już ostrzegaliśmy, nie
będziemy tłumaczyli wszystkich szczegółów działania przedstawianego kodu, jednak dysponujesz już
wszystkim, co jest potrzebne, by go przeanalizować, a jeśli poświęcisz mu dostatecznie dużo czasu,
na pewno zrozumiesz wszystkie jego tajniki. Nie ma w nim nic, czego byś już nie widział w tej książce
(z jedynym wyjątkiem, który za chwilę opiszemy). A zatem do roboty…
Nasz kod zorganizujemy w formie trzech metod, które umieścimy w obiekcie modelu. Oto one.
Q
generateShipLocations. To jest główna metoda. Tworzy ona tablicę VKLSV w obiekcie
modelu, a w niej tyle okrętów, ile wynosi wartość właściwości QXP6KLSV.
Q
generateShip. Metoda ta odpowiada za utworzenie jednego okrętu, umieszczonego gdzieś
na planszy. Jego położenie może, choć nie musi, pokrywać się z położeniem innych okrętów.
Q
collision. Metoda wymaga przekazania jednego okrętu i sprawdza, czy zajmowane
przez niego pola nie pokrywają się z innymi okrętami.
Funkcja generateShipLocations
Zacznijmy od metody JHQHUDWH6KLS/RFDWLRQV. Metoda ta będzie zawierać pętlę, w której będą tworzone
nowe okręty, aż do momentu, gdy w tablicy VKLSV znajdzie się ich odpowiednio dużo. Po wygenerowaniu
każdego nowego okrętu (do czego będzie używana metoda JHQHUDWH6KLS) zostanie wywołana metoda
FROOLVLRQ, która upewni się, że nowy okręt nie koliduje z żadnym z okrętów już znajdujących się na
planszy. Jeśli taka kolizja zostanie wykryta, funkcja usunie nowy okręt i spróbuje wygenerować następny.
W poniższym kodzie trzeba zwrócić uwagę na jedną rzecz, na pętlę do while. Pętla GRZKLOH działa
bardzo podobnie do pętli ZKLOH, jednak różni się od niej tym, że najpierw są wykonywane instrukcje
umieszczone wewnątrz pętli, a dopiero później sprawdzany warunek jej kontynuacji. Przekonasz się,
że niektóre warunki logiczne można wygodniej zaimplementować, używając właśnie pętli GRZKLOH
niż ZKLOH, choć zdarza się to raczej rzadko.
Tę metodę dodamy do obiektu modelu.
generateShipLocations: function() {
simy
Dla każdego okrętu mune
wa
mo
zaj
ć
wa
ero
gen
wy
y.
przez niego pola plansz
var locations;
for (var i = 0; i < this.numShips; i++) {
Generujemy nowy zestaw współrzędnych…
do {
Używamy
tu pętli do
while!
locations = this.generateShip();
} while (this.collision(locations));
this.ships[i].locations = locations;
}
},
390
Kiedy już wygenerujemy prawidłowy zestaw
pól dla nowego okrętu, zapisujemy je w tablicy
locations obiektu okrętu, w tablicy model.ships.
Rozdział 8.
…po czym sprawdzamy, czy nie
pokrywają się one z polami zajmowan
przez któryś z już istniejących okrę ymi
Jeśli pokrywają się, musimy pono tów.
wnie
wygenerować nowy zestaw pól. A
zatem
generujemy zestawy pól tak dług
o, jak
długo będą występować kolizje.
Łączenie wszystkiego w całość
Implementacja metody generateShip
Metoda JHQHUDWH6KLS tworzy tablicę zawierającą losowo wygenerowane współrzędne
pól zajmowanych przez jeden okręt, przy czym nie zwraca uwagi na to, czy będą one
kolidowały z położeniem innych okrętów. Metodę tę napiszemy w kilku krokach.
Pierwszym będzie losowy wybór kierunku rozmieszczenia okrętu, a może on być
umieszczony w pionie lub poziomie. Kierunek ten wybierzemy na podstawie wartości
liczby losowej. Jeśli liczba będzie równa 1, przyjmiemy, że okręt jest rozmieszczony
w poziomie; jeśli wylosowana zostanie liczba 0, okręt będzie rozmieszczony w pionie.
Do wyboru liczby losowej użyjemy naszych starych znajomych, czyli metod
0DWKUDQGRP oraz 0DWKIORRU, dokładnie tak samo, jak robiliśmy to wcześniej.
Także tę metodę dodajemy do obiektu modelu.
generateShip: function() {
var direction = Math.floor(Math.random() * 2);
Generacja
losowych liczb
0 i 1 przypomina
rzucanie monetą.
om, by wygenerować
Używamy metody Math.rand mnożymy wynik
1,
do
liczbę z zakresu od 0
z zakresu od
razy 2, by uzyskać liczbę 2). Następnie
od
ą
ejsz
mni
nak
(jed
2
0 do
na 0 lub 1
przekształcamy tę liczbę loor.
th.f
Ma
ody
met
przy użyciu
var row, col;
if (direction === 1) {
// Generujemy poczÈtkowe pole okrÚtu w ukïadzie poziomym.
} else {
// Generujemy poczÈtkowe pole okrÚtu w ukïadzie pionowym.
Przyjmujemy, że jeśli zmienna
direction ma wartość 1,
okręt będzie rozmieszczony
w poziomie...
…a jeśli zmienna direction będzie
mieć wartość 0, okręt będzie
rozmieszczony w pionie.
}
Najpierw wygenerujemy pierwsze pole zajmowane przez nowy
okręt, np. wiersz = 0 i kolumna = 3. W zależności od kierunku,
początkowe położenie okrętu będzie określane w oparciu
o nieco inne reguły (już za chwilę przekonasz się, dlaczego).
var newShipLocations = [];
pól zajmowanych przez
Określanie współrzędnych definiowania pustej
nowy okręt zaczynamy od ziemy do niej dodawać
będ
tablicy, a potem kolejno
pól.
współrzędne następnych
Liczba iteracji pętli
odpowiada długości okrętu…
for (var i = 0; i < this.shipLength; i++) {
if (direction === 1) {
// Dodajemy do tablicy pola okrÚtu w ukïadzie poziomym.
} else {
// Dodajemy do tablicy pola okrÚtu w ukïadzie pionowym.
}
}
return newShipLocations;
Po wygenerowaniu wszystkich
pól zwracamy tablicę.
…a podczas każdej z nich
dodajemy do tablicy
ędne
newShipLocations współrz
jednego pola. Także tutaj
musimy zastosować nieco
odmienny kod, zależnie do zie
kierunku, w jakim okręt będ
rozmieszczany.
Resztę kodu zaczniemy uzupełniać
od następnej strony.
},
jesteś tutaj
391
Obliczanie współrzędnych pól okrętu
Generacja początkowego pola okrętu
Skoro już wiesz, w jakim kierunku okręt będzie umieszczony, możesz się zająć generacją
współrzędnych zajmowanych przez niego pól. Najpierw zajmiemy się współrzędnymi
początkowymi (czyli współrzędnymi pierwszego pola), reszta sprowadzi się do wygenerowania
współrzędnych dwóch następnych kolumn (jeśli okręt jest umieszczony w poziomie) lub
dwóch następnych wierszy (jeśli jest umieszczony w pionie).
Aby określić położenie początkowe, musimy wygenerować dwie liczby losowe — wiersz
i kolumnę. Obie te liczby muszą należeć do zakresu od 0 do 6, tak by okręt zmieścił się na
planszy. Musisz jednak pamiętać, że jeśli okręt będzie umieszczony w poziomie, początkowy
numer kolumny musi należeć do zakresu od 0 do 4, by okręt w całości zmieścił się na planszy.
o może być umieszczony
Okręt rozmieszczany poziom
u…
rsz
w dowolnym wie
row = Math.floor(Math.random() * this.boardSize);
col = Math.floor(Math.random() * (this.boardSize - 3));
…jednak pierwszą
kolumnę należy wybrać
tak, by na planszy
zmieściły się jeszcze
dwa pozostałe pola
zajmowane przez okręt.
Dlatego od właściwości boardSize
(7) odejmujemy 3, by numer
początkowej kolumny zawsze
mieścił się w zakresie od 0 do 4.
(Pamiętaj, że boardSize jest
właściwością modelu).
I podobnie, jeśli okręt ma być rozmieszczany pionowo, numer
początkowego wiersza musi się mieścić w zakresie od 0 do 4, by
na planszy pozostało miejsce dla pozostałych pól zajmowanych
przez okręt.
rszu
o musi zaczynać się w wie
Okręt rozmieszczany pionow planszy zostało dostatecznie
na
o pól…
o numerze od 0 do 4, by
h zajmowanych przez nieg
dużo miejsca dla pozostałyc
row = Math.floor(Math.random() * (this.boardSize - 3));
col = Math.floor(Math.random() * this.boardSize);
jednak może być umieszczony
w dowolnej kolumnie.
392
Rozdział 8.
Łączenie wszystkiego w całość
Dokończenie metody generateShip
Po dodaniu tego kodu pozostaje jedynie upewnić się, że zarówno początkowe pole zajmowane
przez okręt, jak i dwa pozostałe zostaną zapisane w tablicy QHZ6KLS/RFDWLRQV.
generateShip: function() {
var direction = Math.floor(Math.random() * 2);
To jest kod służący do wygenerowania
współrzędnych początkowego pola
zajmowanego przez okręt.
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);
}
var newShipLocations = [];
poprzedniej
wartość 3 (z
Zastąpiliśmy wością this.shipLength,
strony) właści ziej ogólny i mógł
rd
by kod był ba nie od długości okrętu.
eż
działać niezal
for (var i = 0; i < this.shipLength; i++) {
if (direction === 1) {
pewność,
Tutaj używamy nawiasów, by mieć
wartość
zanim
col,
do
ne
doda
nie
zosta
i
że
znaków.
ch
łańcu
na
a
owan
wert
skon
zostanie
To jest kod dla okrętu rozmieszczanego
poziomo. Rozbijmy go na czynniki
pierwsze i przeanalizujmy…
newShipLocations.push(row + ”” + (col + i));
Nowe współrzędne
dodajemy do tablicy
newShipLocations.
Współrzędne te są
łańcuchem znaków
składającym się z wiersza
(początkowego wiersza
wyznaczonego powyżej)…
iększonej o wartość
…oraz wartości kolumny pow ej iteracji i wynosi 0,
wsz
pier
zmiennej i. Podczas
zątkowej kolumny.
zatem jest to tylko numer poc to następna
zie
będ
acji
iter
jnej
kole
czas
Pod
— jeszcze następna.
kolumna, a podczas trzeciej
isane takie
zap
aną
zost
icy
tabl
w
A zatem
„03”.
oraz
”
„02
,
„01”
współrzędne jak
} else {
newShipLocations.push((row + i) + ”” + col);
To samo robimy tutaj,
z tym że dla okrętu
rozmieszczanego
pionowo.
}
Zatem w tym przypadku
modyfikujemy numer wiersza,
a nie kolumny, dodając do
niego w każdej iteracji
wartość zmiennej i.
}
return newShipLocations;
},
czanego pionowo
W przypadku okrętu rozmieszjak „31”, „41” oraz „51”.
uzyskamy takie współrzędne
ędne
Kiedy już zapisaliśmy współrzócimy
zwr
icy,
tabl
w
pól
ich
ystk
wsz
metodę,
ją do kodu, który wywołał tę cations.
ipLo
eSh
erat
gen
ody
czyli do met
Pamiętaj, że kiedy dodajemy
łańcuch znaków i liczbę, operator
+ oznacza konkatenację, a nie
dodawanie, dlatego też uzyskamy
wynik będący łańcuchem znaków.
jesteś tutaj
393
Metoda collision
Unikanie kolizji!
Metoda FROOLVLRQ wymaga przekazania okrętu i sprawdza, czy którekolwiek
z zajmowanych przez niego pól nie nakłada się — czy też nie koliduje — z dowolnym
innym polem zajmowanym przez jakikolwiek inny okręt już umieszczony na planszy.
, by zobaczyć,
Zerknij na stronę 390 metoda
ana
ływ
wo
wy
t
jes
ie
gdz
collision.
Metodę tę zaimplementujemy przy użyciu dwóch zagnieżdżonych pętli IRU.
Zewnętrzna pętla będzie analizować wszystkie okręty zapisane w modelu (a konkretnie
we właściwości PRGHOVKLSV). Z kolei pętla wewnętrzna będzie analizować wszystkie
pola zajmowane przez nowy okręt, zapisane we właściwości ORFDWLRQV, i sprawdzać,
czy którekolwiek z nich nie jest już zajęte przez inny okręt.
locations to tablica współrzędnych pól
zajmowanych przez nowy okręt, który
chcielibyśmy umieścić na planszy.
collision: function(locations) {
for (var i = 0; i < this.numShips; i++) {
var ship = model.ships[i];
for (var j = 0; j < locations.length; j++) {
Dla każdego okrętu już umieszczonego
na planszy…
…sprawdzamy, czy którekolwiek z pól
zajmowanych przez nowy okręt nie
występuje w tablicy locations już
istniejącego okrętu.
if (ship.locations.indexOf(locations[j]) >= 0) {
return true;
}
}
}
return false;
}
Wykonanie instrukcji return
wewnątrz zagnieżdżonych
pętli zatrzymuje iteracje
wszystkich tych pętli oraz
powoduje natychmiastowe
zakończenie wykonywania
metody i zwrócenie wartości.
W celu sprawdzenia, czy współrzędne już
istnieją, używamy metody indexOf; jeśli
zatem zwrócony indeks będzie większy
lub równy 0, będzie to sygnałem, że
współrzędne zostały użyte; w takim
przypadku zwracamy wartość true
(oznaczającą wystąpienie kolizji).
Jeśli realizacja metody dotarła do tego
miejsca, oznacza to, że nie udało
się odnaleźć pary dwóch identycznych
współrzędnych, a zatem zwracamy
wartość false (oznaczającą brak kolizj
i).
WYSIL
SZARE KOMÓRKI
W tym kodzie używamy dwóch pętli: zewnętrznej, analizującej wszystkie okręty w modelu, oraz wewnętrznej,
sprawdzającej wystąpienie kolizji w poszczególnych polach nowego okrętu. W zewnętrznej pętli korzystamy ze
zmiennej i, natomiast w pętli wewnętrznej — ze zmiennej M. Dlaczego zastosowaliśmy dwie zmienne o różnych
nazwach?
394
Rozdział 8.
Łączenie wszystkiego w całość
Dwie ostatnie zmiany
Napisaliśmy już cały kod konieczny do losowego rozmieszczania okrętów na planszy; obecnie
jedyną rzeczą, która pozostała do zrobienia, jest zintegrowanie go z pozostałym kodem aplikacji.
Wprowadź w swoim kodzie dwie ostatnie zmiany pokazane poniżej, a następnie weź swoją grę
w okręty na ostateczną jazdę próbną!
var model = {
boardSize: 7,
numShips: 3,
shipLength: 3,
Usuń podane na stałe
współrzędne okrętów…
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: [””, ””, ””] } ],
…i zastąp je tablicami
zawierającymi
początkowo same 0.
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;
model.generateShipLocations();
}
Metodę model.generateShipLocations
wywołujemy w funkcji init, dzięki czemu
zostanie wykonana bezpośrednio po
wczytaniu gry, zanim jeszcze użytkownik
zacznie zabawę. To właśnie dlatego, kiedy
zaczniesz grać, wszystkie okręty będą już
rozmieszczone na planszy.
No i nie zapomnij o dodaniu
wywołania metody, która
wygeneruje losowe położenia
okrętów, wypełniając te puste
tablice w modelu.
Nie zapomnij pobrać gotowych kodów gry
w okręty z serwera FTP wydawnictwa Helion
— ftp://ftp.helion.pl/przyklady/prjsrg.zip
jesteś tutaj
395
Testowanie gry w okręty
Ostateczna jazda próbna
To już jest OSTATNIA jazda próbna naszej gry, w której
okręty są rozmieszczane na planszy losowo. Upewnij
się, że do pliku battleship.js dodałeś cały kod, a następnie
odśwież w przeglądarce plik battleship.html i spróbuj zagrać!
Przetestuj grę bardzo dokładnie. Rozegraj kilka partii, za
każdym razem odświeżając stronę, by wygenerować nowe,
losowe położenie okrętów.
Hm, a to sposób
na drobne oszustwo!
Aby oszukać grę, wyświetl okno konsoli
JavaScript, a następnie wpisz w nim
PRGHOVKLSV. Kiedy naciśniesz klawisz
Enter, w oknie zostaną wyświetlone
trzy obiekty okrętów wraz z tablicami
ORFDWLRQVi KLWV. Teraz będziesz już
doskonale wiedział, które pola planszy
zajmują okręty. Pamiętaj: nie usłyszałeś
tego od nas!
Konsola JavaScript
> model.ships
Object,
Object ]
hits: Array[3],
hits: Array[3],
hits: Array[3]
locations: Array[3],
[ Object,
locations: Array[3],
locations: Array[3]
0: ”63”
0: ”20”
0: ”60”
1: ”64”
1: ”21”
1: ”61”
2: ”65”
2: ”22”
2: ”62”
Dzięki temu za każdym razem
pokonasz komputer.
396
Rozdział 8.
Łączenie wszystkiego w całość
Gratulujemy, czas na własny startup!
Właśnie napisałeś wspaniałą aplikację
internetową, składającą się ze 150 (albo
coś koło tego) wierszy kodu, prostej strony
WWW i arkusza stylów CSS. Zgodnie
z tym, co napisaliśmy, jej kod należy do
Ciebie. Teraz pomiędzy Tobą i funduszem
inwestycyjnym stoi tylko napisanie dobrego
biznesplanu. Czy ktoś powiedział, że to będzie
dla Ciebie jakąkolwiek przeszkodą!?
A zatem po wykonaniu tej całej ciężkiej
pracy możesz się zrelaksować i zagrać
kilka partyjek okrętów. Bardzo to
zajmujące, prawda?
Jednak, poważnie mówiąc, to
dopiero zaczynamy. Wystarczy
tylko, że JavaScript zyska
nieco więcej mocy, a będziemy
w stanie pisać w nim aplikacje,
które z powodzeniem będą mogły
rywalizować z pisanymi w innych językach
i kompilowanymi do kodu maszynowego.
W tym rozdziale napisaliśmy naprawdę
sporo kodu. Zjedz zatem smaczną kolację
i porządnie odpocznij, aby wszystkie zdobyte
umiejętności dobrze się utrwaliły. Jednak
zanim to zrobisz, przeczytaj jeszcze kilka
celnych uwag. Nie pomijaj ich, powtarzanie
jest prawdziwą siłą napędową nauki!
jesteś tutaj
397
Kontrola jakości
tów
s
e
t
z
i
g
Uwa
. Kiedy
niej myślę
jm
a
n
e
y
rz
p
zę te sam
błąd, tak
ownie wpis
n
o
p
Znalazłam
ie
j
n
u
p
b
Spró
t, a nastę
trafienie.
trafię okrę
m kolejne
ka
s
zy
u
,
e
n
współrzęd
przekonaj!
ię
s
m
a
i s
sposób
właściwy
że
o
m
A
?
y to błąd
A zatem cz
gry?
działania
go
ależałoby
błąd, jak n
to
że
j.
,
e
y
iż
m
isz n
Jeśli założy
ozycje zap
woje prop
S
?
ić
w
ra
pop
398
Rozdział 8.
Łączenie wszystkiego w całość
CELNE SPOSTRZEŻENIA
Q
Do utworzenia struktury gry w okręty
używamy kodu HTML, wygląd gry określamy
przy użyciu stylów CSS, a jej działanie
implementujemy w kodzie JavaScript.
Q
Dzięki zastosowaniu obiektów, z których każdy
ma odrębne obowiązki, wszystkie elementy
gry możemy zaimplementować i przetestować
niezależnie od pozostałych.
Q
Do aktualizacji obrazków reprezentujących celne
i chybione strzały używamy identyfikatorów
poszczególnych elementów WG!
Q
Q
W formularzu zastosowaliśmy przycisk
typu ”button”. Dołączyliśmy do niego
procedurę obsługi zdarzeń, dzięki czemu
nasz kod będzie wiedział, kiedy gracz wpisał
współrzędne pola.
Aby ułatwić tworzenie i testowanie modelu,
położenie okrętów początkowo podaliśmy
na stałe. Po upewnieniu się, że model
działa prawidłowo, zastąpiliśmy te ustalone
współrzędne kodem, który rozmieszcza okręty
losowo.
Q
W modelu zastosowaliśmy właściwości, takie
jak QXP6KLSV oraz VKLS/HQJWK, dzięki czemu
w kodzie metod nie musieliśmy podawać na
stałe wartości, które w przyszłości mogą ulec
zmianie.
Q
Tablice udostępniają metodę indexOf, która
przypomina metodę LQGH[2I łańcuchów
znaków. Wymaga ona przekazania wartości
i zwraca indeks pierwszego wystąpienia tej
wartości w tablicy bądź -1, jeśli wartość nie
została odnaleziona.
Q
Przy użyciu techniki tworzenia łańcuchów
odwołań można łączyć (za pomocą operatora
kropki) kilka odwołań do obiektów, eliminując
w ten sposób konieczność stosowania wielu
instrukcji oraz zmiennych tymczasowych.
Q
Pętla do while działa podobnie do ZKLOH,
choć różni się od niej tym, że warunek
kontynuacji jest sprawdzany po jednokrotnym
wykonaniu instrukcji umieszczonych w ciele
pętli.
Q
Kontrola jakości jest bardzo ważnym
elementem tworzenia kodu. Wymaga
sprawdzenia nie tylko prawidłowych, lecz także
nieprawidłowych danych wejściowych.
Q
Aby pobrać dane wpisane w polu tekstowym,
należy odczytać wartość właściwości value
elementu tego pola.
Q
Mechanizmy pozycjonowania CSS pozwalają
na precyzyjne rozmieszczanie elementów na
stronie.
Q
Nasz kod został zaimplementowany
w formie trzech obiektów: model, view oraz
controller.
Q
Każdy obiekt w grze ma jedno przeznaczenie
i wynikające z niego obowiązki.
Q
Obiekt modelu, PRGHO, służy do
przechowywania stanu gry i implementacji
logiki odpowiadającej za modyfikacje tego
stanu.
Q
Obiekt widoku, YLHZ, służy do aktualizacji
wyglądu gry w odpowiedzi na zmiany widoku.
Q
Obiekt kontrolera, FRQWUROOHU, służy do
połączenia wszystkich elementów gry w jedną,
działającą całość, przekazywania współrzędnych
pól wpisywanych przez użytkownika do modelu
w celu zmiany stanu gry i sprawdzania, czy gra
nie została zakończona.
jesteś tutaj
399
Efekty ostrzału próbnego
Już niedługo dowiesz się, jak wyświetlać obrazy celnych i chybionych strzałów na planszy
do gry, używając kodu JavaScript. Zanim napiszemy faktyczny kod, musisz nabyć trochę
wprawy w symulatorze HTML. Przygotowaliśmy dwie klasy CSS i obecnie czekają na
użycie w ćwiczeniach. Dodaj te dwie reguły CSS do swojego kodu, a następnie wyobraź
sobie, że na planszy są trzy okręty rozmieszczone w następujących polach:
2NUÚW$%&
2NUÚW&'(
2NUÚW%%%
Koniecznie pobierz wszystko,
co jest Ci potrzebne, w tym
także dwa obrazki, których
będziesz potrzebował do
wykonania ćwiczenia.
O oto współrzędne strzałów użytkownika.
$')%&&
Twoim zadaniem jest dodanie do odpowiednich komórek siatki (czyli wybranych
elementów WG! w tablicy HTML) jednej z dwóch przedstawionych poniżej klas, by
w odpowiednich miejscach planszy pojawiły się obrazki reprezentujące celny oraz
chybiony strzał.
.hit {
background: transparent url(”ship.png”) no-repeat center center;
}
.miss {
background: transparent url(”miss.png”) no-repeat center center;
}
A oto rozwiązanie. Klasę KLW powinieneś dodać do
elementów WG! o identyfikatorach: "34", "12", "26",
a klasę .miss – do elementów o identyfikatorach:
"00", "55", "25". Aby dodać klasę do elementu,
musisz zastosować atrybut class, tak jak
pokazaliśmy na poniższym przykładzie.
<td class=”miss” id=”55”>
Po dodaniu klas
w odpowiednich
elementach Twoja
plansza do gry powinna
wyglądać tak jak ta.
400
Rozdział 8.
Łączenie wszystkiego w całość
Ćwiczenie
Rozwiązanie
Nadszedł czas na projektowanie obiektów. Zaczniemy od obiektu widoku. Musisz pamiętać, że obiekt
ten jest odpowiedzialny za aktualizację planszy gry wyświetlanej w przeglądarce. Przyjrzyj się stronie
przedstawionej poniżej i sprawdź, czy będziesz umiał określić metody, które chcemy zaimplementować
w obiekcie widoku. Poniżej napisz deklaracje tych metod (chodzi o same deklaracje; ich kodem zajmiemy
się już niebawem) oraz jeden lub dwa komentarze na temat każdej z nich. Poniżej przedstawiliśmy nasze
rozwiązanie.
To jest komunikat.
Komunikaty będą
łańcuchami znaków,
takimi jak „Trafiony!”,
„Spudłowałeś” albo
„Zatopiłeś mój okręt!”.
W tym miejscu planszy
widok wyświetlił
w siatce obrazek
reprezentujący PUDŁO.
A tutaj widok wyświetlił
w siatce obrazek statku.
var view = {
Zwróć uwagę, że definiujemy obiekt
i zapisujemy go w zmiennej view.
// Ta metoda wymaga podania ïañcucha z komunikatem, a nastÚpnie
// wyĂwietla go w obszarze komunikatöw na stronie.
displayMessage: function(msg) {
// Wkrötce uzupeïnimy ten kod!
}
displayHit: function(location) {
Twoje propozycje metod
zapisz tutaj.
// Tutaj zostanie podany kod.
},
displayMiss: function(location) {
// Tutaj zostanie podany kod.
}
};
jesteś tutaj
401
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Wiesz już, jak opisaliśmy planszę do gry, zastanów się zatem, w jaki sposób można
by reprezentować okręt w modelu (chodzi tylko o położenie, trafieniami zajmiemy
się później). Spośród podanych poniżej odpowiedzi zaznacz najlepszą.
‰ Użyj dziewięciu zmiennych, z których każda będzie
przechowywać współrzędne jednej komórki
zajmowanej przez okręty. Sposób podobny do
zastosowanego w rozdziale 2.
‰ Zastosuj tablicę, której elementy będą odpowiadały
poszczególnym komórkom planszy (czyli będzie ich
w sumie 49). W każdej z komórek tej tablicy, która
zawiera jakiś fragment statku, należy zapisać jego
numer.
‰ Użyj tablicy składającej się z dziewięciu komórek do
zapisania położenia każdego z okrętów. Komórki 0
– 2 będą zawierały współrzędne pierwszego okrętu,
komórki 3 – 5 współrzędne drugiego itd.
Albo tutaj zapisz swoje
rozwiązanie.
402
Rozdział 8.
‰ Użyj trzech odrębnych tablic, z których każda
będzie się składać z trzech komórek i zawierać
współrzędne pól zajmowanych przez jeden okręt.
‰ Użyj obiektu o nazwie VKLS, który będzie zawierał
trzy właściwości ze współrzędnymi trzech pól
planszy zajmowanych przez dany okręt. Utwórz trzy
takie obiekty i zapisz je w tablicy o nazwie VKLSV.
‰ _________________________________________
_________________________________________
_________________________________________
Każde z tych rozwiązań zdałoby egzamin!
(Poszukując najlepszego rozwiązania,
sprawdziliśmy każde z nich). Jednak
w książce zastosujemy to rozwiązanie.
Łączenie wszystkiego w całość
Okrętowe magnesiki. Rozwiązanie
8İ\MSU]HGVWDZLRQ\FKSRQLİHMLQIRUPDFMLRRVWU]HOLZDQ\FKSU]H]Xİ\WNRZQLNDSRODFKSODQV]\
RUD]WDEOLF\]GDQ\PLRNUĐWyZE\XPLHĤFLþQDSODQV]\PDJQHVLNLRNUĐWyZRUD]FK\ELRQ\FK
VWU]DâyZ&]\JUDF]RZLXGDâRVLĐ]DWRSLþNWyU\NROZLHN]RNUĐWyZ"3LHUZV]HRVWU]HODQHSROH
]D]QDF]\OLĤP\]D&LHELH
Oto współrzędne strzałów:
Zaznacz te strzały na planszy.
A6, B3, C4, D1, B0, D4, F0, A1, C6, B1, B2, E4, B6
A oto nasze rozwiązanie:
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”] ];
Wszystkie okręty zostały zatopione!
A tu jest plansza do gry oraz magnesiki.
Pozostałe
magnesiki.
jesteś tutaj
403
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Poćwiczymy teraz stosowanie struktury danych okrętów, by zasymulować różne operacje z nimi związane.
Bazując na podanej poniżej definicji okrętów, odpowiedz na pytania oraz uzupełnij puste miejsca w kodzie.
Nie zapomnij sprawdzić odpowiedzi, zanim zaczniesz dalszą lekturę, gdyż jest to bardzo ważny aspekt
działania gry.
var ships = [{ locations: [”31”, ”41”, ”51”], hits: [””, ””, ””] },
{ locations: [”14”, ”24”, ”34”], hits: [””, ”hit”, ””] },
{ locations: [”00”, ”01”, ”02”], hits: [”hit”, ””, ””] }];
Okręty 2. i 3. O które pola chodzi? ____________________
C4, A0
Które okręty zostały już trafione? _________________
tak
Okręt 2
Gracz sprawdza pole „D4”, czy to jest trafienie? ____________
Który to okręt? ___________
nie
Gracz sprawdza pole „B3”, czy to jest trafienie? ____________
Który to okręt? ___________
Uzupełnij poniższy fragment kodu, tak by odwoływał się do środkowej komórki drugiego okrętu
i wyświetlał jej wartość przy użyciu metody FRQVROHORJ.
1
var ship2 = ships[____];
var locations = ship2.locations;
1
console.log(”WspöïrzÚdne pola to: ” + locations[____]);
Uzupełnij poniższy kod, by sprawdzić, czy pierwsza komórka trzeciego okrętu jest trafiona.
2
var ship3 = ships[____];
hits
var hits = ship3._____;
hits[0] === ”hit”) {
if (_____
console.log(”Aua, trafienie pierwszej komörki trzeciego okrÚtu!”);
}
Dokończ poniższy kod, tak by oznaczyć trafienie trzeciej komórki pierwszego okrętu.
shipl = ships[0];
var ______
hits
var hits = ship1._______;
2
hit
hits[____]
= ________;
404
Rozdział 8.
Łączenie wszystkiego w całość
Magnesiki z kodem. Rozwiązanie
$OJRU\WPUR]PLHV]F]DQLDRNUĐWyZQDSODQV]\]RVWDâ]DSLVDQ\QDPDJQHVLNDFKSU]\F]HSLRQ\FK
GRORGyZNLZORVRZHMNROHMQRĤFL&]\SRWUDILV]SRQRZQLHSRXNâDGDþPDJQHVLNLZHZâDĤFLZHM
NROHMQRĤFLE\RGWZRU]\þG]LDâDMċF\DOJRU\WP"3RQLİHM]DPLHĤFLOLĤP\QDV]HUR]ZLċ]DQLH
W pÚtli obsïuĝ wszystkie okrÚty,
ktöre majÈ byÊ uĝywane w grze.
Wygeneruj losowy kierunek (pionowy
lub
poziomy), w jakim zostanÈ rozmiesz
czone
poszczegölne pola zajmowane prze
z okrÚt.
Wygeneruj losowe miejsce do
umieszczenia okrÚtu.
Sprawdě, czy
poïoĝenie no
okrÚtu nie ko
wego
liduje z ĝadn
ym z juĝ
istniejÈcych
okrÚtöw.
Dodaj poïoĝenie nowego okrÚtu do
tablicy ships.
jesteś tutaj
405
406
Rozdział 8.
9. Programowanie asynchroniczne.
Obsługa zdarzeń
Po przeczytaniu tego rozdziału zdasz sobie sprawę z tego, że to
nie są przelewki i nie jesteśmy już w Kansas. Do tej pory pisałeś kod,
który zazwyczaj wykonywany był od samego początku do końca — oczywiście
Twój kod mógł być nieco bardziej skomplikowany i zawierać kilka funkcji, obiektów
i metod, jednak w jakimś momencie ten kod po prostu był wykonany wiersz
po wierszu. Cóż, jest nam bardzo przykro, że mówimy Ci to tak późno, jednak
typowy kod JavaScript nie jest pisany w taki sposób. Kod pisany w tym
języku reaguje na zdarzenia. Jakiego rodzaju zdarzenia? Może to być kliknięcie
elementu strony przez użytkownika, przesłanie danych z serwera, upływ pewnego
okresu czasu w przeglądarce, jakaś zmiana wprowadzona w DOM oraz wiele
innych. W rzeczywistości, w niewidoczny dla nas sposób, w przeglądarce cały
czas zachodzą jakieś zdarzenia. W tym rozdziale jeszcze raz przemyślisz swoje
podejście do sposobu pisania kodu JavaScript i dowiesz się, dlaczego trzeba pisać kod
reagujący na zdarzenia oraz jak należy to robić.
to jest nowy rozdział
407
Ćwiczenia ze zdarzeń
WYSIL
SZARE KOMÓRKI
Wiesz, co robi przeglądarka, prawda? Pobiera stronę oraz całą jej zawartość, a następnie ją wyświetla. Jednak
przeglądarka robi znacznie więcej. A co konkretnie? Zaznacz wszystkie z wymienionych poniżej czynności,
które według Ciebie przeglądarka wykonuje za kulisami. Jeśli nie jesteś pewny, spróbuj zgadnąć.
Wie, kiedy cała strona została wczytana
i wyświetlona.
Rejestruje wszystkie kliknięcia, które
wykonujemy na stronie, niezależnie od tego,
czy został kliknięty przycisk, odnośnik,
czy jakikolwiek inny element strony.
Wie, kiedy użytkownik przesyła formularz.
Wie, kiedy użytkownik naciska klawisz na
klawiaturze.
Śledzi ruchy wskaźnika myszy.
Śledzi upływ czasu i zarządza licznikami
czasu oraz zdarzeniami związanymi
z czasem.
Pobiera dodatkowe dane potrzebne stronie.
Śledzi, kiedy strona została przewinięta
lub kiedy użytkownik zmienił jej wielkość.
Wie, kiedy zakończono wypiekanie
ciasteczek.
Wie, kiedy w elemencie zostanie
umieszczone tzw. miejsce wprowadzania
(ang. input focus).
Zaostrz ołówek
Wybierz dwa spośród opisanych powyżej zdarzeń. Gdyby przeglądarka mogła informować
Twój kod o ich zajściu, w jaki fajny lub interesujący sposób mógłbyś to wykorzystać?
Nie, zdarzenia związanego
sz
z wypiekaniem ciasteczek nie może
zastosować w swoim przykładzie!
408
Rozdział 9.
Programowanie asynchroniczne
Czym są zdarzenia?
Bez wątpienia obecnie już wiesz, że po pobraniu i wyświetleniu strony przeglądarka nie
spoczywa na laurach i przestaje działać. Za jej kulisami dzieje się całkiem sporo: użytkownik
klika przyciski, śledzone są ruchy wskaźnika myszy, dodatkowe dane są pobierane przez
sieć, zmienia się wielkość okna przeglądarki, tworzone są liczniki czasu, które po upływie
zadanego okresu czasu wyzwalają wykonanie określonego kodu, może się zmienić adres
strony wyświetlanej w przeglądarce itd. Wszystko to może powodować zgłaszanie zdarzeń.
Zawsze wtedy, gdy pojawia się zdarzenie, istnieje także możliwość jego obsłużenia, czyli
podania kodu, który zostanie wywołany w momencie zgłoszenia konkretnego zdarzenia.
Oczywiście nie mamy obowiązku obsługiwania jakichkolwiek zdarzeń — jednak będziemy
musieli to robić, jeśli zechcemy, by w odpowiedzi na nie działo się coś interesującego — np.
by po kliknięciu przycisku do listy odtwarzania została dodana nowa piosenka; by odebrane
nowe dane zostały przetworzone i wyświetlone na stronie lub gdy upłynie zadany okres
czasu, można było poinformować użytkownika, że niebawem wygaśnie rezerwacja biletów
na miejsca w pierwszym rzędzie na jakiś koncert itd.
Tak dla Twojej wiadomości:
użytkownik troszkę przesunął
wskaźnik myszy. Gdybyś chciał,
mogłabym Ci powiedzieć, o ile
go przesunął.
Hej, chciałam
tylko powiedzieć, że
wczytywanie strony
zostało zakończone.
Właśnie zakończyło
się odtwarzanie klipu
wideo na stronie.
Ta usługa internetowa
właśnie odpowiedziała na
Twoje żądanie, mam już
dane, które przesłała.
Wiele bardziej
zaawansowanych
zdarzeń, takich jak
związane z usługami
geolokalizacji,
opisaliśmy w książce
HTML5. Rusz głową!
W tej książce
poprzestaniemy
na najprostszych
typach zdarzeń.
Przeglądarka
Dobra, informuję, że
właśnie upłynęła minuta.
Użytkownik właśnie
przesłał formularz, który
był umieszczony na stronie.
Zawsze wtedy, gdy pojawia się jakieś zdarzenie,
istnieje także możliwość podania kodu, który je obsłuży.
jesteś tutaj
409
Procedury obsługi zdarzeń
Czym jest procedura obsługi zdarzeń?
Procedury obsługi zdarzeń piszemy po to, by obsługiwać zdarzenia. Są
one zazwyczaj niewielkimi fragmentami kodu, które wiedzą, co zrobić,
kiedy zostanie zgłoszone zdarzenie. Od strony kodu procedury obsługi
zdarzeń są zwyczajnymi funkcjami. Kiedy pojawia się zdarzenie,
wywoływana jest funkcja, która je obsługuje.
Można także spotkać
programistów, którzy nazywają
je wywołaniami zwrotnymi
lub odbiorcami zdarzeń.
Aby Twoja procedura została wywołana w momencie wystąpienia
zdarzenia, musisz ją najpierw zarejestrować. Jak się przekonasz,
można to zrobić na pięć różnych sposobów, zależnie od rodzaju
obsługiwanego zdarzenia. Dalej w tym rozdziale przedstawimy je
wszystkie, na razie jednak zaczniemy od najprostszego przypadku,
który już wcześniej poznałeś, czyli zdarzenia load generowanego
po zakończeniu wczytywania strony.
Hej,
przeglądarko! Mam tu
kawałek kodu, który musi
być wykonany, kiedy skończysz
wczytywać stronę. I pamiętaj,
że mi się spieszy!?
Nigdy nie wyluzujesz? Twoja
procedura obsługi jest gotowa do
użycia i jak tylko skończę pobierać
stronę, na pewno ją wywołam.
Przeglądarka
Procedura obsługi
— kod, który zostanie
wykonany później,
po zakończeniu
wczytywania strony.
410
Rozdział 9.
Programowanie asynchroniczne
Jak napisać pierwszą procedurę obsługi zdarzeń?
Nie ma lepszego sposobu zrozumienia zdarzeń, niż napisanie procedury ich obsługi
i podłączenie jej do rzeczywistego zdarzenia. Pamiętasz zapewne, że omawialiśmy już
kilka przykładów obsługi zdarzeń — w tym obsługi zakończenia wczytywania strony
— jednak nigdy nie wyjaśniliśmy wyczerpująco, jak to działa. Zdarzenie load jest
zgłaszane, kiedy przeglądarka zakończy wczytywanie całej treści strony i ją wyświetli
(no i oczywiście utworzy DOM reprezentujący jej zawartość).
Teraz przeanalizujemy dokładnie wszystko, co trzeba zrobić, by napisać procedurę
obsługi zdarzeń i zapewnić, że zostanie wywołana po zgłoszeniu zdarzenia load.
1
1DMSLHUZPXVLP\QDSLVDþIXQNFMĐNWyUDEĐG]LHREVâXJLZDþ]GDU]HQLHload
NLHG\]RVWDQLHRQR]JâRV]RQHSU]H]SU]HJOċGDUNĐ:QDV]\PSU]\SDGNX
IXQNFMDWDRJâRVLMHG\QLHFDâHPXĤZLDWXĵ-Dĝ\MÚĵ.
Procedura obsługi zdarzeń
jest zwyczajną funkcją.
function pageLoadedHandler() {
alert(”-a ĝyjÚ!”);
}
Pamiętaj, że takie funkcje
są także często określane jako
procedura obsługi zdarzenia
lub wywołania zwrotne.
2
To jest nasza funkcja, nadamy jej
nazwę pageLoadedHandler; jedn
ak
Ty możesz ją nazwać zupełnie
inaczej.
Ta procedura obsługi nie robi
zbyt wiele. Ogranicza się do
wyświetlenia komunikatu.
6NRURQDV]DSURFHGXUDREVâXJL]GDU]HQLDMHVWMXİJRWRZDPXVLP\Mċ]F]\PĤ
VNRMDU]\þE\SU]HJOċGDUNDZLHG]LDâDİHPXVLMċZ\ZRâDþJG\SRMDZLVLĐ
]GDU]HQLHload:W\PFHOXXİ\MHP\ZâDĤFLZRĤFLonloadRELHNWXobject:
window.onload = pageLoadedHandler;
nazwę
W przypadku zdarzenia load przypisujemy
funkcji właściwości onload obiektu window.
Teraz, kiedy zostanie wygenerowane
zdarzenie load, przeglądarka
wywoła funkcję pageLoadedHandler.
3
Niebawem przekonasz się, że
procedury obsługi odmiennych typów
zdarzeń są określane na różne
sposoby.
,WRZV]\VWNR6NRURMXİQDSLVDâHĤNRGPRİHP\VSRNRMQLHXVLċĤþ
ERZLHP\İHSR]DNRĚF]HQLXZF]\W\ZDQLDVWURQ\SU]HJOċGDUNDZ\ZRâD
IXQNFMĐSU]\SLVDQċZâDĤFLZRĤFLwindow.onload.
jesteś tutaj
411
Testowanie procedur obsługi zdarzeń
Jazda próbna procedury obsługi zdarzeń
Nie ociągaj się, utwórz plik event.html i dodaj do niego kod, który pozwoli
przetestować procedurę obsługi zdarzeń. Wyświetl stronę w przeglądarce
i sprawdź, czy zobaczysz okno z komunikatem.
<!doctype html>
<html lang=”pl”>
Na początku przeglądarka
wczytuje stronę i rozpoczyna
analizę jej kodu HTML oraz
tworzenie DOM.
<head>
<title> -a ĝyjÚ! </title>
Kiedy przeglądarka dociera do kodu
JavaScript, zaczyna go wykonywać.
<meta charset=”utf-8”>
<script>
window.onload = pageLoadedHandler;
function pageLoadedHandler() {
alert(”-a ĝyjÚ!”);
}
Na razie kod jedynie definiuje funkcję
i przypisuje ją właściwości window.onload.
Pamiętaj, że funkcja ta zostanie wywołana,
kiedy przeglądarka skończy wczytywać
stronę.
</script>
</head>
<body>
</body>
</html>
Następnie przeglądarka
kontynuuje analizę kodu HTML.
ML
ończy analizę kodu HT
Kiedy przeglądarka zakM, wywołuje naszą procedurę
i przygotowywanie DO
obsługi zdarzeń...
...która w naszym prz
ypadku
wyświetli komunikat
„Ja żyję!”.
WYSIL
SZARE KOMÓRKI
Czy można by użyć procedur obsługi zdarzeń, gdyby nie było funkcji?
412
Rozdział 9.
Programowanie asynchroniczne
Jeśli planujesz,
by kiedyś zostać prawdziwą
programistką JavaScript, musisz
się nauczyć, jak postępować ze
zdarzeniami.
Dotąd używaliśmy — jak można by rzec — trochę linowego podejścia do pisania
i wykonywania kodu: otrzymywaliśmy algorytm, taki jak testowanie płynów do
robienia baniek lub generacja piosenki o 99 butelkach piwa, pisaliśmy kod realizujący
ten algorytm krok po kroku, do początku do samego końca.
Czy pamiętasz grę w okręty? Jej kod raczej nie pasuje do takiego liniowego schematu
realizacji — oczywiście napisałeś kod, który inicjalizował model — jednak główna
część programu działała w zupełnie inny sposób. Za każdym razem gdy chciałeś oddać
strzał, musiałeś wpisać współrzędne w polu formularza i kliknąć przycisk Ognia!.
Kliknięcie przycisku powodowało następnie całą sekwencję akcji, których efektem
było wykonanie następnego ruchu. W tym przypadku kod reagował na czynność
wykonaną przez użytkownika.
Organizacja kodu w taki sposób, by reagował na zdarzenia, jest całkowicie innym
sposobem pojmowania działania kodu. Aby pisać kod w taki sposób, trzeba
uwzględnić zdarzenia, jakie mogą być zgłaszane, i zastanowić się, jak nasz kod ma
na nie reagować. Informatycy lubią mawiać, że taki kod jest asynchroniczny, gdyż
będzie wykonywany później, w momencie gdy zostanie wygenerowane zdarzenie, jeśli
w ogóle to nastąpi. Taki sposób programowania zmienia także nasz punkt widzenia,
gdyż musimy zrezygnować z implementowania algorytmu krok po kroku, a zamiast
tego tworzyć aplikację składającą się z wielu procedur, obsługujących wiele różnego
rodzaju zdarzeń.
jesteś tutaj
413
Planowanie gry z wykorzystaniem zdarzeń
Poznajemy zdarzenia, pisząc grę
Zdarzenia najlepiej poznawać na praktycznych przykładach, a zatem spróbujmy zdobyć trochę
doświadczeń w obsłudze zdarzeń, pisząc prostą grę. Będzie ona działać w następujący sposób:
wczytujemy stronę, na której zostanie wyświetlony obraz. Nie będzie to zwyczajny obraz, a obraz
bardzo nieostry. Twoim zadaniem będzie odgadnięcie, co to za obraz. Aby sprawdzić odpowiedź,
wystarczy kliknąć obraz, a w miejsce zamazanego zostanie wyświetlony obraz w normalnej postaci.
Oto przykład.
To jest nieostra
wersja obrazu.
Hmm, co to
może być?
Wyobraź sobie,
że kiedy myślisz
nad odpowiedzią,
słychać coraz
bardziej natarczywe
werble…
uznasz,
A kiedy już ka jest
ja
,
sz
ie
w
że
odpowiedź,
prawidłowa iknąć
kl
zy
rc
wysta
zamiast
obrazek, by stał
zo
go
nieostre
obraz
wyświetlony postaci.
j
ne
al
rm
no
w
Zacznijmy od napisania kodu HTML. W grze użyjemy dwóch obrazków JPG — jeden będzie
prezentował obraz nieostry, a drugi — normalny. Pliki obrazków noszą, odpowiednio, nazwy:
zeroblur.jpg oraz zero.jpg. A oto kod strony.
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title> Zgadnij co to za obraz </title>
<style> body { margin: 20px; } </style>
<script> </script>
</head>
<body>
<img id=”zero” src=”zeroblur.jpg”>
</body>
</html>
414
Rozdział 9.
To tylko prosty kod HTML, z elementem
<script> gotowym, by umieścić w nim
kod. Zamiast umieszczać kod skryptu
w osobnym pliku, uprościmy naszą grę
i zapiszemy go bezpośrednio w kodzie
HTML. Jak się niedługo przekonasz, kod
niezbędny do zaimplementowania naszej
gry będzie dosyć krótki.
A to jest nieostry obraze
k.
Nadaliśmy mu identyfikat
or
„zero”. Zaraz zobaczysz,
jak można użyć tego
identyfikatora…
Programowanie asynchroniczne
Implementacja gry
Nie ociągaj się i wyświetl ten plik w przeglądarce, aby zobaczyć zamazany obrazek.
Zaimplementowanie gry będzie wymagać zareagowania na kliknięcie obrazka
i zastąpienia go wersją, w której obraz będzie wyraźnie widoczny.
Tak się szczęśliwie składa, że za każdym razem, gdy jakiś element HTML zostanie
kliknięty (a w przypadku urządzeń mobilnych — dotknięty), generowane jest zdarzenie.
Twoim zadaniem jest napisanie procedury obsługi tego zdarzenia, która w odpowiedzi
na kliknięcie zastąpi nieostrą wersję obrazka obrazem w normalnej postaci. Poniżej
opisaliśmy, jak masz to wykonać.
Przeanalizujmy te czynności krok po kroku i napiszmy odpowiedni kod.
1
Odwołaj się do obiektu elementu obrazu w DOM
i przypisz odpowiednią procedurę obsługi jego
właściwości onclick.
2
W swojej procedurze obsługi zdarzeń napisz kod,
który zmieni wartość atrybutu src obiektu obrazka,
tak by używał nie wersji nieostrej, lecz normalnej.
Krok 1. Odwołanie do obiektu obrazka w DOM
Odwoływanie się do obrazka jest już dla Ciebie czymś banalnie prostym; musisz
jedynie użyć naszej dobrej znajomej, metody JHW(OHPHQW%\,G.
var image = document.getElementById(”zero”);
Tutaj pobieramy referencję
elementu obrazka i zapisu do
jemy ją
w zmiennej image.
Jednak musimy przy tym zadbać o to, by ten kod był wykonywany już
po utworzeniu DOM; dlatego też użyjemy właściwości onload obiektu
window. Nasz kod umieścimy wewnątrz funkcji, nadamy jej nazwę
init, którą przypiszemy właściwości onload.
window.onload = init;
function init() {
Pamiętaj, że nie możemy pobrać obrazka
z DOM, dopóki przeglądarka nie zakończy
wczytywania strony.
Tu tworzymy funkcję init i używamy jej
jako procedury obsługi zdarzeń onload,
dzięki czemu będziemy mieć pewność,
że jej kod zostanie wykonany dopiero
po wczytaniu całej zawartości strony.
var image = document.getElementById(”zero”);
}
W kodzie funkcji init pobieramy
referencję do obrazka
o identyfikatorze „zero”.
Pamiętaj, że w języku JavaScript
kolejność definiowania funkcji nie
ma znaczenia. Dlatego też nic nie
stoi na przeszkodzie, by zdefiniować
funkcję init już po tym, jak została
przypisana właściwości onload.
jesteś tutaj
415
Dodawanie procedury obsługi zdarzeń
Krok 2. Dodanie procedury obsługi zdarzeń i aktualizacja obrazka
Aby dodać procedurę, która będzie obsługiwać kliknięcia obrazka, musimy jedynie
przypisać wybraną funkcję właściwości onclick elementu obrazka. Załóżmy, że nasza
funkcja będzie nosić nazwę showAnswer — jej definicję znajdziesz u dołu strony.
window.onload = init;
function init() {
var image = document.getElementById(”zero”);
image.onclick = showAnswer;
}
Używamy obiektu obrazka
i przypisujemy funkcję obspobranego z DOM
jego właściwości onclick. ługującą zdarzenia
Teraz musimy napisać funkcję showAnswer, która wyświetli obraz
w jego normalnej postaci, odpowiednio modyfikując w tym celu wartość
właściwości src.
Także tutaj najpierw musimy
pobrać obiekt obrazka z DOM.
function showAnswer() {
var image = document.getElementById(”zero”);
image.src = ”zero.jpg”;
}
Kiedy już pobierzemy obraze
możemy go podmienić, zmi k,
wartość właściwości src i eniając
w niej nazwę pliku zawierazapisując
normalną wersję obrazka. jącego
Jazda próbna
Zabierzmy naszą prostą grę na jazdę próbną. Upewnij się, że
w pliku o nazwie image.html znajduje się cały niezbędny kod
napisany w językach HTML, CSS oraz JavaScript. Sprawdź,
czy w tym samym katalogu znajdują się także dwa pliki
obrazków, pobrane wraz z przykładami do książki z serwera
FTP wydawnictwa Helion. Kiedy już to zrobisz, wyświetl plik
w przeglądarce i wypróbuj działanie gry!
Kliknij dowolne miejsce obrazka —
spowoduje to wywołanie funkcji
showAnswer obsługującej kliknięcia,
czyli zdarzenia click. Wywołanie
funkcji spowoduje zmianę wartości
właściwości src obrazka i wyświetlenie
prawidłowej odpowiedzi.
416
Rozdział 9.
Pamiętaj, że nieostra wersja
obrazu nosi nazwę „zeroblur.jpg”,
natomiast normalna nazywa się
„zero.jpg”.
Programowanie asynchroniczne
Chwileczkę, dlaczego wewnątrz
funkcji showAnswer ponownie musimy
wywoływać metodę getElementById?
Chyba nie do końca rozumiem, jak ten
kod jest wykonywany.
No cóż, zrozumienie sposobu wykonywania kodu
zawierającego wiele procedur obsługi zdarzeń faktycznie
może być trudne. Pamiętaj, że funkcja init zostaje wywołana
po zakończeniu wczytywania strony. Jednak funkcja showAnswer
zostanie wykonana znacznie później — dopiero po kliknięciu
obrazka. Oznacza to, że te dwie procedury obsługi zdarzeń są
wykonywane niezależnie od siebie, w różnych momentach.
Dodatkowo musisz także pamiętać o zakresach. Wewnątrz funkcji
init obiekt zwrócony przez metodę JHW(OHPHQW%\,G zapisujemy
w zmiennej lokalnej o nazwie image; oznacza to, że w chwili
zakończenia wykonywania tej funkcji skończy się zasięg tej zmiennej
i zostanie ona zniszczona. Dlatego też później, kiedy zostanie
wywołana funkcja showAnswer, ponownie musimy pobrać z DOM
obiekt elementu obrazka. Oczywiście moglibyśmy zapisać go
w jakiejś zmiennej globalnej; jednak nadmierne stosowanie takich
zmiennych może być mylące i prowadzić do powstawania kodu
w większym stopniu narażonego na błędy, a bez wątpienia czegoś
takiego chcielibyśmy unikać.
Nie istnieją
głupie pytania
P: Czy zmiana wartości
właściwości src obiektu obrazka jest
odpowiednikiem zmiany atrybutu src
przy użyciu metody setAttribute?
O
: W tym przypadku tak. Kiedy pobierasz
element HTML z DOM przy użyciu
metody JHW(OHPHQW%\,G, uzyskujesz
obiekt elementu dysponujący kilkoma
właściwościami i metodami. Wszystkie
obiekty elementów mają np. właściwość
id, w której jest zapisywany identyfikator
danego elementu (oczywiście, o ile tylko
zostanie podany w kodzie HTML). Obiekt
elementu obrazka dysponuje także
właściwością src, w której jest zapisywana
nazwa pliku, podana w kodzie HTML,
w atrybucie src elementu <img>.
Jednak nie wszystkie atrybuty odpowiadają
właściwościom obiektów elementów,
w ich przypadku wartości trzeba
ustawiać i pobierać przy użyciu metod
setAttribute oraz getAttribute.
Wartościami atrybutów src oraz id
można manipulować na oba sposoby —
posługując się właściwością lub metodami
getAttribute i setAttribute.
P: Czy to oznacza, że jedna procedura
obsługi jest wywoływana w drugiej?
O: Nie. Procedura obsługi zakończenia
wczytywania to kod, który jest wykonywany,
gdy przeglądarka wczyta całą zawartość
strony. Kiedy procedura ta zostanie
wywołana, określamy z kolei procedurę
obsługującą kliknięcia obrazka, zapisując
nazwę funkcji we właściwości onclick
elementu obrazka, jednak nie zostanie ona
wywołana, aż do momentu, gdy klikniesz
obrazek. Kiedy to zrobisz (a może to
nastąpić długo po zakończeniu wczytywania
strony), zostanie wywołana funkcja
showAnswer. A zatem obie te procedury
obsługi są wywoływane w różnym czasie.
jesteś tutaj
417
Ćwiczenia z przebiegu wykonywania kodu
BĄDŹ przeglądarką
Poniżej zamieściliśmy kod gry. Twoim zadaniem
jest wcielić się w rolę przeglądarki i określić,
co musisz zrobić po każdym ze zdarzeń.
Po wykonaniu ćwiczenia zajrzyj do
naszej odpowiedzi podanej pod koniec
rozdziału i upewnij się, czy udało Ci
się wszystko zrobić. Pierwszą odpowiedź
podaliśmy za Ciebie.
window.onload = init;
function init() {
var image = document.getElementById(”zero”);
image.onclick = showAnswer;
}
function showAnswer() {
var image = document.getElementById(”zero”);
image.src = ”zero.jpg”;
}
Podczas
wczytywania
strony…
To jest kod, który masz wykonać…
Najpierw definiujemy funkcje init i showAnswer.
Gdy zostanie
wygenerowane
zdarzenie load…
Gdy zostanie
wygenerowane
zdarzenie click
obrazka…
Tu zapisz swoje odpowiedzi.
418
Rozdział 9.
Programowanie asynchroniczne
WYSIL
SZARE KOMÓRKI
A gdybyś miał całą stronę nieostrych obrazków, z których każdy mógłbyś kliknąć, by podmienić go na wersję
normalną? Jak zaprojektowałbyś kod, żeby zapewnić sobie taką możliwość? Zapisz kilka pomysłów. Jaki
mógłby być naiwny sposób rozwiązania takiego problemu? Czy jest jakiś sposób zaimplementowania takiego
rozwiązania, który wymagałby wprowadzenia jedynie minimalnych zmian w kodzie, którym już dysponujemy?
Judyta: Cześć
panowie. Na razie gra
w odgadywanie obrazów
działa świetnie. Jednak
naprawdę powinniśmy ją
rozszerzyć o wyświetlanie
większej liczby obrazów
na stronie.
Kuba
Kuba: Dokładnie, właśnie
o
tym myślałem.
Judyta Józek
Józek: A ja nawet już mam kilka nowych obrazków,
których możemy użyć, wystarczy napisać kod.
Zastosowałem tę samą konwencję nazewniczą:
”zero.jpg”, ”zeroblur.jpg”, ”one.jpg”,
”oneblur.jpg” itd.
Kuba: Czy będziemy musieli pisać nowe procedury
obsługi zdarzeń click dla każdego obrazka? To byłoby
sporo powtarzającego się kodu. W końcu każda z takich
procedur robiłaby dokładnie to samo: zastępowała
nieostry obraz jego normalną wersją, prawda?
Józek: Dokładnie. Nie jestem pewny, czy wiem, jak
użyć tej samej procedury obsługi zdarzeń do obsługi
większej liczby obrazków. Czy to w ogóle jest możliwe?
Judyta: Tym, co na pewno możemy zrobić, jest
przypisanie tej samej procedury obsługi, czyli tej
samej funkcji, do właściwości onclick każdego
obrazka na stronie.
Józek: A zatem ta sama funkcja będzie wywoływana
za każdym razem, gdy zostanie kliknięty jakiś
obrazek, tak?
Judyta: Tak. Użyjemy funkcji showAnswer do
obsługi zdarzeń click każdego obrazka.
Kuba: Hm… A skąd będziemy wiedzieć, który
obrazek należy podmienić na normalną wersję?
Józek: O co ci chodzi? Czy procedura obsługi
zdarzeń nie będzie tego wiedzieć?
Kuba: A skąd ma to wiedzieć? Obecnie nasza
funkcja showAnswer zakłada, że kliknęliśmy obrazek
o identyfikatorze ”zero”. Skoro jednak wywołujemy
tę funkcję dla każdego obrazka na stronie, nasz kod
będzie musiał działać dla każdego z nich.
Józek: A… no tak… racja. W takim razie skąd
będziemy wiedzieli, który obrazek został kliknięty?
Judyta: Czytałam trochę o zdarzeniach i sądzę, że
jest sposób, by procedura obsługi zdarzeń wiedziała,
który element został kliknięty przez użytkownika.
Jednak tym zajmiemy się nieco później. Zaczniemy
od dodania do gry nowych obrazków i zobaczymy, jak
dodać tę samą procedurę obsługi zdarzeń do każdego
z nich… Dopiero potem spróbujemy wymyślić, jak
określić, który obrazek został kliknięty.
Józek: Kuba: Brzmi dobrze!
jesteś tutaj
419
Dodawanie do gry kolejnych obrazków
Dodajmy więcej obrazków
Pobierz obrazki
Dostaliśmy cały zestaw nowych obrazków, zatem zacznijmy od umieszczenia
ich na naszej stronie. Dodamy ich pięć, dzięki czemu na stronie będzie
w sumie wyświetlonych sześć obrazków. Zmodyfikujemy także nieco
kod CSS, by pomiędzy poszczególnymi obrazkami były niewielkie odstępy.
Wszystkie niezbędne obrazki znajdziesz
w katalogu rozdzial09, w przykładach
do książki, które można pobrać
z serwera FTP wydawnictwa Helion,
ftp://ftp.helion.pl/przyklady/prjsrg.zip
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title> Zgadnij co to za obraz </title>
<style>
body { margin: 20px; }
img { margin: 20px; }
Za pomocą tej właściwośc
i
obrazka margines o szeroko dodajemy wokół
ści 20 pikseli.
</style>
<script>
window.onload = init;
function init() {
var image = document.getElementById(”zero”);
image.onclick = showAnswer;
};
function showAnswer() {
var image = document.getElementById(”zero”);
image.src = ”zero.jpg”;
}
Jeśli weźmiesz stronę na krótką
jazdę próbną, będzie ona wyglądać
tak jak ta.
</script>
</head>
<body>
<img id=”zero” src=”zeroblur.jpg”>
<img id=”one” src=”oneblur.jpg”>
<img id=”two” src=”twoblur.jpg”>
<img id=”three” src=”threeblur.jpg”>
<img id=”four” src=”fourblur.jpg”>
<img id=”five” src=”fiveblur.jpg”>
</body>
</html>
420
A tu dodaliśmy pięć nowych obrazków. Zwróć
uwagę, że w każdym z nich zastosowaliśmy ten
sam schemat nazewnictwa atrybutów id i src.
Już za chwilę zobaczysz, jak go wykorzystamy…
Rozdział 9.
Programowanie asynchroniczne
Teraz musimy przypisać tę samą procedurę obsługi
zdarzeń do właściwości onclick każdego obrazka
Na naszej stronie znajduje się więcej obrazków. Obecnie możesz jednak kliknąć tylko
pierwszy z nich (przedstawiający Mona Lisę), aby zobaczyć jego normalną postać.
A co ze wszystkimi pozostałymi?
Oczywiście moglibyśmy napisać osobne procedury obsługi dla każdego obrazka, jednak
z wcześniejszych rozważań już wiesz, że byłoby to uciążliwym marnotrawstwem kodu.
Spójrz na kod przedstawiony poniżej.
window.onload = init;
function init() {
var image0 = document.getElementById(”zero”);
Każdy element obrazka umieszczony
na stronie możemy pobrać
image0.onclick = showImageZero;
i przypisać mu odrębną funkcję
var image1 = document.getElementById(”one”);
obsługującą zdarzenia click.
Musielibyśmy zrobić to sześć razy,
image1.onclick = showImageOne;
choć tutaj pokazaliśmy kod tylko
...
dla dwóch elementów.
Tu znalazłyby się kody dla
}
pozostałych czterech obrazków.
function showImageZero() {
var image = document.getElementById(”zero”);
alibyśmy
image.src = ”zero.jpg”;
Dodatkowo potrzebow ujących
obsług
i
kcj
fun
u
ści
sze
}
dla każdego
zdarzenia, po jednej
e.
function showImageOne() {
oni
str
na
a
obrazk
var image = document.getElementById(”one”);
image.src = ”one.jpg”;
}
Tutaj musielibyśmy podać cztery dodatkowe funkcje.
...
WYSIL
SZARE KOMÓRKI
Jakie są wady pisania odrębnych funkcji obsługujących zdarzenia dla
poszczególnych obrazków? Zaznacz wszystkie, które według Ciebie są
uzasadnione.
Dużo powtarzającego się kodu.
Gdybyśmy musieli zmienić kod w jednej funkcji,
najprawdopodobniej musielibyśmy to także
zrobić we wszystkich pozostałych.
Generuje dużo kodu.
Utrudnia to śledzenie wszystkich obrazków
i procedur ich obsługi.
Trudno je uogólnić, by operowały na
dowolnej liczbie obrazków.
Utrudnia innym programistom pracę nad kodem.
jesteś tutaj
421
Stosowanie tej samej funkcji do obsługi wielu zdarzeń
Jak użyć tej samej funkcji do obsługi wszystkich obrazków?
Pisanie odrębnych funkcji do obsługi kliknięć poszczególnych obrazków nie jest dobrym sposobem
rozwiązania naszego problemu. Dlatego też spróbujemy użyć naszej istniejącej funkcji showAnswer
do obsługi kliknięć wszystkich obrazków umieszczonych na stronie. Oczywiście będzie to wymagało
wprowadzenia niewielkich zmian w jej kodzie. Aby skorzystać z funkcji showAnswer do obsługi
wszystkich obrazków, będziemy musieli zrobić dwie rzeczy.
1
3U]\SLVDþIXQNFMčshowAnswerMDNRSURFHGXUčREVãXJL
]GDU]HęclickZNDİG\PREUD]NXXPLHV]F]RQ\PQDVWURQLH
2
=PRG\ILNRZDþNRGIXQNFMLshowAnswer w taki sposób,
E\Z\ĤZLHWODãDQRUPDOQĈSRVWDþGRZROQHJRREUD]ND
a nie tylko ]HURMSJ
Co więcej, wszystkie te zmiany chcemy wykonać w sposób ogólny, by działały, jeśli nawet na
stronie znajdzie się więcej obrazków. Innymi słowy, kiedy dobrze napiszemy nasz kod, będziemy
mogli dodawać do strony nowe obrazki (lub usuwać z niej obrazki już istniejące) bez konieczności
wprowadzania jakichkolwiek zmian w kodzie. A zatem zaczynajmy!
Przypisanie procedury obsługi wszystkim obrazkom na stronie
Oto pierwszy problem, który musimy rozwiązać: w naszym dotychczasowym kodzie używamy
metody JHW(OHPHQW%\,G, by pobrać referencję do obrazka ”zero” i przypisać funkcję showAnswer
jego właściwości onclick. Jednak zamiast używać odrębnych wywołań metody JHW(OHPHQW%\,G
dla wszystkich obrazków na stronie, pokażemy łatwiejsze rozwiązanie: za jednym zamachem
pobierzemy wszystkie obrazki, a następnie określimy procedurę obsługi dla każdego z nich. Do tego
celu zastosujemy metodę DOM, której jeszcze nie poznałeś: GRFXPHQWJHW(OHPHQWV%\7DJ1DPH.
Wymaga ona przekazania jednego argumentu, czyli nazwy znacznika, np. img, S lub div, a zwraca
listę wszystkich pasujących elementów. A zatem spróbujmy ją zastosować w kodzie.
function init() {
var image = document.getElementById(”zero”);
cego
rego kodu pobierają cego
Pozbędziemy się sta
ają
eśl
okr
i
ro”
„ze
ntu
referencję do eleme i zdarzeń.
jego procedurę obsług
image.onclick = showAnswer;
var images = document.getElementsByTagName(”img”);
for (var i = 0; i < images.length; i++) {
images[i].onclick = showAnswer;
}
Teraz będziemy pobierać elementy ze
strony na podstawie nazwy znacznika,
img. W ten sposób odnajdziemy
i uzyskamy wszystkie obrazki
umieszczone na stronie. Wyniki
zwrócone przez metodę zapiszemy
w zmiennej images.
};
licy
iemy zawartość tab
Następnie przeglądn zapisanych w niej
z
u
dem
images i każ
y procedurę obsługi
obrazków przypiszem tnie mówiąc, do obsługi
kre
kon
er.
zdarzeń click, a
y funkcji showAnsw
tych zdarzeń użyjem ściwości onclick
wła
Tutaj przypisujemy kcję showAnswer.
każdego obrazka fun
422
Rozdział 9.
Programowanie asynchroniczne
document.getElementsByTagName pod lupą
Metoda GRFXPHQWJHW(OHPHQWV%\7DJ1DPH działa bardzo podobnie do metody GRFXPHQWJHW(OHPHQW%\,G,
z tą różnicą, że zamiast pobierać element na podstawie jego identyfikatora, pobiera elementy na
podstawie nazwy znacznika; w naszym przypadku jest to ”img”. Oczywiście strona HTML może zawierać
wiele elementów <img>, a zatem metoda może zwracać nie tylko jeden element, lecz dowolnie wiele
elementów; może też nie zwrócić żadnego — zależnie od tego, ile obrazków umieściliśmy na stronie.
W naszej obrazkowej grze na stronie znajduje się sześć elementów <img>, co oznacza, że metoda zwróci
listę sześciu obiektów elementów img.
Metoda zwraca listę obiektów elementów,
które odpowiadają przekazanej nazwie
znacznika.
var images = document.getElementsByTagName(”img”);
Zwracana jest przypominająca tablicę
lista obiektów. Nie jest to dokładnie
tablica, lecz struktura danych, która
ma bardzo podobne możliwości.
Tutaj umieść nazwę
znacznika zapisaną
w cudzysłowach (nie
zapisuj jednak nawiasów
kątowych: < oraz >!).
Zwróć uwagę na literę „s”.
Oznacza ona, że metoda może
zwrócić dowolnie wiele elementów.
Nie istnieją
P: Napisaliście, że metoda
getElementsByTagName zwraca listę.
Czy mieliście na myśli tablicę?
O
: Metoda ta zwraca obiekt, który
można traktować jak tablicę, lecz
jest to obiekt typu 1RGH/LVW. To
kolekcja obiektów 1RGH, co z kolei jest
technicznym określeniem obiektów
elementów tworzących DOM.
Można na niej operować, tak jak na
tablicy, czyli pobrać liczbę elementów
w kolekcji, odczytując właściwość
length, a następnie odwoływać się do
poszczególnych elementów przy użyciu
zapisu z nawiasami kwadratowymi.
Jednak podobieństwa pomiędzy
obiektami 1RGH/LVW i tablicami na tym
się kończą, dlatego też operując na nich,
należy zachować ostrożność. Zazwyczaj
nie trzeba wiedzieć wiele więcej na
temat obiektów 1RGH/LVW, chyba że
mamy zamiar dodawać lub usuwać
elementy DOM.
głupie pytania
P: A zatem procedurę obsługi
zdarzeń click mogę dodawać do
dowolnych elementów?
O
: Właściwie tak. Wystarczy pobrać
wybrany element strony i uzyskać do
niego dostęp, a następnie przypisać
funkcję jego właściwości onclick.
I gotowe. Jak już się przekonałeś,
taka funkcja może być używana tylko
w jednym elemencie bądź też może
służyć do obsługi zdarzeń w wielu
elementach. Oczywiście elementy,
które nie mają wizualnej prezentacji
na stronie, takie jak VFULSW! lub
<head>, nie obsługują takich zdarzeń
jak zdarzenia click.
P: Czy do funkcji obsługujących
zdarzenia są przekazywane
jakiekolwiek argumenty?
O
: To doskonałe pytanie i zadane
w odpowiednim czasie. Owszem, są
do nich przekazywane argumenty
i zaraz przedstawimy obiekt zdarzenia
(określany także jako obiekt event),
który jest przekazywany do niektórych
funkcji obsługujących zdarzenia.
P: Czy elementy udostępniają także
inne typy zdarzeń? Czy click są
jedynymi?
O
: Istnieje także całkiem sporo innych
zdarzeń, widziałeś jedno z nich w kodzie
gry w okręty: chodzi o zdarzenie
NH\SUHVV. Obsługująca je funkcja
była wywoływana za każdym razem,
gdy użytkownik nacisnął klawisz Enter
w polu formularza. Dalej w tym rozdziale
omówimy kilka kolejnych typów zdarzeń.
jesteś tutaj
423
Dyskusja na temat obiektu zdarzenia
No dobrze, Judyto, mamy
już jedną procedurę obsługi zdarzenia,
showAnswer, obsługującą kliknięcia wszystkich
obrazków. Powiedziałaś, że wiesz, jak określić,
który obrazek został kliknięty w momencie
wywoływania funkcji showAnswer?
Judyta: Tak, wiem. Za każdym razem, gdy zostanie wywołana funkcja
obsługująca zdarzenia click, jest do niej przekazywany obiekt zdarzenia.
Można go używać do pobierania różnego rodzaju informacji na temat
danego zdarzenia.
Józek: Takich jak ta, który obrazek został kliknięty?
Judyta: Mówiąc nieco bardziej ogólnie, pozwala on określić element,
w którym zdarzenie zostało wygenerowane. Jest także określany jako element
docelowy (ang. target).
Józek: Czym jest ten element docelowy?
Judyta: Jak już powiedziałam, to element, w którym zdarzenie zostało wygenerowane.
Kiedy klikniesz konkretny obrazek, stanie się on elementem docelowym.
Józek: A zatem kiedy kliknę obrazek o identyfikatorze ”zero”, stanie się on elementem docelowym?
Judyta: Precyzyjnie rzecz ujmując, stanie się nim obiekt elementu reprezentującego ten obrazek.
Józek: Możesz powtórzyć?
Judyta: Wyobraź sobie, że obiekt elementu jest tym, co uzyskujesz w efekcie wywołania metody
GRFXPHQWJHW(OHPHQW%\,G, do której przekazany został identyfikator ”zero”. To obiekt, który w DOM
reprezentuje obrazek widoczny na stronie.
Józek: No dobrze, a w jaki sposób można pobrać ten element docelowy? Wygląda na to, że to właśnie jego
potrzebujemy, żeby dowiedzieć się, który obrazek został kliknięty.
Judyta: Jest on zapisany jako jedna z właściwości obiektu zdarzenia.
Józek: Super. To chyba doskonale nada się na potrzeby funkcji showAnswer. Załatwimy to błyskawicznie.
Ale chwila, czyli do funkcji showAnswer jest przekazywany obiekt zdarzenia?
Judyta: Tak.
Józek: Jak nasza funkcja showAnswer działała do tej pory? Mówisz, że jest do niej przekazywany obiekt zdarzenia,
ale my nie zdefiniowaliśmy w niej żadnego parametru, w którym można go zapisać!
Judyta: Pamiętaj, że JavaScript pozwala ignorować parametry, jeśli nie chcemy ich używać.
Józek: No tak, racja.
Judyta: Jeszcze jedno Józku. Nie zapomnij, że będziesz musiał dowiedzieć się, jak zmienić właściwość src
obrazka i zapisać w niej odpowiednią nazwę obrazu w jego normalnej postaci. Obecnie zakładamy, że nosi on
nazwę ĵ]HURMSJĵ, jednak to założenie już dłużej nie będzie obowiązywać.
Józek: Może skorzystamy z atrybutu id obrazka i na jego podstawie określimy nazwę tego pliku? Identyfikatory
wszystkich obrazków odpowiadają nazwom plików zawierających normalną postać poszczególnych obrazków.
Judyta: To wygląda na świetny pomysł.
424
Rozdział 9.
Programowanie asynchroniczne
Jak działa obiekt zdarzenia?
W momencie wywoływania funkcji obsługującej zdarzenia click jest do niej
przekazywany obiekt zdarzenia — w rzeczywistości taki obiekt jest przekazywany do
znacznej większości zdarzeń skojarzonych z obiektowym modelem dokumentu (DOM).
Obiekt ten zawiera ogólne informacje o zdarzeniu, takie jak element, w którym
zdarzenie zostało wygenerowane, oraz kiedy to się stało. Dodatkowo znajdują się
w nim także informacje charakterystyczne dla konkretnego zdarzenia, np. dla kliknięcia
elementu myszą będą to współrzędne klikniętego punktu.
Istnieją także inne rodzaje
zdarzeń (czyli takie, które nie są
związane z DOM), a poznamy je
dalej w tym rozdziale.
Prześledźmy działanie obiektu zdarzenia.
W ramach przykładu
przeanalizujemy naszą grę.
Po kliknięciu wybranego obrazka…
“
generowane jest zdarzenie click…
je
…co z kolei powodu zdarzenia…
utworzenie obiektu
“
Zdarzenie
…który następnie zostaje
przekazany do odpowiedniej
procedury obsługi.
function showAnswer(eventObj) {
...
}
W procedurze obsługi możemy użyć obiektu zdarzenia do pobierania
różnego rodzaju informacji o zdarzeniu, takich jak jego typ, element,
w którym zdarzenie zostało wygenerowane itd.
Jakie informacje możemy znaleźć w obiekcie zdarzenia? Jak już wspominaliśmy, są
to zarówno informacje ogólne, jak i szczegółowe. Te drugie zależą od typu zdarzenia
i wrócimy do nich już niebawem. Do informacji ogólnych należy m.in. właściwość
target zawierająca referencję do obiektu, który wygenerował zdarzenie. Jeśli zatem
kliknęliśmy jakiś element strony, np. obrazek, będzie on obiektem docelowym, który
możemy odczytać w następujący sposób:
function showAnswer(eventObj) {
var image = eventObj.target;
}
Właściwość ta
informuje, kt rget
ór
wygenerował y element
zdarzenie.
Obejrzyj to!
Jeśli używasz
przeglądarki IE8 lub
starszej, koniecznie
przeczytaj dodatek.
W starszych wersjach przeglądarki
Internet Explorer obiekt zdarzenia
jest używany nieco inaczej.
jesteś tutaj
425
Ćwiczenie z obiektem zdarzenia
:
?
7
KTO CO ROBI?
7
7
Przekonałeś się już, że obiekt zdarzenia (w przypadku zdarzeń związanych z elementami DOM)
dysponuje właściwościami zapewniającymi dostęp do wielu informacji o zdarzeniu. Poniżej
przedstawiamy kilka innych właściwości tego obiektu. Dopasuj każdą z tych właściwości do
informacji, które można z niej odczytać.
target
Chcesz wiedzieć, jak daleko od górnej krawędzi
okna przeglądarki kliknął użytkownik? Jeśli tak,
służę pomocą.
type
Przechowuję obiekt, w którym zostało wygenerowane
zdarzenie. Mogę zawierać obiekty różnych typów,
jednak najczęściej są to obiekty elementów.
timeStamp
Używasz urządzenia z ekranem dotykowym? Jeśli
tak, pozwolę Ci określić, ile palców dotyka ekranu.
keyCode
Jestem łańcuchem znaków, takim jak ”click” lub
”load”, który informuje o tym, co właśnie się stało.
clientX
Chcesz wiedzieć, kiedy zdarzenie zostało
wygenerowane? Jeśli tak, jestem tą właściwością,
której szukasz.
clientY
Chcesz wiedzieć, jak daleko od lewej krawędzi
okna przeglądarki kliknął użytkownik?
Chętnie Ci pomogę.
touches
426
Rozdział 9.
Powiem Ci, jaki klawisz nacisnął użytkownik.
Programowanie asynchroniczne
Zaprzęgamy obiekt zdarzenia do pracy
Skoro już dowiedziałeś się trochę o zdarzeniach — a konkretnie o tym, jak obiekt zdarzenia
zostaje przekazany do procedury obsługi zdarzeń click — spróbujmy określić, jak
wykorzystać informacje zapisane w obiekcie zdarzenia do wyświetlenia na stronie obrazka
w normalnej postaci. Zaczniemy od ponownego przedstawienia kodu HTML strony.
<!doctype html>
...
ony.
To jest kod HTML str
<body>
<img id=”zero” src=”zeroblur.jpg”>
<img id=”one” src=”oneblur.jpg”>
<img id=”two” src=”twoblur.jpg”>
<img id=”three” src=”threeblur.jpg”>
<img id=”four” src=”fourblur.jpg”>
<img id=”five” src=”fiveblur.jpg”>
Każdy z obrazków ma swój identyfikator,
który odpowiada nazwie pliku
zawierającego normalną postać obrazu.
A zatem normalna postać obrazka
o identyfikatorze „zero” jest zapisana
w pliku zero.jpg. Z kolei normalna
postać obrazka o identyfikatorze „one”
jest zapisana w pliku one.jpg itd.
</body>
</html>
Zwróć uwagę, że wartość atrybutu id każdego z obrazków odpowiada nazwie pliku zawierającej
normalną postać obrazu (z pominięciem rozszerzenia .jpg). W takim razie, gdybyśmy mogli
odczytać wartość tego atrybutu, można by dodać do niej łańcuch ĵMSJĵ i uzyskać w ten
sposób pełną nazwę pliku. Kiedy już ją utworzymy, możemy zapisać ją we właściwości src
odpowiedniego obrazka. Poniżej pokazujemy, jak to zrobić.
Pamiętaj, że za każdym razem, gdy
klikniemy obrazek, do funkcji zostanie
przekazany obiekt zdarzenia.
function showAnswer(eventObj) {
var image = eventObj.target;
var name = image.id;
name = name + ”.jpg”;
image.src = name;
}
ektu zdarzenia
Właściwość target obiektu elementu
obi
do
ą
ncj
ere
ref
t
jes
nięty obrazek.
reprezentującego klik
Możemy użyć właściwości id tego obiek
by pobrać nazwę obrazu w normalnej tu,
posta
ci.
W końcu uzyskaną nazwę pliku
zapisujemy we właściwości src.
Jak wiesz, po zmianie wartości właściwości src obrazka
przeglądarka natychmiast pobierze nowy obraz i wyświetli go
na stronie w miejscu poprzedniego.
jesteś tutaj
427
Testowanie gry w obrazy
Testujemy obiekt zdarzenia i właściwość target
Sprawdź, czy zaktualizowałeś kod pliku image.html, a następnie wybierz się na jazdę próbną.
Wskaż jeden z obrazów, spróbuj odgadnąć, czym jest w rzeczywistości, a następnie kliknij, by
wyświetlić go w normalnej postaci. Zastanów się nad konstrukcją tej aplikacji, która nie działa
liniowo — od początku do końca kodu — lecz została zaprojektowana i napisana jako zestaw
akcji wykonywanych w odpowiedzi na zdarzenia generowane po kliknięciu jednego z obrazków.
Zastanów się także nad tym, w jaki sposób udało się obsłużyć zdarzenia generowane przez
wszystkie obrazki z wykorzystaniem jednej funkcji, której kod jest na tyle mądry, by wiedzieć,
Teraz możemy kliknąć
dowolny obraz, aby
który obrazek został kliknięty. Spróbuj rozegrać partyjkę. Co się stanie, kiedy dwa razy
wyświetlić go
klikniesz ten sam obrazek? Czy coś w ogóle się stanie?
w normalnej postaci.
Nie istnieją
głupie pytania
P
: Czy także do procedury obsługi zdarzeń load
jest przekazywany obiekt zdarzenia?
O: Owszem. I także on zawiera właściwość target,
w której jest zapisana referencja do obiektu okna, czas,
w jakim zdarzenie zostało wygenerowane, oraz jego
typ, czyli łańcuch ”load”. Bezpiecznie można jednak
stwierdzić, że podczas obsługi zdarzeń load obiekt
zdarzenia zazwyczaj nie jest używany, gdyż nie ma w nim
żadnych informacji, które w tych okolicznościach byłyby
przydatne. Przekonasz się, że czasami obiekt zdarzenia
będzie przydatny, a czasami nie — zależy to od rodzaju
obsługiwanego zdarzenia. Jeśli nie masz pewności, co
zawiera ten obiekt dla konkretnego rodzaju zdarzenia,
zajrzyj do specyfikacji języka JavaScript.
428
Rozdział 9.
WYSIL
SZARE KOMÓRKI
A co należałoby zrobić, gdybyś chciał, by po kilku
sekundach od kliknięcia obrazka ponownie została
wyświetlona jego nieostra wersja? Jak zrobić coś
takiego?
Programowanie asynchroniczne
Zdarzenia bez tajemnic
Temat dzisiejszego wywiadu brzmi:
Rozmowa z przeglądarką o zdarzeniach
Rusz głową: Witaj Przeglądarko. Zawsze nam miło, kiedy
znajdujesz dla nas czas. Wiemy, że jesteś bardzo zapracowana.
Rusz głową: A co to oznacza dla naszych czytelników
uczących się języka JavaScirpt?
Przeglądarka: Też jest mi miło. Ale faktycznie, macie rację,
zarządzanie tymi wszystkimi zdarzeniami sprawia, że jestem
cały czas na nogach.
Przeglądarka: Cóż, załóżmy, że piszecie procedurę obsługi,
która wymaga złożonych obliczeń — takich, których
wykonanie zajmuje dużo czasu. Tak długo, jak długo
procedura obsługi wykonuje obliczenia, ja siedzę i czekam,
aż ona skończy. Dopiero kiedy to nastąpi, mogę się zająć
kolejnym zdarzeniem w kolejce.
Rusz głową: Swoją drogą, jak nimi zarządzasz? Uchyl rąbka
tajemnicy skrywającego tę magię.
Przeglądarka: Jak wiecie, zdarzenia są generowane niemal
nieustannie. Użytkownik przesuwa wskaźnik myszy lub wykonuje
jakieś gesty na urządzeniach mobilnych, jakieś dane są przesyłane
siecią, upływa czas odmierzany przez liczniki… Jestem jak
centralny dworzec kolejowy. Naprawdę jest czym zarządzać.
Rusz głową: Zakładaliśmy, że nie musisz wiele robić, jeśli
nie zostanie zdefiniowana odpowiednia procedura obsługi
zdarzeń…
Przeglądarka: Nawet jeśli nie została zdefiniowana, to
i tak jest sporo do zrobienia. Ktoś musi odebrać zdarzenie,
zinterpretować je, sprawdzić, czy istnieje procedura jego
obsługi. A jeśli taka się znajdzie, muszę zagwarantować,
że zostanie wykonana.
Rusz głową: A zatem w jaki sposób zarządzasz tymi wszystkimi
zdarzeniami? Co się dzieje, gdy w tej samej chwili zostanie
wygenerowanych wiele zdarzeń? W końcu ty jesteś tylko jedna.
Przeglądarka: To fakt — w krótkim okresie czasu może
zostać wygenerowanych wiele zdarzeń. Czasami pojawiają
się one zbyt szybko, bym mogła je na bieżąco obsługiwać.
W takich sytuacjach, kiedy przychodzą nowe zdarzenia,
ustawiam je w kolejce. Następnie ją przeglądam i tam, gdzie
to konieczne, wywołuję odpowiednie procedury obsługi.
Rusz głową: Rany… To brzmi jak praca w fast foodzie
w godzinach szczytu!
Rusz głową: O rany! Czy to się często zdarza? Czy często
musisz czekać na wykonanie takiego wolnego kodu?
Przeglądarka: Ano… zdarza się, jednak zazwyczaj
programiści bardzo łatwo potrafią się zorientować, że strona
lub aplikacja nie reaguje dostatecznie szybko ze względu
na wolno działające procedury obsługi zdarzeń. Zatem
ten problem nie pojawia się często, o ile tylko programiści
wiedzą, jak działają kolejki zdarzeń.
Rusz głową: A teraz wiedzą to wszyscy nasi czytelnicy!
Wróćmy zatem do zdarzeń. Czy dużo jest różnych rodzajów
zdarzeń?
Przeglądarka: Oj tak. Istnieją zdarzenia związane z siecią,
zdarzenia generowane przez liczniki czasu, zdarzenia DOM
związane ze stroną i jeszcze kilka innych. Niektóre z tych
rodzajów, takie jak zdarzenia DOM, generują obiekty zdarzenia
zawierające wiele informacji na ich temat. Przykładowo
w przypadku kliknięcia obiekt zdarzenia zawiera współrzędne
klikniętego punktu, w przypadku naciśnięcia klawisza —
informacje o tym, który klawisz został naciśnięty itd.
Rusz głową: A zatem poświęcasz naprawdę sporo czasu na
zarządzanie zdarzeniami. Czy to jest najlepszy sposób, w jaki
mogłabyś spożytkować swój czas? Przecież musisz się także
zajmować pobieraniem, przetwarzaniem i wyświetlaniem stron.
Przeglądarka: A pewnie… Gdyby zamówienia pojawiały się
co milisekundę albo coś koło tego!
Przeglądarka: Zdarzenia są naprawdę bardzo ważne.
Obecnie trzeba pisać strony, które są interaktywne
i zajmujące, a do tego zdarzenia są niezbędne.
Rusz głową: Czy przetwarzasz kolejkę zdarzenie po
zdarzeniu?
Rusz głową: No tak… Oczywiście. Czasy prostych stron
WWW minęły bezpowrotnie.
Przeglądarka: Dokładnie, a musicie wiedzieć jeszcze jedną
ważną rzecz na temat JavaScriptu: istnieje tylko jedna kolejka
i jeden „wątek zarządzający”, co oznacza, że tylko ja jedna
zajmuję się po kolei wszystkimi zdarzeniami.
Przeglądarka: Otóż to. O rany, ta kolejka wkrótce się
przepełni… Muszę lecieć!
Rusz głową: Jasne… Do następnego razu!
jesteś tutaj
429
Kolejka zdarzeń
Zdarzenia i kolejki
Już wiesz, że przeglądarka zarządza kolejką zdarzeń. I że za kulisami strony
przeglądarka bezustannie pobiera zdarzenia z tej kolejki i je obsługuje,
wywołując odpowiednie procedury obsługi, o ile tylko istnieją.
Przeglądarka
Uwaga! Nadchodzi
kolejne zdarzenie.
Użytkownik właśnie kliknął
kolejny element strony.
Kolejka zdarzeń
=DNRĚF]RQRZF]\W\ZDQLHVWURQ\
8İ\WNRZQLNNOLNQċâ
/LF]QLNF]DVX]DNRĚF]\âRGOLF]DQLH
:\VâDQRIRUPXODU]
Uwaga na głowę, nadlatuje
kolejne zdarzenie.
Użytkownik właśnie kliknął inny
element strony.
8İ\WNRZQLNNOLNQċâ
a
Przeglądarkkolejkę od
przegląda jstarszych
zdarzeń na ych,
sz
do najnow jąc kolejno
przetwarzach.
każde z ni
8İ\WNRZQLNSRQRZQLHNOLNQċâ
.ROHMQHNOLNQLĐFLH
,QQ\OLF]QLNF]DVX]DNRĚF]\âRGOLF]DQLH
:\VâDQRIRUPXODU]
2GHEUDQRGDQHSU]HVâDQHSU]H]VLHþ
Koniecznie trzeba wiedzieć, że przeglądarka przetwarza zdarzenia kolejno,
jedno po drugim, a zatem, jeśli to tylko możliwe, należy dbać o to, by
procedury obsługi zdarzeń były krótkie i wydajne. W przeciwnym razie
cała kolejka może się wypełnić oczekującymi zdarzeniami, a przeglądarka
zdenerwuje się, bo będzie musiała je obsługiwać. A jakie mogą być negatywne
skutki dla Ciebie? Tworzony interfejs użytkownika stanie się wolny i przestanie
sprawnie reagować na poczynania użytkownika.
430
Rozdział 9.
Kiedy sprawy przybiorą naprawdę
zły obrót, pojawi się okno dialogowe
informujące o wolnym działaniu
skryptu. Będzie ono oznaczać,
że przeglądarka się poddała!
Programowanie asynchroniczne
$KRMNDPUDFLH:7ZRMHUĊFHWUDILáDPDSD]GURJąGRVNDUEX3RWU]HEXMHP\
7ZRMHMSRPRF\ZRNUHĞOHQLXMHJRZVSyáU]ĊGQ\FK:W\PFHOXPXVLV]
QDSLVDüNRGNWyU\EĊG]LHZ\ĞZLHWODáZVSyáU]ĊGQHZVNDĨQLNDP\V]\
SU]HVXZDQHJRZREV]DU]HPDS\)UDJPHQWWHJRNRGXSU]HGVWDZLOLĞP\QD
QDVWĊSQHMVWURQLHMHGQDNFKFHP\ĪHE\ĞJRGRNRĔF]\á
To jest mapa, a X
oznacza miejsce
ukrycia skarbu!
Twój kod wyświetli
współrzędne poniżej
mapy.
Kiedy już napiszesz kod
,
współrzędnych skarbu spr określenie
do przesunięcia wskaźn owadzi się
ika
i umieszczenia go na X. myszy
P.S. Zachęcamy
do zrobienia tego
ćwiczenia, gdyż
sądzimy, że piraci
nie będą zbyt
szczęśliwi, jeśli nie
otrzymają swoich
współrzędnych…
A swoją drogą,
informacje te będą
Ci potrzebne do
dokończenia kodu.
Zdarzenie mousemove
Zdarzenie mousemove jest przekazywane do procedury obsługi, kiedy użytkownik przesunie
wskaźnik myszy w obszarze konkretnego elementu. Procedurę obsługi tego zdarzenia określa
się przy użyciu właściwości onmousemove. Kiedy już to zrobimy, do wskazanej funkcji będzie
przekazywany obiekt dysponujący następującymi właściwościami:
clientX oraz clientY — to wyrażone w pikselach położenie wskaźnika myszy w poziomie (x)
i pionie (y) względem, odpowiednio, lewej oraz górnej krawędzi okna przeglądarki;
screenX oraz screenY — to wyrażone w pikselach położenie wskaźnika myszy w poziomie (x)
i pionie (y) względem, odpowiednio, lewej oraz górnej krawędzi ekranu;
SDJH; oraz SDJH< — to wyrażone w pikselach położenie wskaźnika myszy w poziomie (x)
i pionie (y) względem, odpowiednio, lewej oraz górnej krawędzi strony.
jesteś tutaj
431
Ćwiczenie z obsługi zdarzenia mousemove
$QLHFKPQLH3RQLĪHMSU]HGVWDZLOLĞP\NRG:REHFQHMSRVWDFL]DZLHUDRQ
PDSĊXPLHV]F]RQąQDVWURQLHRUD]DNDSLWWHNVWXXPRĪOLZLDMąF\Z\ĞZLHWODQLH
RGF]\WDQ\FKZVSyáU]ĊGQ\FK7ZRLP]DGDQLHPMHVWXUXFKRPLHQLHFDáHJR
NRGX]ZLą]DQHJR]REVáXJą]GDU]HĔ3RZRG]HQLD:NRĔFXQLHFKFLHOLE\ĞP\
E\ĞZQDMEOLĪV]\PF]DVLHWUDILáGRVNU]\QL'DY\¶HJR-RQHVD«
Ćwiczenie
<!doctype html>
<html lang=”en”>
<head>
<meta charset=”utf-8”>
<title>Skarb piratöw</title>
<script>
window.onload = init;
function init() {
var map = document.getElementById(”map”);
__________________________________
}
obsługi zdarzeń.
Tutaj określ procedurę
function showCoords(eventObj) {
var map = document.getElementById(”coords”);
___________________________
___________________________
map.innerHTML = ”WspöïrzÚdne na mapie: ”
+ x + ”, ” + y;
}
W tym miejscu musisz
</script>
odczytać współrzędne.
</head>
<body>
<img id=”map” src=”map.jpg”>
<p id=”coords”>Przesuñ wskaěnik myszy na mapÚ, by odczytaÊ wspöïrzÚdne...</p>
</body>
</html>
wdziwej
ieść ten kod na pra
Kiedy skończysz, um l ją w przeglądarce,
iet
św
je tutaj.
stronie WWW, wy
współrzędne i zapisz
a następnie odczytaj
____________________________________
432
Rozdział 9.
Programowanie asynchroniczne
Jeszcze więcej zdarzeń
Na razie omówiliśmy trzy rodzaje zdarzeń: zdarzenie load, generowane gdy
przeglądarka zakończy wczytywanie strony, zdarzenie click, generowane gdy
użytkownik kliknie jakiś element strony, oraz zdarzenie mousemove, generowane gdy
użytkownik przesunie wskaźnik myszy w obszarze określonego elementu. Zapewne
poznasz także wiele innych rodzajów zdarzeń, takich jak zdarzenia generowane, gdy
jakieś dane zostaną przesłane przez sieć, zdarzenia związane z geolokalizacją czy też
generowane przez liczniki czasu (a to tylko kilka spośród wielu dostępnych).
Określenie procedury obsługi każdego z tych zdarzeń wymagało przypisania funkcji
do odpowiedniej właściwości, np. onload, onmousemove lub onclick. Jednak nie
wszystkie zdarzenia działają w taki sposób — np. procedury obsługi zdarzeń związanych
z upływem czasu nie są określane przy użyciu właściwości, lecz poprzez wywołanie
funkcji VHW7LPHRXW, do której przekazywana jest nazwa odpowiedniej funkcji.
Przeanalizujmy przykład. Załóżmy, że chcesz wykonać jakąś czynność po upływie pięciu
sekund. Poniżej pokazaliśmy, jak można to zrobić przy użyciu funkcji VHW7LPHRXW oraz
timerHandler.
function timerHandler() {
Najpierw napiszemy procedurę obsługi zdarzenia.
Zostanie ona wykonana, gdy przeglądarka
wygeneruje zdarzenie informujące o upływie
zadanego okresu czasu.
alert(”Hej! I co tak siedzisz, gapiÈc siÚ na ekran? Zröb coĂ!”);
}
W naszym przypadku obsługa zdarzenia ogranicza się
jedynie do wyświetlenia komunikatu.
t, która wymaga
A tu wywołujemy funkcję setTimeou dury obsługi zdarzenia
setTimeout(timerHandler, 5000);
przekazania dwóch argumentów: proce ekundach).
oraz okresu czasu (wyrażonego w milis
Korzystanie
z funkcji setTimeout
przypomina
nieco stosowania
czasomierza.
W tym przypadku
prosimy licznik
czasu, by odczekał
pięć sekund (czyli
5000 milisekund).
Po upływie tego czasu ma
zostać wywołana funkcja
timerHandler.
WYSIL
Przetestuj swój licznik czasu
Nie siedź bezczynnie! Czas przetestować ten kod! Po prostu wrzuć go do najprostszej
strony WWW, a następnie wyświetl ją w przeglądarce. Początkowo nie zobaczysz nic,
jednak po pięciu sekundach pojawi się okno dialogowe z komunikatem.
SZARE KOMÓRKI
W jaki sposób sprawić,
by okno z komunikatem
pojawiało się nieustannie
co pięć sekund?
Bądź cierpliwy, poczekaj pięć sekund,
a zobaczysz to, co pokazaliśmy na tym
obrazku. Jeśli jednak spędzisz przed
komputerem kilka minut bez widocznego
rezultatu, możesz go lekko kopnąć…
Nie, tylko żartowaliśmy… W takim
przypadku lepiej sprawdź swój kod.
jesteś tutaj
433
Funkcja setTimeout
No dobrze, czas brać się do
roboty. Mam tu licznik czasu, który ma
odmierzyć 5000 milisekund, i procedurę
obsługi, którą należy wykonać po
upływie tego czasu.
Jak działa funkcja setTimeout?
Teraz spróbujemy dokładnie przeanalizować, co właściwie
się stało.
1
2
:PRPHQFLH]DNRĚF]HQLD
Licznikami czasu
zarządza Twoja
ZF]\W\ZDQLDVWURQ\Z\NRQXMHP\
przeglądarka.
GZLHRSHUDFMHGHILQLXMHP\
IXQNFMĐRQD]ZLHtimerHandler oraz
Z\ZRâXMHP\IXQNFMĐVHW7LPHRXW
NWyUDSRPLOLVHNXQGZ\JHQHUXMH
]GDU]HQLHLQIRUPXMċFHRXSâ\ZLH
]DGDQHJRRNUHVXF]DVX.LHG\WR
QDVWċSL]RVWDQLHZ\ZRâDQDSURFHGXUD
REVâXJLWHJR]GDU]HQLD
3RGF]DVJG\OLF]QLNRGOLF]D
XSâ\ZF]DVXSU]HJOċGDUND
Z\NRQXMHVZRMHVWDQGDUGRZH
G]LDâDQLD
5000, 4999, 4998…
… 6, 5, 4, 3, 2, 1, 0.
3
uje
Przeglądarka nadzor
pracę wszystkich
szem,
liczników czasu (ow
może ich być więcej
niż jeden) i zarządza
skojarzonymi z nimi
procedurami obsługi
zdarzeń, które trzeba
będzie wywołać.
.LHG\OLF]QLNGRWU]HGRZDUWRĤFL
SU]HJOċGDUNDZ\ZRâDSURFHGXUĐREVâXJL
]GDU]HQLD
Upłynęło 5000
milisekund, odliczanie
zostało zakończone,
wywołajmy zatem
procedurę obsługi.
Zdarzenie zostaje wygenerow
ane, kiedy licznik zakończy
odmierzanie podanego okresu
wykonuje procedurę obsługi czasu. Przeglądarka
zdarzenia i wywołuje
przekazaną funkcję.
4
=RVWDMHZ\ZRâDQDSURFHGXUDREVâXJLNWyUD
Z\ĤZLHWODRNQRGLDORJRZH]NRPXQLNDWHP
function timerHandler() {
alert(”Hej! I co tak siedzisz, gapiÈc siÚ na ekran? Zröb coĂ!”);
}
Kiedy przeglądarka wykona naszą
procedurę obsługi, zostanie
wyświetlone to okno dialogowe!
434
Rozdział 9.
Procedura
obsługi została wykonana,
mogę już zapomnieć o tym
liczniku czasu.
Programowanie asynchroniczne
Czy ja dobrze zrozumiałem:
kiedy używamy funkcji setTimeout,
przekazujemy jedną funkcję
w wywołaniu drugiej?
Dokładnie! Czy pamiętasz, jak na początku tego rozdziału napisaliśmy, iż
czytając go, poczujesz, że nie jesteśmy już w Kansas? No cóż, to jest właśnie
ten moment naszego filmu, w którym obraz z czarno-białego zmienia się na
kolorowy. Jednak wróćmy do pytania. Owszem, zdefiniowaliśmy funkcję,
a następnie przekazaliśmy ją w wywołaniu funkcji VHW7LPHRXW (która
w rzeczywistości jest metodą).
setTimeout(timeHandler, 50000);
A oto i ona, referencja do naszej funkcji
jest przekazywana do funkcji setTimeout
(czyli innej funkcji).
Dlaczego tak robimy i co to oznacza? Zastanówmy się nad tym: działanie
funkcji VHW7LPHRXW polega na utworzeniu licznika czasu i skojarzeniu
z nim procedury obsługi. Procedura ta jest wykonywana, kiedy licznik
odmierzy zadany okres czasu. Aby poinformować funkcję VHW7LPHRXW, jaką
procedurę należy wykonać, przekazujemy do niej referencję do funkcji. Funkcja
VHW7LPHRXW zapisze tę referencję, by użyć jej później, kiedy upłynie zdany
okres czasu.
Jeśli pomyślisz: „To ma sens”, to świetnie. Z drugiej strony, możesz sobie
pomyśleć: „Przepraszam? Przekazywać funkcję do funkcji? Też coś!”. W takim
przypadku masz zapewne doświadczenia w korzystaniu z takich języków
jak C lub Java, w których podobne przekazywanie jednych funkcji do drugich nie
jest czymś powszechnie spotykanym. No cóż… W języku JavaScript to normalne;
co więcej, możliwość takiego przekazywania funkcji jest niezwykle użytecznym
narzędziem, zwłaszcza w kontekście pisania kodu reagującego na zdarzenia.
Jednak najprawdopodobniej myślisz sobie: „Chyba to rozumiem, ale nie
jestem pewny”. Jeśli tak, nie przejmuj się. Na razie wyobraź sobie, że do
funkcji VHW7LPHRXW przekazujesz referencję do funkcji, która ma zostać
wykonana po upływie zadanego okresu czasu. W następnym rozdziale znacznie
dokładniej opiszemy funkcje oraz ich możliwości (takie jak przekazywanie ich
do innych funkcji). Zatem te informacje powinny Ci na razie wystarczyć.
jesteś tutaj
435
Pytania o zdarzenia
Ćwiczenie
To jest kod.
Przyjrzyj się przedstawionemu poniżej fragmentowi kodu i pomyśl, czy na jego
podstawie będziesz potrafił określić, jak działa funkcja VHW,QWHUYDO. Jest ona
podobna do funkcji VHW7LPHRXW, lecz działa nieco inaczej. Sprawdź odpowiedź,
którą zamieściliśmy pod koniec tego rozdziału.
var tick = true;
function ticker() {
if (tick) {
console.log(”Tik”);
tick = false;
} else {
console.log(”Tak”);
tick = true;
}
}
setInterval(ticker, 1000);
.RQVROD-DYD6FULSW
7LN
7DN
7LN
7DN
7LN
7DN
7LN
7DN
Tu zapisz swoją analizę.
A to są wyniki.
Nie istnieją
głupie pytania
P: Czy istnieje możliwość zatrzymania licznika
utworzonego przez funkcję setInterval?
O: Tak. Wywołanie tej funkcji zwraca identyfikator licznika.
Można go przekazać do innej funkcji, FOHDU,QWHUYDO,
by zatrzymać działanie licznika.
P: Używając właściwości onload, kojarzę ze zdarzeniem
jedną procedurę obsługi. Jednak w przypadku setTimeout
wygląda na to, że mogę skojarzyć dowolnie wiele procedur
obsługi z dowolną liczbą liczników czasu?
O
P: Napisaliście, że setTimeout jest metodą, ale wygląda
jak funkcja. Do jakiego obiektu należy ta metoda?
: Dokładnie. Wywołując metodę VHW7LPHRXW, tworzymy licznik
czasu i kojarzymy z nim procedurę obsługi. Takich liczników może
być dowolnie wiele. Przeglądarka zadba o skojarzenie każdego
licznika z odpowiednią procedurą obsługi.
O: Dobre pytanie. Technicznie rzecz biorąc, moglibyśmy napisać
P: Czy istnieją także inne przykłady przekazywania
ZLQGRZVHW7LPHRXW, jednak ze względu na to, że window
jest obiektem globalnym, możemy pominąć jego nazwę i użyć
samego VHW7LPHRXW; w praktyce takie rozwiązanie jest najczęściej
spotykane.
P
: Czy mogę pominąć window także wtedy, gdy używam
właściwości window.onload?
O: Możesz, choć większość osób tego nie robi, obawiając się, że
właściwość ta jest na tyle często stosowana (gdyż jest dostępna
także w innych elementach), iż pominięcie określenia, o którą
z właściwości onload chodzi, może być mylące.
436
Rozdział 9.
funkcji do innych funkcji?
O: I to bardzo wiele. Przekonasz się, że w języku JavaScript
przekazywanie funkcji jest bardzo popularnym rozwiązaniem.
Korzysta z niego wiele funkcji wbudowanych, takich jak
VHW7LPHRXW oraz VHW,QWHUYDO, lecz sam się przekonasz,
że w wielu przypadkach także kod tworzony przez Ciebie
może pobierać argumenty będące funkcjami. Jednak to tylko
fragment całej historii, a w następnym rozdziale poznasz ją
w najdrobniejszych szczegółach i przekonasz się, że w JavaScripcie
z funkcjami można robić naprawdę wiele interesujących rzeczy.
Programowanie asynchroniczne
Panowie, próbuję dokończyć
tę aplikację z podmienianiem
obrazków. Pracuję właśnie nad tym, by
kliknięty obrazek po kilku sekundach
ponownie wracał do nieostrej postaci.
Józek: Brzmi świetnie… Założę się, że
używasz funkcji VHW7LPHRXW?
Franek: Taki mam plan, jednak nie jestem
pewny, skąd mam się dowiedzieć, który obrazek
należy zmienić.
Kuba: Co masz na myśli?
Franek: Mój kod działa w ten sposób, że po
kliknięciu obrazka tworzę licznik czasu, który
zadziała po upływie dwóch sekund. Kiedy to nastąpi,
wywoływana jest funkcja o nazwie reblure.
Józek: A w tej funkcji musisz wiedzieć, który obrazek
należy zmienić na nieostrą wersję?
Franek: Dokładnie. Nie przekazuję do tej funkcji żadnego argumentu — wywołuje
ją przeglądarka po upływie zadanego okresu czasu, więc nie mam jak przekazać jej
informacji o obrazku, który należy zmienić. Chyba utknąłem.
Kuba: A czy przeglądałeś dokumentację funkcji VHW7LPHRXW?
Franek: Nie… Wiem tylko tyle, ile przekazała mi Judyta, że do funkcji VHW7LPHRXW
przekazywana jest funkcja i długość okresu czasu podana w milisekundach.
Kuba: Do wywołania VHW7LPHRXW możesz także dodać argument, który po upływie
podanego czasu zostanie przekazany jako argument wywołania podanej procedury
obsługi.
Franek: O! To świetnie. A zatem mogę przekazać referencję do obrazka, który
należy zmienić, a potem zostanie ona przekazana do mojej funkcji, kiedy upłynie
zadany okres czasu?
Kuba: Dokładnie.
Franek: Popatrz Józku, jak dużo może dać rozmowa o kodzie, prawda?
Józek: No pewnie. Wypróbujmy teraz ten kod…
jesteś tutaj
437
Zastosowanie setTimeout w naszej grze w obrazy
Kończenie gry
Nadszedł czas, aby zakończyć naszą grę w obrazy. Chcemy, by po upływie kilku
sekund automatycznie znowu pojawiła się nieostra wersja obrazka. A zgodnie z tym,
czego się właśnie dowiedzieliśmy, w wywołaniu funkcji VHW7LPHRXW można przekazać
argument, który później zostanie przekazany do procedury obsługi. Sprawdźmy, jak
to zrobić.
window.onload = function() {
Ten kod jest dokładnie taki sam
jak wcześniej. Nic się w nim
nie zmieniło…
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;
name = name + ”.jpg”;
image.src = name;
setTimeout(reblur, 2000, image);
}
function reblur(image) {
var name = image.id;
name = name + ”blur.jpg”;
image.src = name;
mencie wyświetlenia
Jednak teraz, w mo postaci wywołujemy
j
lne
ma
obrazu w nor
eout, która
także funkcję setTim zdarzenia po
e
spowoduje zgłoszeni d.
upływie dwóch sekun
Naszą procedurą obsługi będzie funkcja
reblur (pokazana poniżej), zostanie
ona wywołana po 2000 milisekund
(czyli po dwóch sekundach), przy czym
przeglądarka przekaże do niej argument
— obiekt obrazka, który należy zmienić.
Teraz, kiedy zostanie wywołana ta
funkcja, przeglądarka przekaże do
niej obrazek.
}
Nasza funkcja może odebrać obiekt
obrazka i użyć go do odczytania
identyfikatora elementu, na podstawie
którego utworzy nazwę pliku nieostrej
wersji obrazu. Kiedy zapiszemy ją we
właściwości src, przeglądarka zastąpi
normalną wersję obrazu jego wersją
nieostrą.
438
Rozdział 9.
Obejrzyj to!
Ten dodatkowy argument funkcji setTimeout
nie jest dostępny w przeglądarce IE8
i starszych.
Niestety, to prawda. Ten kod nie będzie działać, jeśli Ty bądź
Twoi użytkownicy będziecie używać przeglądarki Internet
Explorer 8 lub starszej. Jednak czytając tę książkę, w ogóle nie
powinieneś korzystać z tak starej wersji przeglądarki! Dalej
w książce pokażemy inne rozwiązanie, które pozwoli rozwiązać
ten problem (zarówno w przeglądarce IE8, jak i jej starszych
wersjach).
Programowanie asynchroniczne
Jazda testowa z licznikiem czasu
To nie był zbyt długi fragment kodu do dodania, a jednak wprowadził znaczące zmiany w
sposobie działania gry. Teraz, kiedy klikniesz obrazek, przeglądarka w niewidoczny sposób
(przy wykorzystaniu licznika czasu) zaczyna odliczać, kiedy powinna wywołać funkcję reblur,
która ponownie wyświetli na stronie nieostrą wersję obrazu. Zwróć uwagę na asynchroniczny
charakter działania aplikacji — to Ty decydujesz, kiedy klikniesz obrazek, jednak fragmenty
kodu aplikacji są wykonywane w różnym czasie, zależnie do generowanych zdarzeń click
oraz zdarzeń generowanych przez liczniki czasu. Nie ma żadnego superalgorytmu sterującego
działaniem gry, kontrolującego, jak kod zostanie wykonany i kiedy; jest tylko kilka
niewielkich fragmentów kodu, które przygotowują obsługę zdarzeń, a następnie je obsługują.
Teraz, kiedy klikniemy
obrazek, zostanie
wyświetlony w normalnej
postaci, a następnie po
upływie dwóch sekund
ponownie zostanie
podmieniony na swoją
nieostrą wersję.
Dobrze przetestuj tę aplikację,
klikając kilka obrazków w krótkim
okresie czasu. Czy zawsze
wszystko działa prawidłowo?
Zajrzyj ponownie do kodu
i spróbuj się trochę zastanowić
nad tym, w jaki sposób
przeglądarka przechowuje
informacje o wszystkich obrazkach,
które trzeba podmienić.
Nie istnieją
głupie pytania
P: Czy do funkcji obsługującej licznik P: Funkcja showAnswer obsługuje
czasu, określanej przy użyciu funkcji
setTimeout, mogę przekazać tylko
jeden argument?
zdarzenia, a mimo to tworzy nową
procedurę obsługi zdarzeń, reblur?
Czy to jest prawidłowe rozwiązanie?
O: Nie, możesz ich przekazać dowolnie
O: Zauważyłeś to. W praktyce, w języku
wiele: żaden, jeden lub kilka.
P: A co z obiektem zdarzenia?
Dlaczego nie jest przekazywany do
funkcji określanej przez wywołanie
setTimeout?
O: Obiekt zdarzenia jest zazwyczaj
używany w procedurach obsługi zdarzeń
związanych z DOM. Funkcja VHW7LPHRXW
nie przekazuje takiego obiektu do określanej
przez siebie funkcji obsługi, gdyż nie jest
ona skojarzona z żadnym konkretnym
elementem.
JavaScript takie rozwiązania są spotykane
dosyć często. Jest całkowicie normalne, że
kod jakiejś procedury obsługi zdarzeń określa
procedury obsługi innych zdarzeń. Właśnie
taki styl tworzenia aplikacji określiliśmy na
początku tego rozdziału nazwą programowanie
asynchroniczne. Do utworzenia gry w obrazy
nie napisaliśmy algorytmu działającego
liniowo od początku do końca. Zamiast tego
przygotowaliśmy procedury obsługi, które
umożliwiają prowadzenie gry w momencie
generowania zdarzeń. Prześledź, co się
dzieje po kliknięciu kilku różnych obrazków
oraz przyjrzyj się różnym wywołaniom
wykonywanym w celu wyświetlenia normalnej
i nieostrej wersji obrazu.
P: A zatem istnieją zdarzenia związane
z elementami DOM, z licznikami czasu…
Czy dużo jest takich rodzajów zdarzeń?
O: Wiele zdarzeń powszechnie używanych
w języku JavaScript to zdarzenia związane
z elementami DOM (takie jak zdarzenia
click) lub z licznikami czasu (tworzone
przy użyciu funkcji VHW7LPHRXW
oraz VHW,QWHUYDO). Istnieją także
zdarzenia związane z konkretnymi API,
np. generowane przez mechanizmy
geolokalizacji, LocalStorage, zdarzenia
wątków roboczych itd. (Więcej informacji
o nich znajdziesz w książce HTML5. Rusz
głową!). I w końcu istnieje także cała
grupa zdarzeń związanych z operacjami
wejścia-wyjścia, np. zdarzenie informujące
o odebraniu danych przy użyciu obiektu
;PO+WWS5HTXHVW (także o nim możesz
przeczytać w książce HTML5. Rusz głową!)
lub gniazd sieciowych (ang. Web Sockets).
jesteś tutaj
439
Stosowanie zdarzenia mouseover
Cześć Panowie,
użytkownicy komputerów
stacjonarnych chcieliby mieć możliwość
wyświetlenia normalnej wersji obrazu
poprzez przesunięcie nad nim wskaźnika
myszy. Czy możecie zaimplementować
coś takiego?
Judyta: Aby zaimplementować taką możliwość, trzeba będzie skorzystać ze zdarzenia
mouseover. Jego procedurę obsługi można dodać do dowolnego elementu, używając
właściwości onmouseover:
myElement.onmouseover = myHandler;
Judyta: Oprócz niego istnieje także zdarzenie mouseout, które informuje, kiedy wskaźnik
myszy został usunięty z obszaru elementu. Do określania procedury obsługi tego zdarzenia
służy właściwość onmouseout.
Ćwiczenie
Przerób kod aplikacji w taki sposób, by normalna wersja obrazu była
wyświetlana po umieszczeniu w jego obszarze wskaźnika myszy oraz by po
usunięciu wskaźnika z obszaru obrazka ponownie była wyświetlana jego
nieostra wersja. Nie zapomnij przetestować kodu oraz porównać swojego
rozwiązania z naszym, zamieszczonym pod koniec rozdziału.
Tutaj zapisz kod JavaScript.
440
Rozdział 9.
Programowanie asynchroniczne
Ćwiczenie
Po zakończeniu gry w obrazki Judyta napisała prostą aplikację z myślą
o zbliżającym się, cotygodniowym spotkaniu zespołu. Zaplanowała mały
konkurs. Wygra go osoba, która pierwsza opisze działanie jej kodu.
Kto wygra ten konkurs? Kuba, Józek, Franek? A może Ty?
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Nie zmieniaj mojej wielkoĂci, mam ïaskotki!</title>
<script>
function resize() {
var element = document.getElementById(”display”);
element.innerHTML = element.innerHTML + ” To ïaskocze!”;
}
</script>
</head>
<body>
<p id=”display”>
Cokolwiek robisz, nie zmieniaj wielkoĂci okna! Ostrzegam!
</p>
<script>
window.onresize = resize;
</script>
</body>
</html>
Tutaj zapisz swoje notatki dotyczące sposobu
działania kodu Judyty. Jakie zdarzenia są w nim
wykorzystywane? W jaki sposób jest określana ich
procedura obsługi? I w końcu kiedy te zdarzenia są
generowane? Nie ograniczaj się do samych notatek
— wypróbuj działanie aplikacji w przeglądarce.
jesteś tutaj
441
Funkcje laboratoryjne
Laboratorium kodu
Znaleźliśmy fragment bardzo podejrzanego kodu, w którego przetestowaniu musisz nam pomóc. Choć
wykonaliśmy jego początkową analizę i wygląda, że jest to na 100% standardowy kod JavaScript, jednak
jest w nim coś dziwnego. Poniżej znajdziesz dwie próbki kodu. Musisz określić, co w każdej z nich wygląda
dziwnie, przetestować je, by upewnić się, czy kod działa, a następnie przeanalizować go i zrozumieć jak,
działa. Zabierz się do pracy i zapisz swoje uwagi na tej stronie. Na następnej stronie znajdziesz naszą analizę.
Próbka nr 1
var addOne =
function(x) {
UHWXUQ[
`
YDUVL[ DGG
2QH Próbka nr 2
n() {
ad = functio
window.onlo
WDQD RVWDïDZF]\
]
D
Q
R
WU
6
UW
DOH
}
DO try this at
442
Rozdział 9.
Programowanie asynchroniczne
Laboratorium kodu: analiza
Próbka nr 1
var addOne = function(x) {
return x + 1;
};
var six = addOne(5);
Na pierwszy rzut oka wydaje się,
że ten kod po prostu definiuje
funkcję, która do przekazanej
wartości parametru dodaje jeden,
a następnie ją zwraca.
Kiedy jednak przyjrzymy się
dokładniej, okazuje się, że nie
jest to zwyczajna definicja funkcji.
Wygląda raczej na definicję
zmiennej, której przypisujemy
funkcję i to funkcję, która
nie ma nazwy.
Co więcej, zaraz potem
wywołujemy tę funkcję, używając
przy tym nazwy zmiennej, a nie
nazwy funkcji podanej w jej
definicji.
To faktycznie bardzo dziwne (choć
może nieco przypominać sposób,
w jaki są definiowane metody
obiektów).
Próbka nr 2
window.onload = function() {
DOHUW 6WURQD]RVWDïDZF]\WDQD }
Tutaj mamy coś podobnego.
Zamiast zdefiniować funkcję,
a następnie przypisać jej nazwę
właściwości onload obiektu
window, bezpośrednio zapisujemy
funkcję w tej właściwości.
I także w tym przypadku
funkcja nie ma nazwy.
DO try this at
Dodaliśmy ten kod do strony WWW i przetestowaliśmy. Wydaje się, że działa zgodnie
z tym, czego można by się spodziewać. W przypadku próbki nr 1 wywołanie funkcji
zapisanej w zmiennej addOne powoduje zwrócenie wartości o jeden większej od przekazanej
w wywołaniu. Wygląda na to, że wszystko jest w porządku. W przypadku próbki nr 2
po wyświetleniu strony zostaje pokazany komunikat „Strona została wczytana!”.
Na podstawie tych testów można by wyciągnąć wniosek, że można definiować funkcje
pozbawione nazw i używać ich w miejscach, w których jest oczekiwane wyrażenie.
jesteś tutaj
443
Podsumowanie rozdziału
A co to wszystko
oznacza? Zostań z nami.
Nasze odkrycia dotyczące tych
dziwnych funkcji przedstawimy
w następnym rozdziale…
CELNE SPOSTRZEŻENIA
Q
Przeważająca część tworzonego kodu JavaScript służy
do reagowania na zdarzenia.
Q
Istnieje wiele rodzajów zdarzeń, na które kod może
reagować.
Q
W celu zareagowania na zdarzenie tworzy się funkcję
procedury obsługi zdarzenia, a następnie rejestruje
ją. Przykładowo w celu zarejestrowania procedury
obsługi zdarzeń click przypisuje się nazwę funkcji
właściwości onclick wybranego elementu.
Q
Nie ma obowiązku obsługi jakichkolwiek zdarzeń.
Obsługujemy je tylko wtedy, gdy jesteśmy nimi
zainteresowani.
Q
Procedurami obsługi są zazwyczaj funkcje, gdyż
pozwalają one na przygotowanie kodu, który zostanie
wykonany później (w momencie wygenerowania
zdarzenia).
Q
Q
444
Kod tworzony w celu obsługi zdarzeń różni się od kodu,
który jest wykonywany od początku do końca i którego
realizacja na tym się kończy. Procedury obsługi zdarzeń
mogą być wykonywane w dowolnej chwili i w dowolnej
kolejności, bo działają asynchronicznie.
Zdarzenia, które są związane z elementami DOM (czyli
zdarzenia DOM), powodują przekazanie do procedury
obsługi obiektu zdarzenia.
Rozdział 9.
Q
Obiekt zdarzenia udostępnia właściwości zawierające
dodatkowe informacje na temat konkretnego
zdarzenia, takie jak W\SH (typ zdarzenia, np. ”click”
lub ”load”) lub target (określająca obiekt,
w którym zdarzenie zostało wygenerowane).
Q
W starszych wersjach przeglądarki Internet Explorer
(IE8 i starszych) używany jest inny model zdarzeń. Więcej
informacji na jego temat można znaleźć w dodatku.
Q
Wiele zdarzeń może zachodzić w bardzo krótkich
odstępach czasu. Kiedy pojawia się zbyt wiele zdarzeń
i przeglądarka nie jest w stanie ich obsługiwać
na bieżąco, są zapisywane w kolejce zdarzeń
(w kolejności, w jakiej są generowane), dzięki czemu
przeglądarka będzie mogła je kolejno obsługiwać.
Q
Jeśli procedury obsługi są złożone, będą spowalniać
obsługę zdarzeń zapisywanych w kolejce, gdyż
procedury obsługi zapisanych w niej zdarzeń są
wykonywane jedna po drugiej.
Q
Funkcje setTimeout oraz setInterval służą do
generowania zdarzeń po upływie określonego czasu.
Q
Metoda getElementsByTagName zwraca strukturę
1RGH/LVW zawierającą dowolną liczbę (w tym
żadnego) obiektów elementów. (1RGH/LVW jest
strukturą danych przypominającą tablicę, zatem jej
zawartość można przeglądać w pętli).
Programowanie asynchroniczne
Zupa zdarzeń
load
ruje to
Przeglądarka genezakończy
dy
kie
,
nie
ze
zdar
ny.
wczytywanie stro
click
jest
rzenie
To zda wane, gdy
genero sz (lub
nie.
kliknie esz) na stro
i
n
k
u
st
resize
unload
To zdarzenie jest
generowane, kiedy zam
okno przeglądarki lub ykasz
zdecydujesz się przejś
ć
na inną stronę.
To zdarzenie
jest generowane
w odpowiedzi na
zmianę wielkości
okna przeglądarki.
t
elemen
tronie esz
s
a
n
ż
mo
kając
ciłeś
Umieś ? Jeśli tak, darzenie, kliarzanie.
>
z
<video erować to nający odtw
wygen sk rozpoczy
przyci
pause
mouseout
mouseover
Z kolei to zdarzenie
wygenerujesz, kiedy
przesuniesz wskaźnik
myszy poza obszar
zajmowany przez element.
To zdarzenie możesz
wygenerować,
przesuwając wskaźnik
myszy do obszaru
elementu.
e
To zdarz ane, gdy
w
ro
e
n
ciągać
ge
sz prze
zacznie
element.
z,
nie wygenerujes
Z kolei to zdarze zatrzymujący
k
klikając przycis
odtwarzanie.
To zdarzenie jest
generowane za każdym
razem, gdy naciśniesz
jakiś klawisz.
Wygenerujesz to
zdarzenie, kiedy
przesuniesz wskaźnik
myszy w obszarze
elementu.
rt
dragstanie jest
play
keypress
mousemove
p
dro
e
ie zostani
To zdarzen ane, kiedy
wygenerow rzeciągany
p
upuścisz
t.
en
elem
touchstar
t
To
zdarzen
wygenero ie możesz
w
urządzenia ać na
w ekran d ch wyposażonych
dotkniesz otykowy, kiedy
element. i przytrzymasz
touchend
To zdarzenie jest
generowane, kiedy
skończysz dotykanie ekr
anu.
Opisane zdarzenia load, click, mousemove,
mouseover, mouseout, resize oraz zdarzenia
JHQHURZDQHSU]H]OLF]QLNLF]DVX
WRMHG\QLHGUREQ\XãDPHNFDãRĤFL
]DJDGQLHQLDMDNLPVĈ]GDU]HQLDLLFK
REVãXJD6NRV]WXMWHMS\V]QHM]XS\
SHãQHM]GDU]Hę]NWyU\PLEčG]LHV]VLč
QLHMHGQRNURWQLHVSRW\NDãL]NWyU\FK
EčG]LHV]NRU]\VWDãWZRU]ĈFVZRMH
DSOLNDFMHLQWHUQHWRZH
jesteś tutaj
445
Rozwiązanie ćwiczenia
Zaostrz ołówek
Rozwiązanie
Wybierz dwa spośród opisanych powyżej zdarzeń. Gdyby przeglądarka mogła
informować Twój kod o ich zajściu, w jaki fajny lub interesujący sposób mógłbyś to
wykorzystać?
Rozważmy zdarzenie informujące o przesłaniu formularza. Jeśli zostaniemy
poinformowani o tym zdarzeniu, moglibyśmy pobrać wszystkie dane wpisane przez
użytkownika i sprawdzić ich poprawność (np. czy w polu służącym do podania
numeru telefonu użytkownik wpisał coś, co wygląda na taki numer, bądź czy
zostały wypełnione wszystkie wymagane pola). Po wykonaniu takiej weryfikacji
moglibyśmy przesłać dane na serwer.
A teraz weźmy zdarzenie informujące o ruchach wskaźnika myszy. Gdybyśmy byli
informowani o każdym przesunięciu wskaźnika myszy, moglibyśmy napisać aplikację
do rysowania działającą bezpośrednio w przeglądarce.
Jeśli będziemy informowani o przewinięciu strony w dół, możemy tworzyć
interesujące rozwiązania, takie jak wyświetlanie kolejnych obrazków wraz
z wyświetlaniem kolejnych partii strony.
446
Rozdział 9.
Programowanie asynchroniczne
BĄDŹ przeglądarką. Rozwiązanie
Poniżej zamieściliśmy kod gry. Twoim zadaniem
jest wcielić się w rolę przeglądarki i określić,
co musisz zrobić po każdym ze
zdarzeń. Poniżej przedstawiliśmy
nasze rozwiązanie.
window.onload = init;
function init() {
var image = document.getElementById(”zero”);
image.onclick = showAnswer;
}
function showAnswer() {
var image = document.getElementById(”zero”);
image.src = ”zero.jpg”;
}
Podczas
wczytywania
strony…
Gdy zostanie
wygenerowane
zdarzenie load…
Gdy zostanie
wygenerowane
zdarzenie click
obrazka…
Najpierw definiujemy funkcje init i showAnswer.
Ustawiamy procedurę obsługi zdarzeń load — będzie nią
funkcja init.
Wywoływana jest funkcja obsługująca zdarzenia load — init.
Pobieramy obrazek o identyfikatorze „zero”.
Określamy, że procedurą obsługi zdarzeń click będzie
funkcja showAnswer.
Wywoływana jest funkcja showAnswer.
Pobieramy obrazek o identyfikatorze „zero”.
Atrybutowi src obrazka przypisujemy wartość „zero.jpg”.
jesteś tutaj
447
Rozwiązanie zadania
:
?
KTO CO ROBI?
7
7
7
ROZWIĄZANIE
Przekonałeś się już, że obiekt zdarzeń (w przypadku zdarzeń związanych z elementami DOM)
dysponuje właściwościami zapewniającymi dostęp do wielu informacji o zdarzeniu. Poniżej
przedstawiamy kilka innych właściwości tego obiektu. Dopasuj każdą z tych właściwości do
informacji, które można z niej odczytać.
target
Chcesz wiedzieć, jak daleko od górnej krawędzi
okna przeglądarki kliknął użytkownik? Jeśli tak,
służę pomocą.
type
Przechowuję obiekt, w którym zostało wygenerowane
zdarzenie. Mogę zawierać obiekty różnych typów,
jednak najczęściej są to obiekty elementów.
timeStamp
Używasz urządzenia z ekranem dotykowym? Jeśli
tak, pozwolę Ci określić, ile palców dotyka ekranu.
keyCode
clientX
Chcesz wiedzieć, kiedy zdarzenie zostało
Uwaga: właściwość timeStamp
wygenerowane? Jeśli tak, jestem tą
nie jest dostępna w starszych
wersjach Internet Explorera.
właściwością, której szukasz.
clientY
Chcesz wiedzieć, jak daleko od lewej krawędzi
okna przeglądarki kliknął użytkownik?
Chętnie Ci pomogę.
touches
448
Jestem łańcuchem znaków, takim jak ”click” lub
”load”, który informuje o tym, co właśnie się stało.
Rozdział 9.
Powiem Ci, jaki klawisz nacisnął użytkownik.
Programowanie asynchroniczne
$KRMNDPUDFLH:7ZRMHUĊFHWUDILáDPDSD]GURJąGRVNDUEX3RWU]HEXMHP\7ZRMHM
SRPRF\ZRNUHĞOHQLXMHJRZVSyáU]ĊGQ\FK:W\PFHOXPXVLV]QDSLVDüNRGNWyU\EĊG]LH
Z\ĞZLHWODáZVSyáU]ĊGQHZVNDĨQLNDP\V]\SU]HVXZDQHJRZREV]DU]HPDS\)UDJPHQW
WHJRNRGXSU]HGVWDZLOLĞP\QDQDVWĊSQHMVWURQLHMHGQDNFKFHP\ĪHE\ĞJRGRNRĔF]\á
Ćwiczenie
Rozwiązanie
$QLHFKPQLH3RQLĪHMSU]HGVWDZLOLĞP\NRG:REHFQHMSRVWDFL]DZLHUDRQPDSĊ
XPLHV]F]RQąQDVWURQLHRUD]DNDSLWWHNVWXXPRĪOLZLDMąF\Z\ĞZLHWODQLHRGF]\WDQ\FK
ZVSyáU]ĊGQ\FK7ZRLP]DGDQLHPMHVWXUXFKRPLHQLHFDáHJRNRGX]ZLą]DQHJR]REVáXJą
]GDU]HĔ3RZRG]HQLD:NRĔFXQLHFKFLHOLE\ĞP\E\ĞZQDMEOLĪV]\PF]DVLHWUDILáGR
VNU]\QL'DY\¶HJR-RQHVD«
<!doctype html>
<html lang=”en”>
<head>
<meta charset=”utf-8”>
<title>Skarb piratöw</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 = ”WspöïrzÚdne na mapie: ” + x + ”, ” + y;
}
</script>
</head>
<body>
<img id=”map” src=”map.jpg”>
<p id=”coords”>Przesuñ wskaěnik myszy na mapÚ, by odczytaÊ wspöïrzÚdne...</p>
</body>
</html>
Kiedy umieściliśmy wskaźnik
myszy na samym środku X,
uzyskaliśmy te współrzędne.
197, 183
jesteś tutaj
449
Rozwiązanie ćwiczenia
Przyjrzyj się przedstawionemu poniżej fragmentowi kodu i pomyśl, czy na jego
podstawie będziesz potrafił określić, jak działa funkcja VHW,QWHUYDO. Jest ona
podobna do funkcji VHW7LPHRXW, lecz działa nieco inaczej. Poniżej zamieściliśmy
nasze rozwiązanie.
Ćwiczenie
Rozwiązanie
To jest kod.
function ticker() {
if (tick) {
console.log(”Tik”);
tick = false;
} else {
console.log(”Tak”);
tick = true;
}
}
setInterval(ticker, 1000);
.RQVROD-DYD6FULSW
7LN
7DN
7LN
7DN
7LN
7DN
7LN
7DN
A to są wyniki.
Tu zapisz swoją analizę.
Podobnie jak setTimeout, także funkcja setInterval wymaga przekazania dwóch
argumentów: pierwszym z nich jest funkcja, która będzie używana do obsługi zdarzeń,
a drugim okres czasu odmierzany przez licznik.
Jednak w odróżnieniu od setTimeout, funkcja setInterval będzie uruchamiać licznik
czasu nie raz, lecz wiele razy. W rzeczywistości będzie to robić w nieskończoność!
(Choć okazuje się, że możemy jej kazać, by przestała). W tym przykładzie funkcja
setInterval będzie co 1000 milisekund (czyli co sekundę) wywoływać funkcję ticker.
Funkcja ticker sprawdza wartość zmiennej tick i na jej podstawie określa, czy należy
wyświetlić w oknie konsoli łańcuch „Tik”, czy „Tak”.
A zatem funkcja setInterval generuje zdarzenie po upływie określonego okresu czasu,
a następnie ponownie uruchamia licznik.
var t = setInterval(ticker, 1000);
clearInterval(t);
450
Rozdział 9.
licznika,
Aby zatrzymać działanie tego y przez
należy zapisać wynik zwrócon w zmiennej…
wywołanie funkcji setInterval
…a następnie, kiedy będziemy chcieli
zakończyć działanie licznika, przekazać go
w wywołaniu funkcji clearInterval.
Programowanie asynchroniczne
Ćwiczenie
Rozwiązanie
Przerób kod aplikacji w taki sposób, by normalna wersja obrazu była wyświetlana po
umieszczeniu w jego obszarze wskaźnika myszy oraz by po usunięciu wskaźnika z obszaru
obrazka ponownie była wyświetlana jego nieostra wersja. Nie zapomnij przetestować kodu.
Poniżej przedstawiliśmy nasze rozwiązanie.
window.onload = function() {
var images = document.getElementsByTagName(”img”); Na początku usunęliśmy instrukcję,
for (var i = 0; i < images.length; i++) {
która zapisywała we właściwości onclick
procedurę obsługi zdarzeń.
images[i].onclick = showAnswer;
images[i].onmouseover = showAnswer;
images[i].onmouseout = reblur;
}
};
function showAnswer(eventObj) {
Następnie określiliśmy procedurę
obsługi zdarzeń mouseover,
przypisując właściwości
onmouseover funkcję showAnswer.
A tu informujemy, że do obsługi
zdarzeń mouseout ma być używana
funkcja reblur (wcześniej była ona
wywoływana przez licznik czasu).
W tym celu zapisujemy tę funkcję
we właściwości onmouseout obrazka.
var image = eventObj.target;
var name = image.id;
name = name + ”.jpg”;
image.src = name;
setTimeout(reblur, 2000, image);
Do wyświetlania nieostrej wersji obrazka nie
będziemy już używali licznika czasu — zrobimy to
przy użyciu zdarzenia informującego o usunięciu
wskaźnika myszy z obszaru elementu.
}
function reblur(eventObj) {
var image = eventObj.target;
var name = image.id;
name = name + ”blur.jpg”;
Teraz funkcja reblur służy do obsługi zdarzeń
mouseout, aby zatem wyświetlić nieostrą wersję
odpowiedniego obrazka, musimy dysponować
obiektem zdarzenia. Do pobrania obiektu obrazka
wykorzystamy właściwość target, dokładnie tak
samo jak w funkcji showAnswer. Z wyjątkiem
instrukcji pobierającej obiekt obrazka dalszy kod
funkcji jest taki sam jak wcześniej.
image.src = name;
}
jesteś tutaj
451
Rozwiązanie ćwiczenia
Ćwiczenie
Rozwiązanie
Po zakończeniu gry w obrazki Judyta napisała prostą
aplikację z myślą o zbliżającym się, cotygodniowym
spotkaniu zespołu. Zaplanowała mały konkurs. Wygra go
osoba, która pierwsza opisze działanie jej kodu. Kto wygra
ten konkurs? Kuba, Józek, Franek? A może Ty?
<!doctype html>
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Nie zmieniaj mojej wielkoĂci, mam ïaskotki!</title>
<script>
function resize() {
var element = document.getElementById(“display”);
element.innerHTML = element.innerHTML + “ To ïaskocze!”;
}
</script>
</head>
Procedura obsługi zdarzeń nosi nazwę resize. Jej działanie
sprowadza się do dodania fragmentu tekstu do aktualnej
zawartości akapitu o identyfikatorze „display”.
<body>
<p id=”display”>
Cokolwiek robisz, nie zmieniaj wielkoĂci okna! Ostrzegam!
</p>
Zdarzenie, które nas interesuje, nosi nazwę resize;
a zatem określamy funkcję, która będzie je obsługiwać
window.onresize = resize; (o nazwie resize), zapisując ją we właściwości onresize
obiektu window.
<script>
</script>
</body>
</html>
Procedurę obsługi zdarzeń resize określamy
w skrypcie umieszczonym na samym końcu strony.
Pamiętaj, że zostanie on wykonany dopiero wtedy,
kiedy strona zostanie w całości wczytana, mamy
więc pewność, że procedura obsługi zdarzeń nie
zostanie określona zbyt szybko.
Kiedy zmieniasz wielkość okna przeglądarki,
wywoływana jest funkcja resize, która
aktualizuje stronę, dodając do zawartości akapitu
o identyfikatorze „display” łańcuch znaków
(a konkretnie „ To łaskocze!”).
452
Rozdział 9.
10. Funkcje pierwszej klasy
Wyzwolone funkcje
Teraz, kiedy
już tak dobrze znamy
funkcję, jesteśmy na prostej
drodze… Te dni w country
club…
Myślę, że te funkcje
zrobiły coś z waszymi
mózgami. Znajdujemy się
na środku pustego pola,
a ja jestem waszym
sąsiadem przebranym
za kelnera.
Poznaj funkcje, a potem baw się na całego. Każda sztuka, rzemiosło czy też
dyscyplina sportowa mają swoją kluczową cechę, która odróżnia graczy przeciętnych
od wirtuozów. W przypadku języka JavaScript jest to prawdziwe i dokładne zrozumienie
funkcji. W języku JavaScript funkcje mają kluczowe znaczenie, a wiele technik służących
do projektowania i organizacji kodu bazuje na zaawansowanej znajomości funkcji
i umiejętności korzystania z nich. Droga prowadząca do poznania funkcji na takim poziomie
jest interesująca i niejednokrotnie wymagająca dla mózgu, zatem dobrze się do niej
przygotuj. W tym rozdziale będziesz się czuł jak dziecko oprowadzane przez pana Willy’ego
Wonkę po fabryce czekolady — będziesz w nim kontynuował poznawanie funkcji w języku
JavaScript, a przy okazji zobaczysz dziwaczne, wariackie i cudowne rzeczy.
Choć nie będziesz musiał śpiewać
żadnych piosenek.
to jest nowy rozdział
453
Wprowadzenie do wyrażeń funkcyjnych
Tajemnicze, podwójne życie słowa kluczowego function
Do tej pory deklarowaliśmy funkcje w następujący sposób:
Standardowa deklaracja funkcji,
składająca się ze słowa
kluczowego function, nazwy
funkcji, parametru oraz bloku
kodu.
function quack(num) {
for (var i = 0; i < num; i++) {
console.log(”Kwak!”);
}
}
quack(3);
Funkcję możemy wywołać, podając jej nazwę, a zaraz za nią
pisząc parę nawiasów, opcjonalnie zawierających wszelkie
niezbędne argumenty.
Powyższy kod nie zawiera niczego zaskakującego, przeanalizujmy jednak
używaną terminologię. Formalnie rzecz biorąc, pierwsza instrukcja powyższego
fragmentu kodu jest deklaracją funkcji, tworzącą funkcję o podanej nazwie —
w tym przypadku jest to quack — której następnie będzie można używać do
odwoływania się do funkcji oraz jej wywoływania.
Na razie wszystko idzie dobrze, jednak nasza historia staje się nieco bardziej
tajemnicza, gdyż — jak mogłeś się przekonać na końcu poprzedniego
rozdziału — słowa kluczowego function można także używać w inny sposób.
var fly = function(num) {
To już nie wygląda standardowo: funkcja nie
ma nazwy, a poza tym została umieszczona
z prawej strony operatora przypisania, co
oznaczałoby, że jest zapisywana w zmiennej.
for (var i = 0; i < num; i++) {
console.log(”Latam!”);
}
};
Także taką funkcję możemy wywołać,
choć w tym przypadku musimy użyć
zmiennej fly.
fly(3);
Kiedy użyjemy funkcji w taki sposób — czyli w jakiejś instrukcji, np.
w instrukcji przypisania — nazywamy ją wyrażeniem funkcyjnym (ang.
function expression). Zwróć uwagę, że — w odróżnieniu od deklaracji
funkcji — taka funkcja nie ma nazwy. Co więcej, takie wyrażenie
zwraca wartość, która jest zapisywana w zmiennej; w naszym
przypadku jest to zmienna fly. A co to za wartość? No cóż, najpierw
zapisujemy ją w zmiennej fly, a następnie wywołujemy… Zatem
musi to być referencja do funkcji.
454
Rozdział 10.
.RGRZDQLHQDSRZDĝQLH
Referencja do funkcji jest dokładnie
tym, czego można się spodziewać:
referencją, która odwołuje się
do funkcji. Takiej referencji można użyć,
aby wywołać funkcję, lub — o czym się
niebawem przekonasz — można ją zapisywać
w zmiennych albo obiektach, przekazywać
do innych funkcji czy zwracać jako wynik
ich wykonania (tak samo jak referencje do
obiektów).
quack
function quack(num) {
for (var i = 0; i < num; i++) {
console.log(”Kwak!”);
}
}
Funkcje pierwszej klasy
Deklaracje funkcji a wyrażenia funkcyjne
Niezależnie od tego, czy użyjesz deklaracji funkcji, czy też wyrażenia funkcyjnego, uzyskasz dokładnie
to samo: funkcję. Jaka zatem jest różnica pomiędzy nimi? Czy deklaracja funkcji jest jedynie bardziej
wygodna, czy też w wyrażeniach funkcyjnych jest coś, co sprawia, że są użyteczne? A może są to jedynie
dwa różne sposoby, by wyrazić dokładnie to samo?
Na pierwszy rzut oka można by powiedzieć, że pomiędzy deklaracjami funkcji oraz wyrażeniami
funkcyjnymi nie ma zbyt dużych różnic. Jednak w rzeczywistości istnieje pomiędzy nimi jedna,
fundamentalna różnica. Aby ją zrozumieć, należy przeanalizować, jak przeglądarka traktuje nasz kod
w trakcie wykonywania. A zatem przyjrzyjmy się przeglądarce, kiedy zajmuje się analizą i wykonaniem
kodu strony.
No super… Na tej stronie
jest jakiś kod, który muszę
przeanalizować i wykonać.
Przeglądarka
Pierwszą rzeczą,
jaką zawsze robię, jest
przeglądnięcie kodu
w poszukiwaniu deklaracji
funkcji.
Teraz odnaleźliśmy
wyrażenie funkcyjne
umieszczone
w instrukcji
przypisania. To także
nie jest deklaracja…
Idziemy dalej.
No i w końcu mamy
deklarację funkcji.
Musimy sobie jakoś
z nią poradzić.
Zajmiemy się tym na
następnej stronie…
Kiedy uporamy się już z deklaracją
funkcji, kolejny fragment kodu także
będziemy mogli zignorować, gdyż nie
zawiera żadnych deklaracji funkcji.
Ta deklaracja zmiennej nie jest
deklaracją funkcji, a zatem na razie
ją ignorujemy i przechodzimy dalej.
var migrating = true;
var fly = function(num) {
for (var i = 0; i < num; i++) {
console.log(”Latam!”);
}
};
function quack(num) {
for (var i = 0; i < num; i++) {
console.log(”Kwak!”);
}
}
if (migrating) {
quack(4);
fly(4);
}
jesteś tutaj
455
Jak są przetwarzane deklaracje funkcji
Przetwarzanie deklaracji funkcji
var migratin
g = true;
Kiedy przeglądarka przetwarza stronę — jeszcze zanim wykona jakikolwiek
kod — poszukuje w niej deklaracji funkcji. Kiedy uda się jej odnaleźć taką
deklarację, tworzy funkcję, a uzyskaną referencję zapisuje w zmiennej, której
nazwa odpowiada nazwie funkcji. Cały ten proces pokazaliśmy poniżej.
var fly = fu
nction(num)
{
for (var i =
0; i < num;
i++) {
console.log(
”Latam!”);
}
};
Tutaj mamy deklarację
funkcji umieszczoną
w kodzie. Zobaczmy, co
przeglądarka z nią zrobi…
No dobra, mam tu
deklarację funkcji, zajmę się
nią, zanim zabiorę się za
pozostałe rzeczy…
function quac
k(num) {
for (var i =
0; i < num;
i++) {
console.log(
”Kwak!”);
}
}
if (migrating
) {
quack(4);
fly(4);
}
Mam zamiar przygotować sobie
tę funkcję i odłożyć gdzieś na bok, ale
tak, żebym mogła ją pobrać później,
kiedy nadejdzie czas jej wywołania.
function quack(num) {
for (var i = 0; i < num; i++) {
console.log(”Kwak!”);
function quack(num) {
for (var i = 0; i < num; i++) {
console.log(”Kwak!”);
}
}
}
A funkcja ma nazwę
quack, zatem przygotuję
także zmienną o nazwie quack,
w której zapiszę referencję do
funkcji.
456
Rozdział 10.
}
quack
To jest nasza funkcja,
przygotowana do
późniejszego użycia,
np. kiedy zostanie
wywołana.
Funkcje pierwszej klasy
I co dalej? Przeglądarka wykonuje kod
Przeglądarka zadbała już o wszystkie deklaracje funkcji, wraca więc na sam początek kodu
i zaczyna go wykonywać liniowo od początku do końca. Sprawdźmy, jak wygląda sytuacja
w przeglądarce na tym etapie działania.
ue
r
t
migrating
Wygląda
na to, że mam
utworzyć zmienną
o nazwie migrating
i początkowej wartości
wynoszącej true.
Zaczynamy od prostego
przypisania określającego
wartość zmiennej. Już
wcześniej pokazywaliśmy,
jak to zrobić.
var migrating = true;
var fly = function(num) {
for (var i = 0; i < num; i++) {
console.log(”Latam!”);
}
Widzę, że
w następnej instrukcji
też pojawia się nowa zmienna,
o nazwie fly. Utworzę ją
zatem…
fly
};
function quack(num) {
Wygląda
na to, że
będziemy
potrzebowali
zmiennej
o nazwie fly.
for (var i = 0; i < num; i++) {
console.log(”Kwak!”);
}
}
if (migrating) {
quack(4);
fly(4);
}
A po prawej stronie
jest zapisane wyrażenie funkcyjne.
Przygotuję sobie tę funkcję tak, bym
mogła pobrać ją później, kiedy trzeba będzie
ją wywołać.
function(num) {
for (var i = 0; i < num; i++) {
console.log(”Latam!”);
}
}
A ponieważ to jest wyrażenie
funkcyjne, zatem muszę utworzyć
referencję do tej nowej funkcji.
Teraz zostaje już
tylko zapisać tę
referencję w zmiennej
fly.
ie
dobn ,
j, po
j
Tuta cześnie my
jak wgotowuje
przy cję do
funk złego
przysia.
użyc
fly
Teraz bierzemy
referencję do funkcji
i zapisujemy ją
w zmiennej
o nazwie fly.
jesteś tutaj
457
Wywoływanie funkcji
var migrating =
true;
Idziemy dalej… Instrukcja warunkowa
var fly = function
(num) {
for (var i = 0;
i < num; i++) {
console.log(”Latam!” var migratin
g = true;
);
}
Tu już byliśmy,
Kiedy już przeglądarka zadbała o zmienną fly, może przejść
idziemy dalej.
dalej. Kolejną instrukcją jest deklaracja funkcji quack, którą
przeglądarka zajęła się podczas pierwszego etapu przetwarzania
kodu. Dlatego też teraz można tę deklarację pominąć i przejść
do instrukcji warunkowej umieszczonej poniżej. Zobaczmy, jak
to wygląda…
};
var fly = function
(num) {
for (var i = 0;
i < num; i++) {
function quack(nu
m) {
console.log(”Latam!”
);
for (var i = 0;
}
i < num; i++)
{
console.log(”Kwak!”)};
;
}
}
function quack(nu
m) {
for (var i = 0;
i < num; i++) {
console.log(”Kwak!”)
;
}
if (migrating) {
quack(4);
Zobaczmy, zmienna
migrating ma wartość true,
zatem muszę wykonać kod umieszczony
w instrukcji if. Z tego, co widzę, jest
tam umieszczone wywołanie funkcji
quack. Wiem, że to jest wywołanie, gdyż
składa się z nazwy funkcji, quack, oraz
pary nawiasów.
quack(4);
fly(4);
}
}
if (migrating) {
quack(4);
fly(4);
}
Pamiętaj, że zmienna quack
jest referencją do funkcji,
która już wcześniej została
przygotowana.
z
na prze
.
utworzo nazwie quack
ja
c
k
n
Oto fu ję funkcji o
deklarac
quack
function quack(num) {
for (var i = 0; i < num; i++) {
W wywołaniu funkcji jest
argument, zatem przekażę
go do funkcji…
console.log(”Kwak!”);
}
}
4
W celu wywołania
przekazujemy jako funkcji
kopię wartości arg parametr
umentu…
function quack(num) {
for (var i = 0; i < num; i++) {
console.log(”Kwak!”);
}
}
…i wykonam kod umieszczony w jej ciele,
co spowoduje czterokrotne wyświetlenie
napisu „Kwak!” w oknie konsoli.
458
Rozdział 10.
stępnie
…a na ujemy ciało
n
o
k
y
w
funkcji.
Funkcje pierwszej klasy
var migrating =
true;
Zbliżamy się do końca…
var fly = functi
on(num) {
for (var i = 0;
i < num
Pozostało już tylko wywołanie funkcji fly, utworzonej z wykorzystaniem
wyrażenia funkcyjnego. Przekonajmy się, jak przeglądarka sobie z tym poradzi.
; i++) {
console.log(”Latam
!”);
}
};
Hej, popatrz… Następne
wywołanie funkcji. Wiem, że to
wywołanie, gdyż użyto w nim nazwy
zmiennej, fly, oraz pary nawiasów.
function quack(num
) {
for (var i = 0;
i <
fly(4);
}
num; i++) {
console.log(”Kwak!
”);
}
if (migrating) {
quack(4);
fly(4);
}
Pamiętaj, że zmienna
fly zawiera referencję do funkcji,
która została przygotowana już
wcześniej.
To jest funkcja,
do której
odwołuje się
zmienna fly.
fly
function(num) {
for (var i = 0; i < num; i++) {
console.log(”Latam!”);
}
}
W wywołaniu funkcji jest
argument, zatem przekażę
go do funkcji…
4
Aby wywołać funkcj
przekazujemy jako ę,
kopię wartości arg parametr
umentu…
function(num) {
for (var i = 0; i < num; i++) {
console.log(”Latam!”);
}
…a następnie wykonujemy
ciało funkcji.
}
Następnie wykonam ciało funkcji, co
spowoduje czterokrotne wyświetlenie
słowa „Latam!” w oknie konsoli.
jesteś tutaj
459
Pytania dotyczące wyrażeń funkcyjnych
Zaostrz ołówek
Jakie wnioski dotyczące deklaracji funkcji oraz wyrażeń funkcyjnych możesz
wyciągnąć na podstawie sposobu, w jaki przeglądarka traktuje kod funkcji quack
i fly? Zaznacz każde z poniższych stwierdzeń, które jest słuszne. Zanim przejdziesz
dalej, sprawdź odpowiedzi, które podaliśmy pod koniec tego rozdziału.
‰ Deklaracje funkcji są przetwarzane przed
przetworzeniem pozostałych fragmentów kodu.
‰ Deklaracje funkcji są instrukcjami, natomiast
wyrażenia funkcyjne są używane w instrukcjach.
‰ Wyrażenia funkcyjne są przetwarzane później,
wraz z pozostałymi fragmentami kodu.
‰ Proces wywoływania funkcji utworzonej przy
użyciu deklaracji jest dokładnie taki sam jak
funkcji utworzonej za pomocą wyrażenia
funkcyjnego.
‰ Deklaracje funkcji nie zwracają referencji do
utworzonej funkcji; zamiast tego tworzą zmienną
o nazwie odpowiadającej nazwie funkcji
i zapisują w niej tę funkcję.
‰ Deklaracje są starym i sprawdzonym sposobem
tworzenia funkcji.
‰ Wyrażenie funkcyjne zwraca referencję do nowej
funkcji, utworzonej przez to wyrażenie.
‰ Zawsze staramy się używać deklaracji funkcji,
gdyż są one przetwarzane wcześniej.
‰ Referencje do funkcji można zapisywać
i przechowywać w zmiennych.
P: Widziałem wyrażenia, takie
jak 3+4 lub Math.random()*6, ale
w jaki sposób funkcja może być
wyrażeniem?
O: Wyrażenie to cokolwiek, co po
przetworzeniu zwraca wartość. Wyrażenie
3+4 daje 7, Math.random()*6 zwraca
wartość losową, a wyrażenie funkcyjne
zwraca referencję do funkcji.
P: Ale deklaracja funkcji nie jest
wyrażeniem?
O: Nie, deklaracja funkcji jest instrukcją.
Możesz ją sobie wyobrazić jako ukrytą
instrukcję przypisania, która tworzy
zmienną i zapisuje w niej referencję do
funkcji. Wyrażenie funkcyjne nigdzie nie
zapisuje referencji do funkcji — używając
go, musisz to zrobić samodzielnie.
460
Rozdział 10.
Nie istnieją
głupie pytania
P: Co mi daje posiadanie zmiennej
zawierającej referencję do funkcji?
O: Cóż, przede wszystkim możesz jej
użyć do wywołania tej funkcji:
myFunctionReference();
Możesz też przekazać tę referencję do
innej funkcji lub zwrócić referencję jako
wynik działania funkcji. Jednak nieco
wyprzedzamy fakty. Wrócimy do tego
już niebawem.
P: Czy wyrażenia funkcyjne
pojawiają się wyłącznie po prawej
stronie operatora przypisania?
O: Nie tylko. Wyrażenia funkcyjne
mogą się pojawiać w wielu różnych
miejscach, dokładnie tak samo jak
wszelkie inne wyrażenia. Uważaj, bo
to naprawdę świetne pytanie i już za
chwilkę wrócimy do tego zagadnienia.
P: No dobrze, zmienna może
zawierać referencję do funkcji?
Ale do czego taka zmienna się
odwołuje? Do kodu umieszczonego
w ciele funkcji?
O: To dobry sposób wyobrażania sobie
funkcji. Jednak lepiej wyobrażać je sobie
jako trochę skrystalizowaną wersję
kodu, gotową do natychmiastowego
wykonania. Niedługo przekonasz się, że
te skrystalizowane funkcje mają w sobie
coś więcej niż sam kod umieszczony
w ich ciele.
Funkcje pierwszej klasy
Funkcje tworzone przy użyciu
deklaracji oraz wyrażeń funkcyjnych są
wywoływane w dokładnie taki sam sposób.
Jaka jest zatem różnica pomiędzy deklaracją
i wyrażeniem? Mam wrażenie, jakby mi umykało
coś bardzo subtelnego.
Ta różnica faktycznie jest subtelna. Przede
wszystkim masz rację — niezależnie od tego, czy użyjemy
deklaracji, czy wyrażenia funkcyjnego, i tak uzyskamy
funkcję. Jednak pomiędzy nimi są pewne istotne różnice.
Przede wszystkim w przypadku deklaracji funkcja jest
tworzona przed przetworzeniem pozostałych fragmentów
kodu. W przypadku zastosowania wyrażenia funkcyjnego
funkcja jest tworzona w trakcie wykonywania kodu.
Kolejna różnica pomiędzy deklaracjami i wyrażeniami
funkcyjnymi jest związana z nazwami. Kiedy stosowana
jest deklaracja, nazwa funkcji jest używana do utworzenia
zmiennej, służącej do odwoływania się do funkcji. Z kolei
w przypadku stosowania wyrażeń funkcyjnych nazwy
zazwyczaj nie podajemy i w efekcie jawnie przypisujemy
funkcję do zmiennej w kodzie bądź też używamy wyrażenia
funkcyjnego w inny sposób.
y
sposob
Te inne awimy dalej
przedst rozdziale.
w tym
A teraz zapamiętaj dobrze te różnice i schowaj w jakimś
zakamarku swojego mózgu, gdyż już niedługo będą Ci
potrzebne. Na razie jednak wystarczy, żebyś zapamiętał,
w jaki sposób są przetwarzane deklaracje funkcji
i wyrażenia funkcyjne oraz jak są tworzone nazwy funkcji.
jesteś tutaj
461
Ćwiczenia z deklaracji funkcji i wyrażeń funkcyjnych
BĄDŹ przeglądarką
Poniżej znajdziesz kod JavaScript. Twoim zadaniem jest wcielić się w rolę
przeglądarki, która go wykonuje. W ramce widocznej z prawej strony
zapisz wszystkie tworzone funkcje. Pamiętaj, by przeanalizować kod dwa
razy: za pierwszym razem masz przetworzyć tylko deklaracje
funkcji, natomiast za drugim masz obsłużyć wyrażenia
funkcyjne.
var midi = true;
Zapisz w odpowiedniej kolejności nazwy
tworzonych funkcji. Jeśli funkcja została
o,
utworzona przy użyciu wyrażenia funkcyjneg
ano.
zapis
ją
której
w
nej,
zmien
nazwę
uj
zanot
Pierwszą funkcję zapisaliśmy za Ciebie.
var type = ”piano”;
var midiInterface;
function play(sequence) {
// tu jest jakiĂ kod
}
var pause = function() {
stop();
}
function stop() {
// tu jest jakiĂ kod
}
function createMidi() {
// tu jest jakiĂ kod
}
if (midi) {
midiInterface = function(type) {
// tu jest jakiĂ kod
};
}
462
Rozdział 10.
play
Funkcje pierwszej klasy
O tym, dlaczego funkcje są także wartościami
Oczywiście, wszyscy uważamy, że funkcje to coś, co się wywołuje, lecz można je także traktować
jak wartości. Ta wartość jest w rzeczywistości referencją do funkcji, a jak już widziałeś, taką
referencję uzyskujemy zawsze, niezależnie od tego, czy zdefiniujemy funkcję przy użyciu
deklaracji, czy też wyrażenia funkcyjnego.
function(num) {
...
}
fly
Zmienna może zawierać
referencję do funkcji.
A to jest
.
faktyczna funkcja
Jedną z najprostszych operacji, jakie możemy wykonywać na funkcjach,
jest zapisywanie ich w zmiennych. A tak można to zrobić.
function quack(num) {
for (var i = 0; i < num; i++) {
console.log(”Kwak!”);
}
}
var fly = function(num) {
for (var i = 0; i < num; i++) {
console.log(”Latam!”);
}
};
Deklaracja funkcji zadba o przypisanie
referencji do zmiennej o nazwie
odpowiadającej nazwie funkcji,
w naszym przypadku jest to quack.
Kiedy stosujesz wyrażenie funkcyjne, musisz sam
zadbać o zapisanie uzyskanej referencji w zmiennej.
W tym przypadku referencję zapisuje się w zmiennej fly.
Kiedy zapiszesz wartość zmiennej fly
w zmiennej superFly, superFly będzie
zawierać referencję do funkcji; a zatem
dodając do niej parę nawiasów i argument,
możemy ją wywołać!
var superFly = fly;
superFly(2);
var superQuack = quack;
superQuack(3);
cje. Pamiętasz
nasze dwie funk
To ponownie są cja quack jest definiowana przy nia
zapewne, że funk, a funkcja fly za pomocą wyraże encje
użyciu deklaracji u przypadkach uzyskujemy refer
ob
funkcyjnego. W są zapisywane, odpowiednio,
do funkcji, które k oraz fly.
ac
w zmiennych qu
I choć zmienna quack została utworzona przy
użyciu deklaracji funkcji, to zapisana w niej
wartość także jest referencją do funkcji,
a zatem możemy ją zapisać w zmiennej
superQuack i wywołać.
Innymi słowy, referencje zawsze będą
referencjami, niezależnie od sposobu,
w jaki zostaną utworzone (przy użyciu
deklaracji czy też wyrażenia funkcyjnego).
Konsola JavaScript
Latam!
Latam!
Kwak!
Kwak!
Kwak!
jesteś tutaj
463
Ćwiczenie z traktowania funkcji jako wartości
Zaostrz ołówek
Aby dobrze zrozumieć pomysł traktowania funkcji jako wartości, zagrajmy
w pewną dobrze znaną grę losową. Konkretnie rzecz biorąc, spróbujmy zagrać
w trzy orzeszki. Uda Ci się wygrać, czy przegrasz? Spróbuj, aby się przekonać.
var winner = function() { alert(”Farciarz!”) };
var loser = function() { alert(”Leszczu!”) };
// W ramach rozgrzewki przetestujmy funkcjÚ.
winner();
Pamiętaj, że te zmienne zawierają
referencje do funkcji winner i loser.
Referencje te możemy dowolnie
zapisywać w innych zmiennych,
podobnie jak wszelkie inne wartości.
// Dla praktyki okreĂlmy wartoĂci zmiennych.
var a = winner;
var b = loser;
var c = loser;
Pamiętaj, że w dowolnym momencie
możemy użyć takiej referencji, by
wywołać funkcję.
a();
b();
// A teraz spröbuj swojego szczÚĂcia w grze.
c = a;
a = b;
b = c;
c = a;
a = c;
a = b;
Wykonaj ten kod
(ręcznie!) i przekonaj
się, czy będziesz
miał szczęście,
czy przegrasz.
b = c;
a();
Zacznij wyobrażać sobie funkcje jako wartości, podobne do liczb, łańcuchów
znaków, wartości logicznych oraz obiektów. Jedyną różnicą pomiędzy tak
pojmowaną funkcją a innymi wartościami jest to, że funkcję można wywołać.
464
Rozdział 10.
Funkcje pierwszej klasy
Funkcje bez tajemnic
Temat dzisiejszego wywiadu brzmi:
Zrozumienie funkcji
Rusz głową: Funkcjo, tak się cieszymy, że znowu udało
się nam zaprosić Cię do programu. Jesteś naprawdę bardzo
tajemnicza i widzowie chcieliby dowiedzieć się o Tobie
czegoś więcej.
Funkcja: To prawda, mam głęboką osobowość.
Rusz głową: Zacznę od tego pomysłu, że można Cię
utworzyć przy użyciu deklaracji lub wyrażenia funkcyjnego.
Dlaczego istnieją dwa sposoby, by Cię zdefiniować?
Czy jeden nie wystarczyłby?
Funkcja: Pamiętaj, że te dwa sposoby definiowania funkcji
służą do dwóch, nieco odmiennych rzeczy.
Rusz głową: Ale efekt jest ten sam — utworzenie funkcji,
prawda?
Funkcja: Owszem, ale trzeba na to spojrzeć trochę
inaczej. Deklaracja funkcji zakulisowo wykonuje także
pewne dodatkowe operacje: tworzy funkcję, lecz oprócz
niej buduje także zmienną i zapisuje w niej referencję do
funkcji. Wyrażenie funkcyjne tworzy funkcję i w efekcie
zwraca referencję do niej, jednak już tylko od ciebie zależy,
co z nią zrobisz.
Rusz głową: Ale czy nie jest tak, że referencje zwracane
przez wyrażenia funkcyjne zawsze są zapisywane
w zmiennych?
Funkcja: Bez wątpienia nie. W rzeczywistości takie
rozwiązanie jest rzadko spotykane. Pamiętaj, że referencja
do funkcji jest wartością. Pomyśl o tym wszystkim, do
czego możemy używać innych wartości, np. referencji do
obiektów. Dokładnie to samo można robić z referencjami
do funkcji.
Rusz głową: Jak to się dzieje, że referencji do funkcji
można używać tak samo jak wszelkich innych wartości?
Przecież funkcje się jedynie deklaruje i wywołuje. Języki
programowania pozwalają tylko na tyle, prawda?
Funkcja: Otóż nie. Musisz zacząć myśleć o funkcjach
jak o wartościach; tak samo jak myślisz o obiektach lub
wartościach typów prostych. Kiedy już uzyskasz dostęp
do funkcji, możesz jej używać na wiele różnych sposobów.
Jednak pomiędzy funkcjami a wartościami innych typów
istnieje jedna podstawowa różnica i to właśnie ona sprawia,
że jestem tym, czym jestem. Funkcje można wywoływać,
aby wykonać umieszczony w nich kod.
Rusz głową: To naprawdę robi wrażenie i wygląda na
potężną możliwość, jednak i tak nie miałbym pojęcia, co,
oprócz zdefiniowania i wywołania, można z Tobą zrobić?
Funkcja: I właśnie w tym miejscu można odróżnić
zwyczajnych programistów od magików, którzy na kontach
mają kwoty o sześciu zerach lub większe. Kiedy traktujesz
funkcje jak wartości, zyskujesz możliwość tworzenia wielu
interesujących konstrukcji programistycznych.
Rusz głową: Czy mogłabyś podać jakieś przykłady?
Funkcja: Pewnie. Załóżmy, że chciałbyś napisać
funkcję, która potrafi sortować wszystko. Nie ma sprawy.
Potrzebujesz funkcji pobierającej dwa argumenty: pierwszym
jest kolekcja tego, co chcesz posortować, a drugim inna
funkcja, która wie, jak porównywać dwa elementy tej
kolekcji. W języku JavaScript taki kod można napisać bez
większego problemu. Wystarczy napisać jedną funkcję
sortującą dowolne kolekcje, a następnie poinformować
ją, jak należy porównywać elementy konkretnej kolekcji,
przekazując do niej drugą funkcję, która wie, jak to zrobić.
Rusz głową: E…
Funkcja: No cóż, jak już wspominałam… to tu odróżniamy
zwyczajnych programistów od prawdziwych magików.
Powtórzę: mamy funkcję sortującą, a do niej przekazujemy
funkcję, która wie, jak wykonać porównanie. Innymi słowy,
traktujemy funkcję jak wartość i przekazujemy ją, jak
wartość, do innej funkcji.
Rusz głową: A co to daje, oprócz tego, że zupełnie
zamiesza w głowach?
Funkcja: Pozwala skrócić kod, ułatwić pracę, poprawić
przejrzystość kodu, zwiększyć jego elastyczność, ułatwić
utrzymanie i zapewnić wyższą pensję.
Rusz głową: To wszystko brzmi świetnie, ale wciąż nie
wiem, jak to osiągnąć.
Funkcja: Dotarcie do takiego poziomu znajomości funkcji
wymaga nieco pracy. Bez wątpienia to jedno z tych zagadnień,
które wymagają od mózgu poszerzenia granic pojmowania.
Rusz głową: No dobrze funkcjo, mój mózg rozszerzył je
już tak bardzo, że zaraz wybuchnie… Dlatego mam zamiar
na chwilę się położyć.
Funkcja: Jasne, nie ma sprawy. Dzięki za wywiad!
jesteś tutaj
465
Funkcje pierwszej klasy
Czy wspominaliśmy już, że w JavaScripcie
funkcje mają status „pierwszej klasy”?
Jeśli zainteresowałeś się JavaScriptem, a miałeś już doświadczenia w używaniu
jakiegoś bardziej tradycyjnego języka programowania, możesz oczekiwać, że
funkcje będą tylko funkcjami. Możesz je deklarować i wywoływać, ale kiedy nie
będziesz robił żadnej z tych dwóch rzeczy, funkcje będą siedzieć cicho i bezczynnie.
Teraz już wiesz, że w języku JavaScript funkcje są wartościami — wartościami,
które można zapisywać w zmiennych. Wiesz także, że z wartościami innych typów,
takimi jak liczby, łańcuchy znaków, a nawet obiekty, można robić wiele różnych
rzeczy — przekazywać do funkcji, zwracać jako wynik wykonania funkcji, a nawet
zapisywać w obiektach i tablicach.
Informatycy mają nawet specjalne określenie dla takich wartości: nazywają je
wartościami pierwszej klasy. Poniżej wymieniliśmy, co można z takimi wartościami robić.
‰ Można zapisać wartość w zmiennej (lub w jakiejś strukturze danych, takiej
jak tablica lub obiekt).
‰ Można przekazać do funkcji.
‰ Można zwrócić jako wynik wywołania funkcji.
Wiesz co? To wszystko można robić także z funkcjami. W rzeczywistości
z funkcjami można robić wszystko to, co z wartościami innych typów. A zatem
w języku JavaScript funkcje można uznać za wartości pierwszej klasy, podobnie
jak wartości wszystkich innych typów, które poznałeś wcześniej: liczby, łańcuchy
znaków, wartości logiczne i obiekty.
Poniżej zamieściliśmy bardziej formalną definicję wartości pierwszej klasy.
u
Wartość pierwszej klasy to wartość, którą w język
inną
lną
dowo
jak
wać
trakto
a
możn
ania
programow
ać jako
wartość, w tym zapisywać w zmiennych, przekazyw
ji.
funkc
nania
wyko
argument oraz zwracać jako wynik
Teraz udowodnimy, że funkcję w języku JavaScript bez problemów można
uznać za wartość pierwszej klasy. Poświęćmy zatem trochę czasu, by przekonać
się, co oznacza dla funkcji bycie wartością pierwszej klasy w każdym z tych
trzech aspektów. Najpierw jednak chcielibyśmy udzielić Ci pewnej drobnej
rady: przestań myśleć o funkcjach jak o czymś specjalnym i odmiennym od
innych wartości dostępnych w języku JavaScript. Traktowanie funkcji jak
wartości niesie ze sobą ogromne możliwości. Naszym celem dalej w tym
rozdziale jest pokazanie Ci, dlaczego tak się dzieje.
466
Rozdział 10.
Zawsze uważaliśmy, że lepszą
nazwą jest „dostęp VIP do
wszystkich miejsc”, ale nikt nas
nie słuchał… Pozostaniemy zatem
przy „pierwszej klasie”.
Funkcje pierwszej klasy
Latanie pierwszą klasą
Następnym razem, kiedy będziesz na rozmowie kwalifikacyjnej i usłyszysz
pytanie: „Co sprawia, że funkcje JavaScript są wartościami pierwszej klasy?”,
odpowiesz na nie bez zająknięcia. Jednak zanim zaczniesz świętować
początek nowej kariery, pamiętaj, że na razie Twoje zrozumienie tego
zagadnienia bazuje wyłącznie na wiedzy książkowej. Bez wątpienia potrafisz
wyrecytować z pamięci, co można robić z funkcjami pierwszej klasy.
‰ Można je zapisywać w zmiennych.
‰ Można je przekazywać do funkcji.
źć
ź pozwoli Ci znale
Jeśli ta odpowied zapomnij o nas!
świetną pracę, niemy dotację w postaci
Chętnie przyjmuje lub bitcoinów.
czekoladek, pizzy
Tym właśnie się teraz zajmiemy.
Tym właśnie się teraz zajmiemy.
‰ Można je zwracać jako wynik wykonania funkcji.
A tym zajmiemy się już niebawem…
Czy potrafisz zastosować te techniki w kodzie albo czy wiesz, kiedy ich zastosowanie
może się okazać pomocne? Nie przejmuj się, właśnie mamy zamiar się tym zająć,
a konkretnie wyjaśnimy, jak przekazywać jedną funkcję do drugiej. W rzeczywistości
zaczniemy od prostej struktury danych, reprezentującej pasażerów samolotu.
danych
To jest struktura sażerów.
reprezentująca pa
var passengers = [
A tu mamy czterech pasażer
ów
(jeśli chcesz, rozszerz tę listę
o rodzinę i przyjaciół).
Wszyscy
pasażerowie są
y.
zapisani w tablic
{ name: ”-anka PÚtlicka”, paid: true },
{ name: ”Dr Zatan”, paid: true },
{ name: ”Stefa WïaĂciwa”, paid: false },
{ name: ”-anek Funkcyjniak”, paid: true } ];
Właściwość name zawiera
zwyczajny łańcuch znaków.
Każdy z pasażerów
jest reprezentowany
przez obiekt
zawierający
właściwości name
i paid.
Z kolei właściwość paid zawiera wartość
logiczną, określającą, czy dany pasażer
zapłacił za przelot, czy nie.
A oto nasz cel: mamy napisać kod, który, zanim zostanie wydane
pozwolenie na start, przejrzy listę pasażerów, by upewnić się, czy są
spełnione wymagane warunki. Przykładowo możemy się upewnić,
czy na pokładzie nie ma nikogo z listy osób, którym zabroniono
przelotów samolotami danego przewoźnika. Dodatkowo
sprawdzimy, czy wszyscy zapłacili za przelot. Moglibyśmy nawet
sporządzić listę wszystkich osób, które weszły na pokład.
WYSIL
SZARE KOMÓRKI2
Zastanów się, w jaki sposób napisałbyś kod wykonujący
każdą z tych trzech operacji (sprawdzenie, czy na
liście pasażerów nie ma osób objętych zakazem lotów,
zweryfikowanie, czy wszyscy pasażerowie zapłacili za
przelot oraz sporządzenie listy pasażerów, którzy już są
na pokładzie samolotu)?
jesteś tutaj
467
Zastosowanie funkcji do sprawdzania pasażerów
Piszemy kod do przetwarzania i sprawdzania pasażerów
Normalnie napisałbyś funkcję dla każdego z tych warunków: jedną, by sprawdzić, czy na
liście pasażerów nie ma osób objętych zakazem lotów, drugą do sprawdzenia, czy każdy
pasażer zapłacił, i trzecią, by wyświetlić wszystkich pasażerów na pokładzie. Gdybyśmy
jednak napisali te funkcje, a następnie spojrzeli na nie z boku, zauważylibyśmy, że wszystkie
wyglądają dosyć podobnie.
function checkPaid(passengers) {
for (var i = 0; i < passengers.length; i++) {
if (!passengers[i].paid) {
return false;
}
function checkNoFly(passengers) {
}
for (var i = 0; i < passengers.length; i++) {
return true;
if (onNoFlyList(passengers[i].name)) {
}
return false;
}
}
function printPassengers(passengers) {
Jedyną rzeczą, którą różnią się
return true;
for (var i = 0; i < passengers.length; i++) {
te dwie funkcje jest warunek:
}
pierwsza sprawdza, czy
console.log(passengers[i].name);
pasażer zapłacił, a druga, czy
}
osób
nie znajduje się na liście
}
objętych zakazem lotów.
Z kolei ta funkcja różni się jedynie tym, że
nie sprawdza żadnego warunku (zamiast tego
wyświetlamy listę pasażerów, używając metody
console.log) i nie zwraca żadnej wartości, jednak
w niej także przeglądamy całą listę pasażerów.
Jak widać, to całkiem sporo powtarzającego się kodu: wszystkie funkcje przeglądają listę
pasażerów i na każdym z nich wykonują jakąś operację. A co się stanie, jeśli w przyszłości
trzeba będzie dodać sprawdzenie kolejnego warunku? Przykładowo sprawdzenie, czy wszystkie
laptopy zostały wyłączone, czy któryś z pasażerów nie wykupił biletu wyższej klasy, czy ktoś nie
ma problemów ze zdrowiem itd. To by oznaczało jeszcze więcej powtarzającego się kodu.
A co moglibyśmy poradzić, gdyby stało się coś jeszcze gorszego, np. struktura danych
zawierająca listę pasażerów zostałaby zmieniona z prostej tablicy na coś zupełnie innego.
W takim przypadku musielibyśmy zmodyfikować każdą z tych funkcji. Przykra sprawa.
Lecz funkcje pierwszej klasy umożliwiają rozwiązanie tego problemu. A oto sposób: napiszemy
jedną funkcję, która będzie potrafiła przeglądnąć całą listę pasażerów, a do niej przekażemy
drugą funkcję, która będzie wiedzieć, jak wykonać interesujący nas test (np. sprawdzić, czy
dana osoba nie jest objęta zakazem lotów, czy dany pasażer zapłacił itd.).
468
Rozdział 10.
Funkcje pierwszej klasy
Spróbuj wykonać pewne prace wstępne związane z przygotowaniem tego rozwiązania. A konkretnie: napisz
funkcję, która pobiera obiekt pasażera jako argument i sprawdza, czy nie znajduje się on na liście osób objętych
zakazem lotów. Funkcja ta będzie zwracać true, jeśli dana osoba jest na liście, oraz wartość false, jeśli jej nie
ma. Napisz także drugą funkcję, która także pobiera obiekt pasażera jako argument i sprawdza, czy zapłacił za
przelot. Funkcja będzie zwracać wartość true, jeśli dany pasażer nie zapłacił, oraz wartość false, jeśli zapłacił.
Poniżej zaczęliśmy już pisać kod tych dwóch funkcji, Tobie pozostaje jedynie go dokończyć. Nasze rozwiązanie
zamieściliśmy na następnej stronie, ale nie podglądaj!
Ćwiczenie
function checkNoFlyList(passenger) {
}
function checkNotPaid(passenger) {
Podpowiedź: Załóż, że nasza lista
osób objętych zakazem lotów zawiera
tylko jedną osobę: dr. Zatana.
}
Zaostrz ołówek
Rozgrzej swój mózg i przygotuj go na przekazanie swojej pierwszej funkcji do innej
funkcji. Spróbuj wykonać poniższy kod (w głowie) i przekonaj się, jaki będzie tego
efekt. Zanim przejdziesz do dalszej lektury, koniecznie sprawdź wyniki.
function sayIt(translator) {
var phrase = translator(”Witam”);
alert(phrase);
}
function hawaiianTranslator(word) {
if (word === ”Witam”) return ”Aloha”;
if (word === ”Do widzenia”) return ”Aloha”;
}
sayIt(hawaiianTranslator);
jesteś tutaj
469
Przekazywanie funkcji do funkcji
Przetwarzanie listy pasażerów
Teraz potrzebujemy funkcji, która będzie pobierać listę pasażerów, oraz innej funkcji,
która będzie wiedzieć, jak wykonać określony test na obiekcie jednego pasażera (np.
sprawdzenie, czy nie znajduje się on na liście osób objętych zakazem lotów). Poniżej
pokazaliśmy, jak to zrobić.
Drugim jest funkcja, która
wie, jak sprawdzić, czy dany
pasażer spełnia określony
warunek.
Funkcja processPassengers
ma dwa parametry.
Pierwszym z nich jest
tablica pasażerów.
function processPassengers(passengers, testFunction) {
for (var i = 0; i < passengers.length; i++) {
Listę pasażerów przeglądamy
kolejno, jeden po drugim.
if (testFunction(passengers[i])) {
return false;
}
A następnie wywołujemy funkcję,
przekazując do niej aktualnie
przetwarzanego pasażera.
}
return true;
}
Jeśli żaden z pasażerów nie spełnił
warunku, docieramy do tego miejsca
funkcji i zwracamy wartość true.
Jeśli funkcja zwróci wartość true, zwra
Innymi słowy, jeśli pasażer nie przes camy false.
pomyślnie (np. nie zapłacił lub znajd zedł testu
uje
osób objętych zakazem lotów), nie zezw się na liście
olimy na start
samolotu!
Teraz potrzebujemy tylko funkcji, które potrafią sprawdzać pasażerów
(na szczęście, napisałeś je na poprzedniej stronie w ramach ćwiczenia
„Zaostrz ołówek”). Oto one.
Uważaj: to jest jeden pasażer (obiekt),
a nie lista pasażerów (tablica obiektów).
function checkNoFlyList(passenger) {
return (passenger.name === ”Dr Zatan”);
}
function checkNotPaid(passenger) {
return (!passenger.paid);
}
470
Rozdział 10.
ażer
dzająca, czy dany pas
To jest funkcja spraw ie osób objętych zakazem
liśc
na
:
nie znajduje się
dku ta lista jest krótka
lotów. W naszym przypa a. Zatem zwracamy true,
tan
obejmuje tylko dr. Za Zatan, oraz false we
dr
a,
jeśli pasażerem jest
przypadkach (co oznacz
wszystkich pozostałych jduje się na liście osób
zna
nie
r
aże
że dany pas
w).
objętych zakazem lotó
A to jest funkcja, która sprawdza, czy dany
pasażer zapłacił za przelot. Sprowadza się
ona do sprawdzenia wartości właściwości paid
obiektu pasażera. Jeśli pasażer nie zapłacił,
funkcja zwraca wartość true.
Funkcje pierwszej klasy
Przekazywanie funkcji do funkcji
No dobrze, a zatem mamy już funkcję (processPassengers), która może pobierać inną
funkcję jako argument, oraz dwie dodatkowe funkcje, które będziemy mogli do niej
przekazywać (checkNoFlyList oraz checkNotPaid).
Nadszedł zatem czas, aby połączyć te wszystkie elementy w jedną całość. Prosimy o werble…
Przekazywanie funkcji do funkcji
jest bardzo łatwe. Wystarczy użyć
nazwy funkcji jako argumentu.
ekazujemy funkcję
W tym przypadku prz
em funkcja
checkNoFlyList. A zat awdzi, czy
spr
rs
nge
sse
sPa
ces
pro
ie nie znajdują się
poszczególni pasażerow zakazem lotów.
ch
ęty
obj
b
osó
na liście
var allCanFly = processPassengers(passengers, checkNoFlyList);
if (!allCanFly) {
console.log(”Samolot nie moĝe wystartowaÊ: na pokïadzie jest pasaĝer objÚty zakazem lotöw.”);
}
Jeśli którykolwiek z pasażerów będzie się znajdował na liście osób
objętych zakazem lotów, funkcja zwróci false, a w oknie konsoli
zostanie wyświetlony stosowny komunikat.
kcję checkNotPaid.
Tutaj przekazujemy fun sPassengers
A zatem funkcja procespasażerów zapłacił
sprawdzi, czy każdy z
var allPaid = processPassengers(passengers, checkNotPaid);
za przelot.
if (!allPaid) {
console.log(”Samolot nie moĝe wystartowaÊ: nie wszyscy zapïacili za przelot.”);
}
Jeśli którykolwiek z pasażerów nie
zapłacił za przelot, funkcja zwróci false,
a w oknie konsoli zostanie wyświetlony
stosowny komunikat.
Pierwsza klasa zawsze
jest lepsza… Oczywiście
mówię o funkcjach…
JazdaLot próbnay
Aby przetestować ten kod, wystarczy dodać go do
prostej strony WWW i wyświetlić ją w przeglądarce.
Konsola JavaScript
6DPRORWQLHPRĝHZ\VWDUWRZDÊQDSRNïDG]LHMHVWSDVDĝHU
REMÚW\]DND]HPORWöZ
6DPRORWQLHPRĝHZ\VWDUWRZDÊQLHZV]\VF\]DSïDFLOL]D
Wygląda na to, że jednak nie
wystartujemy. Mamy problemy
z pasażerami! Dobrze, że to
sprawdziliśmy…
SU]HORW
jesteś tutaj
471
Pytania dotyczące funkcji pierwszej klasy
Ćwiczenie
Znowu przyszła Twoja kolej: napisz funkcję, która przy użyciu metody console.log będzie wyświetlać
w oknie konsoli nazwiska pasażerów oraz informację, czy dana osoba zapłaciła, czy nie. Przekaż ją
w wywołaniu funkcji processPassengers, aby ją przetestować. Rozpoczęliśmy już pisanie kodu tej
funkcji, Ty musisz ją jedynie dokończyć. Zanim zaczniesz dalszą lekturę, sprawdź naszą odpowiedź,
którą zamieściliśmy pod koniec tego rozdziału.
function printPassenger(passenger) {
ój kod.
Tutaj zapisz sw
}
processPassengers(passengers, printPassenger);
Twoja funkcja po
przekazaniu do funkcj
i
processPassengers
powinna wyświetlić
listę pasażerów.
Nie istnieją
głupie pytania
P: Czy nie można by po prostu umieścić całego tego kodu P: A co dokładnie przekazujemy, przekazując jedną
w funkcji processPassengers? Wystarczyłoby umieścić
wszystkie testy wewnątrz pętli, więc w każdej iteracji
byłyby sprawdzane wszystkie warunki i wyświetlane
nazwisko pasażera. Czy takie rozwiązanie nie byłoby
bardziej wydajne?
O: Gdyby Twój kod był krótki i prosty, to owszem, byłoby to
całkiem sensowne rozwiązanie. Jednak nam chodzi o zapewnienie
elastyczności. Co by się stało, gdybyś w przyszłości bezustannie
dodawał nowe testy (np. czy wszyscy wyłączyli swoje laptopy)
lub gdyby zmieniły się wymagania dotyczące już stosowanych
funkcji? Albo gdyby zmieniła się struktura danych zawierająca listę
pasażerów? W takich przypadkach nasze rozwiązanie pozwala na
wprowadzanie zmian i dodatków w sposób eliminujący nadmierną
złożoność i mniej podatny na błędy.
472
Rozdział 10.
funkcję do drugiej?
O: Przekazujemy referencję do funkcji. Możesz ją sobie wyobrażać
jako wskaźnik odwołujący się do wewnętrznej reprezentacji
funkcji. Taką referencję można umieścić w zmiennej, następnie
przypisać innej zmiennej lub przekazać do funkcji jako argument jej
wywołania. A dodanie pary nawiasów za taką referencją spowoduje
wywołanie funkcji.
Funkcje pierwszej klasy
Zaostrz ołówek
Poniżej utworzyliśmy funkcję i przypisaliśmy ją zmiennej o nazwie fun.
function fun(echo) {
function fun(echo) {
console.log(echo);
}
console.log(echo);
};
fun
Przeanalizuj poniższy kod i zapisz wygenerowane wyniki. Spróbuj wykonać to
w głowie, a dopiero potem sprawdź wyniki na komputerze.
fun(”Witam”);
function boo(aFunction) {
aFunction(”bum”);
}
boo(fun);
console.log(fun);
fun(boo);
var moreFun = fun;
moreFun(”Witam ponownie”);
function echoMaker() {
return fun;
}
ty!
Dodatkowe punk go,
(To przedsmak te
co nadchodzi…)
var bigFun = echoMaker();
bigFun(”Czy to echo?”);
zrozum odpowiedzi!
Superważne: zanim przejdziesz dalej, sprawdź i dobrze
jesteś tutaj
473
Aktualizacja pasażerów
Zwracanie funkcji z funkcji
Dotychczas udało się nam sprawdzić dwa wymagania narzucane wartościom
pierwszej klasy: przypisywanie ich do zmiennych oraz przekazywanie do funkcji.
Nie sprawdziliśmy jeszcze przykładu zwracania funkcji jako wyniku wykonania
innej funkcji.
‰Można je zapisywać w zmiennych.
‰Można je przekazywać do funkcji.
Teraz tym się
zajmiemy.
‰Można je zwracać jako wynik wykonania funkcji.
Rozszerzymy nieco przykład z pasażerami samolotu i spróbujemy określić,
dlaczego i kiedy moglibyśmy zwracać jedną funkcję jako wynik wykonania innej
funkcji. W tym celu do każdego obiektu pasażera dodamy nową właściwość,
ticket, która będzie mogła zawierać jedną z dwóch wartości: ĵSLHUZV]D
klasa” lub ĵWXU\VW\F]QDĵ, zależnie od rodzaju zakupionego biletu.
var passengers = [
{ name: ”-anka PÚtlicka”, paid: true, ticket: ”turystyczna” },
{ name: ”Dr Zatan”, paid: true, ticket: ”pierwsza klasa” },
{ name: ”Stefa WïaĂciwa”, paid: false, ticket: ”pierwsza klasa” },
{ name: ”-anek Funkcyjniak”, paid: true, ticket: ”turystyczna” } ];
Dysponując tymi nowymi danymi pasażerów, napiszemy
kod obsługujący różne czynności, które muszą wykonywać
stewardesy.
ności,
Tu wypisaliśmy wszystkie czyn
sa
które musi wykonać stewarde
w celu obsłużenia pasażera.
To, co oferuję
pasażerom, zależy od
rodzaju biletu. W pierwszej klasie
pasażerowie otrzymują wino lub koktajl,
a w klasie turystycznej colę lub
wodę.
function serveCustomer(passenger) {
// Zbieramy zamówienie na napoje.
// Zbieramy zamówienie na posiïek.
// Zbieramy Ămieci.
}
Zacznijmy od
zaimplementowania
funkcji pobierającej
zamówienia na napoje.
Jak zapewne wiesz, obsługa w pierwszej klasie zazwyczaj trochę różni się od
obsługi w klasie turystycznej. W pierwszej klasie możesz zamówić wino lub
koktajl, a w turystycznej tylko colę lub wodę.
474
Rozdział 10.
A przynajmniej tak to
wygląda w filmach…
Funkcje pierwszej klasy
Pisanie kodu do wydawania napojów
W pierwszym podejściu do napisania tego kodu mógłbyś uzyskać coś takiego.
function serveCustomer(passenger) {
if (passenger.ticket === ”pierwsza klasa”) {
alert(”PodaÊ koktajl czy wino?”);
Jeśli pasażer ma bilet
pierwszej klasy, wyświetlamy
okno dialogowe, oferując mu
możliwość wyboru koktajlu bądź
wina.
} else {
alert(”PodaÊ colÚ czy wodÚ?”);
}
Jeśli natomiast pasażer
ma bilet klasy turystycznej,
można mu zaoferować jedynie
colę lub wodę.
// Zbieramy zamówienie na posiïek.
// Zbieramy Ămieci.
}
Nieźle. W prostym kodzie takie rozwiązanie będzie działać całkiem dobrze:
sprawdzamy bilet pasażera i w zależności od jego typu wyświetlamy odpowiedni
komunikat. Zastanówmy się jednak nad potencjalnymi wadami tego rozwiązania.
Nasz kod do zbierania zamówień i wydawania napojów jest bardzo prosty, ale co
by się stało z funkcją serveCunstomer, gdyby problem stał się bardziej złożony?
Może się np. okazać, że musimy obsługiwać trzy klasy pasażerów (pierwszą klasę,
klasę biznesową i turystyczną, a gdyby do tego doszła turystyczna klasa premium,
to byłoby ich aż cztery!). A co zrobimy, jeśli zasady doboru oferowanych napojów
staną się bardziej złożone? Albo gdy oferowane napoje będą zależeć od miejsca,
z którego samolot wylatuje, bądź do którego leci?
Jeśli będziemy musieli poradzić sobie z tymi wszystkimi możliwościami, funkcja
serveCustomer bardzo szybko stanie się rozbudowana i złożona, i zacznie raczej
służyć do zarządzania napojami niż do obsługi pasażerów, a poza tym, kiedy
projektujemy funkcje, powszechnie przyjęta zasada nakazuje, by wykonywały one
tylko jedną rzecz, lecz robiły to naprawdę dobrze.
Przykładowo na
trasie na Hawaje
w pierwszej klasie
serwowane są
zazwyczaj drinki Mai
tai (a przynajmniej
tak słyszeliśmy).
WYSIL
SZARE KOMÓRKI
Jeszcze raz przeczytaj uważnie wszystkie potencjalne problemy wymienione w dwóch ostatnich akapitach na tej stronie.
A następnie zastanów się, jak należałoby zaprojektować funkcję serveCustomer, by koncentrowała się tylko na swoim
podstawowym zadaniu, a jednocześnie pozwalała na przyszłe rozszerzanie strategii serwowania napojów.
jesteś tutaj
475
Organizacja kodu do sprawdzania pasażerów
Pisanie kodu do wydawania napojów — inne podejście
Nasze pierwsze rozwiązanie do obsługi wydawania napojów nie było złe, jednak, jak mogłeś się
przekonać, mogłoby stać się problematyczne w przyszłości, gdyby kod do obsługi wydawania
napojów stał się bardziej złożony. Spróbujmy zatem nieco go przerobić, gdyż istnieje także inny
sposób rozwiązania naszego problemu, polegający na umieszczeniu logiki doboru napojów
w odrębnej funkcji. To podejście pozwoli ukryć całą logikę doboru napojów w jednym miejscu
oraz utworzyć jedno, precyzyjnie zdefiniowane miejsce kodu, które trzeba będzie zmodyfikować,
kiedy zmieni się sposób obsługi wydawania napojów.
Tworzymy nową funkcję, createDrinkOrder,
do której przekazujemy obiekt pasażera.
function createDrinkOrder(passenger) {
Umieścimy w niej całą logikę
związaną z doborem napojów.
if (passenger.ticket === ”pierwsza klasa”) {
alert(”PodaÊ koktajl czy wino?”);
Ten kod nie będzie
już zaśmiecał funkcji
serveCustomer logiką obsługi
wydawania napojów.
} else {
alert(”PodaÊ colÚ czy wodÚ?”);
}
}
Teraz możemy wrócić do funkcji serveCustomer i usunąć z niej całą logikę związaną
z doborem napojów, zastępując ją wywołaniem tej nowej funkcji.
function serveCustomer(passenger) {
if (passenger.ticket === ”pierwsza klasa”) {
Usuwamy logikę z funkcji
serveCustomer…
alert(”PodaÊ koktajl czy wino?”);
} else {
alert(”PodaÊ colÚ czy wodÚ?”);
}
createDrinkOrder(passenger);
// Zbieramy zamówienie na posiïek.
// Zbieramy Ămieci.
}
I zastępujemy ją wywołaniem
funkcji createDrinkOrder.
Do funkcji createDrinkOrder
przekazywany jest obiekt pasażera,
który wcześniej został przekazany
do funkcji serveCustomer.
Taki kod, w którym całą logikę zastąpiło jedno wywołanie funkcji, będzie znacznie
bardziej czytelny. Dodatkowo umieszczenie całej logiki związanej z doborem
napojów w jednym miejscu jest bardzo wygodne. Jednak musisz się wstrzymać
z przetestowaniem tego nowego rozwiązania — słyszeliśmy, że pojawił się jeszcze
jeden problem…
476
Rozdział 10.
Funkcje pierwszej klasy
Chwileczkę, potrzebujemy więcej napojów!
Wstrzymajcie rejestrację pasażerów, dowiedzieliśmy się właśnie, że wydanie
jednego napoju na cały przelot to za mało. Stewardesy twierdzą, że obsługa
pasażera w trakcie typowego lotu wygląda inaczej.
Ludzie,
dajcie spokój, czy
to są jakieś byle
tanie linie lotnicze?
function serveCustomer(passenger) {
createDrinkOrder(passenger);
// Zbieramy zamówienie na posiïek.
createDrinkOrder(passenger);
createDrinkOrder(passenger);
// WyĂwietlamy film.
createDrinkOrder(passenger);
Zaktualizowaliśmy kod,
uwzględniając w nim fakt,
że podczas lotu funkcja
createDrinkOrder będzie
wywoływana kilka razy.
// Zbieramy Ămieci.
}
Jak widać, z jednej strony, zaprojektowaliśmy nasz kod całkiem dobrze, gdyż
dodanie kolejnych wywołań funkcji createDrinkOrder działa bez zarzutu.
Jednak z drugiej strony, zupełnie niepotrzebnie w funkcji createDrinkOrder
za każdym razem określamy typ obsługiwanego pasażera.
Mógłbyś powiedzieć, że to przecież tylko kilka wierszy kodu. No pewnie, jednak
to tylko prosty przykład w książce. A co by było, gdybyś w rzeczywistości musiał
sprawdzać typ biletu, komunikując się z serwerem z aplikacji działającej na
urządzeniu mobilnym? Byłoby to zarówno czasochłonne, jak i kosztowne.
Jednak nie musisz się obawiać, gdyż funkcje pierwszej klasy przybyły właśnie
jak rycerz na białym koniu, aby nam pomóc. Jak się zaraz przekonasz, nasz
problem można rozwiązać, korzystając z możliwości zwracania funkcji jako
wyniku wykonania innej funkcji.
Zaostrz ołówek
Jak sądzisz, co robi przedstawiony poniżej fragment kodu?
Czy potrafisz podać jakieś przykłady jego użycia?
function addN(n) {
var adder = function(x) {
return n + x;
};
return adder;
}
Tutaj zapisz odpowiedź.
jesteś tutaj
477
Zwracanie funkcji jako wyniku wykonania innej funkcji
Przyjmowanie zamówień z wykorzystaniem funkcji pierwszej klasy
Teraz trzeba trochę pogłówkować! W jaki sposób funkcje pierwszej klasy mogą pomóc w rozwiązaniu naszego
problemu. Plan jest taki: zamiast kilkakrotnie wywoływać funkcję createDrinkOrder dla danego pasażera,
wywołamy ją tylko raz, jednak funkcja ta zwróci inną funkcję, która będzie wiedzieć, jakie napoje można
zaoferować konkretnemu pasażerowi. Później, za każdym razem, kiedy będziemy musieli zaoferować danemu
pasażerowi jakieś napoje, wywołamy tę drugą funkcję.
Zacznijmy od przedefiniowania funkcji createDrinkOrder. Kiedy ją teraz wywołamy, przygotuje ona funkcję
obsługującą wydawanie napojów i zwróci ją, zatem będziemy mogli użyć jej później, kiedy to będzie konieczne.
To jest nowa wersja funkcji createDrinkOrder.
Będzie zwracać funkcję, która wie, jakie napoje
można zaoferować konkretnemu pasażerowi.
function createDrinkOrder(passenger) {
var orderFunction;
konamy
Teraz jeden raz wyową
instrukcję warunk etu
określającą typ bil
danego pasażera.
Najpierw tworzymy
zmienną, w której
zapiszemy zwracaną
funkcję.
if (passenger.ticket === ”pierwsza klasa”) {
Jeśli pasażer kupił bilet pierwszej
klasy, tworzymy funkcję, która
wie, jakie napoje można zaoferować
pasażerowi pierwszej klasy.
orderFunction = function() {
alert(”PodaÊ koktajl czy wino?”);
};
} else {
orderFunction = function() {
alert(”PodaÊ colÚ czy wodÚ?”);
};
W przeciwnym razie tworzymy
funkcję, która wie, jakie napoje
można zaoferować pasażerowi
klasy turystycznej.
}
return orderFunction;
A tu zwracamy tę funkcję.
}
A teraz wprowadźmy odpowiednie modyfikacje w kodzie funkcji serveCustomer. Najpierw
wywołamy funkcję createDrinkOrder, aby pobrać funkcję, która wie, jakie napoje można
zaoferować konkretnemu pasażerowi. Później użyjemy jej kilkakrotnie, by zaproponować
pasażerowi napoje.
Teraz funkcja createDrinkOrder
zwraca funkcję, którą zapisujemy
w zmiennej getDrinkOrderFunction.
function serveCustomer(passenger) {
var getDrinkOrderFunction = createDrinkOrder(passenger);
getDrinkOrderFunction();
// Zbieramy zamówienie na posiïek.
getDrinkOrderFunction();
getDrinkOrderFunction();
// WyĂwietlamy film.
getDrinkOrderFunction();
// Zbieramy Ămieci.
}
478
Rozdział 10.
Funkcji zwróconej przez
createDrinkOrder używamy za
każdym razem, gdy chcemy
zaoferować napój danemu
pasażerowi.
Funkcje pierwszej klasy
JazdaLot próbnay
Przetestujmy ten nowy kod. Musimy napisać krótki kod, który kolejno
pobierze każdego pasażera z listy, a następnie wywoła funkcję serveCustomer
i przekaże do niej obiekt tego pasażera. Kiedy już dodasz ten kod do strony,
wyświetl ją w przeglądarce i obsłuż pasażerów.
function servePassengers(passengers) {
for (var i = 0; i < passengers.length; i++) {
serveCustomer(passengers[i]);
}
}
Kod tej funkcji jedynie pobiera każdego
pasażera z tablicy passengers i przekazuje
go w wywołaniu funkcji serveCustomer.
servePassengers(passengers);
Oczywiście, aby przetestować nasze rozwiązanie,
musimy także wywołać nową funkcję
servePassengers. (Przygotuj się na całkiem
sporo okien dialogowych z komunikatami!).
Nie istnieją
głupie pytania
P: Chciałbym się upewnić, że
dobrze rozumiem… Wywołując
createDrinkOrder, uzyskujemy
funkcję, którą musimy wywołać,
żeby przyjąć zamówienie na napoje?
O: Dokładnie tak. Najpierw wywołujemy
createDrinkOrder, by pobrać funkcję
getDrinkOrderFunction, która wie,
co można zaoferować pasażerowi, a
następnie wywołujemy ją za każdym razem,
gdy chcemy zaproponować pasażerowi
jakiś napój. Zwróć uwagę, że funkcja
getDrinkOrderFunction jest znacznie
prostsza niż createDrinkOrder: jej
działanie sprowadza się do wyświetlenia
okna dialogowego z oferowanymi napojami.
Zwracana funkcja odpowiada typowi
biletu pasażera: jeśli kupił on bilet pierwszej
klasy, funkcja getDrinkOrderFunction
zostanie utworzona tak, by oferować
napoje dostępne w pierwszej klasie. Jeśli
jednak pasażer ma bilet klasy turystycznej,
funkcja getDrinkOrderFunction
zostanie utworzona tak, by oferować napoje
dostępne w tej klasie. Kiedy zwracamy
funkcję dostosowaną do typu biletu danego
pasażera, funkcja ta może być prosta,
szybka i łatwa do wywołania, za każdym
razem gdy trzeba zebrać zamówienie
i wydać pasażerowi napój.
P: Ten kod obsługuje jednego
: A skąd funkcja getDrinkOrder
wie, jakie komunikaty wyświetlić?
pasażera: oferuje mu napój, wyświetla
film itd. Czy stewardesy nie obsługują
zazwyczaj wszystkich pasażerów,
nie wyświetlają tego samego filmu
w całym samolocie itp.?
pasażera, na podstawie jego biletu. Przyjrzyj
się jeszcze raz funkcji createDrinkOrder.
serveCustomer dla konkretnego
P
pasażera. W rzeczywistości wygląda
to trochę inaczej. Jednak to ma być
prosty przykład demonstrujący złożone
zagadnienie (zwracanie funkcji jako
wyniku) i daleko mu do doskonałości.
Ale skoro już przyznaliśmy się do błędu,
prosimy o wyjęcie karteczek i…
WYSIL
SZARE KOMÓRKI2
W jaki sposób zmodyfikowałbyś
ten kod tak, by drinki były
oferowane wszystkim pasażerom
i film wyświetlany w całym
samolocie, bez ciągłego
przetwarzania zamówień
w zależności od typu
posiadanego biletu? Czy użyłbyś
funkcji pierwszej klasy?
: Sprawdzaliśmy Cię! I zdałeś. Masz
O: Gdyż tworzymy ją specjalnie dla danego O
rację. Nasz kod wywołuje funkcję
jesteś tutaj
479
Ćwiczenie z funkcji pierwszej klasy
Ćwiczenie
Twoim zadaniem jest dodanie do naszego przykładu trzeciej klasy — turystycznej klasy premium,
w skrócie ”premium”. Pasażerowie tej klasy, oprócz coli i wody, mogą także zamówić wino.
Dodatkowo zaimplementuj funkcję getDinnerOrderFunction oferującą następujące menu.
Klas pierwsza: kurczak lub makaron.
Turystyczna klasa premium: przekąska lub talerz serów.
Klasa turystyczna: orzeszki lub precelki.
Sprawdź naszą wersję rozwiązania, przedstawioną pod koniec tego rozdziału.
Nie zapomnij przetestować swojego kodu.
Implementując to rozwiązanie,
koniecznie użyj funkcji pierwszej
klasy!
480
Rozdział 10.
Funkcje pierwszej klasy
Cola sieciowicka
Firma Cola sieciowicka potrzebuje pomocy w zarządzaniu
kodem jej linii produktowej. Aby wyciągnąć ku niej pomocną
dłoń, rzuć okiem na poniższą strukturę danych, której firma
używa do przechowywania informacji o produkowanych
wodach gazowanych.
Wygląda na to, że informacje o produktach
są przechowywane w formie tablicy obiektów.
Każdy produkt jest jednym obiektem.
var products = [ { name: ”Grejpfrut”, calories: 170, color: ”czerwony”, sold: 8200 },
{ name: ”Pomarañcza”, calories: 160, color: ”pomarañczowy”, sold: 12101 },
{ name: ”Cola”, calories: 210, color: ”karmelowy”, sold: 25412 },
{ name: ”Cola dietetyczna”, calories: 0, color: ”karmelowy”, sold: 43922 },
{ name: ”Cytryna”, calories: 200, color: ”bezbarwny”, sold: 14983 },
{ name: ”Malina”, calories: 180, color: ”róĝowy”, sold: 9427 },
{ name: ”Piwo korzenne”, calories: 200, color: ”karmelowy”, sold: 9909 },
{ name: ”Woda”, calories: 0, color: ”bezbarwny”, sold: 62123 }
];
Każdy obiekt zawiera nazwę, liczbę kalorii, kolor oraz
liczbę butelek sprzedanych w ciągu miesiąca.
Naprawdę potrzebujemy
pomocy w posortowaniu naszych produktów.
Musimy je posortować według każdej z dostępnych
właściwości: nazwy, liczby kalorii, koloru oraz liczby
sprzedanych butelek. Oczywiście chcemy to zrobić
możliwie jak najbardziej efektywnie, a jednocześnie
zachować elastyczność rozwiązania, tak by pozwalało na
wykonywanie sortowania na wiele różnych sposobów.
Analityk z firmy Cola
sieciowicka.
jesteś tutaj
481
Rozmowa o sortowaniu tablic
Franek: Panowie, właśnie miałem telefon z firmy Cola sieciowicka
i zostałem poproszony o pomoc w obsłudze ich linii produktowej. Chcieliby
mieć możliwość sortowania produktów na podstawie dowolnej właściwości,
takiej jak nazwa, liczba sprzedanych butelek, kolor bądź liczba kalorii
w butelce itd. Chcą też, by rozwiązanie było elastyczne, na wypadek gdyby
w przyszłości pojawiły się kolejne właściwości.
Józek: A jak przechowują dane?
Franek: A… Każda woda gazowana jest obiektem zapisanym w tablicy,
przechowującym takie informacje jak nazwa, liczba sprzedanych butelek,
liczba kalorii…
Józek: Rozumiem.
Franek: Moją pierwszą myślą było wyszukanie jakiegoś prostego algorytmu
sortowania i zaimplementowanie go. Firma nie ma wielu produktów, więc
rozwiązanie musi być proste.
Franek
Józek
Kuba
Kuba: A ja znam prostsze rozwiązanie, jednak wymaga znajomości funkcji
pierwszej klasy.
Franek: Lubię słuchać o prostych rozwiązaniach! Ale co wspólnego z nim mają
funkcje pierwszej klasy? Już sama nazwa brzmi, jakby było to coś skomplikowanego.
Kuba: Wcale nie. To zupełnie proste i sprowadza się do napisania funkcji, która wie, jak
porównywać dwie wartości, a następnie przekazania jej do innej funkcji, która z kolei wie, jak wykonać
sortowanie.
Józek: A jak dokładnie miałyby działać te funkcje?
Kuba: Zamiast obsługiwać całe sortowanie, musisz napisać funkcję, która wie, jak porównać dwie
wartości. Załóżmy, że chciałbyś posortować produkty na podstawie właściwości zawierającej liczbę
sprzedanych butelek. Taka funkcja mogłaby wyglądać tak:
function compareSold(product1, product2) {
// kod do porównywania
}
Ta funkcja wymaga
przekazania dwóch
produktów, które
następnie porówna.
Sam kod umieszczony w ciele funkcji uzupełnimy później, ale najważniejsze jest to, że kiedy już ją
napiszemy, będziemy mogli przekazać ją do funkcji sortującej, która wykona całą resztę pracy — musimy
ją jedynie poinstruować, jak ma porównywać wartości.
Franek: Chwila, a gdzie jest ta funkcja sortująca?
Kuba: W rzeczywistości to metoda, która jest dostępna we wszystkich tablicach. Możesz wywołać
metodę sort na rzecz tablicy products i przekazać do niej funkcję, którą napiszemy. A kiedy metoda
sort zostanie wykonana, tablica products będzie posortowana na podstawie jakiegokolwiek kryterium,
którego użyjemy w funkcji compareSold.
Józek: A zatem, jeśli trzeba wykonać sortowanie na podstawie liczby sprzedanych butelek, czyli wartości
liczbowej, wystarczy, że funkcja compareSold określi, która z tych wartości jest większa?
Kuba: Dokładnie. Przyjrzyjmy się nieco dokładniej, jak są sortowane tablice.
482
Rozdział 10.
Funkcje pierwszej klasy
Jak działa metoda sort tablic?
Tablice w języku JavaScript udostępniają metodę sort, która na podstawie
przekazanej funkcji potrafiącej porównywać dwa elementy danej tablicy pozwala
posortować jej zawartość. Poniżej przedstawiamy ogólny sposób działania tej metody
oraz wyjaśniamy, jak ma wyglądać funkcja porównująca: algorytmy sortowania są
powszechnie znane i często implementowane, a ich wspaniałą cechą jest to, że można
ich wielokrotnie używać do sortowania niemal dowolnych zbiorów danych. Istnieje
jednak mały problem: aby posortować konkretny zbiór elementów, funkcja sortująca
musi wiedzieć, jak elementy porównywać. Porównaj sobie sortowanie zbioru liczb
z sortowaniem zbioru imion lub zbioru obiektów. Sposób porównywania wartości
zależy od rodzaju elementów sortowanego zbioru: w przypadku liczb będziemy używać
operatorów >, <, oraz ==, łańcuchy znaków będziemy porównywać alfabetycznie
(w języku JavaScript można używać operatorów >, <, oraz ==), a obiekty porównamy
jeszcze inaczej, w zależności od ich właściwości.
Zanim przejdziemy do sortowania tablicy produktów firmy Cola sieciowcka,
przeanalizujmy prosty przykład. Zastosujemy w nim prostą tablicę liczb i posortujemy
ją w kolejności rosnącej, przy użyciu metody sort. A oto nasza przykładowa tablica.
.RGRZDQLHQDSRZDĝQLH
Tablice języka JavaScript
udostępniają wiele
użytecznych metod,
których możemy używać do
wykonywania na tablicach wielu
różnych operacji. Doskonałą książką,
w której można znaleźć informacje
na temat wszystkich tych metod
oraz sposobów korzystania z nich,
jest książka: JavaScript: The Definitive
Guide (wydana przez wydawnictwo
O’Reilly).
Zaostrz ołówek
var numbersArray = [60, 50, 62, 58, 54, 54];
Teraz musimy napisać funkcję, która wie, jak porównywać dwie wartości zapisane
w tej tablicy. Ponieważ jest to tablica liczb, nasza funkcja będzie musiała porównywać
ze sobą dwie liczby. Założymy, że liczby mają być posortowane w kolejności rosnącej.
Metoda sort oczekuje, że nasza funkcja zwróci wartość większą od zera, jeśli pierwsza
z przekazanych wartości jest większa od drugiej, wartość 0, jeśli obie wartości są
równe, oraz wartość mniejszą od zera, jeśli pierwsza z przekazanych wartości jest
mniejsza od drugiej. Nasza funkcja porównująca będzie wyglądać następująco.
a zatem będziemy
Sortujemy tablicę liczb, e liczby.
dwi
ć
ze sobą porównywa
function compareNumbers(num1, num2) {
if (num1 > num2) {
return 1;
Najpierw sprawdzamy, czy
wartość num1 jest większa
od num2; a jeśli jest,
zwracamy 1.
} else if (num1 === num2) {
return 0;
} else {
return -1;
}
}
Jeśli obie wartości są
równe, zwracamy 0.
Już wiesz, że funkcja porównująca,
którą należy przekazać do metody
sort, ma zwracać wartość
większą od zera, równą zero lub
mniejszą od zera, zależnie od
dwóch porównywanych wartości:
jeśli pierwsza z wartości jest
większa od drugiej, mamy zwrócić
wartość większą od zera, jeśli obie
porównywane wartości są sobie
równe, mamy zwrócić zero, a jeśli
pierwsza wartość jest mniejsza od
drugiej, mamy zwrócić wartość
mniejszą od zera.
Czy potrafisz wykorzystać tę
wiedzę oraz fakt, że funkcja
compareNumbers porównuje
liczby, by zmodyfikować ją
i w znaczący sposób skrócić jej kod?
Sprawdź nasze rozwiązanie
przedstawione pod koniec tego
rozdziału.
I w końcu, jeśli wartość
num1 jest mniejsza od num2,
zwracamy –1.
jesteś tutaj
483
Sortowanie tablicy liczb
Łączymy wszystko w całość
Skoro już napisaliśmy funkcję porównującą, pozostało jedynie wywołać metodę
sort tablicy numbersArray i przekazać do niej tę funkcję. A tak to zrobimy.
var numbersArray = [60, 50, 62, 58, 54, 54];
numbersArray.sort(compareNumbers);
console.log(numbersArray);
Wywołujemy metodę sort na
rzecz tablicy, przekazując do
niej funkcję compareNumbers.
Po wykonaniu metody tablica będzie już
posortowana w kolejności rosnącej, a my,
w ramach sprawdzenia, wyświetlamy ją
w oknie konsoli.
Zwróć uwagę, że metoda sort ma działanie destrukcyjne —
zamiast zwracać nową, odpowiednio posortowaną tablicę,
zmienia zawartość tablicy, na rzecz której została wywołana.
Oto tablica posortowana
rosnąco.
Konsola JavaScript
[50, 54, 54, 58, 60, 62]
Metoda sort posortowała tablicę numbersArray w kolejności rosnącej, ponieważ zwracane
z funkcji porównującej wartości 1, 0 oraz -1 przekazują jej następujące informacje:
Ćwiczenie
1: pierwszy z porównywanych elementów należy umieścić za drugim;
0: elementy są sobie równe, więc można je zostawić w dotychczasowym położeniu;
-1: pierwszy z porównywanych elementów należy umieścić przed drugim.
Zmiana sposobu sortowania na malejący sprowadza się do zmiany sposobu działania kodu na
przeciwny: zwrócenie wartości 1 powinno oznaczać, że drugi z porównywanych elementów
należy umieścić za pierwszym, a wartości -1, że drugi z elementów należy umieścić przed
pierwszym (zwrócenie wartości 0 oznacza to samo). Napisz funkcję porównującą, która
umożliwia posortowanie tablicy liczb w kolejności malejącej.
function compareNumbersDesc(num1, num2) {
if (_____ > _____) {
return 1;
} else if (num1 === num2) {
return 0;
} else {
return -1;
}
}
484
Rozdział 10.
Funkcje pierwszej klasy
W międzyczasie w firmie Cola sieciowicka
Nadszedł czas, abyś uzbrojony w swoją nową wiedzę dotyczącą sortowania tablic w końcu
pomógł firmie Cola sieciowicka. Oczywiście, musisz jedynie napisać funkcję porównującą,
jednak zanim się do tego zabierzesz, przyjrzyj się jeszcze raz tablicy products.
Ale tego nie musimy mówić
przedstawicielom firmy Cola
sieciowicka.
var products = [ { name: ”Grejpfrut”, calories: 170, color: ”czerwony”, sold: 8200 },
{ name: ”Pomarañcza”, calories: 160, color: ”pomarañczowy”, sold: 12101 },
{ name: ”Cola”, calories: 210, color: ”karmelowy”, sold: 25412 },
{ name: ”Cola dietetyczna”, calories: 0, color: ”karmelowy”, sold: 43922 },
{ name: ”Cytryna”, calories: 200, color: ”bezbarwny”, sold: 14983 },
{ name: ”Malina”, calories: 180, color: ”róĝowy”, sold: 9427 },
{ name: ”Piwo korzenne”, calories: 200, color: ”karmelowy”, sold: 9909 },
{ name: ”Woda”, calories: 0, color: ”bezbarwny”, sold: 62123 }
];
Pamiętaj, że każdy element tablicy products jest obiektem.
Nie chcemy porównywać ze sobą samych obiektów, chcemy
porównywać ich właściwości, takie jak name czy sold.
A zatem od czego zaczniemy sortowanie? Na pierwszy ogień wybierzemy sortowanie na podstawie liczby
sprzedanych butelek, w kolejności rosnącej. W tym celu będziemy musieli porównywać właściwości sold
poszczególnych obiektów. Powinieneś jednak zwrócić uwagę na jeszcze jedną rzecz: ponieważ tablica
products zawiera obiekty, zatem do naszej funkcji porównującej będą przekazywane dwa obiekty, a nie
dwie liczby.
function compareSold(colaA, colaB) {
if (colaA.sold > colaB.sold) {
return 1;
Funkcja compareSold pobiera dwa
obiekty reprezentujące produkowane
napoje i porównuje wartość
właściwości sold parametru colaA
z wartością właściwości sold
parametru colaB.
} else if (colaA.sold === colaB.sold) {
return 0;
} else {
return -1;
}
}
Jeśli chcesz, uprość i skróć ten kod
w taki sam sposób, jaki zastosowałeś
w ostatnim ćwiczeniu.
Ta funkcja sprawi, że metoda
sort będzie sortować produkty na
podstawie liczby sprzedanych butelek,
w kolejności rosnącej.
Oczywiście, aby użyć funkcji compareSold do posortowania tablicy products,
wystarczy wywołać jej metodę sort.
products.sort(compareSold);
Pamiętaj, że metody sort można używać do sortowania tablic
zawierających elementy dowolnego typu (liczby, łańcuchy znaków,
obiekty) oraz do sortowania w dowolnej kolejności (rosnącej
i malejącej). Dzięki przekazywaniu do niej funkcji porównującej
zyskujemy elastyczność i możliwość wielokrotnego stosowania
kodu.
jesteś tutaj
485
Testowanie sortowania napojów Cola sieciowicka
Weźmy teraz sortowanie na jazdę próbną
Nadszedł czas, by przetestować kod napisany dla firmy Cola sieciowicka. Cały kod napisany na kilku ostatnich
stronach znajdziesz poniżej, wraz z kilkoma dodatkami, które pozwalają go dokładnie przetestować. Utwórz
zatem prostą stronę WWW (cola.html), umieść w niej ten kod i wypróbuj go.
var products = [ {
{
{
{
{
{
{
{
];
name:
name:
name:
name:
name:
name:
name:
name:
”Grejpfrut”, calories: 170, color: ”czerwony”, sold: 8200 },
”Pomarañcza”, calories: 160, color: ”pomarañczowy”, sold: 12101 },
”Cola”, calories: 210, color: ”karmelowy”, sold: 25412 },
”Cola dietetyczna”, calories: 0, color: ”karmelowy”, sold: 43922 },
”Cytryna”, calories: 200, color: ”bezbarwny”, sold: 14983 },
”Malina”, calories: 180, color: ”róĝowy”, sold: 9427 },
”Piwo korzenne”, calories: 200, color: ”karmelowy”, sold: 9909 },
”Woda”, calories: 0, color: ”bezbarwny”, sold: 62123 }
function compareSold(colaA, colaB) {
if (colaA.sold > colaB.sold) {
return 1;
} else if (colaA.sold === colaB.sold) {
return 0;
} else {
return -1;
}
}
To jest funkcja porównująca,
którą przekażemy do metody
sort…
function printProducts(products) {
for (var i = 0; i < products.length; i++) {
console.log(”Nazwa: ” + products[i].name +
”, liczba kalorii: ” + products[i].calories +
”, kolor: ” + products[i].color +
”, liczba sprzedanych butelek: ” + products[i].sold);
}
}
A zatem najpierw sortujemy tablicę
products przy użyciu metody sort
i funkcji compareSold…
products.sort(compareSold);
printProducts(products);
…a następnie
niki.
wyświetlamy wy
u
po posortowani
óre uzyskaliśmy
ć
A oto wyniki, ktcą funkcji compareSold. Zwró
tablicy za pomoczególne produkty zostały
uwagę, że posz podstawie liczby sprzedanych
posortowane na
butelek.
486
Rozdział 10.
,
…a to jest nowa funkcja
którą napisaliśmy w celu
,
wyświetlania produktów
by ładnie wyglądały
w oknie konsoli.
esz
(Jeśli po prostu napisz
console.log(products), też nie
zobaczysz wyniki, jednak iej).
lep
będą one wyglądały naj
Konsola JavaScript
Nazwa: Grejpfrut, liczba kalorii: 170, kolor: czerwony, liczba sprzedanyc
h butelek: 8200
Nazwa: Malina, liczba kalorii: 180, kolor: róĝowy, liczba sprzedanych butelek:
9427
Nazwa: Piwo korzenne, liczba kalorii: 200, kolor: karmelowy, liczba sprzedanyc
h butelek: 9909
Nazwa: Pomarañcza, liczba kalorii: 160, kolor: pomarañczowy, liczba sprzedanyc
h butelek: 12101
Nazwa: Cytryna, liczba kalorii: 200, kolor: bezbarwny, liczba sprzedanyc
h butelek: 14983
Nazwa: Cola, liczba kalorii: 210, kolor: karmelowy, liczba sprzedanych
butelek: 25412
Nazwa: Cola dietetyczna, liczba kalorii: 0, kolor: karmelowy, liczba sprzedanyc
h butelek: 43922
Nazwa: Woda, liczba kalorii: 0, kolor: bezbarwny, liczba sprzedanych butelek:
62123
Funkcje pierwszej klasy
Ćwiczenie
Skoro już mamy rozwiązanie pozwalające sortować napoje na podstawie liczby sprzedanych butelek,
nadszedł czas, aby napisać funkcje porównujące, które operują na pozostałych właściwościach
obiektu produktu, czyli name, calories oraz color. Uważnie sprawdzaj wyniki wyświetlane
w oknie konsoli; dla każdego rodzaju sortowania upewnij się, że wyniki faktycznie zostały
odpowiednio posortowane. Sprawdź nasze odpowiedzi zamieszczone pod koniec tego rozdziału.
Poniżej zapisz swoje rozwiązania dla
kolejnych trzech funkcji porównujących.
function compareName(colaA, colaB) {
s
Podpowiedź: także podczao możesz
neg
ycz
bet
alfa
a
ani
sortow
oraz ==.
używać operatorów <, >
}
function compareCalories(colaA, colaB) {
}
function compareColor(colaA, colaB) {
Panowie, świetnie
sobie poradziliście!
}
products.sort(compareName);
console.log(”Produkty posortowane wedïug nazwy:”);
printProducts(products);
Dla każdej
nowej funkcji
porównującej
musisz wywołać
metodę sort
i wyświetlić wyniki
w oknie konsoli.
products.sort(compareCalories);
console.log(”Produkty posortowane wedïug liczby kalorii:”);
printProducts(products);
products.sort(compareColor);
console.log(”Produkty posortowane wedïug koloru:”);
printProducts(products);
jesteś tutaj
487
Podsumowanie rozdziału
CELNE SPOSTRZEŻENIA
Q
Funkcje można definiować na dwa
sposoby: przy użyciu deklaracji funkcji
oraz wyrażenia funkcyjnego.
Q
Kiedy przeglądarka przetwarza wyrażenie
funkcyjne, tworzy funkcję, jednak to od nas
zależy, co zrobimy z referencją do tej funkcji.
Q
Referencja do funkcji to wartość, która
odwołuje się do danej funkcji.
Q
Q
Deklaracje funkcji są przetwarzane przed
rozpoczęciem wykonywania kodu.
Wartości pierwszej klasy można zapisywać
w zmiennych, przekazywać do funkcji oraz
zwracać z funkcji jako wyniki ich wykonania.
Q
Referencja do funkcji jest wartością
pierwszej klasy.
Q
Metoda sort tablic wymaga przekazania
funkcji, która wie, jak należy porównywać
dwie wartości zapisane w tej tablicy.
Q
Funkcja przekazywana w wywołaniu
metody sort powinna zwracać
następujące wartość: liczbę większą od 0,
0 oraz liczbę mniejszą od 0.
Q
Q
488
Wyrażenia funkcyjne są przetwarzane
podczas wykonywania pozostałych
fragmentów kodu.
Kiedy przeglądarka przetwarza deklarację
funkcji, tworzy samą funkcję oraz zmienną
o nazwie odpowiadającej nazwie funkcji,
w której zapisuje referencję do tej funkcji.
Rozdział 10.
Funkcje pierwszej klasy
Rozwiązania ćwiczeń
Zaostrz ołówek
Rozwiązanie
Jakie wnioski dotyczące deklaracji funkcji oraz wyrażeń funkcyjnych możesz
wyciągnąć na podstawie sposobu, w jaki przeglądarka traktuje kod funkcji
quack i fly? Zaznacz każde z poniższych stwierdzeń, które jest słuszne.
Poniżej podaliśmy nasze odpowiedzi.
‰ Deklaracje funkcji są przetwarzane przed
przetworzeniem pozostałych fragmentów
kodu.
‰ Wyrażenia funkcyjne są przetwarzane
później, wraz z pozostałymi fragmentami
kodu.
‰ Deklaracje funkcji nie zwracają referencji
do utworzonej funkcji; zamiast tego tworzą
zmienną o nazwie odpowiadającej nazwie
funkcji i zapisują w niej tę funkcję.
‰ Wyrażenie funkcyjne zwraca referencję
do nowej funkcji, utworzonej przez to
wyrażenie.
‰ Proces wywoływania funkcji utworzonej
przy użyciu deklaracji jest dokładnie taki sam
jak funkcji utworzonej za pomocą wyrażenia
funkcyjnego.
‰ Referencje do funkcji można zapisywać
i przechowywać w zmiennych.
‰ Deklaracje funkcji są instrukcjami,
natomiast wyrażenia funkcyjne są używane
w instrukcjach.
‰ Deklaracje są starym i sprawdzonym
sposobem tworzenia funkcji.
‰ Zawsze staramy się używać deklaracji
funkcji, gdyż są one przetwarzane wcześniej.
Niekoniecznie!
jesteś tutaj
489
Rozwiązanie ćwiczenia
BĄDŹ przeglądarką. Rozwiązanie
Poniżej znajdziesz kod JavaScript. Twoim zadaniem jest wcielić się w rolę
przeglądarki, która go wykonuje. W ramce widocznej z prawej strony
zapisz wszystkie tworzone funkcje. Pamiętaj, by przeanalizować kod dwa
razy: za pierwszym razem masz przetworzyć tylko deklaracje funkcji,
natomiast za drugim masz obsłużyć wyrażenia funkcyjne.
var midi = true;
var type = ”piano”;
var midiInterface;
Zapisz w odpowiedniej kolejności nazwy
budowanych funkcji. Jeśli funkcja została o,
utworzona przy użyciu wyrażenia funkcyjneg
ano.
zanotuj nazwę zmiennej, w której ją zapis
Pierwszą funkcję zapisaliśmy za Ciebie.
function play(sequence) {
// tu jest jakiĂ kod
}
var pause = function() {
stop();
}
function stop() {
// tu jest jakiĂ kod
}
function createMidi() {
// tu jest jakiĂ kod
}
if (midi) {
midiInterface = function(type) {
// tu jest jakiĂ kod
};
}
490
Rozdział 10.
play
stop
createMidi
pause
midiInterface
Funkcje pierwszej klasy
Zaostrz ołówek
Rozwiązanie
Aby dobrze zrozumieć pomysł traktowania funkcji jako wartości, zagrajmy w pewną
dobrze znaną grę losową. Konkretnie rzecz biorąc, spróbujmy zagrać w trzy orzeszki.
Uda Ci się wygrać, czy przegrasz? Spróbuj, aby się przekonać. Poniżej zamieściliśmy
nasze rozwiązanie.
var winner = function() { alert(”Farciarz!”) };
var loser = function() { alert(”Leszczu!”) };
// W ramach rozgrzewki przetestujmy funkcjÚ.
winner();
Pamiętaj, że te zmienne
zawierają referencje do funkcji
winner i loser. Referencje te
możemy dowolnie zapisywać
w innych zmiennych, podobnie
jak wszelkie inne wartości.
// Dla praktyki okreĂlmy wartoĂci zmiennych.
var a = winner;
Pamiętaj, że w dowolnym momencie
możemy użyć takiej referencji,
by wywołać funkcję.
var b = loser;
var c = loser;
a();
b();
// A teraz spróbuj swojego szczÚĂcia w grze.
c = a;
a = b;
b = c;
c = a;
a = c;
a = b;
b = c;
c odwołuje się do winner
a odwołuje się do loser
b odwołuje się do winner
c odwołuje się do loser
a odwołuje się do loser
a odwołuje się do winner
b odwołuje się do loser
wywołujemy funkcję…
winner!!
a();
jesteś tutaj
491
Rozwiązania ćwiczeń
Zaostrz ołówek
Rozwiązanie
Rozgrzej swój mózg i przygotuj go na przekazanie swojej pierwszej funkcji do
innej funkcji. Spróbuj wykonać poniższy kod (w głowie) i przekonaj się, jaki
będzie tego efekt. Poniżej zamieściliśmy rozwiązanie.
function sayIt(translator) {
var phrase = translator(”Witam”);
alert(phrase);
Definiujemy funkcję, której argumentem
jest funkcja, a następnie tę przekazaną
funkcję wywołujemy.
}
function hawaiianTranslator(word) {
if (word === ”Witam”) return ”Aloha”;
if (word === ”Do widzenia”) return ”Aloha”;
}
sayIt(hawaiianTranslator);
Ćwiczenie
Rozwiązanie
Do funkcji sayIt
przekazujemy funkcję
hawaiianTranslator.
Znowu przyszła Twoja kolej: napisz funkcję, która przy użyciu metody console.log będzie
wyświetlać w oknie konsoli nazwiska pasażerów oraz informację, czy dana osoba zapłaciła,
czy nie. Przekaż ją w wywołaniu funkcji processPassengers, aby ją przetestować.
Rozpoczęliśmy już pisanie kodu tej funkcji, Ty musisz ją jedynie dokończyć. Poniżej znajdziesz
nasze rozwiązanie.
function printPassenger(passenger) {
var message = passenger.name;
if (passenger.paid === true) {
message = message + ” zapïaciï(a).”;
} else {
message = message + ” nie zapïaciï(a).”;
}
console.log(message);
return false;
}
szego
Ta zwracana wartość nie ma więk
cji
funk
ie
kodz
w
gdyż
a,
znaczeni
rujemy.
processPassengers i tak ją igno
processPassengers(passengers, printPassenger);
492
Rozdział 10.
Konsola JavaScript
-DQND3ÚWOLFND]DSïDFLï D 'U=DWDQ]DSïDFLï D 6WHID:ïDĂFLZDQLH]DSïDFLï D
-DQHN)XQNF\MQLDN]DSïDFLï D Funkcje pierwszej klasy
Zaostrz ołówek
Rozwiązanie
Poniżej utworzyliśmy funkcję i przypisaliśmy ją zmiennej o nazwie fun.
function fun(echo) {
function fun(echo) {
console.log(echo);
}
console.log(echo);
};
zypadku funkcji
Ostrzeżenie: w pr zeglądarka może
pr
oja
Tw
o
bo
i
fun
nie konsoli nieco
wyświetlić w ok Spróbuj wykonać
i.
nik
wy
odmienne
kilku różnych
to ćwiczenie w
.
przeglądarkach
fun
Przeanalizuj poniższy kod i zapisz wygenerowane wyniki. Spróbuj wykonać to w głowie,
a dopiero potem sprawdź wyniki na komputerze.
fun(”Witam”);
Witam
function boo(aFunction) {
aFunction(”bum”);
}
boo(fun);
console.log(fun);
fun(boo);
bum
function fun(echo) { console.log(echo); }
function boo(aFunction) { aFunction(“bum”); }
var moreFun = fun;
moreFun(”Witam ponownie”);
function echoMaker() {
Witam ponownie
return fun;
ty!
Dodatkowe punk go,
te
k
ma
ds
ze
pr
(To
co nadchodzi…)
}
var bigFun = echoMaker();
bigFun(”Czy to echo?”);
Czy to echo?
zrozum odpowiedzi!
Superważne: zanim przejdziesz dalej, sprawdź i dobrze
jesteś tutaj
493
Rozwiązanie ćwiczenia
Twoim zadaniem jest dodanie do naszego przykładu trzeciej klasy — turystycznej klasy premium,
w skrócie ”premium”. Pasażerowie tej klasy, oprócz coli i wody, mogą także zamówić wino.
Dodatkowo zaimplementuj funkcję getDinnerOrderFunction oferującą następujące menu.
Ćwiczenie
Rozwiązanie
Klas pierwsza: kurczak lub makaron.
Turystyczna klasa premium: przekąska lub talerz serów.
Klasa turystyczna: orzeszki lub precelki.
Poniżej zamieściliśmy nasze rozwiązanie:
var passengers = [
{ name: ”-anka PÚtlicka”, paid: true, ticket: ”turystyczna” },
{ name: ”Dr Zatan”, paid: true, ticket: ”pierwsza klasa” },
{ name: ”Stefa WïaĂciwa”, paid: false, ticket: ”pierwsza klasa” },
{ name: ”-anek Funkcyjniak”, paid: true, ticket: ”premium” } ];
śliśmy
function createDrinkOrder(passenger) {
Podczas tego lotu przenie
a Janka Funkcyjniaka do
pan
var orderFunction;
m
turystycznej klasy premiu y kod).
if (passenger.ticket === ”pierwsza klasa”) {
(by przetestować nasz now
orderFunction = function() {
alert(”PodaÊ koktajl czy wino?”);
};
} else if (passenger.ticket === ”premium”) {
orderFunction = function() {
To jest nowy kod obsługujący
turystyczną klasę premium. Teraz
alert(”PodaÊ wino, colÚ czy wodÚ?”);
zwracamy jedną z trzech różnych
};
funkcji obsługujących oferowanie
napojów, dobieranych zależnie od
} else {
typu biletu.
orderFunction = function() {
alert(”PodaÊ colÚ czy wodÚ?”);
};
}
return orderFunction;
wygodne jest
Zwróć uwagę, jakcałej logiki w jednej
}
tej
umieszczenie
jak utworzyć
funkcji, która wie,ję obsługującą
kc
fun
odpowiednią
i dostosowaną do
oferowanie napojów ra.
że
konkretnego pasa
ie do roznoszenia
A kiedy już przyjdz y wykonywać
napojów, nie musim
bo dysponujemy
tej całej logiki — waną do danego
so
sto
do
ją
kc
już fun
pasażera!
494
Rozdział 10.
Funkcje pierwszej klasy
function createDinnerOrder(passenger) {
var orderFunction;
if (passenger.ticket === ”pierwsza klasa”) {
orderFunction = function() {
alert(”PodaÊ kurczaka czy makaron?”);
};
} else if (passenger.ticket === ”premium”) {
orderFunction = function() {
alert(”PodaÊ przekÈskÚ czy talerz serów?”);
};
} else {
orderFunction = function() {
alert(”PodaÊ orzeszki czy precelki?”);
};
}
return orderFunction;
}
Ćwiczenie
Rozwiązanie
Dodaliśmy zupełnie
nową funkcję,
createDinnerOrder,
służącą do tworzenia
funkcji do zbierania
zamówień na posiłek.
Działa ona dokładnie
w taki sam
sposób jak funkcja
createDrinkOrder:
sprawdza typ biletu
pasażera i w zależności
od niego zwraca
odpowiednią funkcję.
function serveCustomer(passenger) {
var getDrinkOrderFunction = createDrinkOrder(passenger);
var getDinnerOrderFunction = createDinnerOrder(passenger);
getDrinkOrderFunction();
Tworzymy odpowiednią
funkcję do zbierania
zamówień na posiłek…
// Zbieramy zamówienie na posiïek.
getDinnerOrderFunction();
…a następnie wywołujemy ją, kiedy
getDrinkOrderFunction();
getDrinkOrderFunction();
// WyĂwietlamy film.
getDrinkOrderFunction();
// Zbieramy Ămieci.
chcemy, by pasażer wybrał jeden
z oferowanych posiłków.
}
function servePassengers(passengers) {
for (var i = 0; i < passengers.length; i++) {
serveCustomer(passengers[i]);
}
}
servePassengers(passengers);
jesteś tutaj
495
Rozwiązania ćwiczeń
Zaostrz ołówek
Rozwiązanie
Jak sądzisz, co robi przedstawiony poniżej fragment kodu? Czy potrafisz podać
jakieś przykłady jego użycia? Poniżej zamieściliśmy nasze rozwiązanie:
function addN(n) {
var adder = function(x) {
return n + x;
};
Ta funkcja wymaga przekazania jednego
argumentu, n. Następnie tworzy funkcję,
która także wymaga przekazania jednego
argumentu, x, i która dodaje wartości n i x.
Zwracana jest ta nowo utworzona funkcja.
return adder;
}
A zatem tutaj użyliśmy funkcji addN do
utworzenia funkcji, która do przekazanego
argumentu dodaje 2,
Metoda sort posortowała tablicę
numbersArray w kolejności rosnącej,
ponieważ zwracane z funkcji porównującej
wartości 1, 0 oraz -1 przekazują jej
następujące informacje:
Ćwiczenie
Rozwiązanie
0: elementy są sobie równe, więc można je zostawić
w dotychczasowym położeniu;
-1: pierwszy z porównywanych elementów należy umieścić
przed drugim.
Zmiana sposobu sortowania na malejący sprowadza się do
zmiany sposobu działania kodu na przeciwny: zwrócenie
wartości 1 powinno oznaczać, że drugi z porównywanych
elementów należy umieścić za pierwszym, a wartości -1,
że drugi z elementów należy umieścić przed pierwszym
(zwrócenie wartości 0 oznacza to samo). Napisz funkcję
porównującą, która umożliwia posortowanie tablicy liczb
w kolejności malejącej.
function compareNumbersDesc(num1, num2) {
if (num2 > num1 ) {
return 1;
} else if (num1 === num2) {
return 0;
} else {
return -1;
}
}
Rozdział 10.
console.log(add2(100));
Zaostrz ołówek
Rozwiązanie
1: pierwszy z porównywanych elementów należy umieścić
za drugim;
496
var add2 = addN(2);
console.log(add2(10));
Już wiesz, że funkcja porównująca, którą należy
przekazać do metody sort, ma zwracać wartość
większą od zera, równą zero lub mniejszą od zera,
zależnie od dwóch porównywanych wartości:
jeśli pierwsza z wartości jest większa od drugiej,
mamy zwrócić wartość większą od zera, jeśli obie
porównywane wartości są sobie równe, mamy
zwrócić zero, a jeśli pierwsza wartość jest mniejsza od
drugiej, mamy zwrócić wartość mniejszą od zera.
Czy potrafisz wykorzystać tę wiedzę oraz fakt, że
funkcja compareNumbers porównuje liczby, by
zmodyfikować ją i w znaczący sposób skrócić jej kod?
A oto nasze rozwiązanie:
function compareNumbers(num1, num2) {
return num1 - num2;
}
Możemy zapisać całą logikę funkcji w jednym
wierszu, zwracając z niej wartość różnicy
num1 – num2. Przeanalizuj kilka przykładów,
by przekonać się, jak działa to rozwiązanie.
Pamiętaj przy tym, że metoda sort oczekuje
liczby większej od 0, 0 lub liczby mniejszej
od 0, a nie konkretnych wartości 1, 0 i –1
(choć często można spotkać kod, który
właśnie je zwraca).
Funkcje pierwszej klasy
Ćwiczenie
Rozwiązanie
Skoro już mamy rozwiązanie pozwalające sortować napoje na podstawie liczby sprzedanych
butelek, nadszedł czas, aby napisać funkcje porównujące operujące na pozostałych
właściwościach obiektu produktu, czyli name, calories oraz color. Uważnie sprawdzaj
wyniki wyświetlane w oknie konsoli; dla każdego rodzaju sortowania upewnij się, że wyniki
faktycznie zostały odpowiednio posortowane. Poniżej przedstawiliśmy nasze rozwiązanie.
Oto są nasze implementacje
każdej z funkcji porównujących.
function compareName(colaA, colaB) {
if (colaA.name > colaB.name) {
return 1;
} else if (colaA.name === colaB.name) {
return 0;
} else {
return -1;
}
}
function compareCalories(colaA, colaB) {
if (colaA.calories > colaB.calories) {
return 1;
} else if (colaA.calories === colaB.calories) {
return 0;
} else {
return -1;
}
}
function compareColor(colaA, colaB) {
if (colaA.color > colaB.color) {
return 1;
} else if (colaA.color === colaB.color) {
return 0;
} else {
return -1;
}
}
products.sort(compareName);
console.log(”Produkty posortowane wedïug nazwy:”);
printProducts(products);
Bez dwóch zdań!
Panowie, świetnie
sobie poradziliście!
Dla każdej
nowej funkcji
porównującej
musisz wywołać
metodę sort
i wyświetlić wyniki
w oknie konsoli.
products.sort(compareCalories);
console.log(”Produkty posortowane wedïug liczby kalorii:”);
printProducts(products);
products.sort(compareColor);
console.log(”Produkty posortowane wedïug koloru:”);
printProducts(products);
jesteś tutaj
497
498
Rozdział 10.
11.)XQNFMHDQRQLPRZH]DVLÚJLGRPNQLÚFLD
Poważne funkcje
Od kiedy poznałem
funkcje anonimowe,
moja siła wyrazu
powiększyła się o 200%.
W poprzednim rozdziale rozłożyłeś funkcje na czynniki pierwsze,
ale wciąż musisz się o nich jeszcze sporo dowiedzieć. W tym rozdziale
będzie prawdziwa jazda na całego. Pokażemy Ci, jak naprawdę korzysta się z funkcji.
To nieszczególnie długi rozdział, jednak bardzo intensywny, a po jego przeczytaniu
siła wyrazu tworzonego przez Ciebie kodu JavaScript będzie większa, niż mógłbyś
przypuszczać. Co więcej, mamy w nim zamiar przedstawić pewne ogólnie przyjęte
idiomy i konwencje związane z tworzeniem i stosowaniem funkcji w języku JavaScript,
dzięki czemu będziesz już mógł skorzystać z kodu pisanego przez współpracowników
lub czerpanego z ogólnie dostępnych bibliotek JavaScript. A jeśli jeszcze nigdy nie słyszałeś
o funkcjach anonimowych i domknięciach (ang. closure), to wiedz, że znalazłeś się
w odpowiednim miejscu.
A może słyszałeś o domknięciach,
lecz nie wiesz, co to jest? Tym
bardziej jest to rozdział, który
powinieneś przeczytać!
to jest nowy rozdział
499
Wprowadzenie do funkcji anonimowych
Rzut oka na inną stronę funkcji…
Poznałeś już dwie strony funkcji: tę formalną, deklaratywną stronę funkcji oraz znacznie bardziej
ekspresyjną stronę wyrażeń funkcyjnych. A teraz nadszedł czas, by przedstawić ich jeszcze inną,
interesującą stronę, czyli funkcje anonimowe.
Mówiąc o funkcjach anonimowych, mamy na myśli funkcje, które nie mają nazwy. Jak coś takiego
jest możliwe? No cóż, kiedy tworzymy funkcje z wykorzystaniem deklaracji, bez wątpienia
mają one nazwy. Jednak w przypadku, gdy budujemy funkcje przy użyciu wyrażeń funkcyjnych,
nie musimy podawać ich nazw.
Pewnie sobie pomyślałeś, że to całkiem interesujące i zapewne naprawdę można tak zrobić, ale co
z tego? Kiedy użyjemy funkcji anonimowych, niejednokrotnie możemy znacząco skrócić nasz kod,
a także sprawić, że będzie bardziej zwięzły i czytelny, efektywniejszy i łatwiejszy w utrzymaniu.
A zatem, przekonajmy się, jak można używać funkcji anonimowych. Zaczniemy od fragmentu
kodu, który poznałeś już wcześniej, i pokażemy, jak wykorzystywać funkcje anonimowe.
Najpierw definiujemy funkcję.
Funkcja ta ma nazwę handler.
To jest procedura obsługi zdarzeń load,
którą tworzymy w standardowy sposób.
function handler() { alert(”O tak, strona zostaïa wczytana!”); }
window.onload = handler;
Następnie zapisujemy tę funkcję we
właściwości onload obiektu window,
używając jej nazwy handler.
Zaostrz ołówek
500
Skorzystaj ze swojej wiedzy na temat funkcji i zmiennych,
aby wskazać, które z poniższych stwierdzeń są prawdziwe.
‰
Zmienna handler zawiera referencję do funkcji.
‰
Kiedy przypisujemy handler właściwości
window.onload, zapisujemy w niej referencję do
funkcji.
‰
Jedynym powodem istnienia zmiennej handler
jest zapisanie jej we właściwości window.onload.
‰
Już nigdy więcej nie użyjemy funkcji handler,
gdyż jest to kod, który z założenia ma być
wykonywany wyłącznie podczas pierwszego
wczytania strony.
Rozdział 11 .
A kiedy strona zostanie
wczytana, przeglądarka
wywoła funkcję handler.
‰
Dwukrotne wywoływanie procedury obsługi
zdarzeń load nie jest dobrym pomysłem — może
ono doprowadzić do wystąpienia problemów,
gdyż ten kod służy zazwyczaj do wykonywania
czynności związanych z inicjalizacją całej strony.
Wyrażenia funkcyjne tworzą referencje do funkcji.
Czy wspominaliśmy, że przypisując funkcję
handler właściwości window.onload,
zapisujemy w niej referencję do funkcji?
Funkcje anonimowe, zasięg i domknięcia
Jak używać funkcji anonimowych?
A zatem chcemy utworzyć funkcję do obsługi zdarzeń load, wiemy jednak, że jest to „funkcja
jednorazowa”, gdyż zdarzenie to jest generowane tylko jeden raz podczas całego okresu prezentacji
strony w przeglądarce. Możemy także zauważyć, że we właściwości window.onload jest zapisywana
referencja do funkcji — a konkretnie rzecz biorąc, referencja do funkcji handler. Ponieważ
jednak funkcja ta jest przeznaczona tylko do jednokrotnego użycia, zatem określanie jej nazwy jest
niepotrzebne, gdyż używamy jej jedynie po to, by zapisać referencję we właściwości window.onload.
Zastosowanie funkcji anonimowej umożliwia oczyszczenie naszego kodu. Funkcja anonimowa jest
po prostu wyrażeniem funkcyjnym, pozbawionym nazwy i zapisanym w miejscu, w którym normalnie
użylibyśmy referencji do funkcji. Aby jednak to wszystko ze sobą powiązać, przeanalizujemy przykład
wykorzystania wyrażenia funkcyjnego w sposób anonimowy.
function handler() { alert(”O tak, strona zostaïa wczytana!”); }
Najpierw usuwamy zmienną
handler, tworząc tym samym
wyrażenie funkcyjne.
window.onload = handler;
function () { alert(”O tak, strona zostaïa wczytana!”); }
window.onload =
;
Następnie przypisujemy je
bezpośrednio właściwości
window.onload.
window.onload = function() { alert(”O tak, strona zostaïa wczytana!”); }
Teraz kod jest znacznie bardziej zwarty.
Niezbędną funkcję przypisujemy bezpośrednio
właściwości onload. Oprócz tego, nie tworzymy
nazwy funkcji, która przypadkowo mogłaby
zostać użyta w innym miejscu kodu (w końcu
nazwa „handler” jest stosowana dosyć często!).
Rany, popatrz!
Nie ma nazwy!
Teraz procedura
obsługi została
przypisana
bezpośrednio
właściwości
window.onload,
bez konieczności
stosowania
niepotrzebnej nazwy.
WYSIL
SZARE KOMÓRKI
Czy w którymś z przykładów przedstawionych wcześniej w tej książce były używane funkcje anonimowe,
choć nie uprzedzaliśmy o tym?
Podpowiedź: może ukrywają się gdzieś w Twoich obiektach?
jesteś tutaj
501
Ćwiczenia z funkcji anonimowych
Zaostrz ołówek
Przedstawiony poniżej fragment kodu zapewnia kilka możliwości zastosowania funkcji
anonimowych. Skorzystaj z nich i użyj funkcji anonimowych wszędzie tam, gdzie to możliwe.
Możesz przekreślić stary kod i obok napisać nowy. I jeszcze jedna rzecz: zakreśl wszystkie
anonimowe funkcje, które już są używane.
window.onload = init;
var cookies = {
instructions: ”WstÚpne rozgrzewanie do 175 stopni...”,
bake: function(time) {
console.log(”Wypiekam ciasteczka.”);
setTimeout(done, time);
}
};
function init() {
var button = document.getElementById(”bake”);
button.onclick = handleButton;
}
function handleButton() {
console.log(”-uĝ moĝna wypiekaÊ ciasteczka.”);
cookies.bake(2500);
}
function done() {
alert(”Ciasteczka sÈ gotowe, wyciÈgnij je, by przestygïy.”);
console.log(”Chïodzenie ciasteczek.”);
var cool = function() {
alert(”Ciasteczka sÈ juĝ zimne, moĝna je jeĂÊ!”);
};
setTimeout(cool, 1000);
}
502
Rozdział 11 .
Funkcje anonimowe, zasięg i domknięcia
Musimy ponownie pomówić o rozwlekłości Twojego kodu
Bardzo nam się nie podoba, że musimy wracać do tego zagadnienia, zwłaszcza że
poświęciłeś dużo wysiłku na naukę funkcji — wiesz, jak je wywoływać, jak przypisywać
zmiennym, jak przekazywać do innych funkcji i jak zwracać jako wynik wykonania innych
funkcji — jednak wciąż pisany przez Ciebie kod jest bardziej rozwlekły, niż to konieczne
(można by także powiedzieć, że nie jesteś tak ekspresyjny, jak mógłbyś być). Przyjrzyj się
poniższemu przykładowi.
ie
To zwyczajna funkcja o nazw komunikat
cookieAlarm, która wyświetla już gotowe.
informujący, że ciasteczka są
function cookieAlarm() {
alert(”-uĝ czas wyjÈÊ ciasteczka z piekarnika.”);
};
Wygląda na to, że ciasteczka
będą gotowe za 10 minut…
tak tylko mówię.
setTimeout(cookieAlarm, 600000);
A tu bierzemy tę funkcję i przekazujemy ją
jako argument wywołania metody setTimeout.
Gdybyś zapomniał, to ten czas jest
wyrażony w milisekundach, a zatem
1000 · 60 · 10 = 600 000.
Choć ten kod wygląda całkiem dobrze, jednak po zastosowaniu funkcji anonimowej możemy go nieco
skrócić. W jaki sposób? No cóż… Pomyśl o zmiennej cookieAlarm umieszczonej w wywołaniu metody
setTimeout. To zmienna, która odwołuje się do funkcji, a zatem w momencie wywoływania metody
setTimeout jest do niej przekazywana referencja do funkcji. Już wiesz, że użycie zmiennej zawierającej
referencję do funkcji jest jednym z kilku sposobów uzyskania takiej referencji, jednak — podobnie jak
w przedstawionym kilka stron wcześniej przykładzie z właściwością window.onload — także i tu możemy
skorzystać z funkcji anonimowej. Zmodyfikujmy zatem ten kod, używając w nim wyrażenia funkcyjnego.
ołaniu metody
Teraz zamiast zmiennej w wyw funkcję,
inną
y
jem
kazu
setTimeout prze
w wywołaniu.
zapisując jej kod bezpośrednio
Zwróć szczególną uwagę na zastosowaną składnię. Użyliśmy
całego wyrażenia funkcyjnego, kończącego się zamykającym
nawiasem klamrowym, które zamknęliśmy przecinkiem
umieszczonym przed kolejnym argumentem, dokładnie
tak samo, jak robimy w przypadku wszystkich innych
argumentów w wywołaniach funkcji.
setTimeout(function() { alert(”-uĝ czas wyjÈÊ ciasteczka z piekarnika.”); }, 600000);
Zapisaliśmy nazwę wywoływanej metody,
setTimeout, za nią nawias otwierający i pierwszy
argument wywołania — wyrażenie funkcyjne.
A tu za wyrażeniem
funkcyjnym zapisaliśmy
drugi argument.
jesteś tutaj
503
Formatowanie anonimowych wyrażeń funkcyjnych
Kogo próbujecie
nabrać? Taka instrukcja tylko
wprowadzi zamieszanie. Kto chciałby
czytać taki długi wiersz kodu? A poza
tym co zrobić, jeśli funkcja będzie
długa i skomplikowana?
W krótkim kodzie taka funkcja zapisana w jednym
wierszu jest w porządku. Jednak w pozostałych
przypadkach masz rację, taki zapis byłby raczej kiepskim
pomysłem. Jednak, jak wiesz, w kodzie JavaScript można
używać dowolnie wielu odstępów, zatem możemy umieścić
w naszym kodzie tak dużo odstępów i znaków nowego
wiersza, ile trzeba, by poprawić jego czytelność. Poniżej
przedstawiliśmy nową, lepiej sformatowaną postać
wywołania metody setTimeout z poprzedniej strony.
Dodaliśmy tu jedynie trochę tzw. białych
znaków, czyli znaków odstępu i nowego
wiersza.
setTimeout(function() {
alert(”-uĝ czas wyjÈÊ ciasteczka z piekarnika.”);
}, 600000);
Cieszymy się, że poruszyłaś
ten problem, gdyż teraz nasz
kod jest znacznie bardziej
czytelny.
504
Rozdział 11 .
Funkcje anonimowe, zasięg i domknięcia
Hej, chwilkę… Chyba rozumiem.
Ponieważ wyrażenia funkcyjne zwracają
referencję do funkcji, zatem możemy
używać ich wszędzie tam, gdzie są
oczekiwane referencje do funkcji.
Trochę się nagadałeś, ale faktycznie
trafiłeś w sedno. To naprawdę jest jedno
z kluczowych zagadnień, niezbędnych do
zrozumienia, że funkcje są wartościami pierwszej
klasy. Jeśli Twój kod oczekuje referencji do
funkcji, w tym miejscu zawsze możesz umieścić
wyrażenie funkcyjne, ponieważ po przetworzeniu
daje ono referencję do funkcji. Jak się właśnie
przekonałeś, jeśli argumentem ma być funkcja,
nie ma sprawy — możesz zamiast niej przekazać
wyrażenie funkcyjne (które przed wykonaniem
wywołania zostanie zastąpione referencją do
funkcji). To samo dotyczy sytuacji, gdy z jednej
funkcji musisz zwrócić inną funkcję — możesz
zwrócić wyrażenie funkcyjne.
jesteś tutaj
505
Pytania o funkcje anonimowe
Ćwiczenie
Teraz upewnimy się, że dobrze zapamiętałeś składnię używaną do przekazywania
anonimowych wyrażeń funkcyjnych w wywołaniach innych funkcji. Zmień poniższy kod
tak, by zamiast zmiennej (w tym przypadku vaccine) argumentem wywołania było
anonimowe wyrażenie funkcyjne.
function vaccine(dosage) {
if (dosage > 0) {
inject(dosage);
}
}
iedź.
Tu zapisz swoją odpow ć
dzi
I nie zapomnij spraw
odpowiedzi, zanim
turę!
podejmiesz dalszą lek
administer(patient, vaccine, time);
Nie istnieją
głupie pytania
P: Stosowanie funkcji anonimowych
w taki sposób wydaje się bardzo
zawiłe. Czy naprawdę muszę o tym
wiedzieć?
O: Owszem, musisz. Anonimowe
wyrażenia funkcyjne bardzo często
są używane w kodzie JavaScript, jeśli
zatem chcesz nauczyć się analizy kodu
napisanego przez innych programistów lub
zrozumieć działanie bibliotek JavaScript,
musisz wiedzieć, jak one działają i jak je
rozpoznawać w kodzie.
P: Czy stosowanie anonimowych
wyrażeń funkcyjnych jest lepsze?
Uważam, że jedynie komplikuje kod
i sprawia, że trudno go czytać
i analizować.
506
Rozdział 11 .
O
: Poczekaj trochę. Po pewnym czasie,
kiedy zobaczysz kod, taki jak ten,
znacznie łatwiej będzie Ci go analizować,
a naprawdę istnieje bardzo wiele sytuacji,
w których taka składnia pozwala
zmniejszyć złożoność kodu, sprawia, że
jest bardziej przejrzysty, a nasze intencje —
łatwiejsze do zauważenia. Z drugiej strony,
przesadne wykorzystanie tej techniki
na pewno może sprawić, że kod będzie
trudniejszy do zrozumienia. Jeśli jednak
zaczniesz jej używać, po pewnym czasie
stanie się łatwiejsza i bardziej przydatna.
Na pewno spotkasz się z wieloma
przykładami kodu, który w bardzo dużym
stopniu korzysta z funkcji anonimowych,
zatem dołączenie tej techniki do
swojego przybornika z narzędziami
programistycznymi jest dobrym pomysłem.
P: Skoro funkcje pierwszej klasy są
tak użyteczne, to dlaczego nie ma ich
w innych językach programowania?
O
: Ależ są (a ludzie, którzy pracują nad
językami, w których ich nie ma, zaczynają
rozważać ich dodanie). Przykładowo
funkcje pierwszej klasy, takie jak w
JavaScripcie, są dostępne w językach
Scheme i Scala. Inne języki, takie jak
PHP, Java (w najnowszej wersji), C# oraz
Objective C, udostępniają większość
lub niektóre z ich możliwości. Wraz ze
wzrostem liczby osób rozpoznających
zalety posiadania funkcji pierwszej klasy
w używanym języku programowania coraz
więcej języków zaczyna je udostępniać.
Jednak każdy język robi to nieco inaczej,
zatem badając analogiczne możliwości
w innych językach, musisz się przygotować
na pewne różnice.
Funkcje anonimowe, zasięg i domknięcia
Kiedy funkcja zostaje zdefiniowana? To zależy…
Jest jeszcze jedna, interesująca rzecz dotycząca funkcji, o której dotąd nie wspominaliśmy. Czy pamiętasz,
że przeglądarka przetwarza kod JavaScript dwukrotnie? W ramach pierwszego przebiegu przetwarzane są
wszystkie deklaracje funkcji, a odnalezione funkcje zostają zdefiniowane. Natomiast podczas drugiego przebiegu
przeglądarka wykonuje kod JavaScript liniowo, od początku do końca; właśnie podczas tego przebiegu są
przetwarzane wyrażenia funkcyjne. A to z kolei określa, gdzie i kiedy będziemy mogli wywoływać funkcje w kodzie.
Aby przekonać się, co to wszystko naprawdę oznacza, przeanalizujemy konkretny przykład. Poniżej przedstawiliśmy
kod z poprzedniego rozdziału, w którym wprowadziliśmy nieznaczne zmiany. Spróbujmy go wykonać.
1 Zaczynamy od samego początku kodu i szukamy umieszczonych w nim deklaracji.
WAŻNE: Przeczytaj to
zgodnie z kolejnością
cyfr. Zacznij od 1, potem
przejdź do 2 itd.
4 Ponownie zaczynamy do początku kodu, jednak tym razem zaczynamy go wykonywać.
var migrating = true;
Zwróć
uwagę, że
przenieśliśmy
tę instrukcję
warunkową
z końca
kodu na jego
początek.
if (migrating) {
5 Tworzymy zmienną migrating i zapisujemy
w niej wartość true.
6 Wyrażenie warunkowe ma wartość true, więc wykonujemy blok kodu.
quack(4);
7 Pobieramy referencję do funkcji ze zmiennej quack
fly(4);
8 Pobieramy referencję do funkcji ze zmiennej
i wywołujemy ją, przekazując do niej wartość 4.
fly… Chwila, ta zmienna jeszcze nie została
zdefiniowana!
}
var fly = function(num) {
for (var i = 0; i < num; i++) {
console.log("Latam!");
}
};
2 Znaleźliśmy deklarację funkcji. Tworzymy zatem tę
function quack(num) {
funkcję i zapisujemy ją w zmiennej quack.
for (var i = 0; i < num; i++) {
console.log("Kwak!");
}
}
3 Docieramy do końca kodu. Udało się znaleźć tylko jedną deklarację funkcji.
jesteś tutaj
507
Kiedy są definiowane funkcje
Co się właśnie stało?
Dlaczego funkcja fly nie była zdefiniowana?
Konsola JavaScript
No dobrze, mogliśmy się przekonać, że funkcja fly nie jest zdefiniowana, kiedy
spróbowaliśmy ją wykonać, ale dlaczego tak się stało? Przecież funkcja quack
zadziałała bez problemów. Jak już pewnie odgadłeś, funkcja fly — w odróżnieniu
od funkcji quack, która została zdefiniowana podczas pierwszego przebiegu
przetwarzania kodu, gdyż została utworzona przy użyciu deklaracji — jest
definiowana podczas drugiego przebiegu, w trakcie którego kod jest wykonywany
od początku do końca. Jeszcze raz przyjrzyjmy się naszemu przykładowi.
Kiedy będziemy przetwarzać ten kod i spróbujemy
wywołać quack, wszystko zadziała zgodnie
z oczekiwaniami, gdyż funkcja quack została
zdefiniowana podczas pierwszego przebiegu
przetwarzania kodu.
var migrating = true;
if (migrating) {
quack(4);
fly(4);
Kwak!
Kwak!
Kwak!
Kwak!
TypeError: undefined is not a function
To się dzieje, kiedy spróbujemy
wywołać funkcję, która nie jest
zdefiniowana.
Jednak kiedy spróbujemy
wywołać funkcję fly,
zostanie wyświetlony błąd,
gdyż funkcja ta nie została
jeszcze zdefiniowana…
}
var fly = function(num) {
for (var i = 0; i < num; i++) {
console.log(”Latam!”);
…a zostanie zdefiniowana
dopiero w momencie
wykonania tej instrukcji,
czyli po wywołaniu funkcji fly.
Możesz zobaczyć komunikat
o błędzie przypominający
przedstawiony tutaj (jego
postać zależy od używanej
przeglądarki): TypeError:
Property ‘fly’ of object
[object Object] is not
a function.
}
};
function quack(num) {
for (var i = 0; i < num; i++) {
console.log(”Kwak!”);
}
}
Co to wszystko oznacza? Zacznijmy od tego, że oznacza to, iż deklaracje funkcji można umieszczać
w dowolnym miejscu kodu — na jego początku, końcu oraz pośrodku — a ich wywołania także mogą być
umieszczane w dowolnych miejscach. Deklaracje tworzą funkcje, które są zdefiniowane w całym kodzie
(rozwiązanie to jest określane jako podnoszenie lub windowanie, ang. hoisting).
Oczywiście w przypadku wyrażeń funkcyjnych sprawa wygląda inaczej, gdyż one nie będą zdefiniowane, aż do
momentu, gdy zostaną wykonane. A zatem jeśli nawet przypiszesz wyrażenie funkcyjne zmiennej globalnej,
jak zrobiliśmy w przypadku zmiennej fly, nie będziesz mógł użyć jej do wywołania funkcji, aż do momentu,
gdy zostanie ona zdefiniowana.
I jeszcze jedno. Obie funkcje w powyższym przykładzie mają zasięg globalny, co oznacza, że kiedy już zostaną
zdefiniowane, będą widoczne w całym kodzie. Trzeba także pamiętać o funkcjach zagnieżdżonych — czyli
funkcjach definiowanych wewnątrz innych funkcji — gdyż ma to wpływ na ich zasięg. Zobacz sam…
508
Rozdział 11 .
Funkcje anonimowe, zasięg i domknięcia
Zagnieżdżanie funkcji
Definiowanie funkcji wewnątrz innej funkcji jest całkowicie dopuszczalne. Oznacza to,
że wewnątrz jednej funkcji można umieścić deklarację innej lub wyrażenie funkcyjne.
A jak to działa? Oto krótka odpowiedź na to pytanie: jedyną różnicą pomiędzy funkcją
zdefiniowaną na najwyższym poziomie kodu a funkcją zdefiniowaną wewnątrz innej
jest ich zasięg. Innymi słowy, umieszczenie jednej funkcji wewnątrz innej ma wpływ na
to, w których miejscach kodu będzie ona widoczna.
Aby zrozumieć to zagadnienie, rozszerzymy nieco nasz przykład, dodając do niego
zagnieżdżone deklaracje funkcji oraz wyrażenia funkcyjne.
var migrating = true;
var fly = function(num) {
var sound = ”Latam!”;
function wingFlapper() {
Tutaj dodajemy deklarację
funkcji o nazwie wingFlapper,
umieszczoną wewnątrz wyrażenia
funkcyjnego fly.
console.log(sound);
}
for (var i = 0; i < num; i++) {
wingFlapper();
A tutaj ją wywołujemy.
}
};
function quack(num) {
var sound = ”Kwak!”;
var quacker = function() {
console.log(sound);
};
Tu dodajemy wyrażenie funkcyjne,
którego wynik jest zapisywany
w zmiennej quacker; przy czym
zarówno wyrażenie, jak i zmienna
są umieszczone wewnątrz
deklaracji funkcji quack.
for (var i = 0; i < num; i++) {
quacker();
A tutaj ją wywołujemy.
}
}
if (migrating) {
quack(4);
fly(4);
}
Ćwiczenie
Przenieśliśmy ten blok kodu na
sam koniec, zatem wywołanie
funkcji fly nie będzie już
przysparzać problemów.
Weź ołówek i zaznacz, jakie fragmenty powyższego kodu obejmuje zasięg funkcji fly, quack,
wingFlapper oraz quacker. Zaznacz także, które fragmenty kodu obejmuje zasięg tych
funkcji, lecz jednocześnie funkcje te nie są tam zdefiniowane.
jesteś tutaj
509
Zagnieżdżanie funkcji i ich zasięg
Jaki wpływ na zasięg ma zagnieżdżanie funkcji?
Funkcje zdefiniowane na głównym poziomie kodu mają zasięg globalny, natomiast
funkcje zdefiniowane wewnątrz innych funkcji mają zasięg lokalny. Przeanalizujemy
kod przedstawiony na poprzedniej stronie i sprawdzimy, jaki zasięg mają
poszczególne funkcje. Jednocześnie zastanowimy się, gdzie każda z tych funkcji
zostanie zdefiniowana (bądź też, gdzie będzie niezdefiniowana).
Wszystko, co zostało zdefiniowane na
najwyższym poziomie kodu, ma zasięg
globalny. Dlatego zarówno fly, jak
i quacker będą zmiennymi globalnymi.
var migrating = true;
Pamiętaj jednak, że funkcja fly będzie
zdefiniowana dopiero po przetworzeniu
wyrażenia funkcyjnego.
var fly = function(num) {
var sound = ”Latam!”;
function wingFlapper() {
console.log(sound);
}
for (var i = 0; i < num; i++) {
na przy
Funkcja wingFlapper jest definiowa nątrz
wew
onej
szcz
umie
ji
arac
użyciu dekl
muje całą
funkcji fly. Zatem jej zasięg obejana w całym
funkcję fly i będzie ona zdefiniow
obszarze ciała tej funkcji.
wingFlapper();
}
};
function quack(num) {
var sound = ”Kwak!”;
var quacker = function() {
console.log(sound);
};
for (var i = 0; i < num; i++) {
quacker();
Funkcja quacker jest definiowana przy użyciu
wyrażenia funkcyjnego, umieszczonego
wewnątrz funkcji quack. A zatem jej zasięg
obejmuje całą funkcję quack, jednak będzie
ona zdefiniowana od momentu przetworzenia
wyrażenia funkcyjnego aż do końca funkcji
quack.
Funkcja quacker będzie zdefiniowana
tylko w tym fragmencie kodu.
}
}
if (migrating) {
quack(4);
fly(4);
}
Zauważ, że reguły określające, kiedy można się odwoływać do
funkcji, są takie same wewnątrz funkcji, jak i na poziomie globalnym.
A zatem jeśli jesteśmy wewnątrz funkcji i funkcja zagnieżdżona została
zdefiniowana przy użyciu deklaracji, to funkcja ta będzie zdefiniowana
w całym obszarze funkcji zewnętrznej. Z drugiej strony, jeśli funkcja
wewnętrzna została zdefiniowana przy użyciu wyrażenia funkcyjnego,
będzie ona zdefiniowana wyłącznie po jego przetworzeniu.
510
Rozdział 11 .
Nie istnieją
głupie pytania
P: Kiedy przekazujemy wyrażenie funkcyjne
do innej funkcji, to przekazywana funkcja musi
zostać zapisana w parametrze, a następnie,
wewnątrz funkcji zewnętrznej, jest traktowana
jako zmienna lokalna. Czy tak?
O: Dokładnie tak. Przekazywanie funkcji jako
argumentu wywołania innej funkcji powoduje
skopiowanie referencji do funkcji przekazywanej
i zapisanie jej w zmiennej parametru funkcji
wywoływanej. A taki parametr zawierający referencję
do funkcji, jak każdy inny parametr, jest zmienną lokalną.
Funkcje anonimowe, zasięg i domknięcia
JavaScriptowe wyzwanie ekstremalne
Potrzebujemy eksperta do spraw funkcji pierwszej klasy i słyszeliśmy, że Ty nim jesteś! Poniżej
znajdziesz dwa fragmenty kodu i musisz nam pomóc w określeniu, co wykonują, bo utknęliśmy. Dla nas oba
fragmenty wyglądają prawie identycznie, z tą różnicą, że jeden używa funkcji pierwszej klasy, a drugi nie.
Na podstawie naszej wiedzy o zasięgu w języku JavaScript oczekiwaliśmy, że próbka nr 1 zwróci wartość 008,
a próbka nr 2 wartość 007. A jednak obie próbki zwracają wartość 008! Czy możesz nam pomóc w zrozumieniu,
dlaczego tak się dzieje?
Próbka nr 1
Sugerujemy, żebyś przyjął
zdecydowaną opinię,
zapisał ją na tej stronie,
a następnie odwrócił kartkę.
var secret =
"007";
function getS
ecret() {
var secret =
"008";
}
Próbka nr 2
function getV
alue() {
return secret
;
}
return getVal
ue();
getSecret();
= "007";
var secret
tSecret() {
function ge
= "008";
var secret
tValue()
function ge
et;
return secr
{
}
alue;
return getV
}
ret();
Fun = getSec
var getValue
);
getValueFun(
Jeszcze nie patrz na
rozwiązanie zamieszczone
pod koniec rozdziału,
wrócimy do tego wyzwania
nieco później.
jesteś tutaj
511
Zasięg leksykalny
Krótka powtórka z zasięgu leksykalnego
zmiennej
cza, że zasięg
Leksykalny oznana podstawie analizy
można określić a nie trzeba z tym czekać
,
struktury kodu go realizacji.
do momentu je
Skoro MuĪ MesteĞmy przy temacie zasiĊgu powtórzymy Meszcze
raz informacMe dotyczące dziaáania zasiĊgu leksykalnego.
Tu mamy zmienną
globalną o nazwie
justAVar.
var justAVar = ”Och, nie przejmuj siÚ, jestem zmiennÈ GLOBALNk!”;
A ta funkcja definiuje
nowy zasięg leksykalny…
function whereAreYou() {
var justAVar = ”Szara, zwyczajna zmienna LOKALNA.”;
…w którym mamy zmienną lokalną o nazwie justAVar,
przesłaniającą zmienną globalną o tej samej nazwie.
return justAVar;
}
var result = whereAreYou();
console.log(result);
Kiedy ta funkcja zostanie wywołana, zwraca wartość zmiennej
justAVar. Tylko której? Używamy zasięgu leksykalnego, zatem
odnajdujemy odpowiednią zmienną justAVar, patrząc na zasięg
najbliższej funkcji. Jeśli zmiennej tam nie znajdziemy, szukamy jej
w zasięgu globalnym.
A zatem kiedy wywołam
y funkcję
whereAreYou, zwróci
ona
wartość lokalnej, a nie
globalnej
zmiennej justAVar.
Konsola JavaScript
Szara, zwyczajna zmienna LOKALNA.
A teraz wprowadĨmy funkcMĊ zagnieĪdĪoną.
var justAVar = ”Och, nie przejmuj siÚ, jestem zmiennÈ GLOBALNk!”;
Tu jest ta sama funkcja.
function whereAreYou() {
var justAVar = ”Szara, zwyczajna zmienna LOKALNA.”;
function inner() {
return justAVar;
}
return inner();
}
Lecz teraz dysponujemy funkcją zagnieżdżoną,
która odwołuje się do zmiennej justAVar. Ale do
której? Także w tym przypadku używamy zmiennej
z najbliższej funkcji zawierającej dany fragment kodu.
A zatem użyjemy tej samej zmiennej, co w poprzednim
przykładzie.
Zauważ, że funkcję inner wywołujemy
w tym miejscu i zwracamy jej wynik.
var result = whereAreYou();
console.log(result);
512
Jak wcześniej, przesłaniamy
zmienną globalną.
Rozdział 11 .
A zatem kiedy wywołamy funkcję
whereAreYou, zostanie wywołana
funkcja inner, która zwróci wartość
lokalnej, a nie globalnej zmiennej
justAVar.
Konsola JavaScript
Szara, zwyczajna zmienna LOKALNA.
Funkcje anonimowe, zasięg i domknięcia
Miejsce, w którym zasięg leksykalny sprawia,
że sprawy stają się interesujące
:prowadĨmy Meszcze Medną maáą modyfikacMĊ. UwaĪnie przyMrzyM siĊ temu
przykáadowi Mest naprawdĊ trudny.
var justAVar = ”Och, nie przejmuj siÚ, jestem zmiennÈ GLOBALNk!”;
function whereAreYou() {
var justAVar = ”Szara, zwyczajna zmienna LOKALNA.”;
Tu nie wprowadziliśmy żadnych
zmian, to te same zmienne
i funkcje, co wcześniej.
function inner() {
return justAVar;
}
Jednak zamiast wywoływać funkcję
inner, tym razem ją zwracamy.
return inner;
}
var innerFunction = whereAreYou();
var result = innerFunction();
console.log(result);
A zatem kiedy wywołamy whereAreYou,
uzyskamy referencję do funkcji inner, którą
zapisujemy w zmiennej innerFunction.
Następnie wywołujemy tę funkcję, zapisujemy
zwrócony przez nią wynik w zmiennej result,
po czym go wyświetlamy.
Kiedy zatem funkcja inner zostanie
wywołana (jako innerFunction) w tym
miejscu, której zmiennej justAVar użyje?
Tej lokalnej czy globalnej?
Znaczenie ma moment
wywołania funkcji. Wywołujemy
inner po jej zwróceniu, kiedy w bieżącym
zasięgu dostępna jest globalna zmienna justAVar,
a zatem zostanie wyświetlony łańcuch „Och, nie
przejmuj się, jestem zmienną GLOBALNĄ!”.
Nie tak szybko.
W zasięgu leksykalnym znaczenie
ma struktura, w której funkcja
została zdefiniowana, a zatem wynikiem
wywołania musi być wartość zmiennej
lokalnej, czyli „Szara, zwyczajna
zmienna LOKALNA.”.
jesteś tutaj
513
Bójka o funkcje
Kiedy kukiełki
okładały się pięściami, sięgnęłam
po specyfikację języka JavaScript
i wygląda na to, że to my mieliśmy rację.
Powyższy kod wyświetli komunikat: „Szara,
zwyczajna zmienna LOKALNA.”.
Ja mam rację!
Mylisz się!!!
Franek: Co masz na myśli, mówiąc, że to wy macie rację? To tak, jakbyście definiowali prawa fizyki albo coś takiego.
Ta zmienna lokalna już nawet nie istnieje… Chodzi mi o to, że kiedy kończy się zasięg zmiennej, przestaje ona istnieć.
Po prostu znika! Nie oglądałeś filmu TRON?
Judyta: Tak może jest w Twoim słabym C++ lub w Javie, ale nie w JavaScripcie.
Kuba: Poważnie, jak to możliwe? Funkcja whereAreYou została wywołana i tyle, a zmienna lokalna justAVar
nie może już przecież istnieć?
Judyta: Gdybyście słuchali, co do was mówię… JavaScript nie działa w taki sposób.
Franek: No dobra, rzuć nam jakąś garść informacji. Jak to działa?
Judyta: Kiedy definiujemy funkcję inner, zmienna justAVar znajduje się w jej zasięgu. Zasięg leksykalny oznacza,
że istotny jest sposób definiowania zmiennych; jeśli zatem używamy zasięgu leksykalnego, to zawsze wtedy, gdy wywołamy
funkcję inner, przyjmie ona, że ta zmienna lokalna wciąż jest dla niej dostępna i może z niej korzystać.
Franek: Ale, jak już powiedziałem, to wygląda tak, jakbyśmy zmieniali definicję praw fizyki. Funkcja whereAreYou,
która zdefiniowała lokalną wersję zmiennej justAVar, została już wykonana i przestała istnieć.
Judyta: To prawda. Funkcja whereAreYou została wykonana, lecz funkcja inner wciąż może skorzystać z jej zasięgu.
Kuba: Ale jak?
Judyta: No dobrze, zobaczmy zatem, co NAPRAWDĘ się dzieje, kiedy definiujemy i zwracamy funkcję…
514
Rozdział 11 .
:
UWAGA REDAKCYJNA
Czy Józek naprawdę
zmienił koszulkę na tej
stronie?
Funkcje anonimowe, zasięg i domknięcia
Funkcje raz jeszcze
Musimy coś wyznać. Nie powiedzieliśmy Ci wszystkiego o funkcjach. Nawet kiedy
zapytałeś o to, na co faktycznie wskazują referencje do funkcji, uprościliśmy nieco
odpowiedź. Powiedzieliśmy coś w stylu: „Na coś, co przypomina skrystalizowaną
funkcję, zawierającą jej blok kodu”.
Teraz jednak nadszedł czas, by wszystko wyjaśnić.
W tym celu przeanalizujemy to, co naprawdę dzieje się podczas wykonywania tego
kodu; zaczniemy od funkcji whereAreYou.
1 Najpierw odnajdujemy zmienną lokalną o nazwie
justAVar. Zapisujemy w niej łańcuch znaków
”Szara, zwyczajna zmienna LOKALNA.”.
function whereAreYou() {
var justAVar = ”Szara, zwyczajna zmienna LOKALNA.”; 2
function inner() {
Nie wspominaliśmy o tym wcześniej,
jednak wszystkie zmienne lokalne są
przechowywane w środowisku.
return justAVar;
3
}
Następnie tworzymy
funkcję o nazwie inner.
justAVar =
"Szara, ... LOKALNA."
return inner;
}
function inner() {
return justAVar;
}
inner
4 A później, kiedy zwracamy tę
funkcję, okazuje się, że zwracamy
nie samą funkcję, lecz funkcję
wraz z środowiskiem, które jest
z nią skojarzone.
To jest środowisko. Zawiera ono
wszystkie zmienne zdefiniowane
w zasięgu lokalnym.
W tym przykładz
tylko jedną zmienie środowisko zawiera
ną, justAVar.
Każda funkcja posiada skojarzone z nią
środowisko, które zawiera zmienne lokalne
dostępne w zasięgu, w którym jest
umieszczona dana funkcja.
function inner() {
return justAVar;
}
justAVar =
"Szara, ... LOKALNA."
Zobaczmy zatem, jak to
środowisko jest używane
w momencie wywołania
funkcji inner…
jesteś tutaj
515
Funkcje i zasięg leksykalny
Wywoływanie funkcji (po raz wtóry)
Skoro już dysponujemy funkcją inner oraz jej środowiskiem, spróbujmy ją
jeszcze raz wywołać i zobaczyć, co się stanie. Poniżej zamieściliśmy kod, który
chcemy wykonać.
var innerFunction = whereAreYou();
var result = innerFunction();
console.log(result);
1
1aMpierw wywoáuMemy funkcMĊ whereAreYou. -uĪ wiemy Īe zwraca ona referencMĊ do
funkcMi. Tworzymy zatem zmienną innerFunction i zapisuMemy w nieM tĊ zwróconą
funkcMĊ. PamiĊtaM Īe referencMa do funkcMi Mest skoMarzona ze Ğrodowiskiem.
function inner() {
return justAVar;
}
var innerFunction = whereAreYou();
inner
Function
Po wykonaniu tej instrukcji będziemy
dysponować zmienną innerFunction
odwołującą się do funkcji (oraz
środowiskiem zwróconym z funkcji
whereAreYou).
2
Nasza nowa
zmienna.
Funkcja oraz jej środowisko.
1astĊpnie wywoáuMemy funkcMĊ innerFunction. : tym celu przetwarzamy
kod umieszczony w ciele teM funkcMi a co wiĊceM robimy to w kontekĞcie MeM
Ğrodowiska.
var result = innerFunction();
function inner() {
return justAVar;
}
inner
Function
Funkcja zawiera pojedynczą instrukcję,
która zwraca wartość zmiennej justAVar.
W celu pobrania tej wartości zaglądamy
do środowiska.
516
justAVar =
"Szara, ... LOKALNA."
Rozdział 11 .
justAVar =
"Szara, ... LOKALNA."
Zmienna justAVar
ma wartość „Szara,
zwyczajna zmienna
LOKALNA.”, a zatem
to ją zwracamy.
, w koĔcu zapisuMemy wartoĞü zwróconą przez funkcMĊ w zmienneM
result i wyĞwietlamy Mą w oknie konsoli.
zm
ie
nn
a
3
LO
KA
LN
A.
”
Funkcje anonimowe, zasięg i domknięcia
aj
a,
Funkcja innerFunction zwraca łańcuch „Szar
liśmy
pobra
który
,
LNA.”
LOKA
na
zmien
ajna
zwycz
nej
zmien
w
go
ujemy
zapis
zatem
,
wiska
ze środo
result.
cz
console.log(result);
na
var result = innerFunction();
result
Teraz musimy tylko wyświetlić ten łańcuch
w oknie konsoli.
Konsola JavaScript
Szara, zwyczajna zmienna LOKALNA
A niech to!
Judyta znowu miała
rację.
Chwila… Czy
Judyta nie
wspominała o domknięciach?
Wygląda na to, że one są
powiązane z tym, co robimy.
Zobaczmy, czy będziemy mogli
dowiedzieć się czegoś na ich temat
i zagiąć Judytę.
Hej, panowie…
To JEST domknięcie.
Lepiej o nich poczytajcie.
jesteś tutaj
517
Pytania dotyczące kontekstu leksykalnego
Nie istnieją
głupie pytania
P: Co mieliście na myśli, pisząc, że zasięg leksykalny
określa, gdzie zmienne będą zdefiniowane?
O
: Pisząc o zasięgu leksykalnym, mieliśmy na myśli to, że w języku
JavaScript reguły określania zasięgu bazują wyłącznie na strukturze
kodu (a nie na dynamicznych właściwościach określanych
w trakcie wykonywania skryptu). Oznacza to, że obszar, w którym
zmienna będzie zdefiniowana, można określić poprzez analizę
struktury kodu. Pamiętaj także, że w języku JavaScript jedynie
funkcje wprowadzają nowy zasięg. A zatem, widząc odwołanie
do zmiennej, należy poszukać, w której funkcji ta zmienna
została zdefiniowana, a zaczynać trzeba od funkcji najbardziej
zagnieżdżonej i podążać do najbardziej zewnętrznej. Jeśli zmiennej
nie udało się znaleźć w żadnej funkcji, oznacza to, że jest zmienną
globalną lub nie została wcześniej zdefiniowana.
P: Jak działa środowisko, w przypadku gdy funkcja
została zdefiniowana głęboko wewnątrz wielu innych
zagnieżdżonych funkcji?
O: Opisując, czym jest środowisko, podaliśmy dosyć uproszczone
informacje, jednak możesz to sobie wyobrażać w taki sposób,
że każda zagnieżdżona funkcja ma swoje własne środowisko,
z własnymi zmiennymi. Następnie tworzony jest łańcuch środowisk
wszystkich zagnieżdżonych funkcji, zaczynając od tej najbardziej
wewnętrznej, aż do zupełnie zewnętrznej.
O
: Rozumiemy, że tak możesz uważać, jednak zaletą zasięgu
leksykalnego jest to, że zawsze możemy spojrzeć na kod, by
określić zasięg, w jakim zmienna została zdefiniowana, i na jego
podstawie ustalić jej wartość. A jak już pokazaliśmy, rozwiązanie
to obowiązuje zawsze, nawet wtedy, kiedy funkcja jest zwracana
z innej i wywoływana znacznie później, w miejscu znajdującym się
poza jej początkowym zasięgiem.
Jednak istnieje także inny powód, dla którego można uznać, że jest
to dobre rozwiązanie — są nim bardzo interesujące możliwości,
jakie ono zapewnia. Zajmiemy się nimi już niedługo.
P: Czy zmienne parametrów także są uwzględniane
w środowisku?
O
: Tak. Jak już zaznaczyliśmy, parametry można uznawać za
zmienne lokalne istniejące wewnątrz funkcji, dlatego też wchodzą
w skład środowiska.
P: Czy muszę dokładnie rozumieć, jak działają
środowiska?
O
: Nie. Musisz natomiast rozumieć reguły zasięgu leksykalnego
związane ze zmiennymi, które opisaliśmy w tym rozdziale. Jednak
teraz już wiesz, że jeśli masz funkcję zwróconą z innej funkcji, cały
czas dysponuje ona swoim początkowym środowiskiem.
Kiedy zatem przychodzi do odnajdywania zmiennej w środowisku,
poszukiwania zaczynają się w środowisku najbliższym, a następnie
postępują wzdłuż łańcucha, aż do momentu odnalezienia
zmiennej. A jeśli nie uda się jej znaleźć, na końcu sprawdzane jest
środowisko globalne.
P: Dlaczego zasięg leksykalny i środowiska funkcji są
dobrymi rozwiązaniami? Sądziłem, że w przedstawionym
przykładzie prawidłową odpowiedzią będzie ”Och, nie
przejmuj siÚ, jestem zmiennÈ GLOBALNk!”. Dla mnie
to byłoby bardziej sensowne rozwiązanie. Faktyczny stan
rzeczy wydaje się mylący i nieintuicyjny.
Pamiętaj, że w języku JavaScript funkcje zawsze są przetwarzane w tym samym
środowisku określającym zasięg, w którym zostały zdefiniowane. Jeśli wewnątrz
funkcji chcemy określić, skąd pochodzi zmienna, trzeba przejrzeć funkcje, w których
została zagnieżdżona, od najbardziej wewnętrznej do najbardziej zewnętrznej.
518
Rozdział 11 .
Funkcje anonimowe, zasięg i domknięcia
Czym właściwie są domknięcia?
No jasne, wszyscy mówią o domknięciach jak o absolutnie niezbędnej możliwości języka, ale ile osób wie,
czym one są i jak ich używać? Naprawdę niewiele. To możliwość, którą wszyscy chcą zrozumieć i którą
chciałyby dysponować wszystkie tradycyjne języki programowania.
Spróbujmy przedstawić problem. Zgodnie z tym, co twierdzi wiele dobrze wykształconych osób z branży,
domknięcia są trudne. Jednak dla Ciebie nie będą dużym problemem. A wiesz dlaczego? Nie, nie… To nie
ma nic wspólnego z tym, że ta książka jest „przyjazna dla mózgu”, ani z tym, że dysponujemy odlotową
aplikacją, którą trzeba było napisać, żeby nauczyć Cię wszystkiego o domknięciach. Przyczyna leży gdzie
indziej, Ty już je poznałeś. Tylko nie pisaliśmy, że to są domknięcia.
A zatem, bez zbędnego zamieszania podajemy superformalną definicję domknięć.
MÈ
'RPNQLÚFLHU]HF]RZQLN'RPNQLÚFLHMHVWIXQNF
VNRMDU]RQÈ]RGZRïXMÈF\PVLÚGRQLHMĂURGRZLVNLHP
Jeśli dobrze Cię wyszkoliliśmy,
powinieneś sobie w tym momencie
myśleć: „O tak, to jest wiedza
warta «dużej podwyżki»”.
No dobrze, możemy się zgodzić z opinią, że ta definicja nie rozwiewa wszystkich wątpliwości.
Ale skąd się w ogóle wzięła taka nazwa jak domknięcie (ang. closure)? Przeanalizujemy to
szybko, gdyż faktycznie może to być jedno z tych kluczowych pytań, które może zaważyć na
losach rozmowy kwalifikacyjnej lub później na losach podwyżki.
Aby zrozumieć słowo domknięcie, należy najpierw zrozumieć ideę domykania funkcji.
Zaostrz ołówek
Oto Twoje zadanie: (1) odszukaj i zakreśl wszystkie zmienne niezależne występujące w poniższym
fragmencie kodu. Zmienna niezależna to termin określający zmienną, która nie została zdefiniowana
w zasięgu lokalnym. (2) Wybierz jedno ze środowisk przedstawionych z prawej strony ramki, które
domyka funkcję. Oznacza to, że środowisko zawiera wartości wszystkich wolnych zmiennych.
function justSayin(phrase) {
beingFunny = true;
notSoMuch = false;
LQ&RQYHUVDWLRQ:LWK ĵ3DZHïĵ
var ending = ””;
if (beingFunny) {
ending = ” -- Tak tylko mówiÚ!”;
} else if (notSoMuch) {
ending = ” -- Nie za bardzo.”;
}
beingFunny = true;
justSayin = false;
oocoder = true;
alert(phrase + ending);
}
Zakreśl zmienne niezależne
występujące w tym kodzie —
czyli zmienne, które nie zostały
zdefiniowane w zasięgu lokalnym.
Spośród tych środowisk wybierz
to, które domyka funkcję.
notSoMuch = true;
phrase = ”Hej, la la la”;
band = ”Policja”;
jesteś tutaj
519
Definiowanie domknięć
Domykanie funkcji
Najprawdopodobniej odgadłeś to już, wykonując ostatnie ćwiczenie, jednak
przeanalizujemy całe zagadnienie raz jeszcze: funkcja zazwyczaj ma zmienne
lokalne definiowane w jej ciele (dotyczy to także ewentualnych parametrów),
lecz może także używać zmiennych, które nie zostały zdefiniowane lokalnie
wewnątrz niej — tzw. zmiennych niezależnych (ang. free variable). Określenie
niezależne pochodzi stąd, że wewnątrz ciała funkcji zmiennym niezależnym nie
jest przypisana żadna wartość (innymi słowy, nie zostały one zadeklarowane
wewnątrz funkcji). A kiedy środowisko zawiera wartości wszystkich zmiennych
niezależnych, mówimy, że domyka ono funkcję. Idąc dalej, jeśli połączymy
funkcję i jej środowisko, uzyskamy domknięcie (ang. closure).
Jeśli zmienna używana
w ciele mojej funkcji nie została
zdefiniowana lokalnie i nie jest zmienną
globalną, możesz się założyć, że pochodzi
ona z jednej z funkcji, w których mnie
zagnieżdżono i jest dostępna w moim
środowisku.
Domknięcie
function justSayin() {
// kod funkcji
}
beingFunny = true;
notSoMuch = false;
LQ&RQYHUVDWLRQ:LWK ĵ3DZHïĵ
Domknięcie powstaje, kiedy połączymy funkcję
wykorzystującą zmienne niezależne ze środowiskiem
udostępniającym wartości, które są powiązane
z tymi zmiennymi.
520
Rozdział 11 .
Funkcje anonimowe, zasięg i domknięcia
To już chyba dziesiąta strona,na
której zajmujemy się tym zagadnieniem.
Czy kiedyś jeszcze wrócimy do JavaScriptu
stosowanego w praktyce? Czy już na
zawsze pozostaniemy w świecie teorii?
Dlaczego w ogóle mam się przejmować tymi
niskopoziomowymi tajnikami działania funkcji?
Przecież wystarczy, że będę pisać funkcje
i je wywoływać, prawda?
Gdyby tylko domknięcia nie były tak diabelnie
przydatne, moglibyśmy się nawet z Tobą zgodzić.
Bardzo nam przykro, że musieliśmy Cię narazić na te wszystkie
trudności związane z nauką domknięć, ale zapewniamy, że to się opłaci.
Bo widzisz, domknięcia nie są jedynie jakąś teoretyczną konstrukcją
programowania funkcyjnego — są bardzo użyteczną techniką
programistyczną. A teraz, kiedy już zrozumiałeś, jak działają (i wcale
nie żartujemy, pisząc, że dobra znajomość domknięć jest czynnikiem,
który może znacząco podnieść Twoje notowania u szefostwa
i współpracowników), nadszedł czas, by nauczyć się, jak ich używać.
I jeszcze jedna, kluczowa sprawa: domknięcia są używane wszędzie.
W tak dużym stopniu staną się one Twoją drugą naturą, że wkrótce
zdasz sobie sprawę, że używasz ich naprawdę bardzo często. Napiszmy
w końcu jakieś domknięcie i przekonajmy się, o czym tu w ogóle
mówimy.
jesteś tutaj
521
Implementacja licznika z użyciem domknięć
Zastosowanie domknięć w celu zaimplementowania
magicznego licznika
Czy myślałeś kiedyś o zaimplementowaniu funkcji działającej jako licznik?
Mogłaby ona wyglądać jakoś tak.
var count = 0;
ną
Tu mamy zmienną global
nt.
cou
wie
naz
o
Każde wywołanie funkcji counter
function counter() {
inkrementuje globalną zmienną count
counter = counter + 1;
i zwraca jej nową wartość.
return count;
Konsola JavaScript
}
Taki licznik moglibyśmy wykorzystać w następujący sposób.
A zatem możemy
inkrementować nasz
licznik i wyświetlać jego
wartość w taki sposób.
console.log(counter());
console.log(counter());
console.log(counter());
1
2
3
Jedynym problemem, jaki możemy wskazać w tym kodzie, jest to, że użyto w nim zmiennej
globalnej count, co może być problematyczne, gdy nad kodem pracuje zespół programistów
(a to dlatego, że ludzie często używają tych samych nazw, co potem powoduje konflikty).
A co byś powiedział na informację, że istnieje sposób zaimplementowania takiego licznika
z wykorzystaniem całkowicie lokalnej i chronionej zmiennej count? Wtedy zyskałbyś licznik,
który nigdy nie będzie kolidował z żadnym innym kodem, a jedynym sposobem inkrementacji
jego wartości będzie wywołanie funkcji (inaczej nazywanej domknięciem).
W celu zaimplementowania licznika z użyciem domknięcia możemy wykorzystać
przeważającą większość kodu przedstawionego powyżej. Patrz i podziwiaj.
count wewnątrz
Tutaj tworzymy zmienną zatem count jest
A
.
ter
oun
keC
ma
funkcji
globalną.
zmienną lokalną, a nie
function makeCounter() {
var count = 0;
function counter() {
count = count + 1;
Teraz tworzymy funkcję
counter, która inkrementuje
wartość zmiennej count.
return count;
}
return counter;
}
A tu zwracamy funkcję counter.
To jest domknięcie. A w środowisku funkcji counter
przechowywana jest wartość zmiennej count.
Czy sądzisz, że ta magiczna sztuczka się uda? Przekonajmy się…
522
Rozdział 11 .
Funkcje anonimowe, zasięg i domknięcia
Próbne odliczanie magicznego licznika
Dodaliśmy trochę kodu, który pozwoli przetestować nasz magiczny licznik.
Dalej — odpalajmy!
function makeCounter() {
var count = 0;
Konsola JavaScript
function counter() {
count = count + 1;
return count;
}
return counter;
}
var doCount = makeCounter();
console.log(doCount());
console.log(doCount());
console.log(doCount());
1
2
3
Nasz licznik działa.
Uzyskaliśmy prawidłowe
wyniki.
Zaglądamy za kulisy…
function counter() {
count = count + 1;
return count;
}
Przeanalizujemy ten kod krok po kroku, by przekonać się,
jak działa.
var count = 0;
1 Wywołujemy funkcję makeCounter, która tworzy
funkcję counter i zwraca ją wraz z jej środowiskiem
zawierającym zmienną niezależną count. Innymi słowy,
funkcja ta tworzy domknięcie. Funkcja zwrócona przez
funkcję makeCounter jest zapisywana w zmiennej
doCount.
function makeCounter() {
var count = 0;
2 Wywołujemy funkcję doCount. Wykonuje ona kod
function counter() {
count = count + 1;
3
return count;
}
return counter;
umieszczony w ciele funkcji counter.
3 Kiedy napotykamy zmienną count, szukamy jej
w środowisku i pobieramy jej wartość. Wartość tę
inkrementujemy, a następnie ponownie zapisujemy
w środowisku i zwracamy jako wyniki wykonania
funkcji.
4 Powtarzamy kroki 2. i 3., wywołując funkcję doCount.
1
2
4
Kiedy wywołujemy funkcję doCount (będącą referencją do
funkcji counter), a w niej musimy pobrać wartość zmiennej
count, użyjemy zmiennej zapisanej w środowisku domknięcia.
Świat zewnętrzny (czyli kod umieszczony w zasięgu globalnym)
nigdy nie zobaczy zmiennej count. Jednak my możemy jej
używać za każdym razem, kiedy wywołamy funkcję doCount.
Co więcej, wywołanie funkcji doCount jest jedynym sposobem
odwołania się do zmiennej count.
To jest
domknięcie.
}
var doCount = makeCounter();
console.log(doCount());
console.log(doCount());
console.log(doCount());
Kiedy wywołamy
funkcję makeCounter,
uzyskamy
domknięcie: funkcję
skojarzoną ze
środowiskiem.
jesteś tutaj
523
Ćwiczymy domknięcia
Teraz Twoja kolej. Spróbuj utworzyć opisane poniżej domknięcia. Zdajemy sobie sprawę, że
początkowo tworzenie domknięć nie jest prostym zadaniem, jeśli zatem będziesz potrzebował
pomocy, zajrzyj do odpowiedzi. Ważne jest jednak, żebyś przerobił te przykłady i doszedł do etapu,
kiedy całkowicie je zrozumiesz.
Ćwiczenie
Domknięcie pierwsze za 10 punktów. Funkcja makePassword pobiera argument reprezentujący hasło
i zwraca funkcję, do której przekazywany jest łańcuch porównywany z hasłem. Funkcja ta zwraca
wartość true, jeśli przekazany łańcuch odpowiada hasłu. (Zanim zrozumiemy działanie domknięcia,
czasami trzeba kilkakrotnie przeczytać jego opis).
function makePassword(password) {
return __________________________ {
return (passwordGuess === password);
};
}
Domknięcie drugie za 20 punktów. Funkcja multN pobiera liczbę (nazwijmy ją n) i zwraca funkcję.
Ta zwrócona funkcja także pobiera liczbę, którą następnie mnoży przez n i zwraca jako wynik wywołania.
function multN(n) {
return __________________________ {
return _____________;
};
}
Domknięcie trzecie za 30 punktów. To jest modyfikacja licznika, który utworzyliśmy na poprzedniej
stronie. Funkcja makeCounter nie pobiera żadnych argumentów, lecz definiuje zmienną
count. Następnie tworzy i zwraca obiekt udostępniający jedną metodę, increment.
Ta metoda inkrementuje wartość zmiennej count, a następnie ją zwraca.
524
Rozdział 11 .
Funkcje anonimowe, zasięg i domknięcia
Tworzenie domknięcia poprzez przekazanie
wyrażenia funkcyjnego jako argumentu
Zwracanie funkcji jako wyniku wykonania innej funkcji nie jest jedynym sposobem
tworzenia domknięć. Domknięcie budowane jest zawsze wtedy, gdy dysponujemy
referencją do funkcji, która korzysta ze zmiennych niezależnych i jest wykonywana
poza zasięgiem, w jakim została utworzona.
Kolejnym sposobem tworzenia domknięć jest przekazywanie funkcji w wywołaniu
innej. Przekazywana funkcja zostanie wykonana w zupełnie innym kontekście niż ten,
w którym została zdefiniowana. Poniżej przedstawiliśmy przykład takiego rozwiązania.
function makeTimer(doneMessage, n) {
setTimeout(function() {
Tu mamy funkcję…
alert(doneMessage);
}, n);
ą niezależną…
…wykorzystującą zmienn
…której użyjemy do obsługi licznika czasu, uruchamianego
za pomocą metody setTimeout
…a nasza funkcja zostanie wykonana po
upływie 1000 milisekund od chwili obecnej,
czyli na długo po wykonaniu funkcji makeTimer.
}
makeTimer(”Ciasteczka sÈ juĝ gotowe!”, 1000);
W powyższym przykładzie przekazujemy do metody setTimeout wyrażenie funkcyjne,
korzystające ze zmiennej niezależnej doneMessage. Jak już wiesz, przekazywane wyrażenie
funkcyjne zostanie przetworzone i zwróci referencję do funkcji, która zostanie przekazana
do metody setTimeout. Metoda setTimeout zapamięta tę funkcję (a właściwie funkcję
i jej środowisko, czyli domknięcie), a następnie po 1000 milisekund wywoła ją.
Także w tym przypadku funkcja przekazana w wywołaniu setTimeout jest domknięciem,
gdyż towarzyszy jej środowisko kojarzące zmienną niezależną, doneMessage, z jej
wartością — ĵ&LDVWHF]NDVÈMXĝJRWRZHĵ.
WYSIL
SZARE KOMÓRKI
A co by się stało, gdyby function handler() {
alert(doneMessage);
Twój kod wyglądał tak.
}
function makeTimer(doneMessage, n) {
setTimeout(handler, n);
}
makeTimer(”Ciasteczka sÈ juĝ gotowe!”, 1000);
WYSIL
SZARE KOMÓRKI
2
Przejrzyj jeszcze raz kod
przedstawiony na stronie 438
w rozdziale 9.
Czy potrafiłbyś zmodyfikować ten
kod, tak by korzystał z domknięcia,
i wyeliminować konieczność
podawania w wywołaniu metody
setTimeout trzeciego argumentu?
jesteś tutaj
525
Domknięcia zwierają środowisko
Domknięcia zawierają rzeczywiste środowisko, a nie jego kopię
Jedną z rzeczy, która często przysparza problemów osobom poznającym domknięcia, jest mylna opinia,
że środowisko, jakim dysponuje domknięcie, musi zawierać kopie wszystkich zmiennych oraz ich
wartości. A tak nie jest. W rzeczywistości środowisko odwołuje się do faktycznych zmiennych używanych
w kodzie, jeśli zatem wartość zostanie zmieniona przez jakiś kod umieszczony poza funkcją domknięcia,
podczas wykonywania tej funkcji wykorzysta ona tę nową wartość.
Zmodyfikujmy nasz przykład, by przekonać się, co to oznacza.
function setTimer(doneMessage, n) {
setTimeout(function() {
alert(doneMessage);
}, n);
Tutaj jest tworzone
domknięcie.
y wartość
A teraz zmieniam age.
ss
Me
ne
do
zmiennej
doneMessage = ”AUm!”;
}
setTimer(”Ciasteczka sÈ juĝ gotowe!”, 1000);
1 Kiedy wywołujemy setTimeout, przekazując
do niej wyrażenie funkcyjne, tworzone jest
domknięcie zawierające funkcję oraz referencję
do jej środowiska.
function() {
alert(doneMessage);
}
var doneMessage =
ĵ&LDVWHF]NDVÈMXĝJRWRZHĵ
setTimeout(function() {
alert(doneMessage);
}, n);
2 Następnie, kiedy poza domknięciem zmienimy
wartość parametru doneMessage na ĵ$8mĵ, jest
ona zmieniana w tym samym środowisku, które
jest używane przez domknięcie.
function() {
alert(doneMessage);
}
YDUGRQH0HVVDJH ĵ$8mĵ
doneMessage = ”AUm!”;
3 1000 milisekund później zostaje wywołana funkcja
umieszczona w domknięciu. Odwołuje się ona do
zmiennej doneMessage, której aktualna wartość
w środowisku wynosi ĵ$8mĵ. A zatem w oknie
dialogowym zostanie wyświetlony łańcuch
znaków ĵ$8mĵ.
function() { alert(doneMessage); }
526
Rozdział 11 .
yje wartości
nie wywołana, uż
Kiedy funkcja zostaage zapisanej w środowisku,
ss
zmiennej doneMe i, którą przypisaliśmy tej
śc
czyli nowej warto setTimer.
ji
zmiennej w funkc
Funkcje anonimowe, zasięg i domknięcia
Tworzenie domknięć jako procedur obsługi zdarzeń
Przyjrzyjmy się jeszcze jednemu sposobowi tworzenia domknięć. Tym razem zbudujemy
domknięcie działające jako procedura obsługi zdarzeń; jest to rozwiązanie dosyć często
spotykane w kodzie JavaScript. Zaczniemy od utworzenia prostej strony WWW, zawierającej
przycisk oraz element <div>, w którym będzie wyświetlany komunikat. Umieszczony na tej
stronie skrypt będzie zliczał liczbę kliknięć przycisku i wyświetlał ją w elemencie <div>.
Poniżej przedstawiliśmy kody HTML i CSS naszej strony, dodaj je do pliku o nazwie
divClosure.html.
<!doctype html>
strona WWW.
To zwyczajna, typowa
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Kliknij mnie!</title>
<style>
body, button { margin: 10px; }
Z prostym arkuszem stylów CSS,
służącym do określenia wyglądu
elementów.
div { padding: 10px; }
</style>
<script>
Tu umieścisz swój kod JavaScript.
// Tu umieĂcimy kod -avaScript.
</script>
</head>
<body>
przycisk oraz element
Na stronie znajdują sięunikaty, które będziemy
<div> prezentujący kom kliknięciu przycisku.
aktualizować po każdym
<button id=”clickme”>Kliknij mnie!</button>
<div id=”message”></div>
</body>
</html>
każdym
A to jest nasz cel: po emy
chc
sku
yci
prz
ciu
nię
klik
aktualizować komunikatncie <div>
prezentowany w eleme razy
ile
i wyświetlać w nim, ty.
przycisk został kliknię
A teraz napiszemy kod. Oczywiście mógłbyś napisać kod realizujący to, o co nam
chodzi w tym przykładzie, bez stosowania domknięcia, jednak — jak się przekonasz
— przy użyciu domknięcia kod będzie bardziej zwarty, a nawet wydajniejszy.
jesteś tutaj
527
Stosowanie domknięć jako procedur obsługi zdarzeń
Kliknij mnie! Bez użycia domknięcia
Najpierw pokażemy, jak można zaimplementować kod tego przykładu bez użycia domknięcia.
globalną, bo gdyby
musiała być zmienną
Zmienna count będzie alnie wewnątrz funkcji handleClick
lok
została zdefiniowana i zdarzeń click w przycisku), byłaby
(czyli procedury obsług obsługi każdego kliknięcia przycisku.
s
var count = 0;
inicjalizowana podcza
W funkcji obsługującej zdarzenie load
lamy
pobieramy element przycisku i okreś
window.onload = function() {
ając
procedurę obsługi zdarzeń clik, używ
właściwości onclick.
var button = document.getElementById(”clickme”);
button.onclick = handleClick;
};
ługi zdarzeń click
To jest procedura obs
.
sku
naszego przyci
Definiujemy zmienną message…
function handleClick() {
var message = ”KliknÈïeĂ mnie ”;
var div = document.getElementById(”message”);
count++;
…pobieramy z DOM element <div>…
…inkrementujemy licznik kliknięć…
div.innerHTML = message + count + ” razy!”;
}
…i aktualizujemy zawartość elementu <div>,
wyświetlając w nim komunikat o liczbie
kliknięć.
Kliknij mnie! Z użyciem domknięcia
Przedstawiona powyżej wersja kodu, w której nie używamy domknięcia, działa doskonale, jej
jedynym problematycznym aspektem jest zastosowanie zmiennej globalnej, która potencjalnie
może przysporzyć problemów. Zmodyfikujmy zatem kod przykładu, by skorzystać z domknięcia,
a następnie spróbujmy porównać oba rozwiązania. Kod pokażemy poniżej, natomiast dokładniejszą
analizę jego działania zamieścimy po przetestowaniu.
window.onload = function() {
var count = 0;
var message = ”KliknÈïeĂ mnie ”;
zmiennymi
Teraz wszystkie używane zmienne są dury
lokalnymi istniejącymi wewnątrz proce wystąpić
obsługi window.onload. Nie mogą zatem
żadne konflikty nazw.
var div = document.getElementById(”message”);
var button = document.getElementById(”clickme”);
button.onclick = function() {
count++;
div.innerHTML = message + count + ” razy!”;
Określamy procedurę obsługi zdarzeń
click, którą będzie wyrażenie funkcyjne
zapisywane we właściwości onclick
przycisku. Dzięki temu wewnątrz tej
funkcji możemy odwoływać się do
wszystkich zmiennych: div, message
oraz count. (Pamiętaj o zasięgu
leksykalnym!).
};
};
528
Ta funkcja używa trzech zmiennych niezależnych: div, message oraz
count; dlatego też na potrzeby procedury obsługi zdarzeń click
tworzone jest domknięcie. A zatem rzeczywiście we właściwości
onclick przycisku zapisywane jest właśnie domknięcie.
Rozdział 11 .
Funkcje anonimowe, zasięg i domknięcia
Jazda próbna z licznikiem kliknięć
No dobrze, połączmy zatem kody HTML i JavaScript, zapisując je
w jednym pliku, divClosure.html, a następnie wypróbujmy działanie strony.
Wczytaj stronę w przeglądarce i kliknij przycisk, aby inkrementować
wartość licznika. Powinieneś zobaczyć, że komunikat wyświetlony
w elemencie <div> został zmodyfikowany. Popatrz jeszcze raz na kod
i upewnij się, że rozumiesz, jak działa. Kiedy już to zrobisz, przewróć
kartkę — na następnej stronie dokładnie analizujemy to rozwiązanie.
<html lang=”pl”>
<head>
<meta charset=”utf-8”>
<title>Kliknij mnie!</title>
<style>
Oto rezultat, który
uzyskaliśmy.
body, button { margin: 10px; }
div { padding: 10px; }
</style>
Uaktualnij kod pliku divClosure.html,
by odpowiadał przykładowi
przedstawionemu na tej stronie.
<script>
window.onload = function() {
var count = 0;
var message = ”KliknÈïeĂ mnie ”;
var div = document.getElementById(”message”);
var button = document.getElementById(”clickme”);
button.onclick = function() {
count++;
div.innerHTML = message + count + ” razy!”;
};
};
</script>
</head>
<body>
<button id=”clickme”>Kliknij mnie!</button>
<div id=”message”></div>
</body>
</html>
jesteś tutaj
529
Dokładna analiza działania domknięcia
Jak działa domknięcie liczące kliknięcia?
Aby zrozumieć, jak działa to domknięcie, prześledzimy, co robi przeglądarka
podczas wykonywania kodu naszego przykładu.
Strona została wczytana, zatem mogę wykonać
procedurę obsługi zdarzeń load. Muszę zdefiniować
kilka zmiennych… O, widzę, że mam też do przetworzenia
jakieś wyrażenie funkcyjne. Zobaczmy… Wygląda na to, że
odwołuje się ono do trzech zmiennych niezależnych, zatem
chyba będzie lepiej, jeśli utworzę domknięcie.
window.onload = function() {
var count = 0;
var message = ”KliknÈïeĂ mnie ”;
var div = document.getElementById(”message”);
var button = document.getElementById(”clickme”);
button.onclick = function() {
count++;
div.innerHTML = message + count + ” razy!”;
};
};
function() {
count++;
div.innerHTML = ...;
}
var message =
ĵ.OLNQÈïHĂPQLHĵ
var count = 0;
var div = [object]
A teraz zapiszę to
domknięcie we właściwości onclick
przycisku Kliknij mnie!. No dobra, to by
było wszystko, co miałam zrobić w ramach
obsługi zdarzenia load… Teraz wystarczy,
że poczekam, aż ktoś kliknie przycisk
na stronie.
530
Rozdział 11 .
knięcie na potrzeby
Przeglądarka tworzy domwłaściwości onclick
we
funkcji zapisywanej
tego
o wchodzące w skład
przycisku. Środowisk y zmienne: div, message
domknięcia zawiera trz
oraz count.
Po wykonaniu funkcji window.onload
nie stanie się nic, dopóki użytkownik
nie kliknie przycisku Kliknij mnie!
Funkcje anonimowe, zasięg i domknięcia
Hej, ktoś kliknął
przycisk! Nadszedł czas, żeby
wykonać jego procedurę obsługi
zdarzeń click, którą przygotowałam
sobie już wcześniej…
już nie istnieje
Choć zmienna button
zakończeniu
po
ta
nię
(została usu
dow.onload), jednak
win
i
kcj
wykonywania fun
ystuje w DOM,
egz
iąż
wc
obiekt przycisku
jest zapisane
lick
onc
a w jego właściwości
nasze domknięcie.
oncli
ck
Och, widzę, że
mamy tu jakieś domknięcie.
No i świetnie, to znaczy, że
w jego środowisku znajdę wartości
tych trzech zmiennych niezależnych.
iera obiekt.
div w domknięciu zaw i window.onload,
Zauważ, że zmienna
kcj
fun
w
ną
ien
y tę zm
Kiedy inicjalizowaliśm ekt zwrócony przez wywołanie metody
zapisaliśmy w niej obi Id; dzięki temu teraz nie musimy
document.getElementByektu z DOM, gdyż już nim dysponujemy.
ponownie pobierać obi rację i sprawia, że nasz kod będzie
To eliminuje jedną ope ej.
działał odrobinkę szybci
Inkrementowałam
wartość zmiennej count
i zadbałam, by wartość ta została
zaktualizowana także w środowisku.
Zaktualizowałam także komunikat
wyświetlany na stronie…
Nie pozostaje mi zatem nic innego,
jak tylko czekać na kolejne
kliknięcie przycisku.
function() {
count++;
div.innerHTML = ...;
}
function() {
count++;
div.innerHTML = ...;
}
var message =
ĵ.OLNQÈïHĂPQLHĵ
var count = 0;
var div = [object]
var message =
ĵ.OLNQÈïHĂPQLHĵ
var count = 1;
var div = [object]
Nasze domknięcie
zni
dopiero po zamknię knie
ciu strony.
Jest cały czas gotow
wkroczyć do akcji, e, by
klikniesz przycisk. gdy
jesteś tutaj
531
Ekstremalne wyzwanie z domknięciami
Po r
az w
tóry
JavaScriptowe wyzwanie ekstremalne
Potrzebujemy eksperta do spraw domknięć i słyszeliśmy, że Ty nim jesteś. Teraz już wiesz, jak działają domknięcia.
Czy zatem potrafisz wyjaśnić, dlaczego obie przedstawione poniżej próbki zwracają wynik 008? Aby to wyjaśnić,
zapisz wszelkie zmienne przechowywane w środowiskach funkcji. Zwróć uwagę, że nic nie stoi na przeszkodzie,
by środowisko było puste. Sprawdź naszą odpowiedź podaną pod koniec rozdziału.
Próbka nr 1
var secret = ”007”;
Środowisko
function getSecret() {
var secret = ”008”;
function getValue() {
return secret;
}
return getValue();
}
getSecret();
Próbka nr 2
var secret = ”007”;
function getSecret() {
var secret = ”008”;
function getValue() {
return secret;
}
return getValue;
}
var getValueFun = getSecret();
getValueFun();
532
Rozdział 11 .
Środowisko
Funkcje anonimowe, zasięg i domknięcia
Zaostrz ołówek
Na początku przyjrzyj się poniższemu fragmentowi kodu.
(function(food) {
if (food === ”ciasteczka”) {
alert(”PoproszÚ o wiÚcej.”);
} else if (food === ”ciasto”) {
Wyrażenie funkcyjne umieszczone
bezpośrednio w instrukcji
wywołania… To już naprawdę
ekstremalne rozwiązanie.
alert(”Mniam mniam.”);
}
})(”ciasteczka”);
Twoim zadaniem jest nie tylko ustalić, jaki będzie wynik wykonania tego
fragmentu kodu, lecz przede wszystkim określić, jak działa. W tym celu
postępuj odwrotnie niż do tej pory, czyli wyodrębnij w kodzie funkcję
anonimową, przypisz ją jakieś zmiennej, a następnie umieść tę zmienną
tam, gdzie wcześniej było wyrażenie funkcyjne. Czy po takich zmianach
kod jest bardziej czytelny? A zatem, co on robi?
jesteś tutaj
533
Podsumowanie rozdziału
CELNE SPOSTRZEŻENIA
Q
Funkcja anonimowa to wyrażenie funkcyjne,
które nie ma nazwy.
Q
Funkcje anonimowe pozwalają na tworzenie
bardziej zwartego kodu.
Q
Deklaracje funkcji są definiowane przed
wykonaniem pozostałych fragmentów kodu.
Q
Q
534
Wyrażenia funkcyjne są przetwarzane
w trakcie wykonywania pozostałych
fragmentów kodu, a zatem nie będą
zdefiniowane aż do momentu wykonania
instrukcji, w których zostały zapisane.
Wyrażenie funkcyjne można przekazać
do innej funkcji bądź też zwrócić jako wynik
wykonania funkcji.
Q
Wyrażenia funkcyjne zwracają referencję
do funkcji, co oznacza, że można ich używać
wszędzie tam, gdzie można korzystać
z referencji do funkcji.
Q
Funkcje zagnieżdżone to funkcje
zdefiniowane wewnątrz innych funkcji.
Q
Funkcje zagnieżdżone mają zasięg lokalny,
podobnie jak zmienne lokalne.
Q
Zasięg leksykalny oznacza, że zasięg
zmiennej można określić na podstawie
analizy kodu.
Rozdział 11 .
Q
W celu powiązania wartości ze zmienną
w funkcji zagnieżdżonej używana jest
wartość zdefiniowana w najbliższej funkcji
zewnętrznej. Jeśli nie uda się znaleźć takiej
wartości, sprawdzany jest zasięg globalny.
Q
Domknięciem nazywamy funkcję wraz ze
skojarzonym z nią środowiskiem.
Q
Domknięcia zawierają wartości zmiennych
dostępnych w danym zasięgu w momencie
tworzenia tego domknięcia.
Q
Zmiennymi niezależnymi stosowanymi
w ciele funkcji są zmienne, które w danej
funkcji nie są powiązane z żadną wartością.
Q
Jeśli funkcja domknięcia zostanie wywołana
w innym kontekście niż ten, w którym została
utworzona, wartości zmiennych niezależnych
są pobierane ze środowiska wchodzącego
w skład domknięcia.
Q
Domknięcia są często stosowane do
zapamiętywania stanu w procedurach
obsługi zdarzeń.
Funkcje anonimowe, zasięg i domknięcia
Zaostrz ołówek
Rozwiązanie
Przedstawiony poniżej fragment kodu zapewnia kilka możliwości zastosowania
funkcji anonimowych. Skorzystaj z nich i użyj funkcji anonimowych wszędzie
tam, gdzie to możliwe. Możesz przekreślić stary kod i obok napisać nowy.
I jeszcze jedna rzecz: zakreśl wszystkie anonimowe funkcje, które już są
używane. Poniżej zamieściliśmy rozwiązanie.
window.onload = init;
var cookies = {
instructions: ”WstÚpne rozgrzewanie do 175 stopni...”,
bake: function(time) {
console.log(”Wypiekam ciasteczka.”);
setTimeout(done, time);
}
};
function init() {
var button = document.getElementById(”bake”);
button.onclick = handleButton;
}
function handleButton() {
console.log(”-uĝ moĝna wypiekaÊ ciasteczka.”);
cookies.bake(2500);
}
function done() {
alert(”Ciasteczka sÈ gotowe, wyciÈgnij je, by przestygïy.”);
console.log(”Chïodzenie ciasteczek.”);
var cool = function() {
alert(”Ciasteczka sÈ juĝ zimne, moĝna je jeĂÊ!”);
};
setTimeout(cool, 1000);
}
jesteś tutaj
535
Rozwiązanie ćwiczenia
Zmodyfikowaliśmy kod, by utworzyć dwa
anonimowe wyrażenia funkcyjne — jedno
zastępujące funkcję init, a drugie — funkcję
handleButton.
ie funkcyjne
jemy wyrażen
Teraz przypisuindow.onload…
w
właściwości
window.onload = function () {
var button = document.getElementById(”bake”);
button.onclick = function() {
console.log(”-uĝ moĝna wypiekaÊ ciasteczka.”);
cookies.bake(2500);
…a drugie wyrażenie
funkcyjne przypisujemy
właściwości button.onclick.
}
};
var cookies = {
instructions: ”WstÚpne rozgrzewanie do 175 stopni...”,
bake: function(time) {
console.log(”Wypiekam ciasteczka.”);
setTimeout(done, time);
}
};
function done() {
alert(”Ciasteczka sÈ gotowe, wyciÈgnij je, by przestygïy.”);
console.log(”Chïodzenie ciasteczek.”);
var cool = function() {
alert(”Ciasteczka sÈ juĝ zimne, moĝna je jeĂÊ!”);
};
Należą Ci się dodatkowe słowa
uznania, jeśli zauważyłeś, że
funkcję cool można umieścić
bezpośrednio w wywołaniu
metody setTimeout, tak jak
pokazaliśmy poniżej.
setTimeout(cool, 1000);
}
setTimeout(function() {
alert(”Ciasteczka sÈ juĝ zimne, moĝna je jeĂÊ!”);
}, 1000);
536
Rozdział 11 .
Funkcje anonimowe, zasięg i domknięcia
Ćwiczenie
Rozwiązanie
Teraz upewnimy się, że dobrze zapamiętałeś składnię używaną do przekazywania anonimowych
wyrażeń funkcyjnych w wywołaniach innych funkcji. Zmień poniższy kod tak, by zamiast
zmiennej (w tym przypadku vaccine) argumentem wywołania było anonimowe wyrażenie
funkcyjne. Oto nasze rozwiązanie.
administer(patient, function(dosage) {
if (dosage > 0) {
inject(dosage);
}
}, time);
Ćwiczenie
Rozwiązanie
Zauważ, że nic nie stoi na
przeszkodzie, by wyrażenie funkcyjne
przekazywane jako argument zapisywać
w kilku wierszach. Jednak uważaj na
składnię, w takich sytuacjach bardzo
łatwo można popełnić błąd!
Teraz Twoja kolej. Spróbuj utworzyć opisane poniżej domknięcia. Zdajemy sobie sprawę,
że tworzenie domknięć nie jest prostym zadaniem.
Poniżej przedstawiliśmy nasze rozwiązanie:
Domknięcie pierwsze za 10 punktów. Funkcja makePassword pobiera argument reprezentujący hasło i zwraca
funkcję, do której przekazywany jest łańcuch porównywany z hasłem. Funkcja ta zwraca wartość true, jeśli
przekazany łańcuch odpowiada hasłu. (Zanim zrozumiemy działanie domknięcia, czasami trzeba kilkakrotnie
przeczytać jego opis).
Wynikiem wywołania funkcji
makePassword jest domknięcie, ko
w którego skład wchodzi środowis
zawierające zmienną niezależną
password.
function makePassword(password) {
return function guess(passwordGuess) {
return (passwordGuess === password);
};
W wywołaniu funkcji makePassword
przekazujemy słowo „tajne”,
a zatem to właśnie ten łańcuch
znaków zostanie zapisany
w środowisku domknięcia.
}
var tryGuess = makePassword(”tajne”);
console.log(”PróbujÚ IJnic z tegoij: ” + tryGuess(”nic z tego”));
console.log(”PróbujÚ IJtajneij: ” + tryGuess(”tajne”));
Zauważ, że używamy tu wyrażenia z funkcją,
która ma nazwę! Nie jest to konieczne, lecz jest
wygodne, gdyż pozwala odwoływać się do funkcji
wewnętrznej przy użyciu nazwy. Dodatkowo zwróć
uwagę, że zwróconą funkcję musimy wywoływać
za pomocą nazwy tryGuess (a nie guess).
A kiedy wywołujemy funkcję tryGuess,
porównujemy przekazany ciąg („nic z tego”
lub „tajne”) z wartością zmiennej password
przechowywaną w środowisku funkcji tryGuess.
Dalsza część rozwiązania znajduje się na następnej stronie…
jesteś tutaj
537
Ciąg dalszy rozwiązania ćwiczenia
Teraz Twoja kolej. Spróbuj utworzyć opisane poniżej domknięcia. Zdajemy sobie sprawę,
że początkowo tworzenie domknięć nie jest prostym zadaniem.
Ćwiczenie
Rozwiązanie
Poniżej przedstawiliśmy ciąg dalszy naszego rozwiązania.
Domknięcie drugie za 20 punktów. Funkcja multN pobiera liczbę (nazwijmy ją n) i zwraca funkcję.
Ta zwrócona funkcja także pobiera liczbę, którą następnie mnoży przez n i zwraca jako wynik wywołania.
function multN(n) {
return function multBy(m) {
cję multN
Wynikiem zwracanym przez funk
owisko
środ
ego
któr
cie,
knię
dom
jest
n.
zawiera zmienną niezależną
return n*m;
};
A zatem wywołujemy multN(3)
i otrzymujemy funkcję, która mnoży
dowolną przekazaną liczbę razy 3.
}
var multBy3 = multN(3);
console.log(”Mnoĝymy liczbÚ 2: ” + multBy3(2));
console.log(”Mnoĝymy liczbÚ 3: ” + multBy3(3));
Domknięcie trzecie za 30 punktów. To jest modyfikacja licznika, który utworzyliśmy wcześniej
w tym rozdziale. Funkcja makeCounter nie pobiera żadnych argumentów, lecz definiuje zmienną
count. Następnie tworzy i zwraca obiekt udostępniający jedną metodę, increment. Metoda ta
inkrementuje wartość zmiennej count, a następnie ją zwraca.
function makeCounter() {
var count = 0;
return {
Ta funkcja przypomina wcześniejszą wersję
funkcji makeCounter, z tą różnicą, że
teraz zwraca obiekt udostępniający metodę
increment, a nie samą funkcję.
increment: function() {
count++;
return count;
}
};
}
var counter = makeCounter();
Metoda increment używa zmiennej
niezależnej, count. A zatem metoda ta jest
domknięciem, którego środowisko zawiera
wartość zmiennej count.
Teraz wywołujemy funkcję makeCounter
i otrzymujemy z powrotem obiekt udostępniający
metodę (czyli domknięcie).
console.log(counter.increment());
console.log(counter.increment());
console.log(counter.increment());
538
Rozdział 11 .
Wywołujemy metodę w standardowy sposób,
a kiedy to robimy, metoda odwołuje się
do zmiennej count przechowywanej w jej
środowisku.
Funkcje anonimowe, zasięg i domknięcia
Zaostrz ołówek
Rozwiązanie
Skorzystaj ze swojej wiedzy na temat funkcji i zmiennych, aby wskazać,
które z poniższych stwierdzeń są prawdziwe. Poniżej znajdziesz nasze
rozwiązanie:
‰Zmienna handler zawiera referencję do funkcji.
‰Kiedy przypisujemy handler właściwości window.
onload, zapisujemy w niej referencję do funkcji.
‰Jedynym powodem istnienia zmiennej handler jest
zapisanie jej we właściwości window.onload.
‰Już nigdy więcej nie użyjemy funkcji handler, gdyż
jest to kod, który z założenia ma być wykonywany
wyłącznie podczas pierwszego wczytania strony.
‰Dwukrotne wywoływanie procedury obsługi
zdarzeń load nie jest dobrym pomysłem — może
ono doprowadzić do wystąpienia problemów, gdyż
ten kod służy zazwyczaj do wykonywania czynności
związanych z inicjalizacją całej strony.
‰Wyrażenia funkcyjne tworzą referencje do funkcji.
‰Czy wspominaliśmy, że przypisując funkcję
handler właściwości window.onload,
zapisujemy w niej referencję do funkcji?
Zaostrz ołówek
Rozwiązanie
Oto Twoje zadanie: (1) odszukaj i zakreśl wszystkie zmienne niezależne występujące w poniższym
fragmencie kodu. Zmienna niezależna to termin określający zmienną, która nie została zdefiniowana
w zasięgu lokalnym. (2) Wybierz jedno z środowisk przedstawionych z prawej strony ramki, które
domyka funkcję. Oznacza to, że środowisko zawiera wartości wszystkich wolnych zmiennych.
Poniżej znajdziesz nasze rozwiązanie.
To środowisko zawiera
dwie zmienne niezależne:
beingFunny oraz notSoMuch.
beingFunny = true;
function justSayin(phrase) {
notSoMuch = false;
var ending = ””;
LQ&RQYHUVDWLRQ:LWK ĵ3DZHïĵ
if (beingFunny) {
ending = ” -- Tak tylko mówiÚ!”;
} else if (notSoMuch) {
ending = ” -- Nie za bardzo.”;
}
beingFunny = true;
justSayin = false;
oocoder = true;
alert(phrase + ending);
}
Zakreśl zmienne niezależne
występujące w tym kodzie
— czyli zmienne, które nie
zostały zdefiniowane w zasięgu
lokalnym.
Spośród tych środowisk
wybierz to, które domyka
funkcję.
notSoMuch = true;
phrase = ”Hej, la la la”;
band = ”Policja”;
jesteś tutaj
539
Wyzwanie rozwiązane
5R]Z
LÈ]D
JavaScriptowe wyzwanie ekstremalne
Potrzebujemy eksperta do spraw domknięć i słyszeliśmy, że Ty nim jesteś. Teraz już wiesz, jak
działają domknięcia, czy zatem potrafisz wyjaśnić, dlaczego obie przedstawione poniżej próbki zwracają
wynik 008? Aby to wyjaśnić, zapisz wszelkie zmienne przechowywane w środowiskach funkcji.
Zwróć uwagę, że nic nie stoi na przeszkodzie, by środowisko było puste. Oto nasze rozwiązanie.
Próbka nr 1
var secret = ”007”;
Środowisko
function getSecret() {
var secret = ”008”;
lue
ji getVa
W funkc st zmienną
secret jeną…
niezależ
function getValue() {
secret = ”008”
return secret;
}
return getValue();
…a zatem zostaje zapisana w środowisku
tej funkcji. Jednak funkcja getSecret nie
zwraca funkcji getValue, dlatego też nigdy nie
zobaczymy domknięcia poza kontekstem,
w którym jest tworzone.
}
getSecret();
Próbka nr 2
var secret = ”007”;
Środowisko
function getSecret() {
var secret = ”008”;
lue
ji getVa
W funkc st zmienną
secret jeną…
niezależ
secret = ”008”
function getValue() {
return secret;
}
return getValue;
}
var getValueFun = getSecret();
getValueFun();
540
Rozdział 11 .
a w tym przypadku tworzymy domknięcie, które
zwracamy jako wynik wywołania funkcji getSecret.
Kiedy zatem wywołamy funkcję getValueFun (czyli
getValue) w innym kontekście (w tym przypadku:
w kontekście globalnym), użyta zostanie wartość
zmiennej secret przechowywana w środowisku.
QLH
Funkcje anonimowe, zasięg i domknięcia
Zaostrz ołówek
Rozwiązanie
Oto nasze rozwiązanie tego zakręconego zadania.
(function(food) {
if (food === ”ciasteczka”) {
alert(”PoproszÚ o wiÚcej.”);
} else if (food === ”ciasto”) {
alert(”Mniam mniam.”);
}
})(”ciasteczka”);
Twoim zadaniem jest nie tylko określenie, jaki będzie wynik wykonania tego fragmentu
kodu, lecz przede wszystkim, jak on działa. W tym celu postępuj odwrotnie niż do tej
pory, czyli wyodrębnij w kodzie funkcję anonimową, przypisz ją jakieś zmiennej,
a następnie umieść tę zmienną tam, gdzie wcześniej było wyrażenie funkcyjne.
Czy po takich zmianach kod jest bardziej czytelny? Co on robi?
var eat = function(food) {
if (food === ”ciasteczka”) {
alert(”PoproszÚ o wiÚcej.”);
Oczywiście, tę instru
mógłbyś także zapisa kcję
eat(„ciasteczka”), lec ć
z
chodziło nam o pokaza
jak zastąpić wyrażeni nie,
funkcyjne zmienną eate
.
Tutaj umieściliśmy wyodrębnioną
funkcję. Nadaliśmy jej nazwę eat.
Gdybyś wolał, mógłbyś ją także
utworzyć przy użyciu deklaracji
funkcji.
} else if (food === ”ciasto”) {
alert(”Mniam mniam.”);
}
};
(eat)(”ciasteczka”);
A zatem w tym miejscu wywołujemy funkcję
eat i przekazujemy do niej łańcuch znaków
„ciasteczka”. Ale do czego służą te dodatkowe
nawiasy?
Sprawa wygląda tak. Czy pamiętasz, że deklaracje funkcji
zaczynają się od słowa kluczowego function, po którym
podawana jest nazwa funkcji? I czy pamiętasz, że wyrażenia
funkcyjne muszą być podawane w jakiejś instrukcji? Jeśli
zatem nie umieścimy nawiasów wokół wyrażenia funkcyjnego,
interpreter JavaScriptu będzie chciał potraktować je jako
deklarację, a nie jako wyrażenie funkcyjne. Jednak żeby
wywołać funkcję eat, nawiasy nie są potrzebne; możemy je
zatem usunąć.
A zatem cały ten kod służył tylko
temu, by podać wyrażenie funkcyjne
bezpośrednio w instrukcji, a następnie
natychmiast wywołać tworzoną przez nie
funkcję, przekazując do niej określony
argument.
A… swoją drogą, ten kod wyświetla
komunikat „Proszę o więcej.”.
jesteś tutaj
541
542
Rozdział 11 .
12. Zaawansowane sposoby konstruowania obiektów.
Tworzenie obiektów
Dotychczas wszystkie obiekty tworzyłeś własnoręcznie. Opracowując każdy
z nich, korzystałeś z literału obiektowego, w którym podawałeś wszystkie właściwości
i metody. Na niewielką skalę takie rozwiązanie będzie się sprawdzać, jednak podczas
tworzenia poważnego kodu będziesz potrzebował czegoś lepszego. Właśnie w tym miejscu
do akcji wkraczają konstruktory obiektów. Konstruktory sprawiają, że tworzenie obiektów
jest znacznie łatwiejsze, a wszystkie budowane obiekty mogą być zgodne z jednym
wzorcem — oznacza to, że konstruktorów używamy po to, by zapewnić, że wszystkie
obiekty będą miały te same właściwości i udostępniały te same metody. Kiedy korzystasz
z konstruktorów, kod obiektów może być znacznie bardziej zwięzły i mniej podatny na
występowanie błędów, zwłaszcza w przypadkach, gdy tworzysz bardzo dużo obiektów.
Po przeczytaniu tego rozdziału będziesz stosował konstruktory z taką wprawą, jakbyś
dorastał w Obiektowie.
to jest nowy rozdział
543
Stosowanie literałów obiektowych
Tworzenie obiektów przy użyciu literałów obiektowych
Do tej pory w książce tworzyliśmy obiekty wyłącznie przy użyciu literałów obiektowych.
Ten sposób budowania obiektów polega na zapisaniu ich w dosłownej postaci w kodzie,
tak jak pokazaliśmy poniżej.
var taxi = {
make: ”SieMoCorp”,
model: ”Taxi”,
year: 1955,
color: ”ĝóïty”,
passengers: 4,
convertible: false,
mileage: 281341,
started: false,
start: function() { this.started = true;},
stop: function() { this.started = false;},
drive: function {
// kod obsïugujÈcy jazdÚ samochodem
Podczas stosowania literału
obiektowego każdą część obiektu
wpisujemy pomiędzy nawiasami
klamrowymi. Kiedy skończymy,
uzyskamy w efekcie normalny
obiekt języka JavaScript. Tak
tworzone obiekty są zazwyczaj
zapisywane w zmiennych,
by można ich było używać
w dalszej części skryptu.
}
};
Literały obiektowe zapewniają wygodny sposób tworzenia obiektów w dowolnym miejscu
kodu. Jednak w przypadkach, w których konieczne jest opracowanie wielu obiektów —
np. całej floty taksówek — raczej nie chcielibyśmy ich wszystkich tworzyć przy użyciu
takich literałów, prawda?
WYSIL
SZARE KOMÓRKI
Zastanów się nad sposobem utworzenia obiektów reprezentujących całą flotę taksówek. Jakich dodatkowych problemów
mogłoby przysporzyć stosowanie w tym celu literałów obiektowych?
Ból palców od wpisywania setek wierszy kodu.
A czy można zagwarantować, że każdy obiekt
taksówki będzie mieć takie same właściwości?
A co będzie, jeśli popełnimy jakiś błąd, literówkę
lub po prostu pominiemy jakąś właściwość?
Wiele literałów obiektowych oznacza dużo
kodu. A czy to nie oznacza wydłużenia czasu
wyświetlania strony w przeglądarce?
544
Rozdział 12
Kod metod start, stop i drive musiałby się
powtarzać w każdym z literałów obiektowych.
A co by się stało, gdybyśmy musieli dodać lub usunąć
jakąś właściwość (albo zmienić sposób działania
metod start lub stop)? Czy nie oznaczałoby to
konieczności zmiany wszystkich taksówek?
Kto korzystałby z taksówki, kiedy mamy BlaBlaCar
albo Uber?
Zaawansowane sposoby konstruowania obiektów
Stosowanie konwencji podczas tworzenia obiektów
Kolejnym rozwiązaniem, z którego korzystaliśmy do tej pory, było tworzenie
obiektów w oparciu o konwencję. Przykładowo zbieraliśmy kilka właściwości
i metod w jedną całość i stwierdzaliśmy: „To jest samochód!” albo „To jest pies!”,
jednak jedynym czynnikiem sprawiającym, że takie dwa obiekty były samochodami
(lub psami), była przyjęta przez nas konwencja.
To na pewno jest jeden z samochodów,
z którymi już mieliśmy do czynienia
wcześniej w tej książce. Ma dokładnie
takie same właściwości i metody.
var taxi = {
make: ”SieMoCorp”,
model: ”Taxi”,
year: 1955,
color: ”ĝóïty”,
passengers: 4,
convertible: false,
mileage: 281341,
started: false,
Takie rozwiązanie może działać na małą skalę, jednak będzie problematyczne
w rozwiązaniach wymagających tworzenia wielu obiektów lub w przypadkach, gdy nad
kodem pracuje wielu programistów, którzy mogą nie znać dobrze przyjętych konwencji.
Jednak nie musisz wierzyć nam na słowo. Przyjrzyj się kilku obiektom, które
przedstawiliśmy wcześniej w książce i które, według tego, co nam powiedziano,
są samochodami.
No dobrze, to też wygląda na
nasz obiekt samochodu, choć
poczekaj… On przecież ma
dopalacze rakietowe. Hmm, nie
jestem pewny, czy to prawdziwy
samochód.
tbird wygląda na wspaniały samochód,
ale nie widzimy w nim wszystkich
podstawowych i niezbędnych
właściwości, takich jak mileage
oraz color. Wygląda także na to,
że dysponuje kilkoma dodatkowymi
właściwościami. Z tego mogą
wyniknąć jakieś problemy…
start: function() {
this.started = true;
},
stop: function() {
}
var rocketCar = {
make: ”Galaxy”,
model: ”4000”,
year: 2001,
color: ”biaïy”,
passengers: 6,
convertible: false,
mileage: 60191919,
started: false,
var toyCar =
{
make: ”Mattel”
,
model: ”PeeWe
e”,
color: ”niebi
eski”,
type: ”nakrÚc
any”,
price: ”10.99
”
};
start: function() {
this.started = true;
},
stop: function() {
this.started = false;
},
drive: function() {
// kod obsïugujÈcy jazdÚ
},
thrust: function(amount) {
// kod obsïugujÈcy przyspieszanie
}
};
Chwileczkę, to
mógłby być
samochód, lecz
nie wygląda
podobnie
do naszych
pozostałych
obiektów. Ma
markę, model
i kolor, ale poza
tym wygląda na
zabawkę, a nie
samochód.
Co on tu robi?
this.started = false;
var tbird = {
make: ”Ford”,
model: ”Thunderbird”,
year: 1957,
passengers: 4,
convertible: true,
started: false,
oilLevel: 1.0,
start: function() {
if (oilLevel > .75) {
this.started = true;
}
},
stop: function() {
this.started = false;
},
drive: function() {
// kod obsïugujÈcy jazdÚ
}
}
jesteś tutaj
545
Gdybym tylko wiedziała,
jak tworzyć obiekty mające taką
samą podstawową strukturę... Dzięki temu
wszystkie moje obiekty wyglądałyby tak samo,
gdyż miałyby te same właściwości, a wszystkie
metody byłyby zdefiniowane w jednym miejscu.
To byłoby jak foremka do ciasteczek, której
mogłabym używać do wycinania takich samych
obiektów. To byłoby cudowne… Szkoda, że to
tylko moje marzenia…
546
Rozdział 12
Zaawansowane sposoby konstruowania obiektów
Prezentacja konstruktorów obiektów
Konstruktory obiektów, nazywane zazwyczaj po prostu „konstruktorami”,
są naszym ulepszonym sposobem tworzenia obiektów. Możesz je sobie
wyobrażać jako niewielkie fabryki, mogące wytwarzać niezliczone ilości
takich samych obiektów.
Z punktu widzenia kodu konstruktor przypomina funkcję zwracającą obiekt:
definiujemy go tylko raz, a następnie wywołujemy za każdym razem, kiedy
potrzebujemy nowego obiektu. Jednak, jak się niebawem przekonasz,
konstruktory mają w sobie coś więcej.
Najlepszym sposobem przekonania się, jak działają konstruktory, jest
napisanie i wypróbowanie jednego z nich. Wróćmy zatem do naszego
starego przyjaciela, obiektu reprezentującego psa, poznanego wcześniej
w tej książce, i napiszmy konstruktor, który pozwoli utworzyć tak wiele
psów, ile tylko będziemy potrzebować. Poniżej przedstawiliśmy wcześniejszą
wersję obiektu Dog, dysponującą właściwościami name, breed oraz weight.
Konstruktory obiektów
oraz funkcje są
ściśle powiązane.
Pamiętaj o tym,
ucząc się tworzenia
i stosowania
konstruktorów.
name
breed
weight
Dog
Gdybyśmy mieli zdefiniować taki obiekt przy użyciu literału
obiektowego, moglibyśmy to zrobić w następujący sposób.
var dog = {
name: ”Burek”,
breed: ”mieszaniec”,
weight: 20
Zwyczajny obiekt psa utworzony
za pomocą literału obiektowego.
Teraz musimy się dowiedzieć,
jak utworzyć całą masę takich
milusińskich.
Osobiście uważam,
że konstruktory
mają charakter iście
frankensteinowski. Stanowią
połączenie elementów
funkcji i obiektów. Czyż
kiedykolwiek stworzono
coś piękniejszego?
};
Jednak nie interesuje nas tylko Burek, chcemy poznać sposób pozwalający
na utworzenie dowolnego psa, który ma własne imię, określoną rasę i wagę.
Jeszcze raz powtarzamy, że w tym celu będziemy musieli napisać jakiś kod
przypominający z wyglądu funkcję, do której dorzucimy trochę składni
obiektu.
Takie wprowadzenie musiało Cię zainteresować — no dalej, przewróć
kartkę i w końcu poznaj konstruktory.
Na następnej
stronie
przekonasz się,
dlaczego.
jesteś tutaj
547
Tworzenie konstruktora
Jak utworzyć konstruktor?
Budowanie konstruktorów jest procesem dwuetapowym: w pierwszym kroku definiujemy
konstruktor, a w drugim używamy go w celu tworzenia obiektów. Najpierw skoncentrujemy
się na napisaniu konstruktora.
Naszym celem jest napisanie konstruktora, którego będziemy mogli używać do tworzenia
psów, a mówiąc bardziej konkretnie: psów, które mają imię, wagę i są określonej rasy.
A zatem zdefiniujemy funkcję nazywaną konstruktorem, która będzie wiedzieć, jak tworzyć
takie psy. Oto ona.
Funkcja konstruktora
wygląda jak
zwyczajna funkcja.
Zwróć jednak uwagę, że jej nazwę
zaczęliśmy pisać od wielkiej litery.
Nie jest to żaden wymóg, lecz
wszyscy stosują tę konwencję.
Parametry funkcji
odpowiadają właściwościom,
które mają mieć obiekty
poszczególnych psów.
function Dog(name, breed, weight) {
this.name = name;
Ta część już nieco bardziej przypomina
obiekt, gdyż przypisujemy poszczególne
parametry do czegoś, co wygląda jak
właściwości.
this.breed = breed;
this.weight = weight;
}
Zwróć uwagę,
Nazwy parametrów
że funkcja
i właściwości nie
konstruktora
muszą być identyczne,
nic nie zwraca.
jednak często są —
to też jest ogólnie
przyjętym rozwiązaniem.
Hm, w konstruktorze nie używamy żadnych zmiennych
lokalnych, co nieco odróżnia go od większości
innych funkcji. Zamiast tego używamy w nim słowa
kluczowego this, które do tej pory pojawiało się tylko
w obiektach.
Zaczekaj, za chwilę pokażemy, jak skorzystać
z tego konstruktora, a wtedy wszystkie te
elementy wskoczą na odpowiednie miejsca
i wszystko nabierze sensu.
Zaostrz ołówek
Potrzebujemy Twojej pomocy. Używaliśmy literałów obiektowych, by tworzyć kaczki. Czy na
podstawie przykładu przedstawionego powyżej mógłbyś napisać konstruktor do tworzenia
kaczek? Poniżej przedstawiliśmy jeden z naszych literałów obiektowych, żebyś miał się na
czym wzorować.
var duck = {
type: ”krzyĝówka”,
canFly: true
Tutaj zapisz konstruktor
tworzący takie kaczki.
}
To jest przykładowy
literał obiektowy kaczki.
548
Rozdział 12
P.S. Wiemy, że jeszcze nie do końca rozumiesz, jak to wszystko
działa, więc na razie skoncentruj się na samej składni.
Zaawansowane sposoby konstruowania obiektów
Jak należy używać konstruktorów?
Napisaliśmy wcześniej, że stosowanie konstruktorów jest procesem dwuetapowym: najpierw
konstruktor należy napisać, a później go używać. A zatem skoro udało się już napisać
konstruktor Dog, możemy go użyć. A tak można to zrobić.
W celu utworzenia obiektu psa
używamy operatora new wraz
z konstruktorem.
Za operatorem
umieszczane
jest wywołanie
konstruktora…
Spróbuj powiedzieć to na
głos: „tworzę obiekt fido,
tworzę nowy obiekt psa
o nazwie Burek. Burek
jest mieszańcem, który
waży 20 kilo”.
…wraz
z argumentami.
var fido = new Dog(“Burek”, “mieszaniec”, 20);
Aby zatem utworzyć nowy obiekt psa, który wabi się ”Burek”, jest rasy ”mieszaniec”
i waży 20 kg, zaczynamy od zapisania słowa kluczowego new, a za nim umieszczamy
wywołanie funkcji konstruktora wraz z odpowiednimi argumentami. Po wykonaniu tej
instrukcji zmienna fido będzie zawierać referencję do naszego nowego obiektu psa.
Skoro mamy już konstruktor do tworzenia psów, możemy utworzyć jeszcze kilka kolejnych.
var fluffy = new Dog(”Dino”, ”pudel”, 16);
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
To chyba trochę łatwiejsze od stosowania literałów obiektowych, prawda? Poza tym
w przypadku tworzenia obiektów psów w taki sposób każdy z nich będzie dysponował
tymi samymi właściwościami: name, breed oraz weight.
Ćwiczenie
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
var fido = new Dog(”Burek”, ”mieszaniec”, 20);
var fluffy = new Dog(”Dino”, ”pudel”, 16);
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
var dogs = [fido, fluffy, spot];
Spróbuj zdobyć trochę szybkich,
praktycznych doświadczeń, aby
cała wiedza dobrze się utrwaliła.
Zapisz poniższy kod w jakiejś
stronie i weź ją na jazdę próbną.
Poniżej zanotuj uzyskane wyniki.
for(var i = 0; i < dogs.length; i++) {
var size = ”maïy”;
if (dogs[i].weight > 10) {
size = ”duĝy”;
}
console.log(”Pies: ” + dogs[i].name
+ ” to ” + size
+ ” pies rasy ” + dogs[i].breed);
}
jesteś tutaj
549
Jak naprawdę działają konstruktory
Sposób działania konstruktorów
Wiesz już, jak można zadeklarować konstruktor oraz używać go do tworzenia nowych
obiektów; jednak warto także zajrzeć za kulisy konstruktorów, by przekonać się, jak
naprawdę działają. A oto klucz do tej tajemnicy: aby zrozumieć konstruktory, należy
wiedzieć, co robi operator new.
Zaczniemy od instrukcji, której użyliśmy wcześniej do określenia wartości zmiennej fido.
var fido = new Dog(”Burek”, ”mieszaniec”, 20);
Przyjrzyjmy się teraz prawej stronie operatora przypisania, gdzie dzieją się najważniejsze
rzeczy. Spróbujmy wykonać to wyrażenie.
1
3LHUZV]ċU]HF]ċNWyUċURELRSHUDWRUnewMHVWXWZRU]HQLH
QRZHJRSXVWHJRRELHNWX
2
1DVWĐSQLHnewVSUDZLDİH]PLHQQDthisRGZRâXMHVLĐGR
WHJRQRZHJRRELHNWX
3
Zapewne pamiętasz
z rozdziału 5,
że this zawiera
referencję do
aktualnego obiektu,
na którym operuje
nasz kod.
this
.LHG\]RVWDâDMXİRNUHĤORQDZDUWRĤþthisZ\ZRâXMHP\IXQNFMĐDog
SU]HND]XMċFGRQLHMDUJXPHQW\”Burek””mieszaniec”RUD]20
”Burek”
”mieszaniec”
20
QDPH%XUHN
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
this
}
4
QDPH%XUHN
Następnie wykonywane jest ciało funkcji. Podobnie jak
przeważająca większość konstruktorów, także funkcja Dog
przypisuje wartości właściwościom nowo utworzonego
obiektu this.
EUHHGPLHV]DQLHF
this
QDPH%XUHN
EUHHGPLHV]DQLHF
osowuje
Wykonanie ciała funkcji Dog dost w nim
ząc
twor
ktu,
obie
go
nowe
ć
rtoś
zawa
wartości
trzy właściwości i przypisując im
ów.
metr
para
odpowiednich
550
Rozdział 12
this
ZHLJKW
Zaawansowane sposoby konstruowania obiektów
5
:NRĚFXNLHG\UHDOL]DFMDIXQNFMLDog]RVWDQLH]DNRĚF]RQD
RSHUDWRUnew]ZUDFDZDUWRĤþthisF]\OLUHIHUHQFMĐGRQRZR
XWZRU]RQHJRRELHNWX=DXZDİİHFDâHWRZ\UDİHQLHVDPR
]ZUDFDZDUWRĤþthisQLHPXVLP\WHJRURELþMDZQLHZVZRLP
NRG]LH$NLHG\Z\UDİHQLH]ZUyFLMXİQRZ\RELHNWPRİHP\
]DSLVDþUHIHUHQFMĐGRQLHJRZ]PLHQQHMfido
Za kulisami
QDPH%XUHN
EUHHGPLHV]DQLHF
ILGR
ZHLJKW
BĄDŹ przeglądarką
Poniżej znajdziesz fragment kodu JavaScript, w którym ukryło się
kilka błędów. Twoim zadaniem jest wcielić się w rolę przeglądarki
i odnaleźć te błędy. Kiedy już wykonasz to ćwiczenie, zajrzyj do
rozwiązania zamieszczonego pod koniec rozdziału i przekonaj
się, czy udało Ci się znaleźć je wszystkie. A poza tym, jesteśmy
już w rozdziale 12., zatem jeśli chcesz, zapisz także swoje uwagi
dotyczące stylu tego kodu. Zasłużyłeś sobie na to.
function widget(partNo, size) {
var this.no = partNo;
var this.breed = size;
}
function FormFactor(material, widget) {
this.material = material,
this.widget = widget,
return this;
}
var widgetA = widget(100, ”duĝy”);
var widgetB = new widget(101, ”maïy”);
var formFactorA = newFormFactor(”plastikowy”, widgetA);
var formFactorB = new ForumFactor(”metalowy”, widgetB);
jesteś tutaj
551
Dodawanie metod do konstruktorów
W konstruktorach można także umieszczać metody
Obiekty tworzone przy użyciu naszego nowego konstruktora Dog przypominają obiekty
budowane i stosowane wcześniej w tej książce, z tą różnicą, że nasze nowo utworzone psy
nie potrafią szczekać (ponieważ nie dysponują metodą bark). Problem ten możemy jednak
bardzo łatwo rozwiązać, gdyż w konstruktorach, oprócz określania wartości właściwości,
można także definiować metody. Rozszerzmy zatem kod naszego obiektu i dodajmy do
niego metodę bark.
Swoją drogą, jak
wiesz, metody
obiektów także są
właściwościami.
Wyróżniają się tylko
tym, że są w nich
zapisane funkcje.
Aby dodać metodę bark, przypisujemy
funkcję — w naszym przypadku jest
to funkcja anonimowa — właściwości
this.bark.
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
this.bark = function() {
if (this.weight > 15) {
alert(this.name + ” szczeka hau!”);
} else {
alert(this.name + ” szczeka hiauu!”);
}
};
}
stkich obiektach,
Zauważ, że podobnie jak we wszye i w tej metodzie
takż
j,
śnie
wcze
my
zyliś
twor
które
obiektu, na rzecz
używamy this, by odwołać się do
a.
ołan
wyw
ała
zost
którego funkcja
Krótka jazda próbna testująca metodę bark
Wystarczy już tego gadania o konstruktorach, dodaj powyższy kod do
strony WWW, a następnie uzupełnij go o następujący fragment kodu,
który pozwoli przetestować nasz nowy konstruktor.
var fido = new Dog(”Burek”, ”mieszaniec”, 20);
var fluffy = new Dog(”Dino”, ”pudel”, 16);
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
var dogs = [fido, fluffy, spot];
for (var i = 0; i < dogs.length; i++) {
dogs[i].bark();
}
Upewnij się, że obiekty
psów szczekają zgodnie
z oczekiwaniami.
552
Rozdział 12
Teraz każdy obiekt będzie
także dysponował metodą
bark, którą będziemy mogli
wywoływać.
Zaawansowane sposoby konstruowania obiektów
Dysponujemy konstruktorem do tworzenia kubków kawy różnej wielkości,
jednak brakuje w nim metod.
Ćwiczenie
Potrzebujemy metody, o nazwie getSize, która zależnie od wielkości kawy
w mililitrach zwróci odpowiedni łańcuch znaków.
250 ml to ĵPDïDĵ kawa,
Q
375 ml to ĵĂUHGQLDĵ kawa,
Q
500 ml to ĵGXĝDĵ kawa.
Q
Potrzebujemy także metody o nazwie toString, która będzie zwracać łańcuch znaków
reprezentujący zamówienie na kawę, w przykładowej postaci: ĵ7ZRMH]DPöZLHQLHGXĝD
kawa Segafredo.”
Swój kod zapisz poniżej, a następnie przetestuj go w przeglądarce. Spróbuj utworzyć zamówienia
na kilka kaw różnej wielkości. Zanim przejdziesz dalej, sprawdź rozwiązanie ćwiczenia.
function Coffee(roast, ounces) {
this.roast = roast;
this.ounces = ounces;
Tutaj zapisz dwie metody,
które masz dodać do
konstruktora.
}
var coffee = new Coffee(”Segafredo”, 375);
console.log(coffee.toString());
var darkRoast = new Coffee(”Parana Caffe”, 500);
console.log(darkRoast.toString());
Oto nasze wyniki,
Twoje powinny
wyglądać identycznie.
Konsola JavaScript
7ZRMH]DPöZLHQLHĂUHGQLDNDZD
Segafredo.
7ZRMH]DPöZLHQLHGXĝDNDZD3DUDQD
Caffe.
jesteś tutaj
553
Pytania dotyczące konstruktorów
Nie istnieją
głupie pytania
P: Dlaczego nazwy konstruktorów
zaczynają się od wielkiej litery?
O: To konwencja przyjęta przez
programistów JavaScript, dzięki której
mogą łatwo określać, która funkcja jest
konstruktorem, a która jedynie zwyczajną
funkcją. Po co? Ponieważ funkcji
konstruktora można używać wyłącznie
wraz z operatorem new. Ogólnie
rzecz biorąc, stosowanie wielkich liter
w konstruktorach ułatwia wyróżnienie ich
w kodzie podczas jego analizy.
O: Nie. Nazwy parametrów mogą być
dowolne. Parametry są używane tylko do
przechowania wartości, które chcemy
zapisać we właściwościach obiektu, aby
dostosować jego zawartość. Znaczenie
mają jedynie nazwy właściwości obiektu.
Pomimo to, ze względu na przejrzystość
kodu, parametry i właściwości często mają
takie same nazwy, dzięki czemu patrząc na
definicję funkcji konstruktora, można się
zorientować, które właściwości są w nim
określane.
: Czy obiekt tworzony przy
P: A zatem z wyjątkiem określania P
użyciu konstruktora jest taki sam
wartości obiektu this konstruktory
przypominają zwyczajne funkcje?
O
jak obiekt budowany za pomocą
literału?
O: Tak, przynajmniej do momentu, kiedy
: Jeśli chodzi o możliwości
konstruktora, to tak. W konstruktorach
można robić dokładnie to samo, co
w normalnych funkcjach, czyli np.
deklarować zmienne i używać ich,
stosować pętle, wywoływać inne funkcje
itd. Jedyną operacją, której nie będziemy
robić w konstruktorze, jest zwracanie
wartości (z wyjątkiem this), gdyż
w takim przypadku konstruktor nie
zwróci obiektu, który miał utworzyć.
obiektów potrzebny jest operator
new? Czy moglibyśmy utworzyć
obiekt w zwyczajnej funkcji i zwrócić
go (tak jak robiliśmy w funkcji
makeCar w rozdziale 5.)?
P: Czy nazwy parametrów
O: Tak, obiekty można tworzyć
konstruktorów muszą odpowiadać
nazwom właściwości?
554
Rozdział 12
zajmiemy się bardziej zaawansowanymi
zagadnieniami projektowania obiektów,
czyli do następnego rozdziału.
P: Dlaczego przy tworzeniu
także w taki sposób, jednak jak już
wspominaliśmy w poprzedniej odpowiedzi,
stosowanie operatora new daje także inne
efekty. Zajmiemy się nimi dokładniej dalej
w tym rozdziale, a następnie wrócimy do
nich w rozdziale 13.
P: Wciąż wprowadza mnie w błąd
użycie słowa kluczowego this
w konstruktorze. Używamy go,
by określać wartości właściwości,
lecz również korzystamy z niego
w metodach obiektu. Czy to są te
same rzeczy?
O: Kiedy wywołujesz konstruktor
(by utworzyć obiekt), wartością this
staje się nowo utworzony obiekt,
zatem cały kod wykonywany w ramach
konstruktora odnosi się do tego obiektu.
Później, kiedy wywołujesz metody tego
obiektu, w this zapisywany jest obiekt,
którego metoda została wywołana.
Dlatego this w metodach zawsze
odwołuje się do obiektu, którego metoda
jest wykonywana.
P: Czy lepiej tworzyć obiekty przy
użyciu konstruktora, czy literału
obiektowego?
O: Obie metody są użyteczne. Konstruktor
przydaje się, kiedy chcemy tworzyć dużo
obiektów o tych samych właściwościach
i metodach. Stosowanie konstruktorów jest
wygodne, pozwala wielokrotnie używać
tego samego kodu i zapewnia spójność
obiektów.
Czasami jednak musimy szybko utworzyć
jakiś obiekt, być może używany tylko
jeden, jedyny raz, a literały pozwalają to
Doskonały
zrobić w sposób zwarty i ekspresyjny.
A zatem wszystko zależy od naszych
potrzeb. Oba rozwiązania są świetnymi
sposobami tworzenia obiektów.
przykład
takiego
rozwiązania
przedstawimy
już niebawem.
Zaawansowane sposoby konstruowania obiektów
6WUHID]DJURĝHQLD
,VWQLHMHSHZLHQDVSHNWNRQVWUXNWRUyZQDNWyU\PXVLV]EDUG]RXZDĪDüQLH]DSRPLQDMRXĪ\FLXRSHUDWRUDnew.
$áDWZRPRĪHVLĊWR]GDU]\üJG\ĪNRQVWUXNWRUMHVWZáDĞFLZLH]Z\F]DMQąIXQNFMąLQLFQLHVWRLQDSU]HV]NRG]LH
E\Z\ZRá\ZDüJREH]WHJRRSHUDWRUD-HGQDNSRPLQLĊFLHRSHUDWRUDnewPRĪHSURZDG]LüGRSRZVWDZDQLDEáĊGQLH
G]LDáDMąFHJRNRGXNWyU\WUXGQR]GLDJQR]RZDüLSRSUDZLü. =REDF]P\FRVLĊG]LHMHNLHG\]DSRPQLP\RVáRZLH
NOXF]RZ\Pnew…
function Album(title, artist, year) {
this.title = title;
this.artist = artist;
To wygląda jak prawidłowy
konstruktor.
this.year = year;
this.play = function() {
// kod metody
};
}
iśmy
Ups… zapomniel
o operatorze new!
Ale może wszystko będzie
w porządku, bo przecież
Album jest funkcją.
var darkside = Album(”Dark Side of the Cheese”,”Pink Mouse”, 1971);
darkside.play();
Spróbujmy, pomimo to, wywołać
metodę play. O… Niedobrze…
Uncaught TypeError: Cannot call method 'play' of undefined*)
/LVWDEH]SLHF]HñVWZD
:SRU]ąGNXSU]HF]\WDMWĊOLVWĊDE\RNUHĞOLüFRPRJáRE\üSU]\F]\Qą
SUREOHPyZ
‰ 3DPLĊWDMĪHRSHUDWRUnewQDMSLHUZWZRU]\QRZ\RELHNWSRF]\P]DSLVXMHJR
w zmiennej this DGRSLHURSRWHPZ\ZRáXMHIXQNFMĊNRQVWUXNWRUD -HĞOLQLH
XĪ\MHV]newQRZ\RELHNWQLJG\QLH]RVWDQLHXWZRU]RQ\
‰ 7RR]QDF]DĪHZV]\VWNLHRGZRáDQLDGRthisXPLHV]F]RQHZNRQVWUXNWRU]H
QLHEĊGąVLĊRGZRá\ZDüGRQRZHJRRELHNWXDOEXPXOHF]UDF]HMGR
JOREDOQHJRRELHNWXDSOLNDFML
Obiekt globalny to
obiekt istniejący na
najwyższym poziomie
kodu, czyli tam, gdzie
są przechowywane
zmienne globalne.
W przeglądarkach jest
to obiekt window.
‰ -HĞOLQLHXĪ\MHV]newQLHEĊG]LHRELHNWXNWyU\PyJáE\]RVWDü]ZUyFRQ\
SU]H]NRQVWUXNWRUWR]NROHLR]QDF]DĪHQLHEĊG]LHĪDGQHJRRELHNWXNWyU\
PRĪQD]DSLVDüZ]PLHQQHMdarksideD]DWHPMHMZDUWRĞFLąEĊG]LHXQGHğQHG.
*) Nieprzechwycony wyjątek TypeError: Nie można wywołać metody ‘play’ obiektu
undefined — przyp. tłum.
jesteś tutaj
555
Jeśli do tworzenia obiektów
używasz konstruktora, lecz podczas
prób odwołania się do nich okazuje
się, że zamiast obiektów uzyskujesz
jedynie wartość undefined, dobrze
sprawdź swój kod i upewnij się, że
wraz z konstruktorami korzystasz
z operatora new.
ę
A jeśli trzymasz otwartą menzurk
wypełnioną płynem nad drogim
laptopem, także powinieneś to
sprawdzić!
556
Rozdział 12
Zaawansowane sposoby konstruowania obiektów
Konstruktor bez tajemnic
Temat dzisiejszego wywiadu brzmi:
Poznajemy new
Rusz głową: new, gdzie się chowałeś do tej pory? Jak to
się stało, że udało się nam dotrzeć do rozdziału 12. i nawet
o Tobie nie słyszeliśmy?
new: W internecie wciąż jest bardzo dużo skryptów, które
mnie nie używają, bądź używają, ale nie rozumieją.
Rusz głową: Dlaczego tak się dzieje?
new: Ponieważ wielu programistów korzysta z literałów
obiektowych bądź też jedynie kopiują i wklejają kod, w którym
jestem używany, lecz nie starają się mnie zrozumieć.
Rusz głową: Tak, to prawda… Literały obiektowe są
wygodne, a sam nie do końca rozumiem, kiedy i jak należy
Ciebie używać.
new: No właśnie, to dlatego, że jestem dosyć
zaawansowaną możliwością języka. Żeby mnie używać,
trzeba najpierw zrozumieć, jak działają obiekty, jak działają
funkcje i jak działa this… To całkiem sporo tematów,
które należy poznać, zanim w ogóle o mnie usłyszysz!
new: Przede wszystkim tworzę nowy obiekt. Wszyscy myślą,
że to funkcje konstruktorów tworzą nowe obiekty, lecz
w rzeczywistości ja to robię. To naprawdę niewdzięczne
zadanie.
Rusz głową: Kontynuuj…
new: No dobrze… Następnie wywołuję funkcję konstruktora
i upewniam się, że w jej ciele słowo kluczowe this będzie się
odwoływać do utworzonego wcześniej obiektu.
Rusz głową: Dlaczego to robisz?
new: Dlatego, by instrukcje umieszczone w ciele funkcji
mogły odwoływać się do obiektu. W końcu sensem
istnienia funkcji konstruktorów jest rozszerzenie obiektu
o nowe właściwości i metody. Jeśli używacie konstruktora
do tworzenia obiektów, takich jak psy lub samochody,
na pewno będziecie chcieli, by te obiekty miały jakieś
właściwości, prawda?
Rusz głową: Oczywiście. A później?
Rusz głową: Czy możemy prosić o krótką prezentację
Twoich możliwości? Skoro nasi czytelnicy znają już obiekty,
funkcje oraz słowo kluczowe this, warto zmotywować ich
do nauki.
new: Później upewniam się, że nowo utworzony obiekt
zostanie zwrócony z konstruktora. To ukłon w kierunku
programistów, aby sami nie musieli pamiętać o zwracaniu
obiektu z funkcji konstruktora.
new: Niech chwilkę pomyślę… No dobrze, niech
będzie: jestem operatorem, który operuje na funkcjach
konstruktorów, by tworzyć obiekty.
Rusz głową: To faktycznie wygląda na znaczne ułatwienie.
Po cóż ktoś, kto Cię pozna, miałby jeszcze używać literału
obiektowego?
Rusz głową: Hm… Nie jest to najlepsza autoprezentacja,
jaką słyszeliśmy.
new: Ech, literał obiektowy i ja jesteśmy starymi
znajomymi. To świetny gość i sam bym go używał, gdybym
musiał szybko utworzyć jakiś obiekt. Ja z kolei jestem
bardziej przydatny, kiedy trzeba zbudować wiele dokładnie
takich samych obiektów, kiedy chcemy mieć pewność, że
skorzystamy z możliwości wielokrotnego wykorzystania
kodu, kiedy zależy nam na spójności, a także w przypadku
korzystania z bardziej zaawansowanych możliwości; ale
o nich musicie się dopiero dowiedzieć.
new: Odczepcie się ode mnie. Jestem operatorem, a nie
specem od autopromocji.
Rusz głową: No dobrze, nawet po takiej prezentacji
pojawiło się kilka pytań, które można Ci zadać. Przede
wszystkim czy jesteś operatorem?
new: Pewnie! Jestem operatorem. Wystarczy mnie
umieścić przed wywołaniem funkcji, a wszystko zmienię.
W końcu operator operuje na swoich operandach. W moim
przypadku operand jest tylko jeden, jest nim wywołanie
funkcji.
Rusz głową: Słusznie, wyjaśnij nam zatem, prosimy, na
czym polega Twoje działanie.
Rusz głową: Bardziej zaawansowanych? O rany, powiedz
nam o nich!
new: Później, teraz nie będę dekoncentrował czytelników.
Porozmawiamy o nich w następnym rozdziale.
Rusz głową: Chyba trzeba będzie jeszcze raz przemyśleć
tematykę tego wywiadu! Na razie…
jesteś tutaj
557
Tworzenie konstruktora samochodów
Nadszedł czas na produkcję masową
W samą porę poznałeś konstruktory,
bo właśnie otrzymaliśmy duże
zamówienie na samochody i nie
bylibyśmy w stanie utworzyć ich
wszystkich ręcznie. Musimy użyć
konstruktorów, aby wyrobić się z zadaniem
na czas. Aby zbudować potrzebny konstruktor, weźmiemy
wszystkie obiekty samochodów, które utworzyliśmy wcześniej
w książce, i użyjemy ich jako wzoru.
Przejrzyj wszystkie rodzaje samochodów, jakie będziemy musieli tworzyć —
przedstawiliśmy je poniżej. Zwróć uwagę, że skorzystaliśmy już z możliwości i zapewniliśmy,
że wszystkie obiekty aut mają takie same właściwości i metody, tak by wszystkie auta były identyczne. Na razie
nie będziemy przejmować się opcjami specjalnymi, zabawkami czy też jakimiś autami rakietowymi (wrócimy do
nich później). Rzuć okiem na poniższe obiekty, a następnie napisz konstruktor, który będzie tworzył obiekty
dowolnych aut mających następujące właściwości i metody.
var chevy = {
make: ”Chevy”,
model: ”Bel Air”,
year: 1957,
color: ”czerwony”,
passengers: 2,
convertible: false,
mileage: 1021,
started: false,
start: function() {
this.started = true;
},
stop: function() {
this.started = false;
},
var fiat =
{
make: ”Fiat”
,
model: ”500
};
”,
year: 1957,
color: ”sza
roniebieski”
,
passengers:
2,
convertible:
false,
mileage: 88
000,
started: fa
lse,
start: func
tion() {...
},
stop: functi
on() {...},
drive: func
tion() {...
}
};
drive: function() {
if (this.started) {
console.log(this.make + ” ” +
this.model + ” robi: brum wrrrr!”);
} else {
console.log(”Najpierw musisz wïÈczyÊ silnik.”);
}
}
};
558
Rozdział 12
var cadi = {
make: ”GM”,
model: ”Cadillac”,
year: 1955,
color: ”jasnobrÈzowy”,
passengers: 5,
convertible: false,
mileage: 12892,
started: false,
start: function() { ... },
stop: function() { ... },
drive: function() { ... }
var taxi = {
make: ”SieMoCorp”,
model: ”Taxi”,
year: 1955,
color: ”yellow”,
passengers: 4,
convertible: false,
mileage: 281341,
started: false,
start: function() { ... },
stop: function() { ... },
drive: function() { ... }
};
Zaawansowane sposoby konstruowania obiektów
Wykorzystaj całą nabytą wiedzę, by napisać konstruktor Car. Sugerujemy, żebyś wykonywał to
ćwiczenie w następującej kolejności.
Ćwiczenie
1
Zacznij od słowa kluczowego function (które już zapisaliśmy), a następnie podaj nazwę
konstruktora. Potem określ parametry; będziesz potrzebował po jednym parametrze dla każdej
właściwości, której chcesz przypisać wartość początkową.
2
Następnie każdej właściwości obiektu przypisz odpowiednią wartość początkową
(upewnij się, że oprócz nazwy właściwości użyłeś także słowa kluczowego this).
3
Na samym końcu dodaj do konstruktora trzy metody samochodów: start, drive oraz stop.
function _______ ( ________________________________________________ ) {
Tutaj zapisz cały
swój kod.
}
Zanim zaczniesz dalszą lekturę, nie zapomnij
zobaczyć naszego rozwiązania tego ćwiczenia,
które zamieściliśmy pod koniec rozdziału.
jesteś tutaj
559
Testowanie konstruktora samochodów
Weźmy nowe samochody na jazdę próbną
Skoro już mamy narzędzia do masowej produkcji samochodów,
utwórzmy kilka z nich i rozłóżmy na czynniki pierwsze. Zacznij od
umieszczenia konstruktora w kodzie dokumentu HTML, a następnie
dodaj do niego kod testowy.
Poniżej przedstawiamy kod, którego użyliśmy; oczywiście możesz go
zmodyfikować lub rozszerzyć.
Notatka. Nie będziesz
w stanie tego zrobić, jeśli
wcześniej nie rozwiązałeś
ćwiczenia na poprzedniej
stronie!
Najpierw używamy konstruktora, aby utworzyć
wszystkie samochody z rozdziału 5.
var chevy = new Car(”Chevy”, ”Bel Air”, 1957, ”czerwony”, 2, false, 1021);
var cadi = new Car(”GM”, ”Cadillac”, 1955, ”jasnobrÈzowy”, 5, false, 12892);
var taxi = new Car(”SieMoCorp”, ”Taxi”, 1955, ”ĝóïty”, 4, false, 281341);
var fiat = new Car(”Fiat”, ”500”, 1957, ”szaroniebieski”, 2, false, 88000);
Ale dlaczego mielibyśmy
na tym poprzestać?
var testCar = new Car(”SieMoCorp”, ”Auto testowe”, 2014, ”morski”, 2, true, 21);
Utwórzmy samochód do jazd próbnych
w tej książce!
Czy zaczynasz dostrzegać, jak łatwe jest tworzenie nowych obiektów
przy użyciu konstruktorów? A teraz weźmy te wszystkie auta na
jazdę próbą.
var cars = [chevy, cadi, taxi, fiat, testCar];
for(var i = 0; i < cars.length; i++) {
cars[i].start();
cars[i].drive();
cars[i].drive();
cars[i].stop();
}
aliśmy. Czy Ty do
Oto wyniki, które otrzym dodałeś jakieś
w
odó
och
sam
ch
any
tow
tes
ć wykonywane działania
własne? Spróbuj zmieni ochodem, w którym nie
sam
hać
jec
j
óbu
(np. spr
A może zmodyfikujesz
został włączony silnik). była wykonywana
e
driv
a
tod
kod tak, by me
losową liczbę razy?
560
Rozdział 12
Jeśli chcesz, dodaj swoje własne
ulubione lub wymyślone samochody.
Konsola JavaScript
Chevy Bel Air robi: brum wrrrr!
Chevy Bel Air robi: brum wrrrr!
GM Cadillac robi: brum wrrrr!
GM Cadillac robi: brum wrrrr!
SieMoCorp Taxi robi: brum wrrrr!
SieMoCorp Taxi robi: brum wrrrr!
Fiat 500 robi: brum wrrrr!
Fiat 500 robi: brum wrrrr!
SieMoCorp Auto testowe robi: brum wrrrr!
SieMoCorp Auto testowe robi: brum wrrrr!
Zaawansowane sposoby konstruowania obiektów
Nie zapominaj jeszcze o literałach obiektowych
Braliśmy już udział w dyskusji o różnicach pomiędzy literałami obiektowymi a konstruktorami
i wspominaliśmy, że literały obiektowe i tak są bardzo przydatne. Jednak nie poparliśmy tego
twierdzenia żadnym przykładem. Spróbujmy zatem nieco zmodyfikować kod konstruktora
Car, abyś mógł zobaczyć, w jakich sytuacjach zastosowanie literałów obiektowych może
uprościć kod, poprawić jego czytelność i uprościć zarządzanie.
Przyjrzymy się jeszcze raz konstruktorowi Car i zobaczmy, w jaki sposób moglibyśmy go
nieco uprościć.
Zwróć uwagę, że używamy tu całkiem sporej
liczby parametrów. Naliczyliśmy siedem.
Im więcej ich będziemy dodawać (a zawsz
e
tak się dzieje, kiedy zmieniają się wymagania
dotyczące obiektu), tym bardziej kod będzi
e
trudniejszy do odczytania.
function Car(make, model, year, color, passengers, convertible, mileage) {
this.make = make;
this.model = model;
this.year = year;
A pisząc kod wywołujący ten konstruktor,
this.color = color;
będziemy musieli upewnić się, że wszystkie
this.passengers = passengers;
argumenty zostały zapisane w dokładnie
takiej samej kolejności.
this.convertible = convertible;
this.mileage = mileage;
this.started = false;
this.start = function() {
this.started = true;
};
// reszta metod
}
A zatem problemem, na który chcemy zwrócić uwagę, jest to, że w konstruktorze Car
mamy całkiem dużo parametrów, co utrudnia utrzymanie i ewentualną modyfikację jego
kodu. Dodatkowo utrudnione jest także pisanie kodu korzystającego z tego konstruktora.
Choć można uznać, że takie trudności mają marginalne znaczenie, jednak okazuje się, że
mogą przyczyniać się do powstawania większej liczby błędów, niż można by przypuszczać,
a co więcej, często są to paskudne błędy, które początkowo bardzo trudno zdiagnozować.
Jednak istnieje pewna popularna technika, której możemy używać podczas przekazywania
argumentów do dowolnej funkcji, w tym także do konstruktorów. Jest całkiem
prosta: należy zebrać wszystkie argumenty, umieścić je w jednym literale obiektowym,
a następnie przekazać ten literał do funkcji — w ten sposób przekażemy wszystkie
wartości umieszczone w jednym pojemniku (literale obiektowym), a co więcej,
nie będziemy musieli przejmować się kolejnością argumentów i parametrów.
Takie błędy trudno się
diagnozuje, gdyż zmiana
kolejności dwóch zmiennych
nie sprawi, że kod stanie się
syntaktycznie nieprawidłowy,
może natomiast sprawić, że
nie będzie działał zgodnie
z oczekiwaniami.
A jeśli pominiemy jedną
wartość, może się zdarzyć
cała masa zwariowanych
rzeczy!
Napiszmy zatem kod, który będzie wywoływał konstruktor Car w opisany powyżej sposób,
a dodatkowo zmodyfikujmy nieco sam konstruktor, by dostosować go do takiego sposobu
wywoływania.
jesteś tutaj
561
Stosowanie literału obiektowego do przekazywania argumentów
Przekazywanie argumentów
przy użyciu literału obiektowego
Wróćmy do kodu konstruktora Car i zastąpmy wszystkie jego argumenty
jednym literałem obiektowym.
Cała zmiana polega na usunięciu każdego argumentu
z wywołania, umieszczeniu go w literale obiektowym
i poprzedzeniu odpowiednią nazwą właściwości. Użyjemy
tu dokładnie tych samych nazw właściwości, które są
stosowane w konstruktorze.
var cadi = new Car(”GM”, ”Cadillac”, 1955, ”jasnobrÈzowy”, 5, false, 12892);
Zachowaliśmy taką
samą kolejność
właściwości, jednak
jeśli chcesz, nie ma
powodu, by jej
nie zmieniać.
var cadiParams = {make: ”GM”,
model: ”Cadillac”,
year: 1955,
color: ”jasnobrÈzowy”,
passengers: 5,
convertible: false,
mileage: 12892};
A teraz możemy zmodyfikować wywołanie konstruktora Car w następujący sposób.
var cadiParams = {make: ”GM”,
model: ”Cadillac”,
year: 1955,
color: ”jasnobrÈzowy”,
passengers: 5,
convertible: false,
mileage: 12892};
YDUFDGL QHZ&DU FDGL3DUDPV Ależ zmiana! Taki kod
nie tylko jest bardziej
przejrzysty, lecz także
bardzie czytelny,
przynajmniej naszym
skromnym zdaniem.
Teraz w wywołaniu konstruktora Car
przekazujemy tylko jeden argument.
A to nie koniec zmian, bo sam konstruktor wciąż wymaga przekazania
siedmiu argumentów, a nie jednego obiektu. Zmodyfikujemy kod
konstruktora, a następnie przetestujemy jego działanie.
562
Rozdział 12
Zaawansowane sposoby konstruowania obiektów
Modyfikacja konstruktora Car
Teraz musisz usunąć wszystkie parametry z konstruktora Car i zastąpić je
właściwościami przekazywanego obiektu. Temu nowemu parametrowi nadamy nazwę
params. Musisz także wprowadzić drobne zmiany w kodzie, by korzystał z tego
obiektu. Poniżej przedstawiamy nową wersję wywołania oraz kodu konstruktora.
var cadiParams = {make: ”GM”,
model: ”Cadillac”,
year: 1955,
color: ”jasnobrÈzowy”,
passengers: 5,
Tu nie ma żadnych zmian,
po prostu odtworzyliśmy
literał obiektowy i wywołanie
konstruktora Car przedstawione
na poprzedniej stronie.
convertible: false,
mileage: 12892};
var cadi = new Car(cadiParams);
Wszystkie siedem
Ale wszystko po kolei. Car zastąpimy jednym,
ora
ukt
str
kon
ów
parametr
zywany obiekt.
reprezentującym przeka
function Car(params) {
this.make = params.make;
this.model = params.model;
this.year = params.year;
this.color = params.color;
e odwołanie do
y
Następnie każd
tru zastępujem
me
ra
starego pa
niej
ed
wi
po
od
do
odwołaniem
kazywanego
właściwości prze
u.
kt
ie
ob
this.passengers = params.passengers;
this.convertible = params.convertible;
this.mileage = params.mileage;
this.started = false;
this.start = function() {
W naszych metodach nigdy nie odwołujemy się
bezpośrednio do parametru. Nie miałoby to większego
sensu, gdyż zależy nam na korzystaniu z właściwości
obiektu (co robimy, używając this). Zatem w kodzie tych
metod nie musimy wprowadzać żadnych zmian.
this.started = true;
};
this.stop = function() {
this.started = false;
};
this.drive = function() {
if (this.started) {
alert(”Brum wrrrr!”);
} else {
alert(”Najpierw musisz wïÈczyÊ silnik.”);
}
};
Jazda próbna
Zaktualizuj obiekt cadi oraz
obiekty wszystkich pozostałych
samochodów, a następnie
przetestuj swój kod.
cadi.start();
cadi.drive();
cadi.drive();
cadi.stop();
}
jesteś tutaj
563
Pytania dotyczące typu
Skopiuj konstruktory Car oraz Dog do jednego pliku, a następnie dodaj do nich poniższy kod.
Uruchom go i sprawdź wyniki.
Ćwiczenie
Konstruktor Dog znajdziesz
na stronie 552.
var limoParams = {make: ”SieMoCorp”,
model: ”limo”,
year: 1983,
color: ”czarny”,
passengers: 12,
convertible: true,
mileage: 21120};
var limo = new Car(limoParams);
var limoDog = new Dog(”BïÚkitna rapsodia”, ”Pudel”, 40);
console.log(limo.make + ” ” + limo.model + ” jest typu ” + typeof limo);
console.log(limoDog.name + ” jest typu ” + typeof limoDog);
Tu zapisz wyniki.
WYSIL
SZARE KOMÓRKI
Załóżmy, że ktoś przekazał Ci obiekt, a Ty chciałbyś wiedzieć, jakiego jest typu
(Car? Dog? A może Superman?), albo czy jest tego samego typu, co jakiś inny
obiekt. Czy przyda Ci się do tego operator typeof?
Nie istnieją
głupie pytania
P: Przypomnijcie mi, proszę, co robi operator typeof?
O: Operator typeof zwraca typ swojego operandu. Jeśli
operandem będzie łańcuch znaków, typeof zwróci ”string”;
jeśli operandem będzie obiekt, operator ten zwróci ”object”
itd. Można go używać na operandach dowolnego typu: liczbach,
łańcuchach znaków, wartościach logicznych oraz wartościach
bardziej złożonych typów, takich jak obiekty lub funkcje. Jednak
operator ten nie potrafi działać dokładniej i odpowiedzieć, czy dany
obiekt jest psem, czy samochodem.
564
Rozdział 12
P: Skoro zatem typeof nie może mi powiedzieć, czy dany
obiekt jest psem, czy samochodem, jak mogę to sprawdzić?
O: Wiele obiektowych języków programowania, takich jak
Java lub C++, zwraca bardzo dużą uwagę na typy obiektów.
W językach tych możemy sprawdzić obiekt i bardzo dokładnie
określić jego typ. Jednak JavaScript traktuje obiekty mniej
rygorystycznie i bardziej dynamicznie. Wielu programistów zaczęło
z tego powodu sądzić, że JavaScript dysponuje systemem obiektów
o mniejszych możliwościach; prawda jest jednak taka, że system
obiektów w języku JavaScript jest w rzeczywistości bardziej
ogólny i elastyczny. Ponieważ system obiektów w JavaScripcie
jest bardziej dynamiczny, zatem jest w nim nieco trudniej ustalić,
czy dany obiekt jest psem, czy samochodem, i zależy to od tego,
czym — według nas — są pies oraz samochód. Okazuje się jednak,
że istnieje jeszcze jeden operator, który może przekazywać nieco
więcej informacji o obiektach… Zatem nie przerywaj lektury.
Zaawansowane sposoby konstruowania obiektów
Zrozumieć instancje obiektów
Nie możesz popatrzeć na obiekt JavaScript i stwierdzić, że jest to obiekt konkretnego typu, takiego
jak samochód lub pies. W języku JavaScript obiekty są strukturami dynamicznymi i każdy obiekt jest
typu ”object”, niezależnie od tego, jakie właściwości i metody posiada. Kiedy jednak wiemy, jaki
konstruktor został użyty do utworzenia obiektu, możemy zdobyć trochę informacji na jego temat.
Pamiętasz, że za każdym razem, gdy wywołujemy konstruktor, używając przy tym operatora new,
tworzymy nową instancję obiektu. A jeśli użyliśmy przy tym, dajmy na to, konstruktora Car, to
nieformalnie mówimy, że jest to obiekt samochodu. Bardziej formalnie powiedzielibyśmy natomiast,
że jest to instancja Car.
Instancja 1
Instancja 2
Instancja 3
Jak już wiesz, każda instancja może mieć swój własny
zestaw właściwości i ich wartości, jednak uznajemy, że
wszystkie obiekty utworzone przy użyciu konstruktora Car
są instancjami Car.
esz
Te wszystkie samochody moż
sobie wyobrazić jako obiekty
ały
tego samego typu, gdyż zost
tego
ciu
uży
przy
ne
utworzo
samego konstruktora.
Określenie, że obiekt jest instancją jakiegoś konstruktora, to coś więcej niż same słowa. Można napisać
kod, który sprawdzi, jaki konstruktor został użyty do utworzenia danego obiektu. Służy do tego operator
instanceof. Przyjrzyj się poniższemu fragmentowi kodu.
var cadiParams = {make: ”GM”, model: ”Cadillac”, year: 1955, color: ”jasnobrÈzowy”,
passengers: 5, convertible: false, miles: 12892};
var cadi = new Car(cadiParams);
if (cadi instanceof Car) {
console.log(”Gratulujemy, to jest samochód!”);
};
Operator instanceof zwraca true, jeśli obiekt został
utworzony przy użyciu podanego konstruktora.
W tym przypadku pytamy: „Czy obiekt cadi jest
instancją utworzoną przy użyciu konstruktora Car?”
Okazuje się, że jedną z rzeczy, które operator new robi za kulisami, jest zapisanie
w tworzonym obiekcie informacji pozwalających na określenie konstruktora użytego do
utworzenia obiektu i to w dowolnym momencie. Operator instanceof korzysta z tych
informacji, by określić, czy obiekt jest instancją podanego konstruktora.
W rzeczywistości to wszystko jest nieco bardziej
skomplikowane, niż tu opisaliśmy, jednak zagadnieniem
tym zajmiemy się ponownie w następnym rozdziale.
Konsola JavaS
cript
Gratulujemy,
to jest
samochód!
jesteś tutaj
565
Ćwiczenie z zastosowania instanceof
Ćwiczenie
Potrzebujemy funkcji o nazwie dogCatcher, która zwróci true, jeśli przekazany do niej obiekt
będzie psem, lub wartość false we wszystkich innych przypadkach. Napisz taką funkcję i przetestuj
jej działanie w poniższym kodzie. Zanim przejdziesz dalej, nie zapomnij sprawdzić naszej odpowiedzi
podanej pod koniec tego rozdziału!
function dogCatcher(obj) {
Tutaj zapisz swój
kod stanowiący
implementację funkcji
dogCatcher.
}
A to jest nasz kod testowy.
function Cat(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
var meow = new Cat(”Filemon”, ”syjamski”, 5);
var whiskers = new Cat(”Rojber”, ”dachowiec”, 6);
var fido = {name: ”Burek”, breed: ”mieszaniec”, weight: 20};
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
this.bark = function() {
if (this.weight > 15) {
alert(this.name + ” szczeka hau!”);
} else {
alert(this.name + ” szczeka hiauu!”);
}
};
}
var fluffy = new Dog(”Dino”, ”pudel”, 16);
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
var dogs = [meow, whiskers, fido, fluffy, spot];
for (var i = 0; i < dogs.length; i++) {
if (dogCatcher(dogs[i])) {
console.log(dogs[i].name + ” to pies!”);
}
}
566
Rozdział 12
Zaawansowane sposoby konstruowania obiektów
A zatem obiekt
jest psem, jeśli został
utworzony tylko i wyłącznie
przy użyciu konstruktora Dog?
Tak, to właśnie tak działa. W języku JavaScript nie jest
stosowane ścisłe pojęcie typu obiektu, kiedy zatem musimy
porównać dwa obiekty i przekonać się, czy oba są psami
bądź kotami, sprawdzamy, czy zostały utworzone przy użyciu
tej samej funkcji konstruktora. Jak już napisaliśmy, kot
jest kotem tylko wtedy, gdy został utworzony przy użyciu
konstruktora Cat, a pies jest psem, jeśli został zbudowany
za pomocą konstruktora Dog.
W następnym rozdziale poznasz konstruktory oraz obiekty
języka JavaScript, które są jeszcze bardziej elastyczne
od tych, które przedstawiliśmy do tej pory. Przykładowo
możemy utworzyć obiekt przy użyciu konstruktora Taxi,
lecz jednocześnie obiekt ten będzie wiedział, że jest także
samochodem. Jednak odłóż te pomysły w jakimś zakątku
swojego mózgu, żebyśmy mogli do nich później wrócić.
jesteś tutaj
567
Modyfikacje obiektów utworzonych przy użyciu konstruktora
Nawet obiekty utworzone przy użyciu konstruktora
mogą mieć własne właściwości
Napisaliśmy już całkiem sporo na temat sposobów używania konstruktorów do tworzenia
obiektów — obiektów, które będą dysponować tym samym zbiorem właściwości i tymi
samymi metodami. Nie wspominaliśmy jednak, że użycie konstruktorów nie uniemożliwia
zmiany tak utworzonego obiektu w coś innego — okazuje się, że nawet obiekty
zbudowane przy użyciu konstruktorów można zmieniać.
Ale o czym my tu właściwie mówimy? Czy pamiętasz, jak przedstawialiśmy literały
obiektowe? Dowiedziałeś się wtedy, jak można dodawać i usuwać ich właściwości po
utworzeniu takiego obiektu. Dokładnie to samo można robić z obiektami tworzonymi
przy użyciu konstruktorów.
Oto nasz pies Burek utworzony
przy użyciu konstruktora Dog.
var fido = new Dog(”Burek”, ”mieszaniec”, 20);
Możemy dodać do niego nową właściwość —
wystarczy przypisać jej jakąś wartość w obiekcie.
fido.owner = ”Kuba”;
delete fido.weight;
Możemy także usunąć właściwość,
korzystając w tym celu z operatora delete.
Możemy także dodawać do obiektu nowe metody.
Aby dodać metodę, wystarczy przypisać
funkcję do nowej właściwości w obiekcie.
fido.trust = function(person) {
return (person === ”Kuba”);
};
Alarm! Funkcja anonimowa!
Widzisz… Jesteśmy wszędzie!
Zauważ, że ta instrukcja zmienia wyłącznie obiekt fido. Jeśli metodę
dodamy do obiektu fido, tylko ten obiekt będzie nią dysponował.
var notBite = fido.trust(”Kuba”);
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
notBite = spot.trust(”Kuba”);
568
Rozdział 12
Ten kod zadziała, gdyż w obiekcie fido jest
zdefiniowana metoda trust. A zatem w zmiennej
notBite zostanie zapisana wartość true.
Ten kod nie zadziała, gdyż w obiekcie spot nie
została zdefiniowana metoda trust; próba jego
wykonania spowoduje wyświetlenie błędu:
„TypeError: Object #<Dog> has no method ‘trust’”.
Zaawansowane sposoby konstruowania obiektów
Jeśli zatem zmienię
obiekt samochodu po jego
utworzeniu, wciąż będzie
samochodem?
Tak, samochód będzie samochodem, nawet jeśli go później
zmienisz. Mamy tutaj na myśli to, że jeśli sprawdzisz, czy Twój samochód
wciąż jest instancją Car, to nią będzie. Jeśli utworzymy obiekt samochodu:
var cadiParams = {make: ”GM”, model: ”Cadillac”,
year: 1955, color: ”jasnobrÈzowy”,
passengers: 5, convertible: false,
miles: 12892};
var cadi = new Car(cadiParams);
możemy do niego dodać nową właściwość, taką jak chrome, i usunąć starą
właściwość convertible:
cadi.chrome = true;
delete cadi.convertible;
a mimo to, cadi wciąż będzie samochodem.
cadi instnceof Car
To właśnie to
mieliśmy na myśli,
wcześniej pisząc,
że JavaScript ma
dynamiczny system
typów.
Zwraca wartość true.
Jednak czy to naprawdę jest samochód w jakimkolwiek praktycznym sensie?
Co by było, gdybyśmy usunęli każdą właściwość takiego obiektu? Czy to
też byłby samochód? Operator instanceof odpowiedziałby na to pytanie
twierdząco. Jednak najprawdopodobniej nasza ocena byłaby przeciwna.
Jest bardzo prawdopodobne, że raczej sporadycznie będziemy chcieli
tworzyć obiekty przy użyciu konstruktora i potem zmieniać je w coś,
czego nie da się rozpoznać jako obiektu utworzonego za pomocą tego
konstruktora. Ogólnie rzecz biorąc, z konstruktorów korzysta się zazwyczaj
do tworzenia obiektów o identycznej postaci. Jeśli jednak potrzebujemy
bardziej elastycznych obiektów, to cóż… JavaScript da sobie z nimi
radę. To Twoim zadaniem, jako projektanta kodu, jest wybór takiego
sposobu korzystania z konstruktorów i obiektów, który będzie dla Ciebie
(oraz Twoich współpracowników) najbardziej sensowny.
jesteś tutaj
569
Obiekt date
Te wbudowane obiekty
naprawdę oszczędzają mi wiele
czasu. Dziś udało mi się wrócić do
domu na tyle wcześnie, że zdążyłem
jeszcze obejrzeć trochę „Rancza”.
Konstruktory stosowane w praktyce
Język JavaScript udostępnia konstruktory służące do tworzenia kilku bardzo
przydatnych obiektów — np. obiektów do obsługi dat i czasu, obiektów
pozwalających na odnajdywanie wzorców w łańcuchach znaków, jak również
obiektów pozwalających spojrzeć na tablice z innej perspektywy. Skoro już
wiesz, jak działają konstruktory oraz jak używać operatora new, jesteś w pełni
przygotowany, by zacząć używać tych konstruktorów, a co ważniejsze, także
obiektów, które tworzą. Rzućmy szybko okiem na dwa z nich, a później
będziesz mógł sam przestudiować je dokładniej.
Zaczniemy od wbudowanego obiektu JavaScript służącego do obsługi dat
i godzin. Aby utworzyć taki obiekt, należy użyć jego konstruktora.
var now = new Date();
Tworzy nowy obiekt Date
reprezentujący bieżącą datę i godzinę.
Wywołanie konstruktora Date zwraca instancję Date, reprezentującą bieżącą,
lokalną datę i godzinę. Dysponując tym obiektem, możemy wywoływać jego
metody, by manipulować datą (oraz godziną), a także pobierać wartości
różnych właściwości przechowujących informacje o dacie i godzinie.
Poniżej przedstawiamy kilka przykładów.
var dateString = now.toString();
Zwraca łańcuch znaków
reprezentujący datę, np. „Sun
Aug 31 2014 20:09:21 GMT+0200
(Środkowoeuropejski czas letni)”.
var theYear = now.getFullYear();
var theDayOfWeek = now.getDay();
Zwraca rok reprezentowanej
daty.
Zwraca numer dnia tygodnia
przez dany obiekt, np. 1 (ozn daty reprezentowanej
aczający poniedziałek).
Przekazując do konstruktora Date odpowiednie argumenty, możemy tworzyć obiekty
reprezentujące dowolne daty i godziny. Załóżmy np., że potrzebujemy obiektu
reprezentującego datę 1 maja 1983 roku; możemy go utworzyć w następujący sposób.
var birthday = new Date(”May 1, 1983”);
Prosty łańcuch znaków reprezentujący datę można
przekazać do konstruktora Date w taki sposób.
Można jeszcze bardziej precyzyjnie określić interesujący nas moment
— wystarczy dołączyć do daty informacje o godzinie.
var birthday = new Date(”May 1, 1983 08:03 pm”);
Oczywiście, to jedynie przedsmak pełnych możliwości obiektu Date,
na pewno sam będziesz chciał przejrzeć pełną listę jego właściwości
i metod; możesz ją znaleźć w książce JavaScript. Programowanie
obiektowe.
570
Rozdział 12
W tym przypadku w łańcuchu
znaków podaliśmy także
informacje o godzinie.
Zaawansowane sposoby konstruowania obiektów
Obiekt Array
A teraz przedstawimy kolejny interesujący, wbudowany obiekt języka JavaScript: obiekt tablicy
— Array. Choć wcześniej tworzyliśmy tablice, używając zapisu z nawiasami kwadratowymi
— [1, 2, 3] — to jednak można je także zbudować z wykorzystaniem konstruktora.
var emptyArray = new Array();
Taka instrukcja tworzy pustą
tablice o zerowej długości.
W powyższym przykładzie utworzyliśmy nową, pustą tablicę. W dowolnej chwili
możemy do niej dodać element, wystarczy w tym celu użyć następującej instrukcji.
emptyArray[0] = 99;
Ta instrukcja powinna wyglądać znajomo.
Dokładnie w taki sposób określaliśmy
wcześniej wartości elementów tablicy.
Możemy także tworzyć tablice o określonej długości. Załóżmy, że
potrzebujemy tablicy zawierającej trzy elementy.
var oddNumbers = new Array(3);
Tworzymy tablicę o długości
3,
a po utworzeniu zapisujemy
w niej wartości.
oddNumbers[0] = 1;
oddNumbers[1] = 3;
oddNumbers[2] = 5;
W tym przykładzie powstała tablica o długości 3. Początkowo wszystkie elementy
tablicy oddNumbers miały wartość undefined, lecz później kolejno określiliśmy
wartość każdego z nich. Do tak utworzonej tablicy bez przeszkód można dodać
kolejne elementy.
Żadne z tych rozwiązań nie powinno być szokująco odmienne od tego, co
robiłeś wcześniej. Jednak obiekt tablicy robi się interesujący dopiero wtedy,
kiedy zaczniemy korzystać z jego metod. Znasz już metodę sort, a poniżej
przedstawiamy kilka kolejnych, interesujących metod obiektu Array.
oddNumbers.reverse();
Ta metoda odwraca kolejność wszystkich wartości zapisanych
w tablicy (a zatem po jej wywołaniu w tablicy oddNumbers będą
zapisane wartości: 5, 3, 1). Zwróć uwagę, że metoda ta modyfikuje
tablicę, na rzecz której została wywołana.
var aString = oddNumbers.join(” - ”);
To wywołanie metody join tworzy łańcuch znaków zawierający
wszystkie wartości tablicy oddNumbers oddzielone od siebie
łańcuchem „ - ", a następnie zwraca go. A zatem to wywołanie
zwróci łańcuch „5 - 3 - 1”.
var areAllOdd = oddNumbers.every(function(x) {
return ((x 2) !== 0);
});
Także w tym przypadku to jedynie wierzchołek góry lodowej,
więc warto zajrzeć do jakiejś książki (takiej jak JavaScript.
Programowanie obiektowe), by dokładniej poznać obiekt Array.
Dysponujesz już całą niezbędną wiedzą, by to zrobić.
Metoda every wymaga przekaza
a następnie wywołuje ją, prze nia funkcji,
kazując do niej
kolejno każdą wartość przechow
ywaną w tablicy
i sprawdza, czy funkcja zwr
óci
false. Jeśli dla każdej wartośc wartość true, czy
wywołanie przekazanej funkcji i zapisanej w tablicy
metoda every zwróci wartość zwróci true, to także
true.
jesteś tutaj
571
Dwa sposoby tworzenia tablic
Poważnie?! Do tej pory
tworzyliśmy tablice zupełnie
inaczej.
Słusznie. Zapis wykorzystujący nawiasy kwadratowe, [], którego do tej pory
używałeś do tworzenia tablic, jest jedynie skróconym sposobem bezpośredniego
wywołania konstruktora Array. Porównaj przedstawione poniżej inne sposoby
tworzenia pustych tablic.
var items = new Array();
var items = [];
Obie te instrukcje robią dokładnie to
samo. Zapis wykorzystujący nawiasy
kwadratowe został wprowadzony
w języku JavaScript, by ułatwić
tworzenie tablic.
I podobnie, jeśli napiszesz następującą instrukcję:
var items = [”a”, ”b”, ”c”];
Taki zapis nazywamy
literałem tablicowym.
będzie ona jedynie uproszczonym zapisem odpowiadającym następującemu
wywołaniu konstruktora Array:
var items = new Array(”a”, ”b”, ”c”);
Jeśli przekażesz więcej niż jeden argument,
powyższe wywołanie utworzy tablicę zawierającą
wszystkie przekazane wartości.
Obiekty utworzone przy użyciu literału tablicowego oraz za pomocą jawnego
wywołania konstruktora są dokładnie takie same, zatem w obu przypadkach
będziesz mógł korzystać z udostępnianych przez nie metod.
Możesz się zastanawiać, po co jawnie wywoływać konstruktor, zamiast używać
zapisu skróconego. Konstruktor przydaje się, kiedy chcemy utworzyć tablicę
w konkretnej wielkości, określonej podczas działania aplikacji, a dopiero
później ustalać jej zawartość; przykład takiego rozwiązania pokazaliśmy poniżej.
var n = getNumberOfWidgetsFromDatabase();
var widgets = new Array(n);
for(var i=0; i < n; i++) {
widgets[i] = getDatabaseRecord(i);
}
Ten kod
najprawdopodobniej
korzysta z dużych tablic,
których wielkość nie
jest znana przed jego
wykonaniem.
A zatem składnia literału tablicowego doskonale nadaje się do szybkiego
tworzenia tablic, natomiast korzystanie z konstruktora Array może mieć sens,
gdy tablice muszą być tworzone programowo. Wedle uznania możesz korzystać
z obu tych zapisów bądź też wybrać jeden z nich.
572
Rozdział 12
Zaawansowane sposoby konstruowania obiektów
Jeszcze więcej zabawy z wbudowanymi obiektami JavaScriptu
Daty i tablice nie są jedynymi wbudowanymi obiektami dostępnymi w języku JavaScript. Dostarcza on znacznie
więcej obiektów, które od czasu do czasu mogą się przydać. Poniżej przedstawiliśmy kilka z nich (jest ich znacznie
więcej, więc jeśli jesteś ciekawy, wyszukaj je w internecie, używając frazy: „JavaScritp obiekty wbudowane”).
Object
RegExp
Konstruktor Object pozwala na tworzenie
obiektów. Podobnie jak w przypadku tablic,
także tutaj użycie literału obiektowego, {},
odpowiada wywołaniu konstruktora 2EMHFW .
Wrócimy do niego dalej w tej książce.
Math
Error
Tego konstruktora można używać
do tworzenia obiektów wyrażeń
regularnych, pozwalających na
wyszukiwanie wzorców, nawet bardzo
złożonych, w łańcuchach znaków.
Ten obiekt udostępnia właściwości
i metody pozwalające na wykonywanie
różnych obliczeń matematycznych.
Oto przykłady: 0DWK3, lub
0DWKUDQGRP .
Ten konstruktor tworzy standardowe
obiekty błędów, ułatwiające
przechwytywanie i obsługę błędów
w kodzie.
Nie istnieją
głupie pytania
P: Nie rozumiem, jak działają
konstruktory Date i Array: można
w nich pominąć argumenty lub
je podawać. Przykładowo jeśli
w przypadku Date pominę argumenty,
uzyskam bieżącą datę, a jeśli je
podam, mogę określić dowolną datę,
jaką chcę. Jak to działa?
O: Słusznie, to prawda. Można pisać
funkcje, które robią różne rzeczy zależnie
od liczby przekazanych argumentów. Jeśli
np. w wywołaniu konstruktora Array
pominiemy argumenty, będzie on wiedział, że
ma utworzyć pustą tablicę; jeśli przekażemy
jeden argument, konstruktor będzie wiedział,
że określa on liczbę elementów tablicy, a jeśli
argumentów będzie więcej, konstruktor uzna,
że są to początkowe wartości elementów.
P: Czy mogę tak robić także
w naszych konstruktorach?
O: Oczywiście. Nie wspominaliśmy o tym,
lecz do każdej funkcji przekazywany jest
obiekt zawierający wszystkie jej argumenty.
Można go użyć, by określić, jakie argumenty
zostały przekazane, i dostosować do nich
wykonywane czynności (więcej informacji
na ten temat znajdziesz w dodatku). Istnieją
także inne techniki, bazujące na sprawdzaniu,
które parametry mają wartość undefined.
P: Już wcześniej używałem
obiektu Math. Dlaczego przed
jego wykorzystaniem nie trzeba
inicjalizować go, pisząc new Math?
O: Świetne pytanie. W rzeczywistości
Math nie jest konstruktorem ani nawet
funkcją — to obiekt. Jak wiesz, Math jest
wbudowanym obiektem, którego można
używać do wykonywania takich operacji
jak pobieranie wartości liczby pi (0DWK3,)
czy generowanie liczb losowych
(Math.random). Wyobraź go sobie jako
literał obiektowy mający wiele użytecznych
właściwości i metod, w dodatku literał
wbudowany, dzięki czemu można go
używać w dowolnym miejscu kodu. Tak
się złożyło, że jego nazwa zaczyna się od
wielkiej litery, abyśmy wiedzieli, że jest
wbudowanym obiektem JavaScriptu.
O: Zawsze możesz sprawdzić, czy zostały
utworzone przy użyciu tego samego
konstruktora.
ILGRLQVWDQFHRI'RJ VSRWLQVWDQFHRI'RJ
Jeśli takie wyrażenie warunkowe zwróci
true, możesz mieć pewność, że obiekty
fido i spot na pewno zostały utworzone
przy użyciu tego samego konstruktora.
P: Jeśli utworzę obiekt, używając
literału obiektowego, to czego będzie
on instancją? A może nie będzie żadną
instancją?
O: Literał obiektowy jest instancją Object.
Obiekt Object możesz sobie wyobrazić
jako najbardziej ogólny rodzaj obiektów,
jakie można tworzyć w języku JavaScript.
W następnym rozdziale dowiesz się
znacznie więcej na temat roli konstruktora
Object w systemie obiektów języka
JavaScript.
P: Wiem, jak sprawdzić, czy obiekt
jest instancją konkretnego konstruktora,
ale w jaki sposób sprawdzić, czy dwa
obiekty mają ten sam konstruktor?
jesteś tutaj
573
Ćwiczenia z tworzenia obiektów
SieMoCorp rewolucjonizuje proces produkcji samochodów poprzez tworzenie
ich na podstawie jednego prototypu. Prototyp ten daje wszystkie niezbędne
podstawy: możliwość uruchamiania silnika, prowadzenia samochodu,
zatrzymywania, jak również kilka właściwości, takich jak marka, model oraz rok
produkcji. Jednak reszta zależy od Ciebie. Chcesz, żeby samochód był czerwony
czy niebieski? Nie ma żadnego problemu — możesz go dostosować. Potrzebujesz
wyszukanego systemu audio? Nie ma problemu — zaszalej i go dodaj.
A zatem masz okazję zaprojektować swój doskonały samochód. Poniżej utwórz
obiekt &DU3URWRW\SH i skonstruuj samochód swoich marzeń. Zanim przejdziesz
dalej, sprawdź nasz projekt zamieszczony pod koniec rozdziału.
Tu narysuj swój
wymarzony samochód.
A tu zapisz
odpowiednio
zmodyfikowany
prototyp.
574
Rozdział 12
function CarProtoype()
{
this.make = ”SieMoCorp”;
this.year = 2013;
this.start = function()
{...};
this.stop = function()
{...};
this.drive = function()
{...};
}
zystko
Hm… Gdzie to wssz się o tym
na
ko
ze
Pr
zmierza?
iale. Swoją
w następnym rozdz właśnie się
drogą, ten rozdział
e, zostały
skończył… No dobrz
trzeżenia”
os
sp
e
eln
„C
ze
zc
jes
eń.
i rozwiązania ćwicz
Zaawansowane sposoby konstruowania obiektów
CELNE SPOSTRZEŻENIA
Q
Literał obiektowy doskonale spełni swoje
zadanie, jeśli musimy utworzyć niewielką
liczbę obiektów.
Q
Konstruktor jest świetny, jeśli musimy
utworzyć wiele podobnych obiektów.
Q
Konstruktory są funkcjami, które powinny
być wywoływane przy użyciu operatora
new. Zgodnie z konwencją, nazwy
konstruktorów zaczynają się wielką literą.
Q
Korzystając z konstruktorów, możemy
tworzyć obiekty o identycznej strukturze
— mające te same właściwości i metody.
Q
Obiekty tworzymy za pomocą operatora
new wraz z funkcją konstruktora.
Q
Q
Q
Operator new, któremu towarzyszy
wywołanie funkcji konstruktora, najpierw
tworzy nowy, pusty obiekt, a następnie,
w ciele konstruktora, zapisuje go
w zmiennej this.
Zmienna this użyta w funkcji konstruktora
pozwala uzyskać dostęp do tworzonego
obiektu i dodawać do niego właściwości.
Funkcja konstruktora automatycznie zwraca
tworzony obiekt.
Q
Jeśli zapomnimy o użyciu operatora new,
nowy obiekt nie zostanie utworzony. Może
to spowodować wystąpienie błędów, które
będzie bardzo trudno znaleźć i poprawić.
Q
Obiekty można dostosowywać, przekazując
argumenty w wywołaniu konstruktora,
a następnie używając ich do określenia
początkowych wartości właściwości
obiektu.
Q
Jeśli konstruktor ma wiele parametrów,
warto zastanowić się nad zastąpieniem ich
jednym obiektem zawierającym wszystkie
te parametry.
Q
Aby sprawdzić, czy obiekt został utworzony
przy użyciu konkretnego konstruktora,
można posłużyć się operatorem
instanceof.
Q
Obiekt utworzony przy użyciu konstruktora
można modyfikować tak samo, jak obiekty
tworzone przy użyciu literału obiektowego.
Q
JavaScript udostępnia wbudowane
konstruktory, których możemy używać do
tworzenia różnych użytecznych obiektów,
takich jak daty, wyrażenia regularne oraz
tablice.
www.FrikShare.pl
jesteś tutaj
575
Rozwiązania ćwiczeń
Zaostrz ołówek
Rozwiązanie
Potrzebujemy Twojej pomocy. Używaliśmy literałów obiektowych, by tworzyć kaczki.
Czy na podstawie przykładu przedstawionego powyżej mógłbyś napisać konstruktor
do tworzenia kaczek? Poniżej przedstawiliśmy jeden z naszych literałów obiektowych,
żebyś miał się na czym wzorować. A oto nasze rozwiązanie.
var duck = {
function Duck(type, canFly) {
type: ”krzyĝówka”,
this.type = type;
canFly: true
this.canFly = canFly;
}
}
Tutaj zapisz konstruktor
tworzący takie kaczki.
To jest przykładowy
literał obiektowy
kaczki.
Ćwiczenie
Rozwiązanie
P.S. Wiemy, że jeszcze nie do końca rozumiesz, jak to
wszystko działa, więc na razie skoncentruj się na samej
składni.
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
Spróbuj zdobyć trochę szybkich,
praktycznych doświadczeń, aby
cała wiedza dobrze się utrwaliła.
Zapisz poniższy kod w jakiejś
stronie i weź ją na jazdę próbną.
Poniżej zanotuj uzyskane wyniki.
var fido = new Dog(”Burek”, ”mieszaniec”, 20);
var fluffy = new Dog(”Dino”, ”pudel”, 16);
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
var dogs = [fido, fluffy, spot];
for(var i = 0; i < dogs.length; i++) {
var size = ”maïy”;
if (dogs[i].weight > 10) {
size = ”duĝy”;
}
console.log(”Pies: ” + dogs[i].name
+ ” to ” + size
+ ” pies rasy ” + dogs[i].breed);
}
576
Rozdział 12
Konsola JavaScript
Pies: Burek to duĝy pies rasy
mieszaniec
Pies: Dino to duĝy pies rasy pude
Pies: Kieï to maïy pies rasy
chihuahua
l
Zaawansowane sposoby konstruowania obiektów
BĄDŹ przeglądarką. Rozwiązanie
Poniżej znajdziesz fragment kodu JavaScript, w którym ukryło się kilka
błędów. Twoim zadaniem jest wcielić się w rolę przeglądarki
i odnaleźć te błędy. Poniżej zamieściliśmy rozwiązanie.
Na początku tych
instrukcji nie jest
potrzebne słowo
kluczowe „var”. Nie
deklarujemy tu nowych
zmiennych, a jedynie
określamy wartości
właściwości obiektu.
my
Tutaj zapisaliśiast
przecinki zam miętaj,
średników. Pa
że wewnątrz
konstruktora
zapisywane są rukcje,
zwyczajne instr nazwaa nie lista pa lonych
ie
wartość oddz cinkami.
ze
pr
ie
eb
si
od
Jeśli funkcja widget ma być inna
pow
konstruktorem, jej nazwa
ry W. Choć
lite
j
lkie
wie
od
się
ać
zaczyn
uje błędu,
wod
spo
nie
ry
lite
ej
mał
użycie
cji
wen
kon
się
mać
trzy
jednak warto
struktorów
zalecającej, by nazwy kon
litery.
zaczynały się od wielkiej
function widget(partNo, size) {
var this.no = partNo;
Kolejna konwencja zaleca, by nazwy parametrów
odpowiadały nazwom właściwości obiektu; a zatem
lepiej byłoby tworzyć tu właściwości this.partNo
oraz this.size.
var this.breed = size;
}
function FormFactor(material, widget) {
this.material = material,
Zwracamy this, co wcale nie jest
potrzebne. Konstruktor zrobi to
automatycznie za nas. Ta instrukcja
nie spowoduje błędu, lecz jest
zupełnie niepotrzebna.
this.widget = widget,
return this;
}
Zapomnieliśmy o new!
var widgetA = widget(100, ”duĝy”);
var widgetB = new widget(101, ”maïy”);
Pomiędzy
new a nazwą
konstruktora musi
być odstęp.
var formFactorA = newFormFactor(”plastikowy”, widgetA);
var formFactorB = new ForumFactor(”metalowy”, widgetB);
A tu nazwa konstruktora
nieprawidłowo zapisana. została
jesteś tutaj
577
Rozwiązanie ćwiczenia
Dysponujemy konstruktorem do tworzenia kubków kawy różnej wielkości,
jednak brakuje w nim metod.
Ćwiczenie
Rozwiązanie
Potrzebujemy metody, o nazwie getSize, która zależnie od wielkości
kawy w mililitrach zwróci odpowiedni łańcuch znaków.
Q
250 ml to ĵPDïDĵ kawa,
Q
375 ml to ĵĂUHGQLDĵ kawa,
Q
500 ml to ĵGXĝDĵ kawa.
Potrzebujemy także metody o nazwie toString, która będzie zwracać łańcuch znaków
reprezentujący zamówienie na kawę w przykładowej postaci: ”Twoje zamówienie:
GXĝDNDZD6HJDIUHGRĵ
Swój kod zapisz poniżej, a następnie przetestuj go w przeglądarce. Spróbuj utworzyć
zamówienia na kilka kaw różnej wielkości. Poniżej przedstawiliśmy nasze rozwiązanie.
function Coffee(roast, ounces) {
this.roast = roast;
this.ounces = ounces;
this.getSize = function() {
aściwość
e sprawdza wł
Metoda getSiz i zwraca odpowiedni
ounces obiektu .
łańcuch znaków
if (this.ounces === 250) {
Pamiętaj, że this będzie
obiektem, którego
metodę wywołaliśmy.
Jeśli zatem wywołamy
metodę houseBlend.
getSize, this będzie
odwoływać się do
obiektu houseBlend.
return ”maïa”;
} else if (this.ounces === 375) {
return ”Ărednia”;
} else if (this.ounces === 500) {
return ”duĝa”;
}
};
this.toString = function() {
a zawartość obiektu
Metoda toString zwraccucha znaków. Używa
łań
mie
for
w
ną
zapisa
eślić wielkość
metody getSize, by okr
y.
kaw
zamówionej
return ”Twoje zamówienie: ” + this.getSize() +
” ” + this.roast + ”.”;
};
}
Tworzymy dwa
obiekty kawy,
a następnie
wywołujemy ich
metody toString
i wyświetlamy
zwrócone
łańcuchy
znaków.
578
Rozdział 12
var coffee = new Coffee(”Segafredo”, 375);
Konsola JavaScript
console.log(coffee.toString());
Twoje zamówienie: Ărednia kawa
Segafredo.
Twoje zamówienie: duĝa kawa Para
na
Caffe.
var darkRoast = new Coffee(”Parana Caffe”, 500);
console.log(darkRoast.toString());
Oto nasze wyniki, Twoje
powinny wyglądać
identycznie.
Zaawansowane sposoby konstruowania obiektów
Wykorzystaj całą nabytą wiedzę, by napisać konstruktor Car. Sugerujemy, żebyś wykonywał to
ćwiczenie w następującej kolejności.
Ćwiczenie
Rozwiązanie
1
Zacznij od słowa kluczowego function (które już zapisaliśmy), a następnie podaj nazwę
konstruktora. Potem określ parametry; będziesz potrzebował po jednym parametrze dla
każdej właściwości, której chcesz przypisać wartość początkową.
2
Następnie każdej właściwości obiektu przypisz odpowiednią wartość początkową
(upewnij się, że oprócz nazwy właściwości użyłeś także słowa kluczowego this).
3
Na samym końcu dodaj do konstruktora trzy metody samochodów: start, drive oraz stop.
Poniżej zamieściliśmy nasze rozwiązanie.
Konstruktor będzie mieć nazwę Car.
1
Będzie miał także siedem parametrów,
po jedynym dla każdej właściwości,
której wartość chcemy określić.
function Car(make, model, year, color, passengers, convertible, mileage) {
2 this.make = make;
this.model = model;
this.year = year;
this.color = color;
this.passengers = passengers;
this.convertible = convertible;
this.mileage = mileage;
this.started = false;
ej
Każdej właściwości obiektu, któr ,
wartość będziemy chcieli określać
ego
jest przypisywana wartość jedn zgodnie
z parametrów. Zwróć uwagę, że y
z ogólnie przyjętą konwencją, nazw
mu
parametru oraz odpowiadającej
właściwości są takie same.
Właściwości started jest przypisywana
wartość początkowa false.
3 this.start = function() {
this.started = true;
};
this.stop = function() {
this.started = false;
};
Metody są dokładnie takie same jak
wcześniej, jednak teraz przypisaliśmy
je właściwościom obiektu, używając
nieco innej składni, gdyż robimy to
w konstruktorze, a nie w literale
obiektowym.
this.drive = function() {
if (this.started) {
alert("Brum wrrrr!");
} else {
alert("Najpierw musisz wïÈczyÊ silnik.");
}
};
}
jesteś tutaj
579
Rozwiązanie ćwiczenia
Ćwiczenie
Rozwiązanie
Skopiuj konstruktory Car oraz Dog do jednego pliku, a następnie
dodaj do nich poniższy kod. Następnie go uruchom i sprawdź wyniki.
A oto nasze wyniki.
var limoParams = {make: ”SieMoCorp”,
model: ”limo”,
year: 1983,
color: ”czarny”,
passengers: 12,
convertible: true,
mileage: 21120};
var limo = new Car(limoParams);
var limoDog = new Dog(”BïÚkitna rapsodia”, ”Pudel”, 40);
console.log(limo.make + ” ” + limo.model + ” jest typu ” + typeof limo);
console.log(limoDog.name + ” jest typu ” + typeof limoDog);
Konsola JavaScript
SieMoCorp limo jest typu object
BïÚkitna rapsodia jest typu object
580
Rozdział 12
Oto wyniki, które
uzyskaliśmy.
Zaawansowane sposoby konstruowania obiektów
Ćwiczenie
Rozwiązanie
Potrzebujemy funkcji o nazwie dogCatcher, która będzie zwracać true, jeśli przekazany
do niej obiekt będzie psem, lub wartość false we wszystkich innych przypadkach.
Napisz taką funkcję i przetestuj jej działanie w poniższym kodzie. Oto nasza odpowiedź.
function dogCatcher(obj) {
if (obj instanceof Dog) {
return true;
} else {
return false;
}
}
Albo w nieco bardziej
zwartej postaci:
function Cat(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
var meow = new Cat(”Filemon”, ”syjamski”, 5);
var whiskers = new Cat(”Rojber”, ”dachowiec”, 6);
var fido = {name: ”Burek”, breed: ”mieszaniec”, weight: 20};
function dogCatcher(obj) {
return (obj instanceof Dog);
}
Konsola JavaScript
Dino to pies!
Kieï to pies!
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
this.bark = function() {
if (this.weight > 15) {
alert(this.name + ” szczeka hau!”);
} else {
alert(this.name + ” szczeka hiauu!”);
}
};
}
var fluffy = new Dog(”Dino”, ”pudel”, 16);
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
var dogs = [meow, whiskers, fido, fluffy, spot];
for (var i = 0; i < dogs.length; i++) {
if (dogCatcher(dogs[i])) {
console.log(dogs[i].name + ” to pies!”);
}
}
jesteś tutaj
581
Samochód marzeń
SieMoCorp rewolucjonizuje proces produkcji samochodów poprzez
tworzenie ich na podstawie jednego prototypu. Prototyp ten daje
wszystkie niezbędne podstawy: możliwość uruchamiania silnika,
prowadzenia samochodu, zatrzymywania, jak również kilka właściwości,
takich jak marka, model oraz rok produkcji. Jednak reszta zależy od Ciebie.
Chcesz, żeby samochód był czerwony czy niebieski? Nie ma żadnego
problemu — możesz go dostosować. Potrzebujesz wyszukanego systemu
audio? Nie ma problemu — zaszalej i go dodaj.
A zatem masz okazję zaprojektować swój doskonały samochód. Poniżej
utwórz obiekt &DU3URWRW\SH i skonstruuj samochód swoich marzeń.
Zanim przejdziesz dalej, sprawdź nasz projekt. Oto on.
Tu narysuj swój
wymarzony samochód.
function CarProtoype()
{
this.make = ”SieMoCorp”;
this.year = 2013;
this.start = function()
{...};
this.stop = function()
{...};
this.drive = function()
{...};
}
var taxi = new CarPrototype();
taxi.model = ”Replika Delorean”;
zystko zmierza?
Hm… Gdzie to ws
taxi.color = ”srebrny”;
taxi.currentTime = new Date();
taxi.fluxCapacitor = {type: ”Mr. Fusion”};
taxi.timeTravel = function(date) {...};
582
Rozdział 12
A tu zapisz
odpowiednio
zmodyfikowany
prototyp.
13. Stosowanie prototypów
Obiekty ekstramocne
Nauka tworzenia obiektów była jedynie początkiem. Nadszedł czas na
wzmocnienie obiektów. Potrzebujesz więcej sposobów, by tworzyć wzajemne związki
pomiędzy obiektami oraz zapewniać możliwość współdzielenia kodu przez takie
powiązane obiekty. Dodatkowo potrzebujesz także sposobów na rozszerzanie i zwiększanie
możliwości istniejących obiektów. Innymi słowy, potrzebujesz więcej narzędzi. W tym
rozdziale przekonasz się, że JavaScript dysponuje naprawdę użytecznym modelem
obiektowym, choć jednocześnie jest on nieco odmienny od modeli stosowanych w innych
obiektowych językach programowania. Zamiast typowego modelu obiektów bazującego
na klasach, w JavaScripcie wykorzystano model bazujący na prototypach, w którym
obiekty mogą dziedziczyć po innych obiektach i rozszerzać ich zachowania. A co on daje?
Już wkrótce się przekonasz. A zatem zaczynajmy…
to jest nowy rozdział
583
Rozmowy o obiektach i dziedziczeniu
Przykro mi, ale będziesz
musiała zapomnieć o dziedziczeniu
tego wszystkiego, czego nauczyłaś
się, poznając Javę i C++.
A jeśli nie uczyłeś
takich klasycznych się
dziedziczenia, ma rozwiązań
sz
gdyż nie będziesz szczęście,
niczego wyrzucać musiał
z głowy.
Jeśli jesteś przyzwyczajony do Javy, C++ lub jakiegoś innego języka,
bazującego na klasycznym modelu dziedziczenia, to musimy uciąć sobie
krótką pogawędkę.
A jeśli nie jesteś, to… Co słyszę, jesteś umówiony na randkę? Zostań z nami i siadaj — także Ty
możesz się czegoś ciekawego dowiedzieć.
Powiemy Ci to prosto z mostu: JavaScript nie korzysta z klasycznego modelu obiektowego,
w którym obiekty tworzy się na podstawie klas. W języku JavaScript w ogóle nie istnieje coś takiego
jak klasy. W JavaScripcie obiekty dziedziczą zachowania po innych obiektach. Takie rozwiązanie
nazywamy dziedziczeniem prototypowym bądź też dziedziczeniem bazującym na prototypach.
JavaScript usłyszał już wiele cierpkich słów (i zmieszanych spojrzeń) od programistów znających
klasyczne rozwiązania programowania obiektowego, musisz jednak pamiętać o jednej rzeczy:
języki korzystające z prototypów są bardziej ogólne od języków, w których stosuje się klasyczny
model obiektowy. Są znacznie bardziej elastyczne, wydajne i ekspresyjne. Okazuje się, że są na tyle
ekspresyjne, że w JavaScripcie można nawet zaimplementować klasyczny model dziedziczenia.
Jeśli nauczyłeś się tradycyjnego programowania obiektowego, usiądź, zrelaksuj się, otwórz umysł
i przygotuj się na coś nieco odmiennego. Jeśli natomiast nie masz najmniejszego pojęcia, co mamy
na myśli, pisząc o „klasycznym programowaniu obiektowym”, znaczy to, że zaczynasz od
zupełnych podstaw, co niejednokrotnie jest korzystne.
584
Rozdział 13
W przyszłości mo
że
się to zmienić:
w następnej wersj
języka JavaScrip i
t
zostać dodane kla mogą
sy
Dlatego warto śle .
doniesienia o roz dzić
wo
i zmianach języka. ju
A to możesz wyko
w ramach ćwiczen nać
ia.
Stosowanie prototypów
Hej, zanim zaczniemy, mamy lepszy
sposób rysowania diagramów obiektów
Stosowane wcześniej w tej książce diagramy obiektów były urocze, jednak to jest
naprawdę poważny rozdział o obiektach, dlatego też i do diagramów przedstawiających
obiekty będziemy musieli podejść nieco poważniej. Prawdę mówiąc, te dotychczasowe
schematy bardzo nam się podobają. Jednak diagramy przedstawiane w tym rozdziale
będą na tyle skomplikowane, że korzystając ze starych rozwiązań, nie moglibyśmy
przedstawić na nich wszystkich niezbędnych informacji.
A zatem bez dalszego ociągania prezentujemy nowy format diagramów.
STARA SZKOŁA
name: “Burek”
NOWE I POPRAWIONE
To są iwości.
właśc
To jest konstruktor.
Dog
breed: “mieszaniec”
weight: 20
To są
właściwości.
bark()
Dog
name: “Burek”
breed: “mieszaniec”
weight: 20
bark()
A to są metody.
A to są metody.
To jest konstruktor.
Zaostrz ołówek
Poćwicz trochę, by upewnić się, że opanowałeś nowy sposób tworzenia diagramów
obiektów. Przyjrzyj się poniższemu obiektowi i przerób go na nowy, poprawiony diagram.
EUDQG´5XV]JâRZċµ
PRGHO
inUse: true
sharpen()
FOHDQ
PencilSharpener
Wypełnij ten
diagram obiektu.
jesteś tutaj
585
Dokładne spojrzenie na obiekty
Ponowna analiza konstruktorów: wielokrotnie
używamy kodu, ale czy robimy to efektywnie?
Czy pamiętasz konstruktor Dog, który napisaliśmy w poprzednim rozdziale?
Przyjrzyjmy się mu jeszcze raz i przypomnijmy, co zyskujemy, kiedy go stosujemy.
function Dog(name, breed, weight) {
this.name = name;
Każdy pies może mieć ten sam zestaw
właściwości, których wartości będą
this.breed = breed;
różne dla każdego psa.
this.weight = weight;
bark = function() {
I każdy pies jest
if (this.weight > 10) {
wyposażony w metodę bark.
console.log(this.name + ” szczeka hau!”);
} else {
console.log(this.name + ” szczeka hiauu!”);
}
A nawet lepiej, we wszystkich
psach używamy dokładnie tego
};
samego kodu.
}
A zatem, korzystając z konstruktora, zyskujemy miły, powtarzalny obiekt psa, którego
zawartość możemy dostosowywać; możemy także wykorzystywać metody zdefiniowane
w tym obiekcie (w naszym przypadku jest to tylko jedna metoda, bark). Co więcej,
konstruktor przekaże każdemu psu dokładnie ten sam kod, co może nam zaoszczędzić
bardzo wiele trudu, w przypadku gdyby w przyszłości coś się zmieniło. Wszystko
świetnie, tylko zobaczmy, co się stanie w trakcie działania skryptu, kiedy zostanie
wykonany poniższy fragment kodu.
var fido = new Dog(”Burek”, ”mieszaniec”, 20);
var fluffy = new Dog(”Dino”, ”pudel”, 16);
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
Powyższy kod powoduje utworzenie trzech obiektów psów. Zastosujmy nasze nowe
diagramy, aby pokazać, jak one wyglądają.
Tu mamy trzy odrębne
obiekty psów, z których
każdy ma swoje własne
właściwości.
A tu każdy
z obiektów ma
własną referencję
do metody bark.
Dog
Rozdział 13
Dog
name: "Dino"
breed: "pudel"
weight: 16
name: ".ieâ",
breed: "chihuahua",
weight: 4
bark()
bark()
bark()
function bark() {
// kod metody bark
}
586
Dog
name: "Burek"
breed: "mieszaniec"
weight: 20
function bark() {
// kod metody bark
}
function bark() {
// kod metody bark
}
Ale chwileczkę, każdy
z tych obiektów ma
swoją własną, niezależną
funkcję bark. Każda
z nich robi dokładnie
to samo, jednak każdy
z psów ma własną kopię
tej metody.
A zatem, z punktu widzenia
kodu, kod metody jest
wielokrotnie używany,
jednak wygląda na to,
że w trakcie wykonywania
kodu każdy z psów będzie
dysponował powtarzającą
się kopią tej samej funkcji.
Stosowanie prototypów
Zdominowałem rynek
psów, a stało się to dzięki Twojemu
konstruktorowi Dog. Zobacz sam…
Hej! Ty tam!
Przeglądarka
Zabijasz nas tymi
wszystkimi metodami, które
tworzysz. Zaraz zabraknie nam
pamięci, a wtedy zabawa się
skończy.
Gdyby to było
urządzenie mobilne,
już byś je „zabił”.
jesteś tutaj
587
Wielokrotnie stosowanie zachowań
Ja uważam, że każdy pies
powinien mieć swoją własną
metodę bark, ale to moje
zdanie.
Czy powielanie metod jest poważnym problemem?
Okazuje się, że jest. Ogólnie rzecz biorąc, nie chcemy, by w efekcie budowania
każdej nowej instancji obiektu przy użyciu konstruktora tworzony był nowy zestaw
metod. Powoduje to pogorszenie wydajności działania przeglądarki i niepotrzebnie
zużywa zasoby komputera, co może być bardzo poważnym problemem, zwłaszcza
na urządzeniach mobilnych. A jak się niebawem przekonasz, istnieją bardziej
elastyczne i użyteczne sposoby tworzenia obiektów w języku JavaScript.
Cofnijmy się zatem o krok i pomyślmy, co było jednym z podstawowych powodów,
dla których zaczęliśmy stosować konstruktory: chodziło o wielokrotne korzystanie
z tych samych zachowań. Pamiętasz zapewne, że mieliśmy kilka obiektów psów
i chcieliśmy, by korzystały one z tej samej metody bark. Po zastosowaniu
konstruktora udało się to zrobić na poziomie kodu, gdyż umieściliśmy metodę
bark w jednym miejscu — wewnątrz konstruktora Dog. W ten sposób udało się
zastosować ten sam kod metody bark za każdym razem, kiedy tworzyliśmy nowy
obiekt. Jednak podczas wykonywania kodu nasze rozwiązanie nie wygląda już tak
dobrze, gdyż każdy obiekt psa otrzymuje własną kopię metody bark.
Powodem tego problemu jest fakt, że nie wykorzystujemy wszystkich możliwości,
jakie zapewnia model obiektowy języka JavaScript, który bazuje na prototypach.
W tym modelu możemy tworzyć obiekty stanowiące rozszerzenie innych obiektów
— tzw. obiektów prototypów.
Aby zademonstrować prototypy… Gdybyśmy tylko mieli gdzieś pod ręką
prototyp psa…
588
Rozdział 13
Zazwyczaj, kiedy piszemy
o „zachowaniu” obiektu,
mamy na myśli zestaw
metod, które dany obiekt
udostępnia.
Stosowanie prototypów
Czym są prototypy?
W języku JavaScript obiekty mogą dziedziczyć właściwości i zachowania
po innych obiektach. Precyzyjniej rzecz ujmując, możemy powiedzieć, że
JavaScript korzysta z rozwiązania nazywanego dziedziczeniem prototypów, przy
czym prototypem nazywany jest obiekt, po którym są dziedziczone zachowania.
Celem takiego rozwiązania jest zapewnienie możliwości dziedziczenia
i wielokrotnego stosowania istniejących właściwości (w tym także metod),
a jednocześnie umożliwienie rozszerzania ich w nowych obiektach.
Wszystko to jest dosyć abstrakcyjne, więc przeanalizujemy to na przykładzie.
Kiedy jeden obiekt dziedziczy
po innym, uzyskuje dostęp do
wszystkich jego właściwości
i metod.
Zaczniemy od prototypu obiektu reprezentującego psa, który mógłby
wyglądać tak.
Jestem prototypem
psa. Mam właściwości,
których potrzebuje każdy
pies, i możesz mnie używać
jako prototypu do utworzenia
dowolnego psa.
Oto prototyp psa. To obiekt zawierający
wszystkie właściwości i metody, których
mogą potrzebować wszystkie inne psy.
Prototyp nie zawiera imienia psa, jego
rasy ani wagi, gdyż właściwości te będą
unikalne dla każdego psa i określane dla
konkretnego psa tworzonego na podstawie
prototypu.
Prototyp Dog
species: "Psowate"
bark()
run()
wag()
Prototyp zawiera właściwości
przydatne dla każdego psa.
Zawiera także metody,
których chcemy
używać we wszystkich
tworzonych psach.
Skoro już dysponujemy dobrym prototypem psa, możemy utworzyć
obiekt psa dziedziczący właściwości po tym prototypie. Nasz obiekt
rozszerzy także ten prototyp, dodając do niego właściwości i zachowania
charakterystyczne dla konkretnego psa. Przykładowo wiemy już, że
dodamy do niego informacje o imieniu, rasie oraz wadze każdego
tworzonego psa.
Przekonasz się, że jeśli którykolwiek z tych psów będzie chciał szczekać,
biegać lub merdać ogonem, będzie mógł uzyskać te zachowania
z prototypu, gdyż po nim je odziedziczy. A zatem utwórzmy kilka
obiektów psów, aby się przekonać, jak to wszystko działa.
jesteś tutaj
589
Psie dziedziczenie
Dziedziczenie po prototypie
Najpierw musimy utworzyć diagramy dla obiektów Burka, Dina i Kła i sprawić, by dziedziczyły po
nowym prototypie psa. Dziedziczenie przedstawimy przy użyciu przerywanych strzałek, prowadzących
od obiektów poszczególnych psów do ich prototypu. Pamiętaj przy tym, że w prototypie umieszczamy
tylko te właściwości i metody, które są wspólne dla wszystkich psów zgodnych z danym prototypem
psa, gdyż wszystkie będą po nim dziedziczyć. Wszystkie właściwości charakterystyczne dla danego psa,
takie jak jego imię lub rasa, zostaną podane w konkretnej instancji obiektu, gdyż dla każdej z nich
właściwości te będą inne.
Prototyp Dog
species: "Psowate"
Oto trzy obiekty psów
dziedziczące po wspólnym
prototypie psa. Prototyp ten
zawiera właściwości (w tym
także metody) współużytkowane
przez wszystkie psy. Same psy
mają także właściwości, których
wartości są charakterystyczne
dla każdego z nich, takie jak
imię, rasa oraz waga.
Tę
nal przer
„dz eży r ywan
ied
ziczozumi ą linię
y p eć j
o”. ako
bark()
run()
wag()
Dog
name: "Burek"
breed: "mieszaniec"
weight: 20
Tworząc obiekt Burka,
musimy tylko określić
jego imię, rasę oraz
wagę.
590
Rozdział 13
Tu znajdują się właściwości
i metody wspólne dla
wszystkich psów.
To samo dotyczy Dina.
I podobnie
dla każdego
utworzonego
psa.
Dog
name: "Dino"
breed: "pudel"
weight: 16
Dog
name: ".ieâ",
breed: "chihuahua",
weight: 4
Stosowanie prototypów
Jak działa dziedziczenie?
W jaki sposób ma działać metoda bark, skoro nie ma jej w obiektach poszczególnych psów,
a znajduje się jedynie w ich prototypie? To właśnie w tym momencie wkracza do akcji
dziedziczenie. Kiedy wywołujemy jakąś metodę na rzecz instancji obiektu, a metody tej nie
można znaleźć w samej instancji, to zostanie podjęta próba odszukania jej w prototypie.
Poniżej pokazaliśmy, jak to działa.
5 W końcu, kiedy już
odnajdziemy metodę bark,
wywołamy ją, co sprawi,
że Burek zacznie szczekać.
function bark() {
// kod metody bark
}
Zacznij tutaj e
jn
i analizuj kole 5.
punkty od 1. do
Prototyp Dog
4
species: "Psowate"
bark()
run()
wag()
function run() {
// kod metody run
W efekcie sprawdzenia prototypu
Dog okazuje się, że jest w nim
dostępna metoda bark.
}
function wag() {
// kod metody wag
}
1 Na początku będziemy potrzebowali
jakiegoś kodu, takiego jak ten.
fido.bark();
To jest zwyczajne
wywołanie metody bark
obiektu fido.
2
W celu wykonania tego kodu
szukamy metody bark w instancji
fido. Okazuje się jednak, że jej
w nim nie ma.
Dog
name: "Burek"
breed: "mieszaniec"
weight: 20
3 Skoro nie możemy znaleźć
metody bark w instancji fido,
w następnym kroku sprawdzamy
prototyp.
Ale w nim nie ma
metody bark! W takim
razie poszukamy jej
wyżej — w prototypie…
Dokładnie tak samo działają właściwości. Kiedy napiszemy kod
korzystający z właściwości fido.name, jej wartość zostanie pobrana
z obiektu fido. Jeśli jednak będziemy potrzebowali wartości
fido.species, najpierw poszukamy jej w obiekcie fido, a kiedy jej
w nim nie znajdziemy, sprawdzimy w prototypie (i tam ją znajdziemy).
jesteś tutaj
591
Stosowanie prototypów w celu dziedziczenia
Skoro już opanowałeś to
nowomodne dziedziczenie, czy mogę
z powrotem uruchomić fabrykę psów?
Ten gość już wcześniej
produkował sporo psów. Oto
kilka spośród utworzonych
przez niego instancji;
każda z nich dziedziczy po
prototypie Dog.
Teraz istnieje tylko
jedna funkcja bark.
To znacznie lepsze.
function bark() {
// kod metody bark
}
Prototyp Dog
function run() {
// kod metody run
species: "Psowate"
}
bark()
run()
wag()
Dog
name: "Burek"
breed: "mieszaniec"
weight: 20
Dog
function wag() {
// kod metody wag
}
Dog
name: "Barnaba"
breed: "baset"
weight: 27
name: "Czort"
breed: "chow chow"
weight: 22
Dog
name: "Fafik"
breed: "mieszaniec"
weight: 10
Dog
name: "Max"
breed: "hotdog"
weight: 20
Dog
name: "Figa"
breed: "dog"
weight: 27
Każdy pies został zmodyfikowany poprzez podanie
imienia, rasy oraz wagi, jednak właściwość
species oraz metoda bark pochodzą z prototypu.
Skoro już rozumiesz, jak działa dziedziczenie, możesz utworzyć
naprawdę wiele obiektów psów. Każdy z nich będzie umiał
szczekać, jednak teraz metoda bark będzie pochodzić z obiektu
prototypu. Udało się zatem zapewnić wielokrotne wykorzystanie
kodu, jednak nie tylko pod względem zapisania kodu tej
metody tylko w jednym miejscu, lecz także dlatego, że w trakcie
wykonywania kodu wszystkie instancje psów będą korzystały
z tej samej metody bark. A to z kolei oznacza, że tworzenie wielu
psów nie będzie się już wiązało z niepotrzebnymi narzutami.
Przekonasz się, że wykorzystując prototypy, będziesz mógł szybko
konstruować obiekty, które będą wielokrotnie wykorzystywać
ten sam kod, a dodatkowo będą mogły być rozszerzane poprzez
dodawanie nowych właściwości i metod.
592
Rozdział 13
Dziękuję! Zanim
zacząłeś korzystać
z dziedziczenia, te
wszystkie obiekty prawie
nas wykończyły!
Stosowanie prototypów
Przesłanianie prototypu
Sam fakt, że odziedziczyliśmy coś po prototypie, nie oznacza,
że jesteśmy na to skazani. Bez trudu możemy przesłaniać
właściwości i metody poprzez określenie ich wartości w instancji
obiektu. Takie rozwiązanie spełni swoje zadanie, gdyż poszukując
właściwości, JavaScript zawsze najpierw sprawdza instancję
obiektu — czyli konkretny obiekt psa — a dopiero potem szuka
jej w prototypie. Gdybyś zatem chciał zastosować inną metodę
bark w obiekcie spot, wystarczy, że ją w nim umieścisz. Kiedy to
zrobisz, JavaScript nie będzie zawracał sobie głowy zaglądaniem
do prototypu, kiedy przed wywołaniem metody bark zacznie jej
poszukiwać.
„hiauu” to nie jest
szczekanie, które by mi odpowiadało.
Potrzebuję czegoś WIĘKSZEGO!
Co powiecie na „HAU HAU” powtórzone
i wielkimi literami?
Zobaczmy, jak wygląda sytuacja, kiedy przesłonimy metodę bark
Kła, by zapewnić mu możliwość głośnego szczekania „HAU HAU”.
Kieł.
Prototyp psa m.
zostaje taki sa
function bark() {
// kod metody bark
}
Prototyp Dog
species: "Psowate"
function run() {
// kod metody run
}
bark()
run()
wag()
kt spot
Natomiast obie dy, dzięki
korzysta z meto„HAU HAU”.
której szczeka
Metoda bark
w prototypie dostępna
używana w obnie jest
lecz wciąż ko iekcie spot,
z niej obiektyrzystają
fido i fluffy.
function wag() {
// kod metody wag
}
Dog
name: ".ieâ",
breed: "chihuahua",
weight: 4
bark()
Kiedy będzie już dostępna
w obiekcie, możemy ją
wywołać.
spot.bark();
To jest niestandardowa
metoda bark, dostępna t.
spo
wyłącznie w obiekcie
function bark() {
QRZ\JïRV.ïD
HAU HAU
}
I znajdujemy ją w nim, więc
nie musimy szukać jej dalej
w prototypie. Kiedy wywołamy tę
metodę, Kieł zaszczeka HAU HAU!
Poszukiwania metody bark
rozpoczynamy od obiektu spot.
jesteś tutaj
593
Ćwiczenie z prototypów
Magnesy z kodem
8âRİ\OLĤP\QDORGyZFHGLDJUDPRELHNWyZDOHNWRĤSU]\V]HGâL]XSHâQLHJRSRPLHV]Dâ
&]\PRİHV]SRPyFQDPJRRGWZRU]\þ"%ĐG]LHP\SRWU]HERZDOLGZyFKLQVWDQFMLSURWRW\SX
URERW-HGQċ]QLFKMHVW5RELNURERW]EXGRZDQ\ZURNXLQDOHİċF\GRGU0RUELXVD
5RELNMHVWZ\SRVDİRQ\ZHZâċF]QLNLSRWUDILFKRG]LþSRNDZĐGR6WDUEXFNVD0DP\
WDNİH5REXVLĐXWZRU]RQċZURNXNWyUDVSU]ċWDPLHV]NDQLH2EHFQLHQDOHİ\RQD
GR*U]HJRU]D-HWVRQD3RZRG]HQLD $MHV]F]HMHGQRPRİHVLĐ]GDU]\þİHQLHNWyUH
]PDJQHVLNyZZLGRF]Q\FKXGRâXVWURQ\EĐGċSRWU]HEQH Prototyp Robot
To prototyp,
po którym mogą
dziedziczyć
Twoje roboty.
Tu narysuj swój diagram.
maker: "Ob.Fa.Ro"
speak()
makeCoffee()
blinkLights()
cleanHouse()
makeCoffee()
onOffSwitch: true
makeCoffee()
Robot
name: "Robik"
function blinkLights() {
REVïXJDEï\VNDQLD
Robot
name: "Robusia"
}
function cleanHouse() {
REVïXJDVSU]ÈWDQLD
}
function makeCoffee() {
// kod parzenia kawy
}
594
Rozdział 13
owner: "Dr. Morbius"
year: 1956
owner: Grzegorz Jetson
year: 1962
function speak() {
NRPXQLNDFMDJïRVRZD
}
function makeCoffee() {
// podawanie kawy
}
Stosowanie prototypów
Jak pobrać prototyp?
Już naprawdę dużo napisaliśmy na temat prototypu psów i sądzimy, że na tym
etapie jesteś już na tyle przygotowany, by zobaczyć przykład przedstawiający
nie same diagramy, lecz kod. W jaki sposób możemy utworzyć lub uzyskać
dostęp do prototypu psa? No cóż, okazuje się, że taki prototyp mieliśmy cały
czas pod ręką, jednak nie wiedzieliśmy o tym.
A tak można pobrać prototyp w kodzie.
Dog.prototype
Jeśli przyjrzymy się konstruktorowi Dog,
okaże się, że posiada on właściwość
prototype, zawierającą referencję do
używanego prototypu.
A teraz, jeśli użyjemy właściwości prototype…
Chwila, chwila… Przecież
Dog jest konstruktorem — czyli
funkcją. Nie pamiętacie? Co to znaczy,
że funkcja ma właściwość?
Nie zwracaj uwagi na suflera! Żartujemy! Masz rację.
Próbowaliśmy przejść nad tą sprawą do porządku dziennego
(i wciąż mamy taki zamiar, przynajmniej jeszcze przez jakiś
czas). Najkrócej rzecz ujmując, sprawa wygląda tak: w języku
JavaScript funkcje są obiektami. W rzeczywistości okazuje
się, że w JavaScripcie niemal wszystko jest obiektem, nawet
tablice, wspominamy o tym na wypadek gdybyś sam się
jeszcze tego nie domyślił.
Jednak na razie nie chcemy, by to zagadnienie Cię
dekoncentrowało. Wystarczy, żebyś wiedział, że funkcje,
oprócz wszystkich możliwości, o których już wiesz, mogą
także posiadać właściwości, a w tym konkretnym przypadku
konstruktor zawsze posiada właściwość prototype. Więcej
informacji o funkcjach oraz o tych wszystkich rzeczach, które
są obiektami, znajdziesz dalej w tej książce. Obiecujemy!
jesteś tutaj
595
Dostosowywanie prototypów
Jak przygotować prototyp?
Już napisaliśmy, że obiekt prototypu można pobrać przy użyciu właściwości prototype
konstruktora Dog. A jakie właściwości i metody są w nim dostępne? No cóż, dopóki
czegoś w nim nie umieścisz, to żadne. Innymi słowy, Twoim zadaniem jest dodanie
do prototypu właściwości i metod. Robimy to zazwyczaj, zanim zaczniemy używać
konstruktora.
A zatem przygotujmy prototyp psa. Na początku będziemy potrzebowali konstruktora,
od którego zaczniemy pracę. Spójrzmy na diagram obiektu, by dowiedzieć się, jak go
przygotować.
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
To konstruktor tworzący
instancje psów. Każda z tych
instancji będzie mieć własne
właściwości name, breed
oraz weight, a zatem dodamy
je do konstruktora.
Dog
name: “Spot”
breed: “Chihuahua”
weight: 10
Jednak wszystkie metody chcemy brać z prototypu,
dlatego też nie musimy umieszczać ich
w konstruktorze.
No dobrze, skoro już mamy konstruktor, zabierzmy się za przygotowanie
prototypu. Chcemy, by prototyp miał właściwość species oraz trzy metody:
bark, run oraz wag. A tak można to zrobić.
Dog.prototype.species = ”Psowate”;
We właściwości species prototypu
zapisujemy łańcuch znaków „Psowate”.
Dog.prototype.bark = function() {
if (this.weight > 10) {
console.log(this.name + ” szczeka hau!”);
} else {
A jeśli chodzi o metody, odpowiednie
funkcje zapiszemy we właściwościach bark,
run i wag prototypu.
console.log(this.name + ” szczeka hiauu!”);
}
};
Dog.prototype.run = function() {
.RGRZDQLHQDSRZDĝQLH
Nie zapomnij o łańcuchu odwołań.
console.log(”Biega!”);
};
Dog.prototype.wag = function() {
console.log(”Merda ogonem!”);
};
596
Rozdział 13
Dog.prototype.species
Zacznij od konstruktora Dog, następnie pobierz
jego właściwość prototype, będącą referencją
do obiektu, który ma mieć właściwość species.
Stosowanie prototypów
Testujemy prototyp, jadąc z psami na spacer
Zapisz poniższy kod w pliku (dog.html), a następnie wyświetl go w przeglądarce,
by przetestować działanie prototypu. Przygotowaliśmy kod po wszystkich zmianach
wprowadzonych na poprzedniej stronie i dodaliśmy do niego kilka wierszy kodu
testowego. Upewnij się, że wszystkie utworzone psy szczekają, biegają i machają
ogonami, tak jak powinny to robić.
function Dog(name, breed, weight) {
this.name = name;
this.breed = breed;
this.weight = weight;
}
Dog.
To jest konstruktor
do prototypu
A tu zaczynamy dodawać
.
ody
met
i
i
ośc
ściw
wła
Dog.prototype.species = ”Psowate”;
Dog.prototype.bark = function() {
if (this.weight > 10) {
console.log(this.name + ” szczeka hau!”);
} else {
console.log(this.name + ” szczeka hiauu!”);
}
};
Dog.prototype.run = function() {
console.log(”Biega!”);
};
Dog.prototype.wag = function() {
console.log(”Merda ogonem!”);
};
Dodamy do niego jedną właściwość
i trzy metody.
Teraz w standardowy
sposób tworzymy
obiekty psów…
var fido = new Dog(”Burek”, ”mieszaniec”, 20);
var fluffy = new Dog(”Dino”, ”pudel”, 16);
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
fido.bark();
fido.run();
fido.wag();
fluffy.bark();
fluffy.run();
fluffy.wag();
spot.bark();
spot.run();
spot.wag();
Ale chwil
chciał sz eczkę, czy Kieł
czekać H
n
AU HAU ie
!?
Konsola JavaScript
…a następnie
wywołujemy te
metody na rzecz
każdego z obiektó
w,
także zupełnie
tak samo jak
wcześniej. Każdy
pies odziedziczył
metody po protot te
ypie.
Każdy pies
szczeka, biega
i macha ogonem.
Burek szczeka hau!
Biega!
Merda ogonem!
Dino szczeka hau!
Biega!
Merda ogonem!
.LHï V]F]HNDKLDXX
Biega!
Merda ogonem!
jesteś tutaj
597
Przesłanianie prototypu
Hej, nie
zapominajcie
o mnie. Prosiłem
o donośniejsze HAU HAU!
Zadbajmy o odpowiedni głos dla Kła
Nie martw się Kle, nie zapomnieliśmy o tobie. Kieł prosił
o donośniejsze HAU HAU!, musimy zatem przesłonić
prototyp i zaimplementować jego własną wersję metody
bark. A zatem aktualizujemy kod.
y tylko zaoszczędzić
Tu znajduje się pozostała część kodu. Chcemwy i takie tam…
węglo
ślad
jszyć
zmnie
,
bitów
lub
drzew
kilka
...
var spot = new Dog(”Kieï”, ”chihuahua”, 4);
spot.bark = function() {
console.log(this.name + ” szczeka HAU HAU!”);
Jedyną zmianą, jaką
wprowadzamy w kodzie,
jest zaimplementowanie
niestandardowej metody
bark w obiekcie spot.
};
spot.bark();
spot.run();
spot.wag();
Nie musimy zmieniać sposobu
wywoływania metody bark obiektu
spot.
Jazda próbna nowej metody bark
Dodaj do pliku nowy kod przedstawiony
powyżej i wypróbuj jego działanie.
Kieł w końcu może już
szczekać tak, jak chciał.
598
Rozdział 13
Konsola JavaScript
Burek szczeka hau!
Biega!
Merda ogonem!
Dino szczeka hau!
Biega!
Merda ogonem!
Kieï szczeka HAU HAU!
Biega!
Merda ogonem!
Stosowanie prototypów
Ćwiczenie
Czy pamiętasz nasz diagram obiektów Robika i Robusi? Teraz go zaimplementujemy.
Napisaliśmy już za Ciebie konstruktor Robot oraz trochę kodu testowego.
Twoim zadaniem jest przygotowanie prototypu robotów oraz zaimplementowanie
obu obiektów. Nie zapomnij o ich gruntownym przetestowaniu.
function Robot(name, year, owner) {
this.name = name;
To jest podstawowy
konstruktor Robot. Wciąż
jednak musisz przygotować
prototyp robotów.
this.year = year;
Tutaj przygotuj prototyp
robotów.
this.owner = owner;
}
Robot.prototype.maker =
Robot.prototype.speak =
Robot.prototype.makeCoffee =
Robot.prototype.blinkLights =
Tu zapisz kod, który utworzy obiekty
Robika i Robusi. Nie zapomnij
dodać do ich instancji wszelkich
unikalnych, charakterystycznych
dla nich właściwości.
var robby =
var rosie =
robby.onOffSwitch =
robby.makeCoffee =
rosie.cleanHouse =
console.log(”Robot ” + robby.name + ” zostaï wyprodukowany przez ” +
robby.maker + ” w roku ” + robby.year +
Użyj tego kodu, by
przetestować utworzone
obiekty i upewnić się,
że działają prawidłowo
i dziedziczą właściwości
oraz metody po prototypie.
”, a teraz jego wïaĂcicielem jest ” + robby.owner);
robby.makeCoffee();
robby.blinkLights();
console.log(”Robot ” + rosie.name + ” zostaï wyprodukowany przez ” +
rosie.maker + ” w roku ” + rosie.year +
”, a teraz jego wïaĂcicielem jest ” + rosie.owner);
rosie.cleanHouse();
jesteś tutaj
599
Rozmyślania o prototypach
Zastanawiam się, jak to możliwe, że
właściwość this.name w metodzie bark
działa prawidłowo, choć metoda ta nie jest
dostępna w samym obiekcie, a w jego
prototypie.
Dobre pytanie. Kiedy nie używaliśmy prototypów,
wszystko było prostsze, gdyż wiedzieliśmy, że w this
jest zapisywany obiekt, którego metoda została
wywołana. Jednak w przypadku wywoływania metody
bark zdefiniowanej w prototypie mógłbyś sądzić, że
w this jest zapisywany obiekt prototypu. Jednak to
nie działa w ten sposób.
Kiedy wywołujemy metodę obiektu, w this
zapisywany jest obiekt, którego metodę wywołujemy.
Jeśli jednak metody nie uda się znaleźć w obiekcie,
a zostanie znaleziona w prototypie, to wartość this
nie ulegnie zmianie. Zmienna this zawsze będzie się
odwoływać do początkowego obiektu — czyli obiektu,
którego metoda została wywołana — i to nawet wtedy,
kiedy metoda będzie zdefiniowana w prototypie.
Jeśli zatem metoda bark została zdefiniowana
w prototypie, w momencie jej wywołania this będzie
się odwoływać do początkowego obiektu i uzyskamy
zamierzony efekt:
Download