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: