diff --git a/files/uk/web/javascript/guide/functions/index.md b/files/uk/web/javascript/guide/functions/index.md index a8accb814..1884a5f81 100644 --- a/files/uk/web/javascript/guide/functions/index.md +++ b/files/uk/web/javascript/guide/functions/index.md @@ -66,6 +66,19 @@ myFunc(arr); console.log(arr[0]); // 30 ``` +Оголошення та вирази функцій можна вкладати одне в одне, що утворює _ланцюжок областей видимості_. Наприклад: + +```js +function addSquares(a, b) { + function square(x) { + return x * x; + } + return square(a) + square(b); +} +``` + +Читайте більше в [функційних областях видимості та замиканнях](#funktsiini-oblasti-vydymosti-ta-zamykannia). + ### Вирази функцій Коли оголошення функції з боку синтаксису є інструкцією, то функції також можна створити за допомогою [виразів функцій](/uk/docs/Web/JavaScript/Reference/Operators/function). @@ -90,7 +103,7 @@ const factorial = function fac(n) { console.log(factorial(3)); // 6 ``` -Вирази функцій зручні при передачі функції як аргументу в іншу функцію. Наступний приклад демонструє функцію `map`, котра повинна отримати функцію як перший аргумент і масив як другий аргумент: +Вирази функцій зручні при передачі функції як аргументу в іншу функцію. Наступний приклад визначає функцію `map`, котра повинна отримати функцію як перший аргумент і масив як другий аргумент. Далі вона викликається з передачею функції, визначеної функційним виразом: ```js function map(f, a) { @@ -100,25 +113,12 @@ function map(f, a) { } return result; } -``` - -В наступному коді `map` отримує функцію, визначену виразом функції, та виконує її для кожного елемента масиву, отриманого як другий аргумент: - -```js -function map(f, a) { - const result = new Array(a.length); - for (let i = 0; i < a.length; i++) { - result[i] = f(a[i]); - } - return result; -} - -const cube = function (x) { - return x * x * x; -}; const numbers = [0, 1, 2, 5, 10]; -console.log(map(cube, numbers)); // [0, 1, 8, 125, 1000] +const cubedNumbers = map(function (x) { + return x * x * x; +}, numbers); +console.log(cubedNumbers); // [0, 1, 8, 125, 1000] ``` Функція повертає: `[0, 1, 8, 125, 1000]`. @@ -210,51 +210,9 @@ const square = function (n) { }; ``` -## Функційна область видимості - -До змінних, визначених всередині функції, не можна звертатися нізвідки поза функцією, тому що змінна визначена лише в області видимості цієї функції. Проте функція може звертатися до всіх змінних та функцій, визначених всередині області видимості, в котрій визначена сама ця функція. - -Інакше кажучи, функція, визначена в глобальній області видимості, може звертатися до всіх змінних, визначених в глобальній області. Функція, визначена всередині іншої функції, на додачу може звертатися до всіх змінних, визначених у такій батьківській функції, а також будь-яких інших змінних, до котрих може звертатися батьківська функція. - -```js -// Наступні змінні визначені в глобальній області видимості -const num1 = 20; -const num2 = 3; -const name = "Шевченко"; - -// Ця функція визначена в глобальній області видимості -function multiply() { - return num1 * num2; -} - -console.log(multiply()); // 60 - -// Приклад укладеної функції -function getScore() { - const num1 = 2; - const num2 = 3; - - function add() { - return `${name} має рахунок ${num1 + num2}`; - } - - return add(); -} - -console.log(getScore()); // "Шевченко має рахунок 5" -``` - -## Область видимості та стек функції - ### Рекурсія -Функція може звертатися до себе й викликати себе. Є три способи, що дають функції змогу звернутися до самої себе: - -1. Ім'я функції -2. [`arguments.callee`](/uk/docs/Web/JavaScript/Reference/Functions/arguments/callee) -3. Доступна в області видимості змінна, що посилається на функцію - -Наприклад, розгляньмо наступне визначення функції: +Функція може звертатися до себе й викликати себе. На неї можна послатися за назвою функційного виразу або оголошення, або через будь-яку доступну в області видимості змінну, що посилається на об'єкт-функцію. Наприклад, розгляньмо наступне визначення функції: ```js const foo = function bar() { @@ -262,11 +220,7 @@ const foo = function bar() { }; ``` -Всередині тіла функції наступні записи – рівносильні: - -1. `bar()` -2. `arguments.callee()` -3. `foo()` +Всередині тіла функції можна посилатись на неї саму за допомогою як `bar`, так і `foo`, і викликати її за допомогою `bar()` і `foo()`. Функція, що викликає сама себе, зветься _рекурсивною функцією_. У певному відношенні рекурсія аналогічна циклові. І рекурсія, і цикл виконують один і той же код декілька разів, обидва потребують умови (аби уникнути нескінченного виконання циклу, або в цьому випадку – радше нескінченної рекурсії). @@ -338,114 +292,71 @@ foo(3); // кінець: 3 ``` -### Вкладені функції та замикання - -Функцію можна вкласти в іншу функцію. Вкладена (внутрішня) функція є приватною для зовнішньої функції. +### Негайно закличні вирази-функції (IIFE) -Також це утворює _замикання_. Замикання – це вираз (найчастіше – функція), що може мати вільні змінні вкупі з середовищем, котре зв'язує ці змінні (це "замикає" вираз). - -Оскільки вкладена функція є замиканням, це означає, що вкладена функція може "успадкувати" аргументи та змінні зовнішньої функції. Інакше кажучи, внутрішня функція містить область видимості зовнішньої функції. - -Підсумовуючи: - -- До внутрішньої функції можуть звертатися лише інструкції зовнішньої функції. -- Внутрішня функція утворює замикання: внутрішня функція може використовувати аргументи та змінні зовнішньої функції, а зовнішня функція – не може використовувати аргументи та змінні внутрішньої. - -Наступний приклад демонструє вкладені функції: +[Негайно закличний вираз-функція (IIFE)](/uk/docs/Glossary/IIFE) – це патерн кодування, що безпосередньо викликає функцію, визначену як вираз. Він має такий вигляд: ```js -function addSquares(a, b) { - function square(x) { - return x * x; - } - return square(a) + square(b); -} -console.log(addSquares(2, 3)); // 13 -console.log(addSquares(3, 4)); // 25 -console.log(addSquares(4, 5)); // 41 -``` - -Оскільки внутрішня функція утворює замикання, можна викликати зовнішню функцію й задати аргументи як для зовнішньої, так і для внутрішньої: - -```js -function outside(x) { - function inside(y) { - return x + y; - } - return inside; -} +(function () { + // Якісь дії +})(); -const fnInside = outside(3); // Уявляйте це так: дай мені функцію, котра додає 3 до того, що їй передадуть -console.log(fnInside(5)); // 8 -console.log(outside(3)(5)); // 8 +const value = (function () { + // Якісь дії + return someValue; +})(); ``` -### Збереження змінних +Замість збереження функції в змінній, ця функція негайно закликається. Це майже еквівалентно простому написанню тіла функції, але має кілька унікальних переваг: -Зверніть увагу, як зберігається `x`, коли повертається `inside`. Замикання мусить зберігати аргументи й змінні в усіх областях видимості, на котрі посилається. Оскільки кожний виклик передає потенційно різні аргументи, нове замикання створюється при кожному виклику `outside`. Пам'ять може бути звільнена лише тоді, коли повернена `inside` більше не доступна. +- Це створює додаткову [область видимості](#funktsiini-oblasti-vydymosti-ta-zamykannia) змінних, що допомагає обмежити змінні тим місцем, де вони корисні. +- Це утворює _вираз_ замість простої послідовності _інструкцій_. Це дає змогу писати складну логіку обчислень при ініціалізації змінних. -В цьому немає відмінності щодо збереження посилань в інших об'єктах, але в цьому випадку така логіка є менш очевидною, бо посилання не встановлюються безпосередньо, і їх не можна дослідити. +Більше про це читайте на сторінці глосарія [IIFE](/uk/docs/Glossary/IIFE). -### Багаторівнева вкладеність +## Функційні області видимості та замикання -Функції можуть бути вкладені на багатьох рівнях. Наприклад: - -- Одна функція (`A`) містить другу функцію (`B`), котра своєю чергою містить третю функцію (`C`). -- І функція `B`, і функція `C` тут утворюють замикання. Тож `B` може звертатися до `A`, а `C` – до `B`. -- Крім цього, оскільки `C` може звертатися до `B`, котра може звертатися до `A`, `C` також може звертатися до `A`. - -Таким чином, замикання можуть вміщати декілька областей видимості; вони рекурсивно вміщають області видимості функцій, котрі вміщають їх. - -Наприклад: +Функції утворюють [область видимості](/uk/docs/Glossary/Scope) для змінних – це означає, що до змінних, визначених всередині функції, не можна звернутися звідусіль поза функцією. Область видимості функції успадковує від усіх областей видимості вищих рівнів. Наприклад, функція, визначена в глобальній області видимості, може звертатися до всіх змінних, визначених в глобальній області видимості. Функція, визначена всередині іншої функції, також може звертатися до всіх змінних, визначених в її батьківській функції, і до будь-яких інших змінних, до яких має доступ батьківська функція. З іншого боку, батьківська функція (і будь-яка інша батьківська область видимості) _не_ має доступу до змінних і функцій, визначених всередині внутрішньої функції. Це забезпечує певного роду інкапсуляцію для змінних внутрішньої функції. ```js -function A(x) { - function B(y) { - function C(z) { - console.log(x + y + z); - } - C(3); - } - B(2); -} -A(1); // Виводить 6 (тобто 1 + 2 + 3) -``` - -В цьому прикладі `C` звертається до `y` з `B` та `x` з `A`. - -Це можливо, тому що: - -1. `B` утворює замикання, що включає `A` (тобто `B` може звертатися до аргументів та змінних `A`). -2. `C` утворює замикання, що включає `B`. -3. Оскільки замикання `C` включає `B`, а замикання `B` включає `A`, то замикання `C` також включає `A`. Це означає, що `C` може звертатися до аргументів і змінних _як_ `B`, _так і_ `A`. Інакше кажучи, `C` _утворює ланцюжок_ з областей видимості `B` й `A`, _в такому порядку_. +// Наступні змінні визначені в глобальній області видимості +const num1 = 20; +const num2 = 3; +const name = "Шамах"; -Проте навпаки це не працює. `A` не може звертатися до `C`, бо `A` не може звертатися до жодних аргументів чи змінних `B`, змінною якої є `C`. Таким чином, `C` залишається приватною лише для `B`. +// Ця функція визначена в глобальній області видимості +function multiply() { + return num1 * num2; +} -### Конфлікти імен +console.log(multiply()); // 60 -Коли два аргументи чи змінні в областях видимості замикання мають однакове ім'я, трапляється _конфлікт імен_. Області видимості глибшої вкладеності отримують пріоритет. Отже, найглибша область отримує найвищий пріоритет, а крайня зовнішня – найнижчий. Так працює ланцюжок областей видимості. Першою в ланцюжку стоїть найглибша область, а останньою – крайня зовнішня. Для прикладу: +// Приклад вкладеної функції +function getScore() { + const num1 = 2; + const num2 = 3; -```js -function outside() { - const x = 5; - function inside(x) { - return x * 2; + function add() { + return `${name} отримав балів: ${num1 + num2}`; } - return inside; + return add(); } -console.log(outside()(10)); // 20 (а не 10) +console.log(getScore()); // "Шамах отримав балів: 5" ``` -Конфлікт імен трапляється в інструкції `return x * 2`, між параметром `inside` – `x`, і змінною `outside` – `x`. Ланцюжок тут – `inside` => `outside` => глобальний об'єкт. Таким чином, `x` з `inside` отримує пріоритет над `x` з `outside`, і повертається `20` (`x` з `inside`), а не `10` (`x` з `outside`). +### Замикання -## Замикання +Також тіло функції звуть _замиканням_. Замикання – це будь-який фрагмент вихідного коду (найчастіше – функція), що посилається на певні змінні, і замикання "запам'ятовує" ці змінні навіть тоді, коли з області видимості, в якій ці змінні були оголошені, вже стався вихід. -Замикання – одна з найпотужніших можливостей JavaScript. JavaScript дає змогу вкладати функції одна в одну й надавати внутрішній функції повний доступ до усіх змінних та функцій, визначених всередині зовнішньої функції (та всіх інших змінних та функцій, до котрих зовнішня функція має доступ). +Замикання зазвичай ілюструють за допомогою [вкладених функцій](#vkladeni-funktsii), аби показати, що вони запам'ятовують змінні поза областю видимості батьківської області видимості; але насправді вкладені функції не є обов'язковими. Технічно кажучи, всі функції в JavaScript утворюють замикання – деякі просто нічого не захоплюють, і замикання навіть не обов'язково мають бути функціями. Ключові складові для _корисного_ замикання такі: -Проте зовнішня функція _не_ має доступу до змінних та функцій, визначених всередині внутрішньої функції. Це породжує певного роду інкапсуляцію змінних внутрішньої функції. +- Батьківська область видимості, що визначає деякі змінні чи функції. Вона повинна мати чіткий термін життя, тобто повинна в певний момент завершити своє виконання. Цю вимогу задовольняють всі області видимості, що не є глобальними; серед таких областей: блоки, функції, модулі тощо. +- Вкладена область видимості, що посилається на деякі змінні чи функції з батьківської області видимості. +- Вкладена область видимості, що переживає термін життя батьківської області. Наприклад, вона зберігається в змінній, що визначена поза батьківською областю видимості, або ж повертається з батьківської області видимості (якщо батьківська область видимості – це функція). +- Потім, коли функція викликається поза батьківською областю видимості, ще можна звертатися до змінних чи функцій, що були визначені в батьківській області видимості, навіть якщо батьківська область видимості завершила виконання. -Крім того, оскільки внутрішня функція має доступ до області видимості зовнішньої функції, змінні та функції, визначені в зовнішній функції, житимуть довше, ніж триватиме виконання цієї зовнішньої функції, якщо внутрішня функція зможе пережити зовнішню функцію. Замикання створюється, коли внутрішня функція якось стала доступною будь-якій області видимості поза зовнішньою функцією. +Далі – типовий приклад замикання: ```js const pet = function (name) { @@ -518,22 +429,55 @@ const getCode = (function () { console.log(getCode()); // "0]Eal(eh&2" ``` -> [!NOTE] -> Існує декілька підводних каменів, які слід мати на увазі під час застосування замикань! -> -> Якщо замкнена функція визначає змінну з таким само ім'ям, як у якоїсь змінної зовнішньої області, то не існує способу знову звернутися до змінної зовнішньої області видимості. (Змінна внутрішньої області "заміщує" зовнішню доти, доки програма не покине внутрішньої області видимості. Можна розуміти це явище як [конфлікт імен](#konflikty-imen).) -> -> ```js example-bad -> // Зовнішня функція визначає змінну на ім'я "name". -> const createPet = function (name) { -> return { -> // Замкнена функція також визначає змінну на ім'я "name". -> setName(name) { -> name = name; // Як звернутися до "name", визначеної зовнішньою функцією? -> }, -> }; -> }; -> ``` +У коді вище вживається патерн [IIFE](#nehaino-zaklychnyi-vyraz-funktsiia-iife). У межах цієї області видимості IIFE існують два значення: змінна `apiCode` і безіменна функція, що повертається й присвоюється змінній `getCode`. `apiCode` перебуває в області видимості поверненої безіменної функції, але не в області видимості жодної іншої частини програми, тож ніяк не можна зчитати значення `apiCode`, окрім як через функцію `getCode`. + +### Функції з кількома рівнями вкладеності + +Функції можуть мати кілька рівнів вкладеності. Наприклад: + +- Функція (`A`) містить функцію (`B`), яка сама містить функцію (`C`). +- І функція `B`, і функція `C` утворюють замикання. Таким чином, `B` може звертатися до `A`, а `C` може звертатися до `B`. +- Крім цього, оскільки `C` може звертатися до `B`, яка може звертатися до `A`, то `C` може звертатися до `A`. + +Таким чином, замикання можуть містити кілька областей видимості; вони рекурсивно вміщають область видимості функцій, що їх містять. Це називається _ланцюгом областей видимості_. Розгляньмо наступний приклад: + +```js +function A(x) { + function B(y) { + function C(z) { + console.log(x + y + z); + } + C(3); + } + B(2); +} +A(1); // Виводить 6 (тобто 1 + 2 + 3) +``` + +У цьому прикладі `C` звертається до `y` з `B` та `x` з `A`. Це можливо, оскільки: + +1. `B` утворює замикання, що включає `A` (тобто `B` може звертатися до аргументів та змінних `A`). +2. `C` утворює замикання, що включає `B`. +3. Оскільки замикання `C` включає `B`, а замикання `B` включає `A`, то замикання `C` також включає `A`. Це означає, що `C` може звертатися до _як до_ аргументів і змінних `B`, _так і до_ аргументів і змінних `A`. Інакше кажучи, `C` _ланцюгує_ області видимості `B` і `A`, _в такому порядку_. + +Проте зворотне твердження є хибним. `A` не може звертатися до `C`, оскільки `A` не може звертатися до жодного аргументу чи змінної `B`, змінною якої є `C`. Таким чином, `C` залишається приватною лише для `B`. + +### Конфлікти імен + +Коли два аргументи або змінні в областях видимості замикання мають однакові імена, виникає _конфлікт імен_. Більш вкладені області видимості мають пріоритет. Таким чином, найглибше вкладена область видимості має найвищий пріоритет, а найменш вкладена – найнижчий. Це називається _ланцюгом областей видимості_. Першою в ланцюгу є найглибше вкладена область видимості, а останньою – найменш вкладена. Розгляньмо наступне: + +```js +function outside() { + const x = 5; + function inside(x) { + return x * 2; + } + return inside; +} +console.log(outside()(10)); // 20 (а не 10) +``` + +Конфлікт імен трапляється на інструкції `return x * 2`, між параметром `x` функції `inside` і змінною `x` функції `outside`. Ланцюг областей видимості тут такий: `inside` => `outside` => глобальний об'єкт. Тому `x` функції `inside` має пріоритет над `x` функції `outside`, і повертається `20` (змінна `x` функції `inside`), а не `10` (змінна `x` функції `outside`). ## Використання об'єкта arguments