|
| 1 | +## Zasięg zmiennych i przestrzenie nazw |
| 2 | + |
| 3 | +Mimo, że JavaScript radzi sobie dobrze ze składnią, opisującą dwa pasujące |
| 4 | +nawiasy klamrowe jako blok, to jednak **nie** wspiera zasięgu blokowego. |
| 5 | +Jedynym zasięgiem jaki istnieje w JavaScript jest *zasięg funkcyjny*. |
| 6 | + |
| 7 | + function test() { // definiuje zasięg (scope) |
| 8 | + for(var i = 0; i < 10; i++) { // nie definiuje zasięgu (scope) |
| 9 | + // count |
| 10 | + } |
| 11 | + console.log(i); // 10 |
| 12 | + } |
| 13 | + |
| 14 | +> **Uwaga:** Jeżeli notacja `{...}` nie jest użyta w przypisaniu, deklaracji return |
| 15 | +> lub jako argument funkcji to zostanie zinterpretowana jako deklaracja bloku, a |
| 16 | +> **nie** jako literał obiektu. W połączeniu z [automatycznym wstawianiem średnika](#core.semicolon), |
| 17 | +> może prowadzić do subtelnych błędów. |
| 18 | +
|
| 19 | +W JavaScripcie nie ma również przestrzeni nazw, co oznacza, że wszystko jest |
| 20 | +definiowane w jednej *globalnie współdzielonej* przestrzeni nazw. |
| 21 | + |
| 22 | +Za każdym razem gdy zmienna jest |
| 23 | +Z każdym odwołaniem do zmiennej JavaScript przeszukuje w górę wszystkie zasięgi |
| 24 | +dopóki nie znajdzie tej zmiennej. W przypadku gdy przeszukiwanie dotrze do globalnego |
| 25 | +zasięgu i nadal nie znajdzie żądanej nazwy to wyrzuca błąd `ReferenceError`. |
| 26 | + |
| 27 | +### Zmora globalnych zmiennych |
| 28 | + |
| 29 | + // script A |
| 30 | + foo = '42'; |
| 31 | + |
| 32 | + // script B |
| 33 | + var foo = '42' |
| 34 | + |
| 35 | +Powyższe dwa skrypty **nie** dają tego samego efektu. Skrypt A definiuje zmienna |
| 36 | +nazwaną `foo` w *globalnym* zasięgu natomiast skrypt B definiuje `foo` |
| 37 | +w *aktualnym* zasięgu. |
| 38 | + |
| 39 | +Jeszcze raz, to wcale nie daje *tego samego efektu*, nie użycie `var` może mieć |
| 40 | +poważne konsekwencje. |
| 41 | + |
| 42 | + // globalny zasięg |
| 43 | + var foo = 42; |
| 44 | + function test() { |
| 45 | + // lokalny zasięg |
| 46 | + foo = 21; |
| 47 | + } |
| 48 | + test(); |
| 49 | + foo; // 21 |
| 50 | + |
| 51 | +Pominięcie słowa `var` w deklaracji wewnątrz funkcji `test` nadpisze wartość |
| 52 | +zmiennej globalnej `foo`. Mimo, że nie wygląda to na początku jak duży problem, |
| 53 | +to posiadając wiele tysięcy linii kodu w JavaScript i nie korzystanie z `var` |
| 54 | +wprowadzi straszne i trudne do wyśledzenia błędy. |
| 55 | + |
| 56 | + // globalny zasięg |
| 57 | + var items = [/* jakaś lista */]; |
| 58 | + for(var i = 0; i < 10; i++) { |
| 59 | + subLoop(); |
| 60 | + } |
| 61 | + |
| 62 | + function subLoop() { |
| 63 | + // scope of subLoop |
| 64 | + for(i = 0; i < 10; i++) { // brakuje słowa var w deklaracji |
| 65 | + // do amazing stuff! |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | +Zewnętrzna pętla zakończy działanie po pierwszym wywołaniu `subLoop`, ponieważ |
| 70 | +`subLoop` nadpisuje wartość globalnej zmiennej `i`. Użycie `var` w drugiej pętli |
| 71 | +`for` pozwoliło by łatwo uniknąć problemu. Słowo kluczowe `var` nie powinno być |
| 72 | +**nigdy** pominięte w deklaracji, chyba że *pożądanym skutkiem* jest wpłynięcie na |
| 73 | +zewnętrzny zasięg. |
| 74 | + |
| 75 | +### Lokalne zmienne |
| 76 | + |
| 77 | +Jedynym źródłem zmiennych lokalnych w JavaScripcie są parametry [funkcji](#function.general) |
| 78 | +oraz zmienne zadeklarowane poprzez deklaracje `var` wewnątrz funkcji. |
| 79 | + |
| 80 | + // globalny zasięg |
| 81 | + var foo = 1; |
| 82 | + var bar = 2; |
| 83 | + var i = 2; |
| 84 | + |
| 85 | + function test(i) { |
| 86 | + // lokalny zasięg fukcji test |
| 87 | + i = 5; |
| 88 | + |
| 89 | + var foo = 3; |
| 90 | + bar = 4; |
| 91 | + } |
| 92 | + test(10); |
| 93 | + |
| 94 | +Zmienne `foo` oraz `i` są lokalnymi zmiennymi wewnątrz zasiegu funkcji `test`, |
| 95 | +natomiast przypisanie wartości do `bar` nadpisze zmienną globalną o tej samej nazwie. |
| 96 | + |
| 97 | +### "Hoisting" - wywindowanie, podnoszenie |
| 98 | + |
| 99 | +JavaScript **winduje** deklaracje. Oznacza to, że zarówno deklaracja ze słowem |
| 100 | +kluczowym `var` jak i deklaracje funkcji `function` zostaną przeniesione na |
| 101 | +początek otaczającego zasięgu. |
| 102 | + |
| 103 | + bar(); |
| 104 | + var bar = function() {}; |
| 105 | + var someValue = 42; |
| 106 | + |
| 107 | + test(); |
| 108 | + function test(data) { |
| 109 | + if (false) { |
| 110 | + goo = 1; |
| 111 | + |
| 112 | + } else { |
| 113 | + var goo = 2; |
| 114 | + } |
| 115 | + for(var i = 0; i < 100; i++) { |
| 116 | + var e = data[i]; |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | +Powyższy kod zostanie przekształcony przed rozpoczęciem wykonania. JavaScript |
| 121 | +przeniesie deklarację zmiennej `var` oraz deklarację funkcji `function` na szczyt |
| 122 | +najbliższego zasięgu. |
| 123 | + |
| 124 | + // deklaracje var zostaną przeniesione tutaj |
| 125 | + var bar, someValue; // ustawione domyślnie na 'undefined' |
| 126 | + |
| 127 | + // deklaracje funkcji zostaną również przeniesione na górę |
| 128 | + function test(data) { |
| 129 | + var goo, i, e; // brak blokowego zasięgu spowoduje przeniesienie tutaj |
| 130 | + if (false) { |
| 131 | + goo = 1; |
| 132 | + |
| 133 | + } else { |
| 134 | + goo = 2; |
| 135 | + } |
| 136 | + for(i = 0; i < 100; i++) { |
| 137 | + e = data[i]; |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + bar(); // powoduje błąd TypeError ponieważ bar jest nadal 'undefined' |
| 142 | + someValue = 42; // przypisania nie zostają zmienione przez 'hoisting' |
| 143 | + bar = function() {}; |
| 144 | + |
| 145 | + test(); |
| 146 | + |
| 147 | +Brak blokowego zasięgu nie tylko przeniesie deklaracje `var` poza ciało pętle, |
| 148 | +ale również spowoduje, że niektóre porównania `if` staną się nieintuicyjne. |
| 149 | + |
| 150 | +W oryginalnym kodzie wewnątrz deklaracja `if` zdaje się modyfikować *zmienną |
| 151 | +globalną* `goo`, podczas gdy faktycznie modyfikuje ona *zmienną lokalną* - po tym |
| 152 | +jak zostało zastosowane windowanie (hoisting). |
| 153 | + |
| 154 | +Bez wiedzy na temat podnoszenia (hoistingu), poniższy kod może wydawać się |
| 155 | +wyrzucać błąd `ReferenceError`. |
| 156 | + |
| 157 | + // sprawdz czy SomeImportantThing zostało zainicjalizowane |
| 158 | + if (!SomeImportantThing) { |
| 159 | + var SomeImportantThing = {}; |
| 160 | + } |
| 161 | + |
| 162 | +Oczywiście powyższy kod działa ze względu na fakt, że deklaracja `var` zostanie |
| 163 | +przeniesiona na początek *globalnego zasięgu*. |
| 164 | + |
| 165 | + var SomeImportantThing; |
| 166 | + |
| 167 | + // inny kod który może ale nie musi zainicjalizować SomeImportantThing |
| 168 | + |
| 169 | + // upewnienie sie że SomeImportantThing zostało zainicjalizowane |
| 170 | + if (!SomeImportantThing) { |
| 171 | + SomeImportantThing = {}; |
| 172 | + } |
| 173 | + |
| 174 | +### Kolejność rozwiązywania nazw |
| 175 | + |
| 176 | +Wszystkie zasięgi w JavaScripcie, włączając *globalny zasięg*, posiadają |
| 177 | +zdefiniowana wewnątrz specjalną nazwę [`this`](#function.this), która wskazuje |
| 178 | +na *aktualny obiekt*. |
| 179 | + |
| 180 | +Zasięg funkcyjny posiada również zdefiniowaną wewnętrznie nazwę |
| 181 | +[`arguments`](#function.arguments), która zawiera listę argumentów przekazaną do |
| 182 | +funkcji. |
| 183 | + |
| 184 | +Na przykład, kiedy próbujemy odczytać zmienną `foo` wewnątrz zasięgu funkcji, |
| 185 | +JavaScript będzie szukać nazwy w określonej kolejności: |
| 186 | + 1. Jeżeli wewnątrz aktualnego zasięgu znajduje się deklaracja `var foo` skorzystaj z niej. |
| 187 | + 2. Jeżeli jeden z parametrów fukcji został nazwany `foo` użyj go. |
| 188 | + 3. Jeżeli fukcja została nazwana `foo` skorzystaj z tego. |
| 189 | + 4. Przejdz do zewnętrznego zasięgu i przejdz do kroku **#1**. |
| 190 | + |
| 191 | +> **Uwaga:** Jeżeli jeden z parametrów fukcji został nazwany `arguments` zapobiegnie |
| 192 | +> to utworzenia domyślnego obiektu `arguments`. |
| 193 | +
|
| 194 | +### Przestrzenie nazw |
| 195 | + |
| 196 | +Powszechnym problemem posiadania tylko jednej globalnej przestrzeni nazw jest |
| 197 | +prawdopodobieństwo wystąpienia kolizji nazw. W JavaScripcie, można łatwo uniknąć |
| 198 | +tego problemu korzystając z *anonimowych wrapperów*. |
| 199 | + |
| 200 | + (function() { |
| 201 | + // autonomiczna "przestrzeń nazw" |
| 202 | + |
| 203 | + window.foo = function() { |
| 204 | + // wyeksponowane domkniecie (closure) |
| 205 | + }; |
| 206 | + |
| 207 | + })(); // natychmiastowe wykonanie funkcji |
| 208 | + |
| 209 | +Nienazwane funkcje są rozpoznane jako [wyrażenia](#function.general), więc |
| 210 | +aby mogły zostać wywołane muszą zostać zewaluowane. |
| 211 | + |
| 212 | + ( // zewaluowanie fukcji znajdującej się wewnątrz nawiasów |
| 213 | + function() {} |
| 214 | + ) // zwrócenie obiektu funkcji |
| 215 | + () // wywołanie rezultatu ewaluacji |
| 216 | + |
| 217 | +Istnieją inne sposoby aby zewaluować i wykonać wyrażenie funkcyjne. Mimo, że |
| 218 | +mają inną składnie, zachowują się dokładnie tak samo. |
| 219 | + |
| 220 | + // Trzy inne sposoby |
| 221 | + !function(){}(); |
| 222 | + +function(){}(); |
| 223 | + (function(){}()); |
| 224 | + |
| 225 | +### Wnioski |
| 226 | + |
| 227 | +Zaleca się aby zawsze używać *anonimowych wrapperów* do hermetyzacji kodu wewnątrz |
| 228 | +jego własnej przestrzeni nazw. To nie tylko chroni kod przed kolizją nazw, ale |
| 229 | +również wprowadza lepszą modularyzację programów. |
| 230 | + |
| 231 | +Ponadto, stosowanie zmiennych globalnych jest uznawane za złą praktykę. |
| 232 | +Jakiekolwiek wykorzystanie zmiennych globalnych wskazuje na źle napisany kod, |
| 233 | +który jest podatny na błędy i trudny do utrzymania. |
| 234 | + |
0 commit comments