diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:43:23 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:43:23 -0500 |
commit | 218934fa2ed1c702a6d3923d2aa2cc6b43c48684 (patch) | |
tree | a9ef8ac1e1b8fe4207b6d64d3841bfb8990b6fd0 /files/uk/web/javascript/closures/index.html | |
parent | 074785cea106179cb3305637055ab0a009ca74f2 (diff) | |
download | translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.gz translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.bz2 translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.zip |
initial commit
Diffstat (limited to 'files/uk/web/javascript/closures/index.html')
-rw-r--r-- | files/uk/web/javascript/closures/index.html | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/files/uk/web/javascript/closures/index.html b/files/uk/web/javascript/closures/index.html new file mode 100644 index 0000000000..1abe835739 --- /dev/null +++ b/files/uk/web/javascript/closures/index.html @@ -0,0 +1,471 @@ +--- +title: Closures +slug: Web/JavaScript/Closures +tags: + - Замикання +translation_of: Web/JavaScript/Closures +--- +<div>{{jsSidebar("Intermediate")}}</div> + +<p class="summary">Замикання - це функції, що посилаються на незалежні (вільні) змінні (змінні, які використовуються локально, але визначені в обмеженій області видимості). Іншими словами, ці функції "пам'ятають" середовище, в якому вони були створені.</p> + +<h2 id="Лексичне_середовище">Лексичне середовище</h2> + +<p>Розглянемо наступне:</p> + +<div> +<pre class="brush: js notranslate">function init() { + var name = "Mozilla"; // name - це локальна змінна, створена в функції init + function displayName() { // displayName() - це внутрішня функція, замикання + alert(name); // використовує змінну, оголошену в батьківській функції + } + displayName(); +} +init();</pre> +</div> + +<p><code>init()</code> створює локальну змінну <code>name</code> та функцію <code>displayName()</code>. <code>displayName()</code> є вкладеною функцією, що оголошена всередині <code>init()</code> та є доступною тільки в тілі цієї функції. <code>displayName()</code> не має локальних змінних, проте вкладені функції мають доступ до змінних зовнішніх функцій саме тому <code>displayName()</code> може використовувати імена змінних, оголошених в батьківській функції <code>init()</code>.</p> + +<p>{{JSFiddleEmbed("https://jsfiddle.net/78dg25ax/", "js,result", 200)}}</p> + +<p><a href="http://jsfiddle.net/xAFs9/3/" title="http://jsfiddle.net/xAFs9/">Запустіть</a> цей код і побачите, що alert() всередині функції displayName() успішно відображає змінну name, оголошену в батьківській функції. Це є прикладом лексичного середовища (lexical environment), яке описує, яким чином парсер звертається до змінних, коли функції вкладені. Слово <em>лексичне</em> посилається на факт, що лексичне середовище використовує розташування, де в коді була створена змінна задля визначення середовища, де ця змінна доступна. Вкладені функції мають доступ до змінних, оголошених у їх зовнішній області.</p> + +<h2 id="Замикання">Замикання</h2> + +<p>Тепер розглянемо наступний приклад:</p> + +<pre class="brush: js notranslate">function makeFunc() { + var name = "Mozilla"; + function displayName() { + alert(name); + } + return displayName; +} + +var myFunc = makeFunc(); +myFunc(); +</pre> + +<p>Результат запуску цього коду буде таким самим, як і в попередньому прикладі функції <code>init()</code>: рядок "Mozilla" буде відображений в спливаючому повідомленні <font face="consolas, Liberation Mono, courier, monospace">alert</font>(). Цікавою відмінністю є те, що внутрішня функція <code>displayName() </code>буде повернена з зовнішньої функції перш ніж вона буде виконана. </p> + +<p>Той факт, що цей код досі працює, може здаватись неочевидним. В деяких мовах програмування локальні змінні існують лише протягом часу виконання функції. Як тільки <code>makeFunc()</code> завершила своє виконання, слід очікувати, що змінна <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: #eeeeee;">name</span></font> не буде більше існувати. Однак у JavaScript цей підхід інакший, оскільки даний код працює, як і очікувалось.</p> + +<p>Це відбувається тому що ці функції в JavaScript утворюють замикання. <em>Замикання</em> це комбінація функції і лексичного середовища (або просто середовища), всередині якого функція була оголошена. Середовище складається з будь-яких локальних змінних, які були в області видимості в той час, коли замикання було створене. В цьому випадку, <code>myFunc</code> є посиланням на екземпляр функції <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: #eeeeee;">displayName</span></font> створений, коли <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: #eeeeee;">makeFunc</span></font> була виконана. Екземпляр <code>displayName</code> включає посилання на власне лексичне середовище в середині якого існує змінна <code>name</code>. З цієї причини, коли <code>myFunc</code> викликається, змінна <code>name</code> залишається доступною для використання і "Mozilla" передається до <code>alert</code>. </p> + +<p>Ось трохи цікавіший приклад — a <code>makeAdder</code> function:</p> + +<pre class="brush: js notranslate">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 +</pre> + +<p>У цьому прикладі ми визначили функцію <code>makeAdder(x),</code> яка приймає єдиний аргумент <code>x</code> і повертає нову функцію. Функція, яку вона повертає, приймає єдиний аргумент <code>y</code>, і повертає суму <code>x</code> і <code>y</code>.</p> + +<p>По суті, <code>makeAdder</code> це фабрика функцій — вона створює функції, які можуть додавати певне значення до свого аргументу. У наведеному вище прикладі ми використовуємо нашу фабричну функцію для створення двох нових функцій — одна додає 5 до свого аргументу, і інша додає 10.</p> + +<p><code>add5</code> і <code>add10</code> є прикладами замикання. Вони поділяють одне визначення тіла функції, але зберігають різні лексичні середовища. В лексичному середовищі <code>add5</code> <code>x</code> - це 5, а в лексичному середовищі <code>add10</code> <code>x</code>- це 10.</p> + +<h2 id="Замикання_на_практиці">Замикання на практиці</h2> + +<p>Замикання корисні, оскільки вони дозволяють зв'язати деякі дані (лексичне середовище) з функцією, яка працює з цими даними. Це має очевидні паралелі з об'єктно-орієнтованим програмуванням, де об'єкти дозволяють нам зв'язати деякі дані (властивості об'єкта) з одним або декількома методами.</p> + +<p>Отже, замикання можна використовувати всюди, де зазвичай використовується об'єкт з одним єдиним методом. </p> + +<p>Ситуації, в яких ви можливо захочете зробити це, особливо поширені в web-розробці. Велика частина front-end коду, написана на JavaScript, основана на обробці подій. Ви визначаєте деяку поведінку, а потім прикріплюєте її до події, яка запускається користувачем (наприклад, клік або натискання клавіші). Код, як правило, прив'язується до події як зворотній виклик <strong>(callback)</strong> - функція, яка виконується у відповідь на виникнення події.</p> + +<p>Наприклад припустимо, що ми хочемо додати кілька кнопок на сторінку, які змінюватимуть розмір тексту. Один із способів зробити це - вказати <code>font-size</code> елементу <code>body</code> у пікселях, а потім встановити розмір інших елементів на сторінці (таких як заголовки) за допомогою відносних одиниць виміру <code>em</code>:</p> + +<pre class="brush: css notranslate">body { + font-family: Helvetica, Arial, sans-serif; + font-size: 12px; +} + +h1 { + font-size: 1.5em; +} + +h2 { + font-size: 1.2em; +} +</pre> + +<p>Такі інтерактивні кнопки розміру тексту можуть змінювати властивість <code>font-size</code> елемента <code>body</code>, і налаштування будуть підхоплені іншими елементами на сторінці завдяки відносним одиницям.</p> + +<p>Ось JavaScript:</p> + +<pre class="brush: js notranslate">function makeSizer(size) { + return function() { + document.body.style.fontSize = size + 'px'; + }; +} + +var size12 = makeSizer(12); +var size14 = makeSizer(14); +var size16 = makeSizer(16); +</pre> + +<p> Тепер <code>size12</code>, <code>size14</code>, і <code>size16</code> є функціями, які будуть змінювати розмір тексту елемента body відповідно до 12, 14 та 16 пікселів. Ми можемо прив'язати їх до кнопок таким чином:</p> + +<pre class="brush: js notranslate">document.getElementById('size-12').onclick = size12; +document.getElementById('size-14').onclick = size14; +document.getElementById('size-16').onclick = size16; +</pre> + +<pre class="brush: html notranslate"><a href="#" id="size-12">12</a> +<a href="#" id="size-14">14</a> +<a href="#" id="size-16">16</a> +</pre> + +<p>{{JSFiddleEmbed("https://jsfiddle.net/vnkuZ/","","200")}}</p> + +<h2 id="Емуляція_приватних_методів_з_замиканнями">Емуляція приватних методів з замиканнями</h2> + +<p>В таких мовах як Java є можливість об'являти методи приватними, що означає їх здатність бути викликаними лише іншими методами того ж класу. </p> + +<p>JavaScript не має власного способу зробити це, але є можливість емуляції приватних методів використовуючи замикання. Приватні методи корисні не лише для обмеження доступу до коду: вони також забезпечують потужний спосіб управління глобальним простором імен, зберігаючи несуттєві методи від захаращення публічного інтерфейсу до вашого коду.</p> + +<p>Ось як визначити деякі загальнодоступні функції, які можуть отримати доступ до приватних функцій та змінних, використовуючи замикання, також відомі як модель модуля (<a class="external" 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 class="brush: js notranslate">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 +</pre> + +<p>Тут багато чого відбувається. В попередніх пригладах кожне замикання мало своє власне оточення. Тут, однак, існує одне лексичне середовище, яке поділяють три функції: <code>counter.increment</code>, <code>counter.decrement</code>, and <code>counter.value</code>.</p> + +<p>Спільне лексичне середовище створюється в тілі анонімної функції, яка викликається одразу після створення. Навколишнє середовище містить два приватні складові: змінну <code>privateCounter</code> і функцію <code>changeBy</code>. Жодна із цих приватних складових не може бути доступна безпосередньо за межами анонімної функції. Натомість до них мають бути доступні три загальнодоступні функції, які повертаються з анонімної обгортки.</p> + +<p>Ці три функції - це замикання, які мають одне і те ж середовище. Завдяки лексичному оточенню JavaScript, кожна з них має доступ до змінної privateCounter та функції changeBy.</p> + +<p>Ми визначаємо анонімну функцію, яка створює лічильник, а потім ми викликаємо її негайно і присвоюємо результат змінній лічильника.Ми можемо зберігати цю функцію в окремій змінній makeCounter і використовувати її для створення декількох лічильників</p> + +<pre class="brush: js notranslate">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 */ +</pre> + +<p>Зауважте, як кожен з двох лічильників підтримує свою незалежність від іншого. Його середовище під час виклику функції makeCounter () щоразу відрізняється. Змінна замикання privateCounter містить кожен раз інший екземпляр.</p> + +<p>Використання замикання таким чином забезпечує ряд переваг, які зазвичай пов'язані з об'єктно-орієнтованим програмуванням, зокрема приховування даних та інкапсуляція.</p> + +<h2 id="Ланцюжок_замикань">Ланцюжок замикань</h2> + +<p>Кожне замикання має три оточення:</p> + +<ul> + <li>Local Scope (власне оточення)</li> + <li>Outer Functions Scope (зовнішнє функціональне оточення)</li> + <li>Global Scope (глобальне оточення)</li> +</ul> + +<p>Поширеною помилкою є не усвідомлення того, що у випадку, коли зовнішня функція сама є вкладеною функцією, доступ до оточення її зовнішньої функції включає вкладене оточення її як зовнішньої функції - ефективно створюючи ланцюг областей оточення. Для демонстрації розглянемо наступний приклад коду.</p> + +<pre class="notranslate">// global scope +var e = 10; +function sum(a){ + return function(b){ + return function(c){ + // outer functions scope + return function(d){ + // local scope + return a + b + c + d + e; + } + } + } +} + +console.log(sum(1)(2)(3)(4)); // log 20 + +// You can also write without anonymous functions: + +// global scope +var e = 10; +function sum(a){ + return function sum2(b){ + return function sum3(c){ + // outer functions scope + return function sum4(d){ + // local scope + return a + b + c + d + e; + } + } + } +} + +var s = sum(1); +var s1 = s(2); +var s2 = s1(3); +var s3 = s2(4); +console.log(s3) //log 20 +</pre> + +<p>У наведеному вище прикладі є низка вкладених функцій, всі з яких мають доступ до області оточення зовнішніх функцій. У цьому контексті можна сказати, що замикання мають доступ до всіх зовнішніх областей функцій.</p> + +<h2 id="Creating_closures_in_loops_A_common_mistake">Creating closures in loops: A common mistake</h2> + +<p>До введення ключового слова let у ECMAScript 2015, загальна проблема із замиканням виникала, коли вони створювалися всередині циклу. Розглянемо наступний приклад: </p> + +<pre class="brush: html notranslate"><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> +</pre> + +<pre class="brush: js notranslate">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(); +</pre> + +<p>{{JSFiddleEmbed("https://jsfiddle.net/v7gjv/", "", 200)}}</p> + +<p>Масив helpText визначає три корисні підказки, кожну пов'язану з ID поля Input. Цикл циклично перераховує ці визначення, підключаючи подію onfocus до кожного, що показує пов'язаний метод help.</p> + +<p>Якщо ви спробуєте цей код, ви побачите, що він не працює, як очікувалося. Незалежно від того, на якому полі ви зосереджені, відображатиметься повідомлення про ваш вік.</p> + +<p>Причиною цього є те, що функції, призначені для фокусування, - це замикання; вони складаються з визначення функції та захопленого оточення з області видимосты функцій setupHelp. Три замикання були створені циклом, але кожне ділиться тим самим єдиним середовищем, яке має змінну зі змінними значеннями (item.help).</p> + +<p>Коли виконуються зворотні виклики onfocus, доступ до item.help в цей момент викликає таку поведінку (дійсно, оскільки значення змінної доступно / обчислюється лише під час виконання), оскільки цикл до цього часу виконувався, а об'єкт змінної елемента (розділений усіма трьома замиканнями) був залишений, вказуючи на останній запис у списку довідкового тексту.</p> + +<p>Одне рішення в цьому випадку - використовувати більше замикань: зокрема, використовувати фабрику функцій, як описано раніше:</p> + +<pre class="brush: js notranslate">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(); +</pre> + +<p>{{JSFiddleEmbed("https://jsfiddle.net/v7gjv/1/", "", 300)}}</p> + +<p>Це працює як очікувалося. Замість того, щоб зворотні виклики мали спільний доступ до одного середовища, функція makeHelpCallback створює нове середовище для кожного з них, в якій довідка посилається на відповідний рядок із масиву helpText.</p> + +<p>Ще один спосіб написати вищезгадане - використання анонімних замикань:</p> + +<pre class="brush: js notranslate">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();</pre> + +<p>Якщо ви не хочете більше використовувати замикання, можете скористатися ключовим словом let з ES6:</p> + +<pre class="brush: js notranslate">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();</pre> + +<p>Цей приклад використовує let замість var, таким чином кожне замикання прив'язує блочну змінну,це означає, що додаткові замикання не потрібні.</p> + +<p>Іншою альтернативою може бути використання forEach () для повторення масиву helpText та приєднання слухача до кожного <p>, як показано:</p> + +<pre class="notranslate">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(); +</pre> + +<h2 id="Performance_considerations">Performance considerations</h2> + +<p>Нерозумно створювати функції в межах інших функцій, якщо замикання не потрібне для конкретного завдання, оскільки це негативно вплине на продуктивність сценарію як з точки зору швидкості обробки, так і споживання пам'яті.</p> + +<p>Наприклад, при створенні нового об'єкта / класу методи, як правило, повинні бути пов'язані з прототипом об'єкта, а не визначені в конструкторі об'єктів. Причина полягає в тому, що щоразу, коли конструктор викликається, методи отримують перепризначення (тобто для кожного створення об'єкта).</p> + +<p>Розглянемо наступний непрактичний, але показовий випадок:</p> + +<pre class="brush: js notranslate">function MyObject(name, message) { + this.name = name.toString(); + this.message = message.toString(); + this.getName = function() { + return this.name; + }; + + this.getMessage = function() { + return this.message; + }; +} +</pre> + +<p>Попередній код не використовує переваги замикань, а тому може виглядати:</p> + +<pre class="brush: js notranslate">function MyObject(name, message) { + this.name = name.toString(); + this.message = message.toString(); +} +MyObject.prototype = { + getName: function() { + return this.name; + }, + getMessage: function() { + return this.message; + } +}; +</pre> + +<p>Однак переробляти прототип не рекомендується, тому наступний приклад ще кращий, оскільки він додається до існуючого прототипу:</p> + +<pre class="brush: js notranslate">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; +}; +</pre> + +<p>Код вище також може бути записаний чистіше з тим же результатом:</p> + +<pre class="brush: js notranslate">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); +</pre> + +<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="/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model">Details of the Object Model</a> for more details. У трьох попередніх прикладах успадкований прототип може бути спільним для всіх об'єктів, і визначення методів не повинно виникати при кожному створенні об'єкта. Докладніше див. Деталі Об'єктної Моделі (<a href="/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model">Details of the Object Model</a>).</p> |