1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
|
---
title: Domknięcia
slug: Web/JavaScript/Closures
translation_of: Web/JavaScript/Closures
original_slug: Web/JavaScript/Domkniecia
---
<div> {{jsSidebar("Intermediate")}}</div>
<div></div>
<p class="summary">Domknięcie jest funkcją skojarzoną z odwołującym się do niej środowiskiem. Innymi słowy domknięcie daje Ci dostęp z funkcji wewnętrznej do zasięgu funkcji zewnętrznej.</p>
<h2 id="Zasięg_leksykalny">Zasięg leksykalny</h2>
<p>Rozważ poniższy przykład:</p>
<div>
<pre class="brush: js">function init() {
var name = "Mozilla"; // name jest zmienną lokalną utworzoną przez funkcję init
function displayName() { // displayName() jest wewnętrzną funkcją, domknięciem
alert(name); // używa zmiennej zdeklarowanej w funkcji nadrzędnej
}
displayName();
}
init();</pre>
</div>
<p><code>init()</code> tworzy zmienną lokalną <code>name</code> oraz funkcję <code>displayName()</code>. <code>displayName()</code> jest funkcją lokalną która została zdefiniowana wewnątrz funkcji <code>init()</code> i jest dostępna tylko wewnątrz tej funkcji. <code>displayName()</code> nie ma własnych zmiennych lokalnych. Jednakże, ponieważ wewnętrzne funkcje mają dostęp do zmiennych zdefiniowanych w funkcjach zewnętrznych, <code>displayName()</code> ma dostęp do zmiennej <code>name</code> zdeklarowanej w funkcji nadrzędnej, <code>init()</code>.</p>
<p>{{JSFiddleEmbed("https://jsfiddle.net/xAFs9/3/", "js,result", 200)}}</p>
<p>Uruchom kod i zauważ że <code>alert()</code> zawarty w funkcji <code>displayName()</code> wyświetlił wartość ze zmiennej <code>name</code>, która jest zdeklarowana w funkcji nadrzędnej. Jest to przykład <em>zasięgu leksykalnego</em>, który opisuje jak parser rozwiązuje zmienne kiedy funkcje są zagnieżdżone. Słowo "leksykalny" odnosi się do faktu że zasięg leksykalny używa lokalizacji zdefiniowania zmiennej w kodzie źródłowym aby określić gdzie ta zmienna jest dostępna. Zagnieżdżone funkcje mają dostęp do zmiennych zdeklarowanych w ich zewnętrznym zasięgu.</p>
<h2 id="Domknięcie">Domknięcie</h2>
<p>Teraz rozważmy następujący przykład:</p>
<pre class="brush: js">function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
</pre>
<p>Jeżeli uruchomisz ten kod przekonasz się że ma takie samo działanie jak poprzedni przykład z funkcją <code>init()</code>: tym razem wartość tekstowa "Mozilla" zostanie wyświetlona w alercie. To, co jest inne, - i interesujące - to to, że wewnętrzna funkcja <code>displayName()</code> została zwrócona z nadrzędnej funkcji przed jej wykonaniem.</p>
<p>Na pierwszy rzut oka może się wydawać nieintuicyjne, że kod nadal pracuje. W niektórych językach programowania zmienne lokalne znajdujące się w funkcji istnieją tylko przez czas trwania tej funkcji. W momencie gdy <code>makeFunc()</code> zostanie wykonana możesz oczekiwać, że zmienna nie będzie już dostępna. Jednakże, ponieważ w naszym przypadku kod nadal pracuje, jak widać nie dotyczy to języka JavaScript.</p>
<p>Spowodowane jest to tym, że omawiane funkcje przybierają w Javascript formę domknięć. <em>Domknięcie</em> jest kombinacją funkcji i leksykalnego środowiska w którym ta funkcja została zdeklarowana. To środowisko zawiera każdą zmienną lokalną która była w zasięgu w momencie kiedy domknięcie zostało stworzone. W tym przypadku, <code>myFunc</code> jest referencją do instancji funkcji <code>displayName</code> stworzonej w momencie działania <code>makeFunc</code>. Instancja <code>displayName</code> zarządza referencją do jej leksykalnego środowiska, w którym istnieje zmienna. Dlatego, kiedy <code>myFunc</code> jest uruchomiona, zmienna pozostaje dostępna do użycia i "Mozilla" może być przekazane do <code>alert</code>.</p>
<p>Poniżej znajduje się znacznie bardziej interesujący przykład — funkcja <code>makeAdder</code>:</p>
<pre><code>function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12</code></pre>
<p>W tym przykładzie, zdefiniowaliśmy funkcję <code>makeAdder(x)</code>, która pobiera argument, <code>x</code>, i zwraca nową funkcję. Zwrócona funkcja pobiera argument, y, i zwraca sume x i y.</p>
<p>W zasadzie <code>makeAdder</code> jest fabryką funkcji — wytwarza funkcje, które mogą dodawać pewną wartość do ich argumentu. W powyższym przykładzie używamy naszej fabryki funkcji do stworzenia dwóch nowych funkcji — jedna, która dodaje 5 do jej argumentu i druga, która dodaje 10.</p>
<p><code>add5</code> i <code>add10</code> są domknięciami. Dzielą ten sam kod zawarty w funkcji <code>makeAdder</code>, ale przechowują różne leksykalne środowisko. W leksykalnym środowisku <code>add5</code>, <code>x</code> wynosi 5, natomiast w leksykalnym środowisku <code>add10</code>, <code>x</code> jest równe 10.</p>
<h2 id="Praktyczne_domknięcia">Praktyczne domknięcia</h2>
<p>Domknięcia są przydatne, ponieważ pozwalają Ci powiązać część danych (środowisko leksykalne) z funkcją, która operuje na tych danych. Jest to oczywista analogia do programowania obiektowego, gdzie obiekty pozwalają nam na powiązanie części danych (właściwości obiektu) z jedną lub dwiema metodami.</p>
<p>W rezultacie możesz użyć dokmnięć w sytuacjach, gdzie normalnie byś użył/a obiektu z wyłącznie jedną metodą.</p>
<p>Potencjalne sytuacje zastosowania powyższego zachowania są szczególnie popularne w sieci. Wiele kodu, który piszemy we front-endowym Javascripcie bazuje na zdarzeniach — definiujemy jakieś zachowanie, następnie dołączamy je do wydarzenia, które jest wywoływane przez użytkownika (kliknięciem myszki lub naciśnięciem klawisza klawiatury). Nasz kod jest najczęściej dołączony jako callback (wywołanie zwrotne): pojedyncza funkcja wykonywana jako odpowiedź na wydarzenie.</p>
<p>Przyjmijmy przykładowo, że chcemy dodać do strony przyciski, które zmieniają wielkość tekstu. Jednym ze sposobów na osiągnięcie tego jest określenie rozmiaru czcionki <code>font-size</code> elementu <code>body</code> w pikselach, następnie ustawienie rozmiaru innych elementów na stronie (takich jak nagłówki) używając jednostki względnej <code>em</code>:</p>
<pre><code>body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}</code></pre>
<p>Nasze interaktywne przyciski zmiany wielkości tekstu mogą zmienić wlaściwość <code>font-size</code> elementu <code>body</code>, a inne elementy strony dostosują się dzięki zastosowaniu jednostki względnej.</p>
<p>Poniżej realizacja w JavaScript:</p>
<pre><code>function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);</code></pre>
<p><code>size12</code>, <code>size14</code>, oraz <code>size16</code> są obecnie funkcjami, które zmienią rozmiar tekstu w <code>body</code> do odpowiednio 12, 14 oraz 16 pixeli. Możemy dołączyć je do przycisków (w tym przypadku linków) jak ponżej:</p>
<pre><code>document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;</code></pre>
<pre><code><a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a></code></pre>
<p>{{JSFiddleEmbed("https://jsfiddle.net/vnkuZ/","","200")}}</p>
<h2 id="Emulowanie_prywatnych_metod_przy_użyciu_domknięć">Emulowanie prywatnych metod przy użyciu domknięć</h2>
<p>Języki takie jak Java dostarczają możliwość zadeklarowania metody jako prywatna, co oznacza, że może ona zostać wywołana wylącznie przez inne metody w tej samej klasie.</p>
<p>JavaScript nie zapewnia do tego wbudowanej metody, jednakże jest możliwa emulacja prywatnych metod przy użyciu domknięć. Prywatne metody nie sa wyłącznie użyteczne z racji możliwości ograniczenia dostępu do kodu: dają również świetną możliwość zarządzania Twoją globalną przestrzenią nazw (namespace) uniemożliwiając nieistotnym metodom zaśmiecenie interfejsu publicznego Twojego kodu.</p>
<p>Poniższy kod ukazuje, w jaki sposób można użyć domknięć do zdefiniowania publicznych funkcji, które mają dostęp do prywatnych funkcji i zmiennych. Używanie dokmnięć w taki sposób znane jest jako <a href="http://www.google.com/search?q=javascript+module+pattern" title="http://www.google.com/search?q=javascript+module+pattern">module pattern</a>:</p>
<pre><code>var counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1</code></pre>
<p>W poprzednich przykładach każde domknięcie miało własne leksykalne środowisko. Jednakże w tym przypadku tworzymy pojedyncze środowisko leksykalne, współdzielone przez trzy funkcje: <code>counter.increment</code>, <code>counter.decrement<font face="Open Sans, arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;"> oraz </span></font></code><code>counter.value</code>.</p>
<p>Owo współdzielone środowisko leksykalne tworzone jest w ciele funkcji anonimowej, która jest wykonana w momencie, gdy tylko zostanie zdefiniowana. Środowisko leksykalne zawiera dwa prywatne przedmioty: zmienną o nazwie <code>privateCounter</code> i funkcję o nazwie <code>changeBy</code>. Żaden z tych prywatnych przedmiotów nie może być wywołany bezpośrednio spoza funkcji anonimowej. Zamiast tego, muszą mieć być one wywołane poprzez trzy funkcje publiczne, które są zwracane z anonimowej klasy opakowującej (wrapper).</p>
<p>Te trzy funkcje publiczne to domknięcia, które współdzielą to samo środowisko. Dzięki JavaScriptowemu zakresowi leksykalnemu, każda z nich ma dostęp do zmiennej <code>privateCounter</code> oraz funkcji <code>changeBy.</code></p>
<p>Zauważysz, że definiujemy anonimową funkcję, która tworzy licznik, a następnie od razu ją wywołujemy i przypisujemy wynik do zmiennej <code>counter</code>. Moglibyśmy przetrzymywać tę funkcję w oddzielnej zmiennej <code>makeCounter</code> i użyć jej do stworzenia kilku liczników.</p>
<pre><code>var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var counter1 = makeCounter();
var counter2 = makeCounter();
alert(counter1.value()); /* Alerts 0 */
counter1.increment();
counter1.increment();
alert(counter1.value()); /* Alerts 2 */
counter1.decrement();
alert(counter1.value()); /* Alerts 1 */
alert(counter2.value()); /* Alerts 0 */</code></pre>
<p>Zauważ, że każdy z dwóch liczników, <code>counter1</code> oraz <code>counter2</code>, jest niezależny od drugiego. Każde domknięcie odnosi się do innej wersji zmiennej <code>privateCounter</code> przez własne domknięcie. Za każdym razem gdy któryś z liczników jest wywołany, jego środowisko leksykalne zmienia się przez zmianę wartości tej zmiennej; jednakże zmiany wartości zmiennej jednego domknięcia nie wpływają na wartość w innym domknięciu.</p>
<p>Używanie domknięć w ten sposób dostarcza wielu korzyści, które normalnie kojarzone sa z programowaniem obiektowym — w szczególności ukrywaniem oraz enkapsulacją danych.</p>
<h2 id="Tworzenie_domknięć_w_pętlach_popularne_błędy">Tworzenie domknięć w pętlach: popularne błędy</h2>
<p>W czasach przed wprowadzeniem definicji <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let" title="let"><code>let</code> </a>w standardzie ECMAScript 2015, popularnym problemem z domknięciami było ich użycie w pętlach.<br>
Rozważmy poniższy kod:</p>
<pre class="brush: html"><code><p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p></code></pre>
<pre class="brush: js"><code>function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();</code></pre>
<p>{{JSFiddleEmbed("https://jsfiddle.net/v7gjv/", "", 200)}}</p>
<p>Tablica <code>helpText</code> definiuje trzy podpowiedzi. Każda z nich jest powiązana z ID inputu z dokumentu. Następnie w pętli <code>for</code> dodawana jest obsługa zdarzenia <code>onfocus</code>, która ma pokazać tekst podpowiedzi podczas ustawienia focusa na pole.<br>
<br>
Jeżeli sprawdzisz działanie tego kodu okaże się, że nie działa tak, jak się spodziewaliśmy. Zawsze zostanie wyświetlona podpowiedź dotycząca ostatniego pola (<code>'Your age (you must be over 16)'</code>).</p>
<p>Powodem takiego działania funkcji zwrotnej obsługującej <code>onfocus</code> jest domknięcie. Składa sie ono z definicji funkcji i przechwyconego kontekstu zakresu z <code>setupHelp</code>. Trzy domknięcia stworzone są w pętli, ale dzielą one to samo środowisko leksykalne, ktore posiada zmienną, która jest aktualizowana (<code>item.help</code>). Wartości <code>item.help</code> są determinowane w pętli, więc kiedy obsługa zdarzenia <code>onfocus</code> zostanie wywołana, <code>item.help</code> będzie miało wartość z ostatniej iteracji pętli.</p>
<p>Rozwiązaniem tego problemu jest użycie kolejnych domknięć, szczególnie fabryki funkcji opisanej wcześniej:</p>
<pre class="brush: js"><code>function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function makeHelpCallback(help) {
return function() {
showHelp(help);
};
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}
setupHelp();</code></pre>
<p>{{JSFiddleEmbed("https://jsfiddle.net/v7gjv/1/", "", 300)}}</p>
<p>To rozwiązanie działa zgodnie z oczekiwaniami. W odróżnieniu od wcześniejszego przykładu, <code>makeHelpCallback</code> tworzy <em>nowe środowisko leksykalne</em> dla każdej funkcji zwrotnej, w której <code>help</code> odnosi się do odpowiadającego stringa z tablicy <code>helpText</code>.</p>
<p>Innym sposobem zapisu rozwiązania z anonimowymi domknięciami jest:</p>
<pre class="brush: js"><code>function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
(function() {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
})(); // Immediate event listener attachment with the current value of item (preserved until iteration).
}
}
setupHelp();</code></pre>
<p>Jeżeli nie chcesz używać domknięć możesz użyć słowa kluczowego <code>let</code> ze standardu ES2015:</p>
<pre class="brush: js"><code>function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
let item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();</code></pre>
<p>Ten przykład używa <code>let</code> zamiast <code>var</code>, więc każde domknięcie wiąże się z blokowym zasięgiem funkcji, dlatego nie potrzeba żadnych dodatkowych domknięć.</p>
<p>Alternatywą może być użycie <code>forEach()</code>, by iterować po tablicy <code>helpText</code> i dodać listener dla każdego elementu <code><input></code>:</p>
<pre class="brush: js"><code>function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
helpText.forEach(function(text) {
document.getElementById(text.id).onfocus = function() {
showHelp(text.help);
}
});
}
setupHelp();</code></pre>
<h2 id="Wątpliwości_wydajnościowe">Wątpliwości wydajnościowe</h2>
<p>Niemądrze jest, aby niepotrzebnie tworzyć funkcje wewnątrz innych funkcji, jeżeli domnkięcia nie są wymagane w danej sytuacji, jako że odbije się to w negatywny sposób na wydajność skryptu, mierzoną poprzez czas wykonywania jak również i używaną pamięć.</p>
<p>Na przykład gdy tworzymy nowy obiekt/klasę, metody powinny być zwykle powiązane z prototypem obiektu zamiast definiowane w konstruktorze obiektu. Przyczyną jest to, że za każdym razem gdy konstruktor zostanie użyty, metody zostaną nadpisane (czyli przy każdym tworzeniu nowego obiektu).</p>
<p>Rozważmy następujący przykład:</p>
<pre><code>function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}</code></pre>
<p>Ponieważ poprzedni przykład nie wykorzystuje zalet, które płyną z wykorzystania domknięć, możemy to przepisać w następujący sposób:</p>
<pre><code>function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype = {
getName: function() {
return this.name;
},
getMessage: function() {
return this.message;
}
};</code></pre>
<p>Jednakże ponowne definiowanie prototypu nie jest rekomendowane. Poniższy przykład zamiast tego dodaje właściwości do istniejącego prototypu:</p>
<pre><code>function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype.getName = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};</code></pre>
<p>Powyższy przykład może też zostać przepisany w bardziej czytelny sposób, z identycznym wynikiem:</p>
<pre><code>function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
(function() {
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}).call(MyObject.prototype);</code></pre>
<p>W poprzednich trzech przykładach odziedziczony prototyp może być współdzielony przez wszystkie obiektu i definicje metod nie muszą występować przy każdorazowym tworzeniu obiektu. Aby dowiedzieć się więcej, zobacz Szczegóły modelu obiektowego.</p>
<p>In the three previous examples, the inherited prototype can be shared by all objects and the method definitions need not occur at every object creation. See <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model">Details of the Object Model</a> for more.</p>
|