diff options
Diffstat (limited to 'files/pl/web/javascript/domkniecia/index.html')
-rw-r--r-- | files/pl/web/javascript/domkniecia/index.html | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/files/pl/web/javascript/domkniecia/index.html b/files/pl/web/javascript/domkniecia/index.html new file mode 100644 index 0000000000..985f5e50ce --- /dev/null +++ b/files/pl/web/javascript/domkniecia/index.html @@ -0,0 +1,408 @@ +--- +title: Domknięcia +slug: Web/JavaScript/Domkniecia +translation_of: Web/JavaScript/Closures +--- +<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> |