Tytuá oryginaáu: JavaScript Patterns
Táumaczenie: Rafaá JoĔca
ISBN: 978-83-246-3821-5
© Helion S.A. 2012.
Authorized Polish translation of the English edition of JavaScript Patterns ISBN 9780596806750 © 2010,
Yahoo!, Inc. All rights reserved.
This translation is published and sold by permission of O’Reilly Media, Inc., the owner of 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/jascwz
MoĪesz tam wpisaü swoje uwagi, spostrzeĪenia, recenzjĊ.
Printed in Poland.
•
Kup książkę
•
Poleć książkę
•
Oceń książkę
•
Księgarnia internetowa
•
Lubię to! » Nasza społeczność
5
Spis tre"ci
Wst#p ............................................................................................................................11
1. Wprowadzenie ............................................................................................................ 15
Wzorce
15
JavaScript — podstawowe cechy
16
Zorientowany obiektowo
16
Brak klas
17
Prototypy
18
"rodowisko
18
ECMAScript 5
18
Narz#dzie JSLint
19
Konsola
20
2. Podstawy ..................................................................................................................... 21
Tworzenie kodu $atwego w konserwacji
21
Minimalizacja liczby zmiennych globalnych
22
Problem ze zmiennymi globalnymi
22
Efekty uboczne pomini#cia var
24
Dost#p do obiektu globalnego
25
Wzorzec pojedynczego var
25
Przenoszenie deklaracji — problem rozrzuconych deklaracji var
26
P#tle for
27
P#tle for-in
29
Modyfikacja wbudowanych prototypów
31
Wzorzec konstrukcji switch
31
Unikanie niejawnego rzutowania
32
Unikanie eval()
32
Konwertowanie liczb funkcj% parseInt()
34
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
6
Spis tre"ci
Konwencje dotycz%ce kodu
34
Wci#cia
35
Nawiasy klamrowe
35
Po$o&enie nawiasu otwieraj%cego
36
Bia$e spacje
37
Konwencje nazewnictwa
38
Konstruktory pisane od wielkiej litery
38
Oddzielanie wyrazów
39
Inne wzorce nazewnictwa
39
Pisanie komentarzy
40
Pisanie dokumentacji interfejsów programistycznych
41
Przyk$ad dokumentacji YUIDoc
42
Pisanie w sposób u$atwiaj%cy czytanie
44
Ocenianie kodu przez innych cz$onków zespo$u
45
Minifikowanie kodu tylko w systemie produkcyjnym
46
Uruchamiaj narz#dzie JSLint
47
Podsumowanie
47
3. Litera$y i konstruktory .................................................................................................49
Litera$ obiektu
49
Sk$adnia litera$u obiektowego
50
Obiekty z konstruktora
51
Pu$apka konstruktora Object
51
W$asne funkcje konstruuj%ce
52
Warto'* zwracana przez konstruktor
53
Wzorce wymuszania u&ycia new
54
Konwencja nazewnictwa
54
U&ycie that
54
Samowywo$uj%cy si# konstruktor
55
Litera$ tablicy
56
Sk$adnia litera$u tablicy
56
Pu$apka konstruktora Array
56
Sprawdzanie, czy obiekt jest tablic%
57
JSON
58
Korzystanie z formatu JSON
58
Litera$ wyra&enia regularnego
59
Sk$adnia litera$owego wyra&enia regularnego
60
Otoczki typów prostych
61
Obiekty b$#dów
62
Podsumowanie
63
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Spis tre"ci
7
4. Funkcje .........................................................................................................................65
Informacje ogólne
65
Stosowana terminologia
66
Deklaracje kontra wyra&enia — nazwy i przenoszenie na pocz%tek
67
W$a'ciwo'* name funkcji
68
Przenoszenie deklaracji funkcji
68
Wzorzec wywo$ania zwrotnego
70
Przyk$ad wywo$ania zwrotnego
70
Wywo$ania zwrotne a zakres zmiennych
72
Funkcje obs$ugi zdarze+ asynchronicznych
73
Funkcje czasowe
73
Wywo$ania zwrotne w bibliotekach
74
Zwracanie funkcji
74
Samodefiniuj%ce si# funkcje
75
Funkcje natychmiastowe
76
Parametry funkcji natychmiastowych
77
Warto'ci zwracane przez funkcje natychmiastowe
77
Zalety i zastosowanie
79
Natychmiastowa inicjalizacja obiektu
79
Usuwanie warunkowych wersji kodu
80
W$a'ciwo'ci funkcji — wzorzec zapami#tywania
82
Obiekty konfiguracyjne
83
Rozwijanie funkcji
84
Aplikacja funkcji
84
Aplikacja cz#'ciowa
85
Rozwijanie funkcji
87
Kiedy u&ywa* aplikacji cz#'ciowej
89
Podsumowanie
89
5. Wzorce tworzenia obiektów ...................................................................................... 91
Wzorzec przestrzeni nazw
91
Funkcja przestrzeni nazw ogólnego stosowania
92
Deklarowanie zale&no'ci
94
Metody i w$a'ciwo'ci prywatne
95
Sk$adowe prywatne
96
Metody uprzywilejowane
96
Problemy z prywatno'ci%
96
Litera$y obiektów a prywatno'*
98
Prototypy a prywatno'*
98
Udost#pnianie funkcji prywatnych jako metod publicznych
99
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
8
Spis tre"ci
Wzorzec modu$u
100
Odkrywczy wzorzec modu$u
102
Modu$y, które tworz% konstruktory
102
Import zmiennych globalnych do modu$u
103
Wzorzec piaskownicy
103
Globalny konstruktor
104
Dodawanie modu$ów
105
Implementacja konstruktora
106
Sk$adowe statyczne
107
Publiczne sk$adowe statyczne
107
Prywatne sk$adowe statyczne
109
Sta$e obiektów
110
Wzorzec $a+cucha wywo$a+
112
Wady i zalety wzorca $a+cucha wywo$a+
112
Metoda method()
113
Podsumowanie
114
6. Wzorce wielokrotnego u%ycia kodu ..........................................................................115
Klasyczne i nowoczesne wzorce dziedziczenia
115
Oczekiwane wyniki w przypadku stosowania wzorca klasycznego
116
Pierwszy wzorzec klasyczny — wzorzec domy'lny
117
Pod%&anie wzd$u& $a+cucha prototypów
117
Wady wzorca numer jeden
119
Drugi wzorzec klasyczny — po&yczanie konstruktora
119
.a+cuch prototypów
120
Dziedziczenie wielobazowe przy u&yciu po&yczania konstruktorów
121
Zalety i wady wzorca po&yczania konstruktora
122
Trzeci wzorzec klasyczny — po&yczanie i ustawianie prototypu
122
Czwarty wzorzec klasyczny — wspó$dzielenie prototypu
123
Pi%ty wzorzec klasyczny — konstruktor tymczasowy
124
Zapami#tywanie klasy nadrz#dnej
125
Czyszczenie referencji na konstruktor
125
Podej'cie klasowe
126
Dziedziczenie prototypowe
129
Dyskusja
129
Dodatki do standardu ECMAScript 5
130
Dziedziczenie przez kopiowanie w$a'ciwo'ci
131
Wzorzec wmieszania
132
Po&yczanie metod
133
Przyk$ad — po&yczenie metody od obiektu Array
134
Po&yczenie i przypisanie
134
Metoda Function.prototype.bind()
135
Podsumowanie
136
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Spis tre"ci
9
7. Wzorce projektowe ....................................................................................................137
Singleton
137
U&ycie s$owa kluczowego new
138
Instancja we w$a'ciwo'ci statycznej
139
Instancja w domkni#ciu
139
Fabryka
141
Wbudowane fabryki obiektów
143
Iterator
143
Dekorator
145
Sposób u&ycia
145
Implementacja
146
Implementacja wykorzystuj%ca list#
148
Strategia
149
Przyk$ad walidacji danych
150
Fasada
152
Po'rednik
153
Przyk$ad
153
Po'rednik jako pami#* podr#czna
159
Mediator
160
Przyk$ad mediatora
160
Obserwator
163
Pierwszy przyk$ad — subskrypcja magazynu
163
Drugi przyk$ad — gra w naciskanie klawiszy
166
Podsumowanie
169
8. DOM i wzorce dotycz&ce przegl&darek ..................................................................... 171
Podzia$ zada+
171
Skrypty wykorzystuj%ce DOM
172
Dost#p do DOM
173
Modyfikacja DOM
174
Zdarzenia
175
Obs$uga zdarze+
175
Delegacja zdarze+
177
D$ugo dzia$aj%ce skrypty
178
Funkcja setTimeout()
178
Skrypty obliczeniowe
179
Komunikacja z serwerem
179
Obiekt XMLHttpRequest
180
JSONP
181
Ramki i wywo$ania jako obrazy
184
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
10
Spis tre"ci
Serwowanie kodu JavaScript klientom
184
.%czenie skryptów
184
Minifikacja i kompresja
185
Nag$ówek Expires
185
Wykorzystanie CDN
186
Strategie wczytywania skryptów
186
Lokalizacja elementu <script>
187
Wysy$anie pliku HTML fragmentami
188
Dynamiczne elementy <script> zapewniaj%ce nieblokuj%ce pobieranie
189
Wczytywanie leniwe
190
Wczytywanie na &%danie
191
Wst#pne wczytywanie kodu JavaScript
192
Podsumowanie
194
Skorowidz .................................................................................................................. 195
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
137
ROZDZIA+ 7.
Wzorce projektowe
Wzorce projektowe opisane w ksi%&ce tak zwanego gangu czworga oferuj% rozwi%zania ty-
powych problemów zwi%zanych z projektowaniem oprogramowania zorientowanego obiektowo.
S% dost#pne ju& od jakiego' czasu i sprawdzi$y si# w wielu ró&nych sytuacjach, warto wi#c
si# z nimi zapozna* i po'wi#ci* im nieco czasu.
Cho* same te wzorce projektowe nie s% uzale&nione od j#zyka programowania i implementa-
cji, by$y analizowane przez wiele lat g$ównie z perspektywy j#zyków o silnym sprawdzaniu
typów i statycznych (niezmiennych) klasach takich jak Java lub C++.
JavaScript jest j#zykiem o lu;nej kontroli typów i bazuje na prototypach (a nie klasach), wi#c nie-
które z tych wzorców okazuj% si# wyj%tkowo proste, a czasem wr#cz banalne w implementacji.
Zacznijmy od przyk$adu sytuacji, w której w j#zyku JavaScript rozwi%zanie wygl%da inaczej
ni& w przypadku j#zyków statycznych bazuj%cych na klasach, czyli od wzorca singletonu.
Singleton
Wzorzec singletonu ma w za$o&eniu zapewni* tylko jedn% instancj# danej klasy. Oznacza to,
&e próba utworzenia obiektu danej klasy po raz drugi powinna zwróci* dok$adnie ten sam
obiekt, który zosta$ zwrócony za pierwszym razem.
Jak zastosowa* ten wzorzec w j#zyku JavaScript? Nie mamy przecie& klas, a jedynie obiekty.
Gdy powstaje nowy obiekt, nie ma w zasadzie drugiego identycznego, wi#c jest on automa-
tycznie singletonem. Utworzenie prostego obiektu za pomoc% litera$u to doskona$y przyk$ad
utworzenia singletonu.
var obj = {
myprop: 'warto,-'
};
W JavaScripcie obiekty nie s% sobie równe, je'li nie s% dok$adnie tym samym obiektem,
wi#c nawet je'li utworzy si# dwa identyczne obiekty z takimi samymi warto'ciami, nie
b#d% równowa&ne.
var obj2 = {
myprop: 'warto,-'
};
obj === obj2; // false
obj == obj2; // false
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
138
Rozdzia$ 7. Wzorce projektowe
Mo&na wi#c stwierdzi*, &e za ka&dym razem, gdy powstaje nowy obiekt tworzony za pomoc%
litera$u, powstaje nowy singleton, i to bez u&ycia dodatkowej sk$adni.
Czasem gdy ludzie mówi% „singleton” w kontek'cie j#zyka JavaScript, maj% na my'li
wzorzec modu$u opisany w rozdziale 5.
U%ycie s$owa kluczowego new
JavaScript jest j#zykiem niestosuj%cym klas, wi#c dos$owna definicja singletonu nie ma tu za-
stosowania. Z drugiej strony j#zyk posiada s$owo kluczowe
new
, które tworzy obiekty na
podstawie funkcji konstruuj%cych. Czasem tworzenie ich w ten sposób jako singletonów mo-
&e by* ciekawym podej'ciem. Ogólny pomys$ jest nast#puj%cy: kilkukrotne wywo$anie funkcji
konstruuj%cej z u&yciem
new
powinno spowodowa* ka&dorazowo zwrócenie dok$adnie tego
samego obiektu.
Przedstawiony poni&ej opis nie jest u&yteczny w praktyce. Stanowi raczej teoretyczne
wyja'nienie powodów powstania wzorca w j#zykach statycznych o 'cis$ej kontroli
typów, w których to funkcje nie s% pe$noprawnymi obiektami.
Poni&szy przyk$ad ilustruje oczekiwane zachowanie (pod warunkiem &e nie wierzy si#
w 'wiaty równoleg$e i akceptuje si# tylko jeden).
var uni = new Universe();
var uni2 = new Universe();
uni === uni2; // true
W tym przyk$adzie
uni
tworzone jest tylko przy pierwszym wywo$aniu konstruktora. Drugie
i kolejne wywo$ania zwracaj% ten sam obiekt. Dzi#ki temu
uni === uni2
(to dok$adnie ten
sam obiekt). Jak osi%gn%* taki efekt w j#zyku JavaScript?
Konstruktor
Universe
musi zapami#ta* instancj# obiektu (
this
), gdy zostanie utworzona po
raz pierwszy, a nast#pnie zwraca* j% przy kolejnych wywo$aniach. Istnieje kilka sposobów,
by to uzyska*.
"
Wykorzystanie zmiennej globalnej do zapami#tania instancji. Nie jest to zalecane podej-
'cie, bo zmienne globalne nale&y tworzy* tylko wtedy, gdy jest to naprawd# niezb#dne.
Co wi#cej, ka&dy mo&e nadpisa* tak% zmienn%, tak&e przez przypadek. Na tym zako+czmy
rozwa&ania dotycz%ce tej wersji.
"
Wykorzystanie w$a'ciwo'ci statycznej konstruktora. Funkcje w j#zyku JavaScript s%
obiektami, wi#c maj% w$a'ciwo'ci. Mo&na by utworzy* w$a'ciwo'*
Universe.instance
i to w niej przechowywa* obiekt. To eleganckie rozwi%zanie, ale ma jedn% wad#: w$a'ci-
wo'*
instance
by$aby dost#pna publicznie i inny kod móg$by j% zmieni*.
"
Zamkni#cie instancji w domkni#ciu. W ten sposób instancja staje si# elementem prywatnym
i nie mo&e zosta* zmieniona z zewn%trz. Cen% tego rozwi%zania jest dodatkowe domkni#cie.
Przyjrzyjmy si# przyk$adowym implementacjom drugiej i trzeciej opcji.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Singleton
139
Instancja we w$a"ciwo"ci statycznej
Poni&szy kod zapami#tuje pojedyncz% instancj# we w$a'ciwo'ci statycznej konstruktora
Universe
.
function Universe() {
// czy istnieje ju& instancja?
if (typeof Universe.instance === "object") {
return Universe.instance;
}
// standardowe dzia)ania
this.start_time = 0;
this.bang = "Wielki";
// zapami,tanie instancji
Universe.instance = this;
// niejawna instrukcja return:
// return this;
}
// test
var uni = new Universe();
var uni2 = new Universe();
uni === uni2; // true
To bardzo proste rozwi%zanie z jedn% wad%, któr% jest publiczne udost#pnienie
instance
.
Cho* prawdopodobie+stwo zmiany takiej w$a'ciwo'ci przez kod jest niewielkie (i na pewno
znacz%co mniejsze ni& w przypadku zmiennej globalnej), to jednak jest to mo&liwe.
Instancja w domkni#ciu
Innym sposobem uzyskania singletonu podobnego do rozwi%za+ klasowych jest u&ycie
domkni#cia w celu ochrony instancji. W implementacji mo&na wykorzysta* wzorzec prywat-
nej sk$adowej statycznej omówiony w rozdziale 5. Tajnym sk$adnikiem jest nadpisanie kon-
struktora.
function Universe() {
// zapami,tanie instancji
var instance = this;
// standardowe dzia)ania
this.start_time = 0;
this.bang = "Wielki";
// nadpisanie konstruktora
Universe = function () {
return instance;
};
}
// testy
var uni = new Universe();
var uni2 = new Universe();
uni === uni2; // true
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
140
Rozdzia$ 7. Wzorce projektowe
Za pierwszym razem zostaje wywo$any oryginalny konstruktor, który zwraca
this
w sposób
standardowy. Drugie i nast#pne wywo$ania wykonuj% ju& zmieniony konstruktor, który ma
dost#p do zmiennej prywatnej
instance
dzi#ki domkni#ciu i po prostu j% zwraca.
Przedstawiona implementacja jest w zasadzie przyk$adem wzorca samomodyfikuj%cej si#
funkcji z rozdzia$u 4. Wad% tego rozwi%zania opisan% we wspomnianym rozdziale jest to, &e
nadpisana funkcja (w tym przypadku konstruktor
Universe()
) utraci wszystkie w$a'ciwo'ci
dodane mi#dzy jej zdefiniowaniem i nadpisaniem. W tej konkretnej sytuacji nic z tego, co zo-
stanie dodane do prototypu
Universe()
po pierwszym obiekcie, nie b#dzie mog$o posiada*
referencji do instancji utworzonej przez oryginaln% implementacj#.
Dla uwidocznienia problemu wykonajmy krótki test. Najpierw kilka wierszy przygotowuj%cych:
// dodanie w)a.ciwo.ci do prototypu
Universe.prototype.nothing = true;
var uni = new Universe();
// ponowne dodanie w)a.ciwo.ci do prototypu
// po utworzeniu pierwszego obiektu
Universe.prototype.everything = true;
var uni2 = new Universe();
Oto w$a'ciwy test:
// tylko oryginalny prototyp jest powi1zany z obiektami
uni.nothing; // true
uni2.nothing; // true
uni.everything; // undefined
uni2.everything; // undefined
// wygl1da prawid)owo:
uni.constructor.name; // "Universe"
// ale to jest dziwne:
uni.constructor === Universe; // false
Powodem, dla którego w$a'ciwo'*
uni.constructor
nie jest ju& taka sama jak konstruktor
Universe()
, jest fakt, i&
uni.constructor
nadal wskazuje na oryginalny konstruktor zamiast
przedefiniowanego.
Je'li prototyp i referencja wskazuj%ca na konstruktor musz% dzia$a* prawid$owo, do wcze-
'niejszej implementacji trzeba wprowadzi* kilka poprawek.
function Universe() {
// zapami,tanie instancji
var instance;
// nadpisanie konstruktora
Universe = function Universe() {
return instance;
};
// przeniesienie w)a.ciwo.ci prototypu
Universe.prototype = this;
// instancja
instance = new Universe();
// zmiana referencji wskazuj1cej na konstruktor
instance.constructor = Universe;
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Fabryka
141
// w)a.ciwa funkcjonalno.4
instance.start_time = 0;
instance.bang = "Wielki";
return instance;
}
Teraz wszystkie testy powinny dzia$a* zgodnie z oczekiwaniami.
// aktualizacja prototypu i utworzenie instancji
Universe.prototype.nothing = true; // true
var uni = new Universe();
Universe.prototype.everything = true; // true
var uni2 = new Universe();
// to ta sama pojedyncza instancja
uni === uni2; // true
// wszystkie w)a.ciwo.ci prototypu dzia)aj1 prawid)owo
// niezale&nie od momentu ich zdefiniowania
uni.nothing && uni.everything && uni2.nothing && uni2.everything; // true
// standardowe w)a.ciwo.ci równie& dzia)aj1 prawid)owo
uni.bang; // "Wielki"
// referencja wskazuj1ca na konstruktor równie& jest prawid)owa
uni.constructor === Universe; // true
Alternatywne rozwi%zanie mog$oby polega* na otoczeniu konstruktora oraz instancji funkcj%
natychmiastow%. Pierwsze wywo$anie konstruktora tworzy obiekt i zapami#tuje go w pry-
watnej zmiennej
instance
. Drugie i kolejne wywo$ania jedynie zwracaj% zawarto'* zmiennej.
Wszystkie poprzednie testy b#d% dzia$a$y równie& dla implementacji przedstawionej poni&ej.
var Universe;
(function () {
var instance;
Universe = function Universe() {
if (instance) {
return instance;
}
instance = this;
// w)a.ciwa funkcjonalno.4
this.start_time = 0;
this.bang = "Wielki";
};
}());
Fabryka
Celem wzorca fabryki jest tworzenie obiektów. Najcz#'ciej fabryk% jest klasa lub metoda sta-
tyczna klasy, której celem jest:
"
wykonanie powtarzaj%cych si# operacji przy tworzeniu podobnych obiektów;
"
zapewnienie u&ytkownikom mo&liwo'ci tworzenia obiektów bez potrzeby znania kon-
kretnego typu (klasy) na etapie kompilacji.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
142
Rozdzia$ 7. Wzorce projektowe
Drugi punkt ma wi#ksze znaczenie w przypadku j#zyków ze statyczn% analiz% typów, w któ-
rych to utworzenie instancji klas nieznanych na etapie kompilacji nie jest zadaniem $atwym.
Na szcz#'cie w j#zyku JavaScript nie trzeba g$owi* si# nad tym zagadnieniem.
Obiekty tworzone przez metod# fabryczn% z regu$y dziedzicz% po tym samym przodku, ale
z drugiej strony s% wyspecjalizowanymi wersjami z pewnymi dodatkowymi rozwi%zaniami.
Czasem wspólny przodek to klasa zawieraj%ca metod# fabryczn%.
Przyjrzyjmy si# przyk$adowej implementacji, która ma:
"
wspólny konstruktor przodka
CarMaker
;
"
metod# statyczn%
CarMaker
o nazwie
factory()
, która tworzy obiekty samochodów;
"
wyspecjalizowane konstruktory
CarMaker.Compact
,
CarMaker.SUV
i
CarMaker.Convertible
,
które dziedzicz% po
CarMaker
i wszystkie s% statycznymi w$a'ciwo'ciami przodka, dzi#ki
czemu globalna przestrze+ nazw pozostaje czysta i $atwo je w razie potrzeby odnale;*.
Implementacja b#dzie mog$a by* wykorzystywana w nast#puj%cy sposób:
var corolla = CarMaker.factory('Compact');
var solstice = CarMaker.factory('Convertible');
var cherokee = CarMaker.factory('SUV');
corolla.drive(); // "Brum, mam 4 drzwi"
solstice.drive(); // "Brum, mam 2 drzwi"
cherokee.drive(); // "Brum, mam 17 drzwi"
Fragment
var corolla = CarMaker.factory('Compact');
to prawdopodobnie najbardziej rozpoznawalna cz#'* wzorca fabryki. Metoda przyjmuje typ
jako tekst i na jego podstawie tworzy i zwraca obiekty danego typu. Nie pojawiaj% si# kon-
struktory wykorzystuj%ce
new
lub litera$y obiektów — u&ytkownik stosuje funkcj#, która two-
rzy obiekty na podstawie typu wskazanego jako tekst.
Oto przyk$adowa implementacja wzorca fabryki, która odpowiada wcze'niejszemu przyk$a-
dowi jego u&ycia:
// konstruktor przodka
function CarMaker() {}
// metoda przodka
CarMaker.prototype.drive = function () {
return "Brum, mam " + this.doors + " drzwi";
};
// statyczna metoda fabryczna
CarMaker.factory = function (type) {
var constr = type,
newcar;
// b)1d, je.li konstruktor nie istnieje
if (typeof CarMaker[constr] !== "function") {
throw {
name: "Error",
message: constr + " nie istnieje"
};
}
// na tym etapie wiemy, &e konstruktor istnieje
// niech odziedziczy przodka, ale tylko raz
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Iterator
143
if (typeof CarMaker[constr].prototype.drive !== "function") {
CarMaker[constr].prototype = new CarMaker();
}
// utworzenie nowej instancji
newcar = new CarMaker[constr]();
// opcjonalne wywo)anie dodatkowych metod i zwrócenie obiektu...
return newcar;
};
// definicje konkretnych konstruktorów
CarMaker.Compact = function () {
this.doors = 4;
};
CarMaker.Convertible = function () {
this.doors = 2;
};
CarMaker.SUV = function () {
this.doors = 17;
};
W implementacji wzorca fabryki nie ma nic szczególnego. Wystarczy wyszuka* odpowiedni%
funkcj# konstruuj%c%, która utworzy obiekt wymaganego typu. W tym przypadku zastoso-
wano bardzo proste odwzorowanie nazw przekazywanych do fabryki na odpowiadaj%ce im
obiekty. Przyk$adem powtarzaj%cych si# zada+, które warto by$oby umie'ci* w fabryce, za-
miast powtarza* osobno dla ka°o konstruktora, jest dziedziczenie.
Wbudowane fabryki obiektów
W zasadzie j#zyk JavaScript posiada wbudowan% fabryk#, któr% jest globalny konstruktor
Object()
. Zachowuje si# on jak fabryka, poniewa& zwraca ró&ne typy obiektów w zale&no'ci
od parametru wej'ciowego. Przekazanie liczby spowoduje utworzenie obiektu konstruktorem
Number()
. Podobnie dzieje si# dla tekstów i warto'ci logicznych. Wszystkie inne warto'ci lub
brak argumentu spowoduj% utworzenie zwyk$ego obiektu.
Oto kilka przyk$adów i testów tego sposobu dzia$ania. Co wi#cej,
Object
mo&na równie&
wywo$a* z takim samym efektem bez u&ycia
new
.
var o = new Object(),
n = new Object(1),
s = Object('1'),
b = Object(true);
// testy
o.constructor === Object; // true
n.constructor === Number; // true
s.constructor === String; // true
b.constructor === Boolean; // true
To, &e
Object()
jest równie& fabryk%, ma ma$e znaczenie praktyczne, ale warto o tym
wspomnie*, by mie* 'wiadomo'*, i& wzorzec fabryki pojawia si# niemal wsz#dzie.
Iterator
We wzorcu iteratora mamy do czynienia z pewnym obiektem zawieraj%cym zagregowane
dane. Dane te mog% by* przechowywane wewn#trznie w bardzo z$o&onej strukturze, ale se-
kwencyjny dost#p do nich zapewnia bardzo prosta funkcja. Kod korzystaj%cy z obiektu nie
musi zna* ca$ej z$o&ono'ci struktury danych — wystarczy, &e wie, jak korzysta* z poje-
dynczego elementu i pobra* nast#pny.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
144
Rozdzia$ 7. Wzorce projektowe
We wzorcu iteratora kluczow% rol# odgrywa metoda
next()
. Ka&de jej wywo$anie powinno
zwraca* nast#pny element w kolejce. To, jak u$o&ona jest kolejka i jak posortowane s% ele-
menty, zale&y od zastosowanej struktury danych.
Przy za$o&eniu, &e obiekt znajduje si# w zmiennej
agg
, dost#p do wszystkich elementów da-
nych uzyska si# dzi#ki wywo$ywaniu
next()
w p#tli:
var element;
while (element = agg.next()) {
// wykonanie dzia)a? na elemencie...
console.log(element);
}
We wzorcu iteratora bardzo cz#sto obiekt agreguj%cy zapewnia dodatkow% metod# pomocni-
cz%
hasNext()
, która informuje u&ytkownika, czy zosta$ ju& osi%gni#ty koniec danych. Inny
sposób uzyskania sekwencyjnego dost#pu do wszystkich elementów, tym razem z u&yciem
hasNext()
, móg$by wygl%da* nast#puj%co:
while (agg.hasNext()) {
// wykonanie dzia)a? na nast,pnym elemencie...
console.log(agg.next());
}
Po przedstawieniu sposobów u&ycia wzorca czas na implementacj# obiektu agreguj%cego.
Implementuj%c wzorzec iteratora, warto w zmiennej prywatnej przechowywa* dane oraz
wska;nik (indeks) do nast#pnego elementu. W naszym przyk$adzie za$ó&my, &e dane to ty-
powa tablica, a „specjalna” logika pobierania tak naprawd# zwraca jej nast#pny element.
var agg = (function () {
var index = 0,
data = [1, 2, 3, 4, 5],
length = data.length;
return {
next: function () {
var element;
if (!this.hasNext()) {
return null;
}
element = data[index];
index = index + 2;
return element;
},
hasNext: function () {
return index < length;
}
};
}());
Aby zapewni* $atwiejszy dost#p do danych i mo&liwo'* kilkukrotnej iteracji, obiekt mo&e
oferowa* dodatkowe metody:
"
rewind()
— ustawia wska;nik na pocz%tek kolejki;
"
current()
— zwraca aktualny element, bo nie mo&na tego uczyni* za pomoc%
next()
bez jednoczesnej zmiany wska;nika.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Dekorator
145
Implementacja tych dodatkowych metod nie sprawi &adnych trudno'ci.
var agg = (function () {
// [jak wy&ej...]
return {
// [jak wy&ej...]
rewind: function () {
index = 0;
},
current: function () {
return data[index];
}
};
}());
Oto dodatkowy test iteratora:
// p,tla wy.wietla warto.ci 1, 3 i 5
while (agg.hasNext()) {
console.log(agg.next());
}
// powrót na pocz1tek
agg.rewind();
console.log(agg.current()); // 1
W konsoli pojawi% si# nast#puj%ce warto'ci: 1, 3 i 5 (z p#tli), a na ko+cu ponownie 1 (po
przej'ciu na pocz%tek kolejki).
Dekorator
We wzorcu dekoratora dodatkow% funkcjonalno'* mo&na dodawa* do obiektu dynamicznie
w trakcie dzia$ania programu. W przypadku korzystania ze statycznych i niezmiennych klas
jest to faktycznie du&e wyzwanie. W j#zyku JavaScript obiekty mo&na modyfikowa*, wi#c
dodanie do nich nowej funkcjonalno'ci nie stanowi wielkiego problemu.
Dodatkow% cech% wzorca dekoratora jest $atwo'* dostosowania i konfiguracji jego oczekiwa-
nego zachowania. Zaczyna si# od prostego obiektu z podstawow% funkcjonalno'ci%. Nast#p-
nie wybiera si# kilka z zestawu dost#pnych dekoratorów, po czym rozszerza si# nimi pod-
stawowy obiekt. Czasem istotna jest kolejno'* tego rozszerzania.
Sposób u%ycia
Przyjrzyjmy si# sposobom u&ycia tego wzorca. Przypu'*my, &e opracowujemy aplikacj#, która
co' sprzedaje. Ka&da nowa sprzeda& to nowy obiekt
sale
. Obiekt zna cen# produktu i potrafi
j% zwróci* po wywo$aniu metody
sale.getPrice()
. W zale&no'ci od aktualnych warunków
mo&na zacz%* „dekorowa*” obiekt dodatkow% funkcjonalno'ci%. Wyobra;my sobie, &e jako
ameryka+ski sklep sprzedajemy produkt klientowi z kanadyjskiej prowincji Québec. W takiej
sytuacji klient musi zap$aci* podatek federalny i dodatkowo podatek lokalny. We wzorcu
dekoratora b#dziemy wi#c „dekorowali” obiekt dekoratorem podatku federalnego i dekora-
torem podatku lokalnego. Po wyliczeniu ceny ko+cowej mo&na równie& doda* dekorator do
jej formatowania. Scenariusz by$by nast#puj%cy:
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
146
Rozdzia$ 7. Wzorce projektowe
var sale = new Sale(100); // cena wynosi 100 dolarów
sale = sale.decorate('fedtax'); // dodaj podatek federalny
sale = sale.decorate('quebec'); // dodaj podatek lokalny
sale = sale.decorate('money'); // formatowanie ceny
sale.getPrice(); // "USD 112.88"
W innym scenariuszu kupuj%cy mo&e mieszka* w prowincji, która nie stosuje podatku lokal-
nego, i dodatkowo mo&emy chcie* poda* cen# w dolarach kanadyjskich.
var sale = new Sale(100); // cena wynosi 100 dolarów
sale = sale.decorate('fedtax'); // dodaj podatek federalny
sale = sale.decorate('cdn'); // sformatuj jako dolary kanadyjskie
sale.getPrice(); // "CAD 105.00"
Nietrudno zauwa&y*, &e jest to wygodny i elastyczny sposób dodawania lub modyfikowania
funkcjonalno'ci utworzonych ju& obiektów. Czas na implementacj# wzorca.
Implementacja
Jednym ze sposobów implementacji wzorca dekoratora jest utworzenie dekoratorów jako
obiektów zawieraj%cych metody do nadpisania. Ka&dy dekorator dziedziczy wówczas tak
naprawd# po obiekcie rozszerzonym przez poprzedni dekorator. Ka&da dekorowana metoda
wywo$uje swoj% poprzedniczk# za pomoc%
uber
(odziedziczony obiekt), pobiera warto'*
i przetwarza j%, dodaj%c co' nowego.
Efekt jest taki, &e wywo$anie metody
sale.getPrice()
z pierwszego z przedstawionych
przyk$adów powoduje tak naprawd# wywo$anie metody dekoratora
money
(patrz rysunek 7.1).
Poniewa& jednak ka&dy dekorator wywo$uje najpierw odpowiadaj%c% mu metod# ze swego
poprzednika,
getPrice()
z
money
wywo$uje
getPrice()
z
quebec
, a ta metod#
getPrice()
z
fedtax
i tak dalej. .a+cuch mo&e by* d$u&szy, ale ko+czy si# oryginaln% metod%
getPrice()
zaimplementowan% przez konstruktor
Sale()
.
Rysunek 7.1. Implementacja wzorca dekoratora
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Dekorator
147
Implementacja rozpoczyna si# od konstruktora i metody prototypu.
function Sale(price) {
this.price = price || 100;
}
Sale.prototype.getPrice = function () {
return this.price;
};
Wszystkie obiekty dekoratorów znajd% si# we w$a'ciwo'ci konstruktora:
Sale.decorators = {};
Przyjrzyjmy si# przyk$adowemu dekoratorowi. To obiekt implementuj%cy zmodyfikowan%
wersj# metody
getPrice()
. Metoda najpierw pobiera zwrócon% przez metod# przodka warto'*,
a nast#pnie j% modyfikuje.
Sale.decorators.fedtax = {
getPrice: function () {
var price = this.uber.getPrice();
price += price * 5 / 100;
return price;
}
};
W podobny sposób mo&na zaimplementowa* dowoln% liczb# innych dekoratorów. Mog% one
stanowi* rozszerzenie podstawowej funkcjonalno'ci
Sale()
, czyli dzia$a* jak dodatki. Co wi#cej,
nic nie stoi na przeszkodzie, by znajdowa$y si# w dodatkowych plikach i by$y implementowane
przez innych, niezale&nych programistów.
Sale.decorators.quebec = {
getPrice: function () {
var price = this.uber.getPrice();
price += price * 7.5 / 100;
return price;
}
};
Sale.decorators.money = {
getPrice: function () {
return "USD " + this.uber.getPrice().toFixed(2);
}
};
Sale.decorators.cdn = {
getPrice: function () {
return "CAD " + this.uber.getPrice().toFixed(2);
}
};
Na koniec przyjrzyjmy si# „magicznej” metodzie o nazwie
decorate()
, która $%czy ze sob%
wszystkie elementy. Sposób jej u&ycia jest nast#puj%cy:
sale = sale.decorate('fedtax');
Tekst
'fedtax'
odpowiada obiektowi zaimplementowanemu w
Sale.decorators.fedtax
.
Nowy obiekt
newobj
dziedziczy obiekt aktualny (orygina$ lub ju& udekorowan% wersj#), któ-
ry jest zawarty w
this
. Do zapewnienia dziedziczenia wykorzystajmy wzorzec konstruktora
tymczasowego z poprzedniego rozdzia$u. Dodatkowo ustawmy w$a'ciwo'*
uber
obiektu
newobj
, by potomek mia$ dost#p do przodka. Nast#pnie niech kod kopiuje wszystkie w$a-
'ciwo'ci z dekoratora do nowego obiektu i zwraca
newobj
jako wynik ca$ej operacji, co spo-
woduje, &e stanie si# on nowym obiektem
sale
.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
148
Rozdzia$ 7. Wzorce projektowe
Sale.prototype.decorate = function (decorator) {
var F = function () {},
overrides = this.constructor.decorators[decorator],
i, newobj;
F.prototype = this;
newobj = new F();
newobj.uber = F.prototype;
for (i in overrides) {
if (overrides.hasOwnProperty(i)) {
newobj[i] = overrides[i];
}
}
return newobj;
};
Implementacja wykorzystuj&ca list#
Przeanalizujmy inn% implementacj#, która korzysta z dynamicznej natury j#zyka JavaScript
i w ogóle nie stosuje dziedziczenia. Dodatkowo, zamiast wymusza* na ka&dej metodzie de-
koruj%cej, by wywo$ywa$a swoj% poprzedniczk#, przekazujemy tu wynik poprzedniej metody
jako parametr nast#pnej.
Taka implementacja znacz%co u$atwia wycofanie udekorowania, czyli usuni#cie jednego
z elementów z listy dekoratorów.
Sposób u&ycia nowej implementacji b#dzie prostszy, bo nie wymaga ona przypisywania
warto'ci zwróconej przez
decorate()
. W tym przypadku
decorate()
jedynie dodaje nowy
element do listy:
var sale = new Sale(100); // cena wynosi 100 dolarów
sale.decorate('fedtax'); // dodaj podatek federalny
sale.decorate('quebec'); // dodaj podatek lokalny
sale.decorate('money'); // formatowanie ceny
sale.getPrice(); // "USD 112.88"
Tym razem konstruktor
Sale()
zawiera list# dekoratorów jako w$asn% w$a'ciwo'*.
function Sale(price) {
this.price = (price > 0) || 100;
this.decorators_list = [];
}
Dost#pne dekoratory s% ponownie implementowane jako w$a'ciwo'ci
Sale.decorators
.
S% prostsze, bo nie musz% ju& wywo$ywa* poprzedniej wersji metody
getPrice()
, by uzy-
ska* warto'* po'redni%. Teraz trafia ona do systemu jako parametr.
Sale.decorators = {};
Sale.decorators.fedtax = {
getPrice: function (price) {
return price + price * 5 / 100;
}
};
Sale.decorators.quebec = {
getPrice: function (price) {
return price + price * 7.5 / 100;
}
};
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Strategia
149
Sale.decorators.money = {
getPrice: function (price) {
return "USD " + price.toFixed(2);
}
};
Interesuj%ce konstrukcje pojawiaj% si# w metodach
decorate()
i
getPrice()
oryginalnego
obiektu. W poprzedniej implementacji metoda
decorate()
by$a w miar# z$o&ona, a
getPrice()
niezwykle prosta. W nowej jest dok$adnie odwrotnie —
decorate()
po prostu dodaje nowy
element do listy, a
getPrice()
wykonuje ca$% istotn% prac#. Prac% t% jest przej'cie przez list#
wszystkich dodanych dekoratorów i wywo$anie dla ka°o z nich metody
getPrice()
z po-
przedni% warto'ci% podan% jako argument metody.
Sale.prototype.decorate = function (decorator) {
this.decorators_list.push(decorator);
};
Sale.prototype.getPrice = function () {
var price = this.price,
i,
max = this.decorators_list.length,
name;
for (i = 0; i < max; i += 1) {
name = this.decorators_list[i];
price = Sale.decorators[name].getPrice(price);
}
return price;
};
Druga implementacja jest prostsza i nie korzysta z dziedziczenia. Prostsze s% równie& metody
dekoruj%ce. Ca$% rzeczywist% prac# wykonuje metoda, która „zgadza” si# na dekoracj#. W tej
prostej implementacji dekoracj# dopuszcza jedynie metoda
getPrice()
. Je'li dekoracja mia-
$aby dotyczy* wi#kszej liczby metod, ka&da z nich musia$aby przej'* przez list# dekoratorów
i wywo$a* odpowiednie metody. Oczywi'cie taki kod stosunkowo $atwo jest umie'ci*
w osobnej metodzie pomocniczej i uogólni*. Umo&liwia$by on dodanie dekorowalno'ci do
dowolnej metody. Co wi#cej, w takiej implementacji w$a'ciwo'*
decorators_list
by$aby
obiektem z w$a'ciwo'ciami o nazwach metod i z tablicami dekorowanych obiektów jako
warto'ciami.
Strategia
Wzorzec strategii umo&liwia wybór odpowiedniego algorytmu na etapie dzia$ania aplikacji.
U&ytkownicy kodu mog% stosowa* ten sam interfejs zewn#trzny, ale wybiera* spo'ród kilku
dost#pnych algorytmów, by lepiej dopasowa* implementacj# do aktualnego kontekstu.
Przyk$adem wzorca strategii mo&e by* rozwi%zywanie problemu walidacji formularzy. Mo&na
utworzy* jeden obiekt sprawdzania z metod%
validate()
. Metoda zostanie wywo$ana nie-
zale&nie od rodzaju formularza i zawsze zwróci ten sam wynik — list# danych, które nie s%
poprawne, wraz z komunikatami o b$#dach.
W zale&no'ci od sprawdzanych danych i typu formularza u&ytkownik kodu mo&e wybra*
ró&ne rodzaje sprawdze+. Walidator wybiera najlepsz% strategi) wykonania zadania i dele-
guje konkretne czynno'ci sprawdze+ do odpowiednich algorytmów.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
150
Rozdzia$ 7. Wzorce projektowe
Przyk$ad walidacji danych
Przypu'*my, &e mamy do czynienia z nast#puj%cym zestawem danych pochodz%cym naj-
prawdopodobniej z formularza i &e chcemy go sprawdzi* pod k%tem poprawno'ci:
var data = {
first_name: "Super",
last_name: "Man",
age: "unknown",
username: "o_O"
};
Aby walidator zna$ najlepsz% strategi# do zastosowania w tym konkretnym przyk$adzie, trzeba
najpierw go skonfigurowa*, okre'laj%c zestaw regu$ i warto'ci uznawanych za prawid$owe.
Przypu'*my, &e nie wymagamy podania nazwiska i zaakceptujemy dowoln% warto'* imienia,
ale wymagamy podania wieku jako liczby i nazwy u&ytkownika, która sk$ada si# tylko z liczb
i liter bez znaków specjalnych. Konfiguracja mog$aby wygl%da* nast#puj%co:
validator.config = {
first_name: 'isNonEmpty',
age: 'isNumber',
username: 'isAlphaNum'
};
Po skonfigurowaniu obiektu
validator
jest on gotowy do przyj#cia danych. Wywo$ujemy
jego metod#
validate()
i wy'wietlamy b$#dy walidacji w konsoli.
validator.validate(data);
if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
}
Efektem wykonania kodu móg$by by* nast#puj%cy komunikat:
Niepoprawna warto,- *age*; warto,- musi by- liczb^, na przyk_ad 1, 3.14 lub 2010
Niepoprawna warto,- *username*; warto,- musi zawiera- jedynie litery i cyfry bez
`adnych znaków specjalnych
Przyjrzyjmy si# implementacji walidatora. Poszczególne algorytmy s% obiektami o z góry
ustalonym interfejsie — zawieraj% metod#
validate()
i jednowierszow% informacj# wyko-
rzystywan% jako komunikat o b$#dzie.
// sprawdzenie, czy podano jak1. warto.4
validator.types.isNonEmpty = {
validate: function (value) {
return value !== "";
},
instructions: "warto,- nie mo`e by- pusta"
};
// sprawdzenie, czy warto.4 jest liczb1
validator.types.isNumber = {
validate: function (value) {
return !isNaN(value);
},
instructions: "warto,- musi by- liczb^, na przyk_ad 1, 3.14 lub 2010"
};
// sprawdzenie, czy warto.4 zawiera jedynie litery i cyfry
validator.types.isAlphaNum = {
validate: function (value) {
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Strategia
151
return !/[^a-z0-9]/i.test(value);
},
instructions: "warto,- musi zawiera- jedynie litery i cyfry bez `adnych znaków
specjalnych"
};
Najwy&szy czas na obiekt
validator
:
var validator = {
// wszystkie dost,pne sprawdzenia
types: {},
// komunikaty o b),dach
// z aktualnej sesji walidacyjnej
messages: [],
// aktualna konfiguracja walidacji
// nazwa => rodzaj testu
config: {},
// metoda interfejsu
// data to pary klucz-warto.4
validate: function (data) {
var i, msg, type, checker, result_ok;
// usuni,cie wszystkich komunikatów
this.messages = [];
for (i in data) {
if (data.hasOwnProperty(i)) {
type = this.config[i];
checker = this.types[type];
if (!type) {
continue; // nie trzeba sprawdza4
}
if (!checker) { // ojej
throw {
name: "ValidationError",
message: "Brak obs_ugi dla klucza " + type
};
}
result_ok = checker.validate(data[i]);
if (!result_ok) {
msg = "Niepoprawna warto,- *" + i + "*; " + checker.instructions;
this.messages.push(msg);
}
}
}
return this.hasErrors();
},
// metoda pomocnicza
hasErrors: function () {
return this.messages.length !== 0;
}
};
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
152
Rozdzia$ 7. Wzorce projektowe
Obiekt
validator
jest uniwersalny i b#dzie dzia$a$ prawid$owo dla ró&nych rodzajów spraw-
dze+. Jednym z usprawnie+ mog$oby by* dodanie kilku nowych testów. Po wykonaniu kilku
ró&nych formularzy z walidacj% Twoja lista dost#pnych sprawdze+ z pewno'ci% si# wyd$u&y.
Ka&dy kolejny formularz b#dzie wymaga$ jedynie skonfigurowania walidatora i uruchomie-
nia metody
validate()
.
Fasada
Wzorzec fasady jest bardzo prosty i ma za zadanie zapewni* alternatywny interfejs obiektu.
Dobr% praktyk% jest stosowanie krótkich metod, które nie wykonuj% zbyt wielu zada+. Stosuj%c
to podej'cie, uzyskuje si# znacznie wi#cej metod ni& w przypadku tworzenia supermetod z wie-
loma parametrami. W wi#kszo'ci sytuacji dwie lub wi#cej metod wykonuje si# jednocze'nie.
Warto wtedy utworzy* jeszcze jedn% metod#, która stanowi otoczk# dla takich po$%cze+.
W trakcie obs$ugi zdarze+ przegl%darki bardzo cz#sto korzysta si# z nast#puj%cych metod:
"
stopPropagation()
— zapobiega wykonywaniu obs$ugi zdarzenia w w#z$ach nadrz#dnych;
"
preventDefault()
— zapobiega wykonaniu przez przegl%dark# domy'lnej akcji dla zda-
rzenia (na przyk$ad klikni#cia $%cza lub wys$ania formularza).
To dwie osobne metody wykonuj%ce odmienne zadania, wi#c nie stanowi% jednej ca$o'ci, ale z dru-
giej strony w zdecydowanej wi#kszo'ci sytuacji s% one wykonywane jednocze'nie. Zamiast wi#c
powiela* wywo$ania obu metod w ca$ej aplikacji, mo&na utworzy* fasad#, która je obie wykona.
var myevent = {
// ...
stop: function (e) {
e.preventDefault();
e.stopPropagation();
}
// ...
};
Wzorzec fasady przydaje si# równie& w sytuacjach, w których za fasad% warto ukry* ró&nice
pomi#dzy przegl%darkami internetowymi. Nic nie stoi na przeszkodzie, by rozbudowa* po-
przedni przyk$ad o inny sposób obs$ugi anulowania zdarze+ przez przegl%dark# IE.
var myevent = {
// ...
stop: function (e) {
// inne
if (typeof e.preventDefault === "function") {
e.preventDefault();
}
if (typeof e.stopPropagation === "function") {
e.stopPropagation();
}
// IE
if (typeof e.returnValue === "boolean") {
e.returnValue = false;
}
if (typeof e.cancelBubble === "boolean") {
e.cancelBubble = true;
}
}
// ...
};
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Po"rednik
153
Wzorzec fasady bywa pomocny w przypadku zmiany interfejsów zwi%zanej na przyk$ad
z refaktoryzacj%. Gdy chce si# zamieni* obiekt na inn% implementacj#, najcz#'ciej mo&e to
zaj%* sporo czasu (je'li jest on naprawd# rozbudowany). Za$ó&my te&, &e ju& powstaje kod
dla nowego interfejsu. W takiej sytuacji mo&na utworzy* przed starym obiektem fasad#, która
imituje nowy interfejs. W ten sposób po dokonaniu rzeczywistej zamiany i pozbyciu si# starego
obiektu ilo'* zmian w najnowszym kodzie zostanie ograniczona do minimum.
Po"rednik
We wzorcu projektowym po'rednika jeden obiekt stanowi interfejs dla innego obiektu. Ró&ni
si# to od wzorca fasady, w którym po prostu istniej% pewne metody dodatkowe $%cz%ce
w sobie wywo$ania kilku innych metod. Po'rednik znajduje si# mi#dzy u&ytkownikiem
a obiektem i broni dost#pu do niego.
Cho* wzorzec wygl%da jak dodatkowy narzut, w rzeczywisto'ci cz#sto s$u&y do poprawy
wydajno'ci. Po'rednik staje si# stra&nikiem rzeczywistego obiektu i stara si#, by ten wykona$
jak najmniej pracy.
Jednym z przyk$adów zastosowania po'rednika jest tak zwana leniwa inicjalizacja. Stosuje
si# j% w sytuacjach, w których inicjalizacja rzeczywistego obiektu jest kosztowna, a istnieje
spora szansa, &e klient po jego zainicjalizowaniu tak naprawd# nigdy go nie u&yje. Po'rednik
mo&e wtedy stanowi* interfejs dla rzeczywistego obiektu. Otrzymuje polecenie inicjalizacji,
ale nie przekazuje go dalej a& do momentu, gdy rzeczywisty obiekt naprawd# zostanie u&yty.
Rysunek 7.2 ilustruje sytuacj#, w której klient wysy$a polecenie inicjalizuj%ce, a po'rednik
odpowiada, &e wszystko jest w porz%dku, cho* tak naprawd# nie przekazuje polecenia dalej.
Czeka z inicjalizacj% w$a'ciwego obiektu do czasu, gdy klient rzeczywi'cie b#dzie wykony-
wa$ na nim prac# — wówczas przekazuje obydwa komunikaty.
Rysunek 7.2. Komunikacja mi.dzy klientem i rzeczywistym obiektem z wykorzystaniem po0rednika
Przyk$ad
Wzorzec po'rednika bywa przydatny, gdy rzeczywisty obiekt docelowy wykonuje kosztow-
ne zadanie. W aplikacjach internetowych jedn% z kosztownych sytuacji jest &%danie sieciowe,
wi#c w miar# mo&liwo'ci warto zebra* kilka operacji i wykona* je jednym &%daniem. Prze-
'led;my praktyczne zastosowanie wzorca w$a'nie w takiej sytuacji.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
154
Rozdzia$ 7. Wzorce projektowe
Aplikacja wideo
Za$ó&my istnienie prostej aplikacji odtwarzaj%cej materia$ wideo wybranego artysty (patrz
rysunek 7.3). W zasadzie mo&esz nawet przetestowa* kod, wpisuj%c w przegl%darce interne-
towej adres http://www.jspatterns.com/book/7/proxy.html.
Rysunek 7.3. Aplikacja wideo w akcji
Strona zawiera list# tytu$ów materia$ów wideo. Gdy u&ytkownik kliknie tytu$, obszar poni&ej
rozszerzy si#, by przedstawi* dodatkowe informacje i umo&liwi* odtworzenie filmu. Szcze-
gó$y dotycz%ce materia$ów oraz adres URL tre'ci wideo nie stanowi% cz#'ci strony — s% po-
bierane poprzez osobne wywo$ania serwera. Serwer przyjmuje jako parametr kilka identyfi-
katorów materia$ów wideo, wi#c aplikacj# mo&na przyspieszy*, wykonuj%c mniej &%da+
HTTP i pobieraj%c za ka&dym razem dane kilku filmów.
Aplikacja umo&liwia jednoczesne rozwini#cie szczegó$ów kilku (a nawet wszystkich) mate-
ria$ów, co stanowi doskona$% okazj# do po$%czenia kilku &%da+ w jedno.
Bez u%ycia po"rednika
G$ównymi elementami aplikacji s% dwa obiekty:
"
videos
— jest odpowiedzialny za rozwijanie i zwijanie obszarów informacyjnych (metoda
videos.getInfo()
) oraz za odtwarzanie materia$ów wideo (metoda
videos.getPlayer()
).
"
http
— jest odpowiedzialny za komunikacj# z serwerem za pomoc% metody
http.make
Request()
.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Po"rednik
155
Bez stosowania po'rednika
videos.getInfo()
wywo$a
http.makeRequest()
dla ka°o
materia$u wideo. Po dodaniu po'rednika pojawi si# nowy obiekt o nazwie
proxy
znajduj%cy
si# mi#dzy
videos
oraz
http
i deleguj%cy wszystkie wywo$ania
makeRequest()
, a tak&e $%-
cz%cy je ze sob%.
Najpierw pojawi si# kod, w którym nie zastosowano wzorca po'rednika. Druga wersja, sto-
suj%ca obiekt po'rednika, poprawi ogóln% p$ynno'* dzia$ania aplikacji.
Kod HTML
Kod HTML to po prostu zbiór $%czy.
<p><span id="toggle-all">Prze_^cz zaznaczone</span></p>
<ol id="vids">
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--2158073">Gravedigger</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--4472739">Save Me</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--45286339">Crush</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--2144530">Don't Drink The Water
</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--217241800">Funny the Way It Is
</a></li>
<li><input type="checkbox" checked><a
href="http://new.music.yahoo.com/videos/--2144532">What Would You Say</a></li>
</ol>
Obs$uga zdarze?
Zanim pojawi si# w$a'ciwa obs$uga zdarze+, warto doda* funkcj# pomocnicz%
$
do pobiera-
nia elementów DOM na podstawie ich identyfikatorów.
var $ = function (id) {
return document.getElementById(id);
};
Stosuj%c delegacj# zdarze+ (wi#cej na ten temat w rozdziale 8.), mo&na obs$u&y* wszystkie
klikni#cia dotycz%ce listy uporz%dkowanej
id="vids"
za pomoc% jednej funkcji.
$('vids').onclick = function (e) {
var src, id;
e = e || window.event;
src = e.target || e.srcElement;
if (src.nodeName !== "A") {
return;
}
if (typeof e.preventDefault === "function") {
e.preventDefault();
}
e.returnValue = false;
id = src.href.split('--')[1];
if (src.className === "play") {
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
156
Rozdzia$ 7. Wzorce projektowe
src.parentNode.innerHTML = videos.getPlayer(id);
return;
}
src.parentNode.id = "v" + id;
videos.getInfo(id);
};
Obs$uga klikni#cia zainteresowana jest tak naprawd# dwoma sytuacjami: pierwsz% dotycz%c%
rozwini#cia lub zamkni#cia cz#'ci informacyjnej (wywo$anie
getInfo()
) i drug% zwi%zan%
z odtworzeniem materia$u wideo (gdy klikni#cie dotyczy$o obiektu z klas%
play
), co oznacza,
&e rozwini#cie ju& nast%pi$o i mo&na bezpiecznie wywo$a* metod#
getPlayer()
. Identyfika-
tory materia$ów wideo wydobywa si# z atrybutów
href
$%czy.
Druga z funkcji obs$uguj%cych klikni#cia dotyczy sytuacji, w której u&ytkownik chce prze$%-
czy* wszystkie cz#'ci informacyjne. W zasadzie sprowadza si# ona do wywo$ywania w p#tli
metody
getInfo()
.
$('toggle-all').onclick = function (e) {
var hrefs,
i,
max,
id;
hrefs = $('vids').getElementsByTagName('a');
for (i = 0, max = hrefs.length; i < max; i += 1) {
// pomi? )1cza odtwarzania
if (hrefs[i].className === "play") {
continue;
}
// pomi? niezaznaczone
if (!hrefs[i].parentNode.firstChild.checked) {
continue;
}
id = hrefs[i].href.split('--')[1];
hrefs[i].parentNode.id = "v" + id;
videos.getInfo(id);
}
};
Obiekt videos
Obiekt
videos
zawiera trzy metody:
"
getPlayer()
— zwraca kod HTML niezb#dny do odtworzenia materia$u wideo (nie-
istotny w rozwa&aniach na temat obiektu po'rednika).
"
updateList()
— wywo$anie zwrotne otrzymuj%ce wszystkie dane z serwera i generuj%ce
kod HTML do wykorzystania przy rozwijaniu szczegó$ów filmów (w tej metodzie rów-
nie& nie dzieje si# nic interesuj%cego).
"
getInfo()
— metoda prze$%czaj%ca widoczno'* cz#'ci informacyjnych i wykonu-
j%ca metody obiektu
http
przez przekazanie
updateList()
jako funkcji wywo$ania
zwrotnego.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Po"rednik
157
Oto istotny fragment obiektu
videos
:
var videos = {
getPlayer: function (id) {...},
updateList: function (data) {...},
getInfo: function (id) {
var info = $('info' + id);
if (!info) {
http.makeRequest([id], "videos.updateList");
return;
}
if (info.style.display === "none") {
info.style.display = '';
} else {
info.style.display = 'none';
}
}
};
Obiekt http
Obiekt
http
ma tylko jedn% metod#, która wykonuje &%danie JSONP do us$ugi YQL firmy Yahoo.
var http = {
makeRequest: function (ids, callback) {
var url = 'http://query.yahooapis.com/v1/public/yql?q=',
sql = 'select * from music.video.id where ids IN ("%ID%")',
format = "format=json",
handler = "callback=" + callback,
script = document.createElement('script');
sql = sql.replace('%ID%', ids.join('","'));
sql = encodeURIComponent(sql);
url += sql + '&' + format + '&' + handler;
script.src = url;
document.body.appendChild(script);
}
};
YQL (Yahoo! Query Language) to uogólniona us$uga internetowa, która oferuje mo&-
liwo'* korzystania ze sk$adni przypominaj%cej SQL do pobierania danych z innych
us$ug. W ten sposób nie trzeba poznawa* szczegó$ów ich API.
Gdy jednocze'nie prze$%czone zostan% wszystkie materia$y wideo, do serwera trafi sze'*
osobnych &%da+; ka&de b#dzie podobne do nast#puj%cego &%dania YQL:
select * from music.video.id where ids IN ("2158073")
Obiekt proxy
Zaprezentowany wcze'niej kod dzia$a prawid$owo, ale mo&na go zoptymalizowa*. Na scen#
wkracza obiekt
proxy
, który przejmuje komunikacj# mi#dzy
http
i
videos
. Obiekt stara si#
po$%czy* ze sob% kilka &%da+, czekaj%c na ich zebranie 50 ms. Obiekt
videos
nie wywo$uje
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
158
Rozdzia$ 7. Wzorce projektowe
us$ugi HTTP bezpo'rednio, ale przez po'rednika. Ten czeka krótk% chwil# z wys$aniem &%-
dania. Je'li wywo$ania z
videos
b#d% przychodzi$y w odst#pach krótszych ni& 50 ms, zostan%
po$%czone w jedno &%danie. Takie opó;nienie nie jest szczególnie widoczne, ale pomaga zna-
cz%co przyspieszy* dzia$anie aplikacji w przypadku jednoczesnego ods$aniania wi#cej ni&
jednego materia$u wideo. Co wi#cej, jest równie& przyjazne dla serwera, który nie musi ob-
s$ugiwa* sporej liczby &%da+.
Zapytanie YQL dla dwóch materia$ów wideo mo&e mie* posta*:
select * from music.video.id where ids IN ("2158073", "123456")
W istniej%cym kodzie zachodzi tylko jedna zmiana: metoda
videos.getInfo()
wywo$uje
metod#
proxy.makeRequest()
zamiast metody
http.makeRequest()
.
proxy.makeRequest(id, videos.updateList, videos);
Obiekt po'rednika korzysta z kolejki, w której gromadzi identyfikatory materia$ów wideo przeka-
zane w ostatnich 50 ms. Nast#pnie przekazuje wszystkie identyfikatory, wywo$uj%c metod# obiektu
http
i przekazuj%c w$asn% funkcj# wywo$ania zwrotnego, poniewa&
videos.updateList()
potrafi
przetworzy* tylko pojedynczy rekord danych.
Oto kod obiektu po'rednicz%cego
proxy
:
var proxy = {
ids: [],
delay: 50,
timeout: null,
callback: null,
context: null,
makeRequest: function (id, callback, context) {
// dodanie do kolejki
this.ids.push(id);
this.callback = callback;
this.context = context;
// ustawienie funkcji czasowej
if (!this.timeout) {
this.timeout = setTimeout(function () {
proxy.flush();
}, this.delay);
}
},
flush: function () {
http.makeRequest(this.ids, "proxy.handler");
// wyczyszczenie kolejki i funkcji czasowej
this.timeout = null;
this.ids = [];
},
handler: function (data) {
var i, max;
// pojedynczy materia) wideo
if (parseInt(data.query.count, 10) === 1) {
proxy.callback.call(proxy.context, data.query.results.Video);
return;
}
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Po"rednik
159
// kilka materia)ów wideo
for (i = 0, max = data.query.results.Video.length; i < max; i += 1) {
proxy.callback.call(proxy.context, data.query.results.Video[i]);
}
}
};
Wprowadzenie po'rednika umo&liwi$o po$%czenie kilku &%da+ pobrania danych w jedno po-
przez zmian# tylko jednego wiersza oryginalnego kodu.
Rysunki 7.4 i 7.5 przedstawiaj% scenariusze z trzema osobnymi &%daniami (bez po'rednika)
i z jednym po$%czonym &%daniem (po u&yciu po'rednika).
Rysunek 7.4. Trzy osobne =>dania do serwera
Rysunek 7.5. Wykorzystanie po0rednika do zmniejszenia liczby =>daA wysyBanych do serwera
Po"rednik jako pami#B podr#czna
W prezentowanym przyk$adzie obiekt
videos
&%daj%cy danych jest na tyle inteligentny, &e
nie &%da tych samych informacji dwukrotnie. Nie zawsze jednak musi tak by*. Po'rednik
mo&e pój'* o krok dalej i chroni* rzeczywisty obiekt
http
przed powielaniem tych samych
&%da+, zapami#tuj%c je w nowej w$a'ciwo'ci
cache
(patrz rysunek 7.6). Gdyby obiekt
videos
ponownie poprosi$ o informacje o tym samym materiale (ten sam identyfikator), po'rednik
wydoby$by dane z pami#ci podr#cznej i unikn%$ komunikacji z serwerem.
Rysunek 7.6. Pami.E podr.czna w obiekcie po0rednika
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
160
Rozdzia$ 7. Wzorce projektowe
Mediator
Aplikacje — du&e czy ma$e — sk$adaj% si# z wielu obiektów. Obiekty musz% si# ze sob% ko-
munikowa* w sposób, który nie uczyni przysz$ej konserwacji kodu prawdziw% drog% przez
m#k# i umo&liwi bezpieczn% zmian# jednego fragmentu bez potrzeby przepisywania wszyst-
kich innych. Gdy aplikacja si# rozrasta, pojawiaj% si# coraz to nowe obiekty. W trakcie refak-
toryzacji obiekty usuwa si# lub przerabia. Gdy wiedz% o sobie za du&o i komunikuj% si#
bezpo'rednio (wywo$uj% si# wzajemnie i modyfikuj% w$a'ciwo'ci), powstaje mi#dzy nimi
niepo&%dany 1cis2y zwi3zek. Je'li obiekty s% ze sob% powi%zane zbyt mocno, nie$atwo zmie-
ni* jeden z nich bez modyfikacji pozosta$ych. Wtedy nawet najprostsza zmiana w aplikacji
nie jest d$u&ej trywialna i bardzo trudno oszacowa*, ile tak naprawd# czasu trzeba b#dzie
na ni% po'wi#ci*.
Wzorzec mediatora ma za zadanie promowa* lu4ne powi3zania obiektów i wspomóc przy-
sz$% konserwacj# kodu (patrz rysunek 7.7). W tym wzorcu niezale&ne obiekty (koledzy) nie
komunikuj% si# ze sob% bezpo'rednio, ale korzystaj% z obiektu mediatora. Gdy jeden z kole-
gów zmieni stan, informuje o tym mediator, a ten przekazuje t# informacj# wszystkim innym
zainteresowanym kolegom.
Rysunek 7.7. Uczestnicy wzorca mediatora
Przyk$ad mediatora
Prze'led;my przyk$ad u&ycia wzorca mediatora. Aplikacja b#dzie gr%, w której dwóch gra-
czy przez pó$ minuty stara si# jak najcz#'ciej klika* w przycisk. Pierwszy gracz naciska kla-
wisz nr 1, a drugi klawisz 0 (spory odst#p mi#dzy klawiszami zapewnia, &e nie pobij% si#
o klawiatur#). Tablica wyników pokazuje aktualny stan rywalizacji.
Obiektami uczestnicz%cymi w wymianie informacji s%:
"
pierwszy gracz,
"
drugi gracz,
"
tablica,
"
mediator.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Mediator
161
Mediator wie o wszystkich obiektach. Komunikuje si# z urz%dzeniem wej'ciowym (klawiatur%),
obs$uguje naci'ni#cia klawiszy, okre'la, który gracz jest aktywny, i informuje o zmianach
wyników (patrz rysunek 7.8). Gracz jedynie gra (czyli aktualizuje swój w$asny wynik) i in-
formuje mediator o tym zdarzeniu. Mediator informuje tablic# o zmianie wyniku, a ta aktu-
alizuje wy'wietlan% warto'*.
Rysunek 7.8. Uczestnicy w grze na szybko0E naciskania klawiszy
Poza mediatorem &aden inny obiekt nie wie nic o pozosta$ych. Dzi#ki temu bardzo $atwo
zaktualizowa* gr#, na przyk$ad doda* nowego gracza lub zmieni* tablic# wyników na wersj#
wy'wietlaj%c% pozosta$y czas.
Pe$na wersja gry wraz z kodem ;ród$owym jest dost#pna pod adresem http://www.jspatterns.com/
book/7/mediator.html
.
Obiekty graczy s% tworzone przy u&yciu konstruktora
Player()
i zawieraj% w$asne w$a'ci-
wo'ci
points
i
name
. Metoda
play()
z prototypu zwi#ksza liczb# punktów o jeden i infor-
muje o tym fakcie mediator.
function Player(name) {
this.points = 0;
this.name = name;
}
Player.prototype.play = function () {
this.points += 1;
mediator.played();
};
Obiekt
scoreboard
zawiera metod#
update()
wywo$ywan% przez mediator po zdobyciu
punktu przez jednego z graczy. Tablica nie wie nic o graczach i nie przechowuje wyniku —
po prostu wy'wietla informacje przekazane przez mediator.
var scoreboard = {
// aktualizowany element HTML
element: document.getElementById('results'),
// aktualizacja wy.wietlacza
update: function (score) {
var i, msg = '';
for (i in score) {
if (score.hasOwnProperty(i)) {
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
162
Rozdzia$ 7. Wzorce projektowe
msg += '<p><strong>' + i + '<\/strong>: ';
msg += score[i];
msg += '<\/p>';
}
}
this.element.innerHTML = msg;
}
};
Czas na obiekt mediatora. Odpowiada on za inicjalizacj# gry oraz utworzenie obiektów graczy
w metodzie
setup()
i 'ledzenie ich poczyna+ dzi#ki umieszczeniu ich we w$a'ciwo'ci
players
.
Metoda
played()
zostaje wywo$ana przez ka°o z graczy po wykonaniu akcji. Aktualizuje ona
wynik (
score
) i przesy$a go do tablicy (
scoreboard
). Ostatnia metoda,
keypress()
, obs$uguje
zdarzenia klawiatury, okre'la, który gracz jest aktywny, i powiadamia go o wykonanej akcji.
var mediator = {
// wszyscy gracze
players: {},
// inicjalizacja
setup: function () {
var players = this.players;
players.home = new Player('Gospodarze');
players.guest = new Player('Go,cie');
},
// kto. zagra), uaktualnij wynik
played: function () {
var players = this.players,
score = {
"Gospodarze": players.home.points,
"Go,cie": players.guest.points
};
scoreboard.update(score);
},
// obs)uga interakcji z u&ytkownikiem
keypress: function (e) {
e = e || window.event; // IE
if (e.which === 49) { // klawisz "1"
mediator.players.home.play();
return;
}
if (e.which === 48) { // klawisz "0"
mediator.players.guest.play();
return;
}
}
};
Ostatni element to uruchomienie i zako+czenie gry.
// start!
mediator.setup();
window.onkeypress = mediator.keypress;
// gra ko?czy si, po 30 sekundach
setTimeout(function () {
window.onkeypress = null;
alert('Koniec gry!');
}, 30000);
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Obserwator
163
Obserwator
Wzorzec obserwatora jest niezwykle cz#sto wykorzystywany w programowaniu po stronie
klienta w j#zyku JavaScript. Wszystkie zdarzenia przegl%darki (poruszenie mysz%, naci'ni#cie
klawisza itp.) to przyk$ady jego u&ycia. Inn% cz#sto pojawiaj%c% si# nazw% tego wzorca s%
zdarzenia w2asne
, czyli zdarzenia tworzone przez programist#, a nie przegl%dark#. Jeszcze
inna nazwa to wzorzec subskrybenta-dostawcy.
G$ównym celem u&ywania wzorca jest promowanie lu;nego powi%zania elementów. Zamiast
sytuacji, w której jeden obiekt wywo$uje metod# drugiego, mamy sytuacj#, w której drugi
z obiektów zg$asza ch#* otrzymywania powiadomie+ o zmianie w pierwszym obiekcie. Sub-
skrybenta nazywa si# cz#sto obserwatorem, a obiekt obserwowany obiektem publikuj%cym
lub ;ród$em. Obiekt publikuj%cy wywo$uje subskrybentów po zaj'ciu istotnego zdarzenia
i bardzo cz#sto przekazuje informacj# o nim w postaci obiektu zdarzenia.
Pierwszy przyk$ad — subskrypcja magazynu
Aby dowiedzie* si#, jak zaimplementowa* wzorzec, pos$u&my si# konkretnym przyk$adem.
Przypu'*my, &e mamy wydawc#
paper
, który publikuje gazet# codzienn% i miesi#cznik. Sub-
skrybent
joe
zostanie powiadomiony o wydaniu nowego periodyku.
Obiekt
paper
musi zawiera* w$a'ciwo'*
subscribers
, która jest tablic% przechowuj%c%
wszystkich subskrybentów. Zg$oszenie si# do subskrypcji polega jedynie na dodaniu nowego
wpisu do tablicy. Gdy zajdzie istotne zdarzenie, obiekt
paper
przejdzie w p#tli przez wszyst-
kich subskrybentów, by ich o nim powiadomi*. Notyfikacja polega na wywo$aniu metody
obiektu subskrybenta. Oznacza to, &e w momencie zg$oszenia ch#ci otrzymywania powia-
domie+ subskrybent musi przekaza* obiektowi
paper
jedn% ze swoich metod w wywo$aniu
metody
subscribe()
.
Obiekt
paper
mo&e dodatkowo umo&liwi* anulowanie subskrypcji, czyli usuni#cie wpisu
z tablicy subskrybentów. Ostatni% istotn% metod% obiektu
paper
jest
publish()
, która wy-
wo$uje metody subskrybentów. Podsumowuj%c, obiekt publikuj%cy musi zawiera* nast#puj%ce
sk$adowe:
"
subscribers
— tablica;
"
subscribe()
— dodaje wpis do tablicy;
"
unsubscribe()
— usuwa wpis z tablicy;
"
publish()
— przechodzi w p#tli przez subskrybentów i wywo$uje przekazane przez
nich metody.
Wszystkie trzy metody potrzebuj% parametru
type
, poniewa& wydawca mo&e zg$osi* kilka
ró&nych zdarze+ (publikacj# gazety lub magazynu), a subskrybenci mog% zdecydowa* si# na
otrzymywanie powiadomie+ tylko o jednym z nich.
Poniewa& powy&sze sk$adowe s% bardzo ogólne i mog% by* stosowane przez dowolnego
wydawc#, warto zaimplementowa* je jako cz#'* osobnego obiektu. W ten sposób b#dzie je
mo&na w przysz$o'ci skopiowa* do dowolnego obiektu, zamieniaj%c go w wydawc# (obiekt
publikuj%cy).
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
164
Rozdzia$ 7. Wzorce projektowe
Oto przyk$adowa implementacja ogólnej funkcjonalno'ci obiektu publikuj%cego, która defi-
niuje wszystkie wymagane sk$adowe oraz metod# pomocnicz%
visitSubscribers()
:
var publisher = {
subscribers: {
any: [] // typ zdarzenia
},
subscribe: function (fn, type) {
type = type || 'any';
if (typeof this.subscribers[type] === "undefined") {
this.subscribers[type] = [];
}
this.subscribers[type].push(fn);
},
unsubscribe: function (fn, type) {
this.visitSubscribers('unsubscribe', fn, type);
},
publish: function (publication, type) {
this.visitSubscribers('publish', publication, type);
},
visitSubscribers: function (action, arg, type) {
var pubtype = type || 'any',
subscribers = this.subscribers[pubtype],
i,
max = subscribers.length;
for (i = 0; i < max; i += 1) {
if (action === 'publish') {
subscribers[i](arg);
} else {
if (subscribers[i] === arg) {
subscribers.splice(i, 1);
}
}
}
}
};
Poni&ej znajduje si# kod funkcji, która przyjmuje obiekt i zamienia go w obiekt publikuj%cy
przez proste skopiowanie wszystkich ogólnych metod dotycz%cych publikacji.
function makePublisher(o) {
var i;
for (i in publisher) {
if (publisher.hasOwnProperty(i) && typeof publisher[i] === "function") {
o[i] = publisher[i];
}
}
o.subscribers = {any: []};
}
Czas na implementacj# obiektu
paper
, który b#dzie publikowa$ gazet# i magazyn.
var paper = {
daily: function () {
this.publish("ciekawy news");
},
monthly: function () {
this.publish("interesuj^c^ analiz~", "magazyn");
}
};
Trzeba jeszcze uczyni* z obiektu wydawc#.
makePublisher(paper);
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Obserwator
165
Po utworzeniu wydawcy mo&emy utworzy* obiekt subskrybenta o nazwie
joe
, który ma
dwie metody.
var joe = {
drinkCoffee: function (paper) {
console.log('W_a,nie przeczyta_em ' + paper);
},
sundayPreNap: function (monthly) {
console.log('Chyba zasn~, czytaj^c ' + monthly);
}
};
Nast#pny krok to obiekt
paper
subskrybuj%cy
joe
(tak naprawd# to
joe
zg$asza si# jako sub-
skrybent do
paper
).
paper.subscribe(joe.drinkCoffee);
paper.subscribe(joe.sundayPreNap, 'magazyn');
Obiekt
joe
udost#pni$ dwie metody. Pierwsza z nich powinna by* wywo$ywana dla domy'lnego
zdarzenia „wszystko”, a druga jedynie dla zdarze+ „magazyn”. Oto kilka zg$osze+ zdarze+:
paper.daily();
paper.daily();
paper.daily();
paper.monthly();
Wszystkie metody publikuj%ce wywo$a$y odpowiednie metody z obiektu
joe
, co spowodo-
wa$o wy'wietlenie w konsoli nast#puj%cego wyniku:
W_a,nie przeczyta_em ciekawy news
W_a,nie przeczyta_em ciekawy news
W_a,nie przeczyta_em ciekawy news
Chyba zasn~, czytaj^c interesuj^c^ analiz~
Bardzo wa&nym elementem ca$ego systemu jest to, &e
paper
nie zawiera w sobie informacji
o
joe
i odwrotnie. Co wi#cej, nie istnieje obiekt mediatora, który wiedzia$by o wszystkich
obiektach. Obiekty uczestnicz%ce w interakcjach s% ze sob% powi%zane bardzo lu;no i bez ja-
kichkolwiek modyfikacji mo&na doda* jeszcze kilku subskrybentów. Co wa&ne,
joe
mo&e
w dowolnym momencie anulowa* subskrypcj#.
Nic te& nie stoi na przeszkodzie, by
joe
równie& zosta$ wydawc% (przecie& to nic trudnego
dzi#ki systemom blogowym i mikroblogowym). Jako wydawca
joe
wysy$a aktualizacj# swojego
statusu do serwisu Twitter:
makePublisher(joe);
joe.tweet = function (msg) {
this.publish(msg);
};
Wyobra;my sobie, &e dzia$ relacji z klientami wydawcy gazety decyduje si# czyta*, co o ga-
zecie s%dzi jej subskrybent
joe
, i dodaje w tym celu metod#
readTweets()
.
paper.readTweets = function (tweet) {
alert('Zwo_ajmy du`e zebranie! Kto, napisa_: ' + tweet);
};
joe.subscribe(paper.readTweets);
Gdy
joe
zamie'ci swój wpis, wydawca (
paper
) go otrzyma.
joe.tweet("nie lubi~ tej gazety");
Wykonanie kodu spowoduje wy'wietlenie w konsoli tekstu „Zwo$ajmy du&e zebranie! Kto'
napisa$: nie lubi# tej gazety”.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
166
Rozdzia$ 7. Wzorce projektowe
Pe$ny kod ;ród$owy przyk$adu oraz mo&liwo'* sprawdzenia wyników jego dzia$ania w konsoli
zapewnia strona HTML dost#pna pod adresem http://www.jspatterns.com/book/7/observer.html.
Drugi przyk$ad — gra w naciskanie klawiszy
Przyjrzymy si# jeszcze jednemu przyk$adowi. Zaimplementujemy t# sam% gr# w naciskanie
klawiszy co przy wzorcu mediatora, ale tym razem u&yjemy wzorca obserwatora. Aby nieco
utrudni* zadanie, zapewnijmy obs$ug# dowolnej liczby graczy, a nie tylko dwóch. Ponownie
skorzystamy z konstruktora
Player()
, który tworzy obiekty graczy, i z obiektu
scoreboard
.
Jedynie obiekt
mediator
zamieni si# w obiekt
game
.
We wzorcu mediatora obiekt
mediator
wiedzia$ o wszystkich uczestnikach gry i wywo$ywa$
ich metody. Obiekt
game
ze wzorca obserwatora nie b#dzie tego robi$ — to same obiekty b#d%
zg$asza$y ch#* otrzymywania informacji o zaj'ciu wybranych zdarze+. Przyk$adowo, obiekt
scoreboard
zg$osi ch#* bycia informowanym o zaj'ciu zdarzenia
scorechange
w obiekcie
game
.
Oryginalny obiekt
publisher
nale&y nieco zmieni*, by upodobni* go do rozwi%za+ znanych
z przegl%darek internetowych.
"
Zamiast metod
publish()
,
subscribe()
i
unsubscribe()
pojawi% si# metody
fire()
,
on()
i
remove()
.
"
Typ zdarzenia (
type
) b#dzie u&ywany ca$y czas, wi#c stanie si# pierwszym parametrem
wszystkich trzech funkcji.
"
Dodatkowy parametr
context
przekazywany wraz z funkcj% powiadomienia umo&liwi
wywo$anie funkcji zwrotnej z odpowiednio ustawion% warto'ci%
this
.
Nowy obiekt
publisher
ma nast#puj%c% posta*:
var publisher = {
subscribers: {
any: []
},
on: function (type, fn, context) {
type = type || 'any';
fn = typeof fn === "function" ? fn : context[fn];
if (typeof this.subscribers[type] === "undefined") {
this.subscribers[type] = [];
}
this.subscribers[type].push({fn: fn, context: context || this});
},
remove: function (type, fn, context) {
this.visitSubscribers('unsubscribe', type, fn, context);
},
fire: function (type, publication) {
this.visitSubscribers('publish', type, publication);
},
visitSubscribers: function (action, type, arg, context) {
var pubtype = type || 'any',
subscribers = this.subscribers[pubtype],
i,
max = subscribers ? subscribers.length : 0;
for (i = 0; i < max; i += 1) {
if (action === 'publish') {
subscribers[i].fn.call(subscribers[i].context, arg);
} else {
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Obserwator
167
if (subscribers[i].fn === arg && subscribers[i].context === context) {
subscribers.splice(i, 1);
}
}
}
}
};
Nowy konstruktor
Player()
wygl%da nast#puj%co:
function Player(name, key) {
this.points = 0;
this.name = name;
this.key = key;
this.fire('newplayer', this);
}
Player.prototype.play = function () {
this.points += 1;
this.fire('play', this);
};
Nowym parametrem przyjmowanym przez konstruktor jest
key
— okre'la on klawisz na
klawiaturze, który gracz b#dzie naciska$, by uzyskiwa* punkty (we wcze'niejszej wersji kodu
klawisze by$y zapisane na sztywno). Dodatkowo utworzenie nowego obiektu gracza powo-
duje zg$oszenie zdarzenia
newplayer
, a ka&de naci'ni#cie klawisza przez gracza skutkuje
zg$oszeniem zdarzenia
play
.
Obiekt
scoreboard
pozostaje bez zmian — nadal aktualizuje tablic# wyników, korzystaj%c
z bie&%cych warto'ci.
Nowy obiekt
game
potrafi 'ledzi* poczynania wszystkich graczy, by móg$ zlicza* wyniki
i zg$asza* zdarzenie
scorechange
. Dodatkowo zg$asza si# on jako subskrybent wszystkich
zdarze+
keypress
przegl%darki, by wiedzie* o wszystkich klawiszach przypisanych poszcze-
gólnym graczom.
var game = {
keys: {},
addPlayer: function (player) {
var key = player.key.toString().charCodeAt(0);
this.keys[key] = player;
},
handleKeypress: function (e) {
e = e || window.event; // IE
if (game.keys[e.which]) {
game.keys[e.which].play();
}
},
handlePlay: function (player) {
var i,
players = this.keys,
score = {};
for (i in players) {
if (players.hasOwnProperty(i)) {
score[players[i].name] = players[i].points;
}
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
168
Rozdzia$ 7. Wzorce projektowe
}
this.fire('scorechange', score);
}
};
Funkcja
makePublisher()
, która zamienia$a dowolny obiekt w obiekt publikuj%cy zdarzenia,
jest identyczna jak w przyk$adzie z wydawc% i gazet%. Obiekt
game
b#dzie zg$asza$ zdarzenia
takie jak
scorechange
. Obiektem publikuj%cym stanie si# równie&
Player.prototype
, by mo&li-
we by$o zg$aszanie zdarze+
play
i
newplayer
wszystkim zainteresowanym.
makePublisher(Player.prototype);
makePublisher(game);
Obiekt
game
zg$asza si# jako subskrybent zdarze+
play
i
newplayer
(a tak&e zdarzenia
keypress
przegl%darki), natomiast obiekt
scoreboard
chce by* powiadamiany o zdarzeniach
scorechange
.
Player.prototype.on("newplayer", "addPlayer", game);
Player.prototype.on("play", "handlePlay", game);
game.on("scorechange", scoreboard.update, scoreboard);
window.onkeypress = game.handleKeypress;
Metoda
on()
umo&liwia subskrybentom okre'lenie funkcji zwrotnej jako referencji (
score
board.update
) lub jako tekstu (
"addPlayer"
). Wersja tekstowa dzia$a prawid$owo tylko
w przypadku przekazania jako trzeciego parametru kontekstu (na przyk$ad
game
).
Ostatni element to dynamiczne tworzenie tylu obiektów graczy (po naci'ni#ciu klawiszy), ile
zostanie za&%danych przez graj%cych.
var playername, key;
while (1) {
playername = prompt("Dodaj gracza (imi~)");
if (!playername) {
break;
}
while (1) {
key = prompt("Klawisz dla gracza " + playername + "?");
if (key) {
break;
}
}
new Player(playername, key);
}
To ju& wszystko w temacie gry. Pe$ny kod ;ród$owy wraz z mo&liwo'ci% zagrania znajduje
si# pod adresem http://www.jspatterns.com/book/7/observer-game.html.
W implementacji wzorca mediatora obiekt
mediator
musia$ wiedzie* o wszystkich obiektach,
by móc w odpowiednim czasie wywo$ywa* w$a'ciwe metody i koordynowa* ca$% gr#. W nowej
implementacji obiekt
game
jest nieco g$upszy i wykorzystuje fakt, i& obiekty zg$aszaj% zdarzenia
i obserwuj% si# nawzajem (na przyk$ad obiekt
scoreboard
nas$uchuje zdarzenia
scorechange
).
Zapewnia to jeszcze lu;niejsze powi%zanie obiektów (im mniej z nich wie o innych, tym lepiej),
cho* za cen# utrudnionej analizy, kto tak naprawd# nas$uchuje kogo. W przyk$adowej grze
wszystkie subskrypcje s% na razie w jednym miejscu, ale gdyby sta$a si# ona bardziej rozbu-
dowana, wywo$ania
on()
mog$yby si# znale;* w wielu ró&nych miejscach (niekoniecznie
w kodzie inicjalizuj%cym). Taki kod trudniej jest testowa*, gdy& trudno od razu zrozumie*,
co tak naprawd# si# w nim dzieje. Wzorzec obserwatora zrywa ze standardowym, proce-
duralnym wykonywaniem kodu od pocz%tku do ko+ca.
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Podsumowanie
169
Podsumowanie
W rozdziale pojawi$y si# opisy kilku popularnych wzorców projektowych i przyk$ady ich
implementacji w j#zyku JavaScript. Omawiane by$y nast#puj%ce wzorce:
"
Singleton
— tworzenie tylko jednego obiektu danej „klasy”. Pojawi$o si# kilka rozwi%-
za+, w tym takie, które stara$y si# zachowa* sk$adni# znan% z j#zyka Java przez zastoso-
wanie funkcji konstruuj%cych. Trzeba jednak pami#ta*, &e z technicznego punktu widze-
nia w j#zyku JavaScript wszystkie obiekty s% singletonami. Nie nale&y te& zapomina*, &e
czasem programi'ci stosuj% s$owo „singleton”, a maj% na my'li obiekty utworzone przy
u&yciu wzorca modu$u.
"
Fabryka
— metoda tworz%ca obiekty o typie przekazanym jako warto'* tekstowa.
"
Iterator
— interfejs umo&liwiaj%cy $atwe przetwarzanie elementów umieszczonych w z$o-
&onej strukturze danych.
"
Dekorator
— modyfikacja obiektów w trakcie dzia$ania programu przez dodawanie do
nich funkcjonalno'ci zdefiniowanych w dekoratorach.
"
Strategia
— zachowanie tego samego interfejsu przy jednoczesnym wyborze najlepszej
strategii jego implementacji (uzale&nionej od kontekstu).
"
Fasada
— zapewnienie bardziej przyjaznego API przez otoczenie typowych (lub ;le za-
projektowanych) metod ich nowymi wersjami.
"
Po1rednik
— otoczenie obiektu zapewniaj%ce kontrol# dost#pu do niego, gdy celem jest
unikni#cie wykonywania kosztownych operacji przez ich grupowanie lub opó;nianie do
momentu, gdy b#d% naprawd# konieczne.
"
Mediator
— promowanie lu;nego powi%zania obiektów przez unikanie bezpo'redniej
komunikacji mi#dzy nimi i zast%pienie jej komunikacj% poprzez obiekt mediatora.
"
Obserwator
— lu;ne powi%zanie mi#dzy obiektami osi%gane przez tworzenie obiektów,
których zmiany mo&na obserwowa*, jawnie zg$aszaj%c si# jako subskrybent (cz#sto mówi
si# równie& o w$asnych zdarzeniach lub wzorcu subskrybenta-dostawcy).
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
170
Rozdzia$ 7. Wzorce projektowe
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
195
Skorowidz
.htaccess, 185
@class, 43
@method, 43
@namespace, 43
@param, 41, 43
@return, 41, 43
<script>, 186, 187
dodawanie elementu, 190
dynamiczne elementy, 189
lokalizacja, 187
A
addEventListener(), 175
alert(), 20
antywzorce, 16
Apache, .htaccess, 185
aplikacja
cz#'ciowa, 85, 86, 89
funkcji, 84, 85
internetowa, 171
apply(), 85, 133, 134
arguments.callee, 55, 83
Array, 56, 57
asynchroniczne, zdarzenia, 73
atrybut, 17
attachEvent(), 175
B
b%belkowanie zdarze+, 177
bind(), 135, 136
break, 32
C
call(), 133, 134
case, 32
CDN, 186
Closure Compiler, 46, 80
console, obiekt, 20
constructor, w$a'ciwo'*, 18, 126
Content Delivery Network, Patrz CDN
Crockford, Douglas, 19, 113
Curry, Haskell, 87
currying, Patrz funkcje, rozwijanie
D
default, 32
dekoratora, wzorzec, 145, 169
implementacja, 146, 147, 148, 149
delegacje zdarze+, wzorzec, 177
delete, operator, 24
dir(), 20
Document Object Model, Patrz DOM
dodatki syntaktyczne, 113
dokumentacja, 41
JSDoc, 41
YUIDoc, 41, 42, 44
DOM, 172
dost#p, 173
modyfikacja, 174
dorozumiane zmienne globalne, 23, 24
dziedziczenie, 18, 136
klasyczne, 115, 116, 126
nowoczesne, 115, 116
prototypowe, 129, 130
przez kopiowanie w$a'ciwo'ci, 131, 132
wielobazowe, 121
E
ECMAScript 5, 18, 19
dodatki, 130
Error(), 62
ES5, Patrz ECMAScript 5
eval(), 19
unikanie, 32, 33
Expires, nag$ówek, 185
extend(), 97, 132
extendDeep(), 97
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
196
Skorowidz
F
fabryki, wzorzec, 141, 142, 143, 169
fasady, wzorzec, 152, 153, 169
Firebug, 132
for, p#tla, 27, 28
for-in, p#tla, 29
Function(), 33, 66
Function.prototype.apply(), 84
funkcje, 17, 65, 66
anonimowe, 66, 68
czasowe, 73
deklaracje, 67, 68
konstruuj%ce, 51, 52
name, w$a'ciwo'*, 68
natychmiastowe, 76, 77, 78, 79, 89
obs$ugi zdarze+ asynchronicznych, 73
po'rednicz%ce, 126
prywatne, 99
rozwijanie, 84, 86, 87, 89
samodefiniuj%ce si#, 75, 76, 90
samowywo$uj%ce si#, 79
terminologia, 66
w$a'ciwo'ci, 82
wywo$ania zwrotnego, 70
wywo$anie, 85
zwracanie, 74, 89
G
globalne zmienne, 22, 23, 24
dorozumiane, 23, 24
gospodarza, obiekty, 17, 18
H
hasOwnProperty(), 29, 30
hoisting, Patrz przenoszenie deklaracji
HTML, wysy$anie pliku fragmentami, 188, 189
HTMLCollection, 27, 28
I
inicjalizacja, 25
leniwa, 153
init(), 79, 80
instanceof, operator, 108
instancja, 115
isArray(), 57
iteratora, wzorzec, 143, 144, 169
J
JavaScript, 15
biblioteki, 94
jako j#zyk obiektowy, 16
sprawdzanie jako'ci kodu, 19
'rodowisko uruchomieniowe, 18
JavaScript Object Notation, Patrz JSON
jQuery, biblioteka, 59, 132
JSDoc, 41
JSLint, 19, 47
JSON, 58
JSON with Padding, Patrz JSONP
JSON.parse(), 58, 59
JSON.stringify(), 59
JSONP, 181, 182, 183
K
klasy, 17, 126
emulacja, 126, 127
kod
konwencje, 34, 35, 36, 37, 38
$atwy w konserwacji, 21, 22
minifikowanie, 46
ocenianie przez innych, 45, 46
usuwanie warunkowych wersji, 80, 81, 90
wielokrotne u&ycie, 115
kodowania, wzorce, 16
komentarze, 40, 41
kompresja, 185
konsola, 20
konstruktory, 54, 119
czyszczenie referencji, 125
po'rednicz%ce, 126
po&yczanie, 119, 121, 122
samowywo$uj%ce, 55
tymczasowe, 124, 126
warto'* zwracana, 53
konwencje kodu, 34, 35
bia$e spacje, 37, 38
nawias otwieraj%cy, 36, 37
nawiasy klamrowe, 35, 36
nazewnictwo, 38, 39, 40, 54
'redniki, 37
wci#cia, 35
konwersja liczb, 34
kopia
g$#boka, 131
p$ytka, 131
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
Skorowidz
197
L
leniwa inicjalizacja, 153
leniwe wczytywanie, 190, 191
liczby, konwersja, 34
litera$y
funkcji, 67
obiektów, 49, 50, 51, 98
tablicy, 56
wyra&enia regularnego, 59, 60
log(), 20
lokalne zmienne, 22
+
$a+cuchy wywo$a+, 112
M
Martin, Robert, 112
mediator, 160
mediatora, wzorzec, 160, 169
przyk$ad, 160, 161, 162
uczestnicy, 160
method(), 113
metody, 17, 49
po&yczanie, 133, 134
prywatne, 95, 96
publiczne, 99
statyczne, 107, 108
uprzywilejowane, 96
minifikacja, 46, 185
modu$y, 100, 101, 102
import zmiennych globalnych, 103
tworz%ce konstruktory, 102
N
najmniejszego przywileju, zasada, 97
name, w$a'ciwo'*, 68
natychmiastowa inicjalizacja obiektu, 79, 90
nazewnictwo, konwencje, 38, 39, 40, 54
nazwane wyra&enie funkcyjne, 66, 67
new, s$owo kluczowe, 54, 138
nienazwane wyra&enie funkcyjne, 66, 68
notacja litera$u obiektu, 49, 50, 51
O
obiekty, 17, 51
b$#dów, 62
globalne, 22, 25
gospodarza, 17, 18
konfiguracyjne, 83, 84, 89
natychmiastowa inicjalizacja, 79, 90
rdzenne, 17
tworzenie, 51, 91
Object(), 18, 51, 143
Object.create(), 130
Object.prototype.toString(), 58
obserwator, 163
obserwatora, wzorzec, 163, 166, 169
obs$uga zdarze+, 175, 176
asynchronicznych, 73
onclick, atrybut, 175
open(), 180
P
parseInt(), 34
parseJSON(), 59
p#tle
for, 27, 28
for-in, 29
piaskownicy, wzorzec, 103, 104, 105, 114
dodawanie modu$ów, 105
globalny konstruktor, 104
implementacja konstruktora, 106
po'rednika, wzorzec, 153, 155, 158, 159, 169
preventDefault(), 152
projektowe, wzorce, 16
prototype, w$a'ciwo'*, 18, 98
modyfikacja, 31
prototypy, 18
$a+cuch, 117, 118, 120, 121
modyfikacja, 31
prywatno'*, 98
wspó$dzielenie, 123, 124
prywatno'*, problemy, 96
przegl%darki, wykrywanie, 194
przenoszenie deklaracji, 26, 27
przestrzenie nazw, 22, 91, 92, 114
Firebug, 94
R
ramki, 184
rdzenne obiekty, 17
RegExp(), 59, 60
rzutowanie niejawne, 32
S
Schönfinkel, Moses, 87
schönfinkelizacja, 87
send(), 180
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ
198
Skorowidz
serializacja, 82, 83
serwer, komunikacja, 179
setInterval(), 33, 73
setTimeout(), 33, 73, 178
singleton, 137, 138, 169
sk$adowe
prywatne, 96
statyczne, 107, 109
skrypty
$%czenie, 184, 185
obliczeniowe, 179
strategie wczytywania, 186
sta$e, 110, 111
stopPropagation(), 152
strategii, wzorzec, 149, 169
strict mode, Patrz tryb 'cis$y
String.prototype.replace(), 60
styl wielb$%dzi, 39
subskrybenta-dostawcy, wzorzec, 163, 169
supermetody, 152
switch, 31, 32
SyntaxError(), 62
K
'rodowisko uruchomieniowe, 18
T
that, 54, 55
this, 22, 53
throw, instrukcja, 62
tryb 'cis$y, 19
TypeError(), 62
typeof, 32, 57
typy proste, otoczki, 61, 62
V
var, 23
efekty uboczne pomini#cia, 24
problem rozrzuconych deklaracji, 26
wzorzec pojedynczego u&ycia, 25
W
walidacja danych, 150
w%tki, symulacja, 178
wczytywanie
leniwe, 190, 191
na &%danie, 191
wst#pne, 192, 193, 194
wielb$%dzi, styl, 39
window, w$a'ciwo'*, 22, 25
with, polecenie, 19
w$a'ciwo'ci, 17, 49
prywatne, 95, 96
statyczne, 107, 110
wydajno'*, 184
wyliczenie, 29
wyra&enia regularne, 59
wyra&enie funkcyjne, 66, 67
nazwane, 66, 67
nienazwane, 66
wywo$anie funkcji, 85
wywo$anie jako obraz, 184
wywo$anie zwrotne, 70, 71, 89
w bibliotekach, 74
zakres zmiennych, 72
wzorce, 11, 15
antywzorce, 16
API, 89
inicjalizacyjne, 89
kodowania, 16
optymalizacyjne, 90
projektowe, 16
X
XHR, Patrz XMLHttpRequest
XMLHttpRequest, 180, 181
Y
Y.clone(), 132
Y.delegate(), 178
Yahoo! Query Language, Patrz YQL
YQL, 157
YUI3, 132, 178
YUIDoc, 41, 42, 44
przyk$ad dokumentacji, 42, 44
Z
zdarzenia, 175
asynchroniczne, 73
delegacje, 177
obs$uga, 175, 176
w$asne, 163
zmienne, 17
globalne, 22, 23, 24, 103
lokalne, 22
Kup ksiąĪkĊ
Pole
ü ksiąĪkĊ