Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке функції вищого порядку в JavaScript (hof)». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Функція вищого порядку (HOF)** - функція, що приймає іншу функцію як аргумент або повертає функцію як результат. Функція яку передаєш - це callback; HOF - це функція що її приймає або створює. ```javascript // Приймає функцію як аргумент [1, 2, 3].map(x => x * 2); // [2, 4, 6] // Повертає функцію function multiplier(factor) { return (n) => n * factor; } const double = multiplier(2); // double(5) => 10 ``` **Ключове:** `map`, `filter`, `reduce` і `forEach` - це вбудовані HOF у JavaScript.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Функція вищого порядку (HOF)** - функція, що приймає іншу функцію як аргумент, повертає функцію як результат, або робить і те, і інше. ## Теорія ### TL;DR - Функції в JavaScript - це значення, їх можна передавати як числа чи рядки - HOF = приймає функцію як вхід, або повертає функцію, або обидва варіанти - Вбудовані приклади, які ти вже знаєш: `map`, `filter`, `reduce`, `forEach` - Використовуй HOF коли одна і та сама операція повторюється з різною поведінкою - передай поведінку, замість того щоб дублювати код - Правило вибору: якщо ловиш себе на написанні одного й того самого циклу з трохи різною операцією всередині - HOF тут доречний ### Швидкий приклад ```javascript // map() - це HOF: приймає функцію і застосовує до кожного елементу const numbers = [1, 2, 3]; const doubled = numbers.map(num => num * 2); // [2, 4, 6] // multiplier() - це HOF: повертає нову налаштовану функцію function multiplier(factor) { return function(number) { return number * factor; }; } const double = multiplier(2); console.log(double(5)); // 10 ``` `map` отримує `num => num * 2` як аргумент. `multiplier` будує і повертає нову функцію, налаштовану через `factor`. Обидві є HOF. ### Навіщо потрібні HOF Без HOF ти дублюєш логіку. З ними пишеш один раз і передаєш те, що змінюється. В цьому і є сенс. Уяви трансформацію масиву: без `map` щоразу пишеш `for`-цикл, оголошуєш новий масив, вручну пушиш результати. З `map` ти просто кажеш «ось що робити з кожним елементом» - і метод бере на себе все інше. Операція залишається незмінною; поведінка, яку ти передаєш, варіюється. Я бачив, як розробники копіювали той самий цикл п'ять разів з трохи різними операціями всередині. Один виклик `map` вирішував би кожен випадок в один рядок. Це стало можливим тому що в JavaScript функції є об'єктами першого класу (first-class citizens). Їх можна присвоювати змінним, передавати як аргументи, повертати з інших функцій. HOF просто використовують це свідомо. ### Коли використовувати - **Трансформація колекцій:** `map`, `filter`, `reduce` для будь-яких списків з API або UI - **Обробка подій:** `addEventListener` приймає callback - твоя функція запускається коли подія спрацьовує - **Асинхронні ланцюжки:** `Promise.then()` приймає функцію що виконується при resolve - **Middleware в Express:** кожен middleware - це HOF що обгортає наступний обробник додатковою логікою - **Спеціалізовані функції:** `multiplier(2)` дає `double`, `multiplier(3)` дає `triple`, з одного визначення Для простої одноразової логіки де звичайний цикл читається краще - HOF не потрібен. ### Як це працює всередині V8 обробляє функції як об'єкти. Коли передаєш функцію в HOF, V8 копіює посилання на цей об'єкт, а не сам код. Коли HOF повертає функцію, повернута функція захоплює зовнішню область видимості через замикання (closure) - V8 виділяє closure cell у купі (heap) з посиланнями на лексичні змінні, тримаючи їх живими поки існує внутрішня функція. Вбудовані методи як `Array.prototype.map` реалізовані на C++ і викликають твій JS-callback на кожній ітерації. ### Типові помилки **Забуваєш викликати повернуту функцію:** ```javascript const doubler = multiplier(2); // Повертає функцію console.log(multiplier(2)); // Виведе [Function] - не число! console.log(doubler(5)); // 10 - ось що потрібно ``` `multiplier(2)` повертає функцію. Її ще потрібно викликати з реальним значенням. **Мутуєш функцію що передана як аргумент:** ```javascript // Погано: додає властивість до оригінального об'єкта функції function badHOF(fn) { fn.customProp = 'mutated'; // Змінює оригінал у зовнішньому коді return fn; } // Добре: обгортаєш, не торкаючись оригіналу function goodHOF(fn) { return (...args) => fn(...args); } ``` Функції - це об'єкти. Додавши до них властивість, змінюєш оригінал поза межами HOF. **Втрата контексту `this` в callback:** ```javascript const obj = { value: 42 }; [1, 2].forEach(function() { console.log(this.value); // undefined - контекст загублено }); [1, 2].forEach(function() { console.log(this.value); // 42 - явне прив'язування повертає контекст }.bind(obj)); ``` **Нестабільні HOF у залежностях useEffect в React:** ```javascript // Погано: нова функція на кожен рендер запускає ефект щоразу useEffect(() => { fetchData(increment); }, [increment]); // Добре: useCallback стабілізує посилання const increment = useCallback(() => setCount(c => c + 1), []); ``` React порівнює залежності за посиланням. Нова функція на кожен рендер виглядає як змінена залежність і повторно запускає ефект. ### Де зустрічається в реальних проектах - `Array.map/filter/reduce`: кожен список у React, ланцюжки в Lodash - Express middleware: `app.use(authMiddleware(handler))` - кожен middleware обгортає наступний - `Promise.then()`: будує ланцюжок асинхронних операцій через передачу обробників - React `useCallback`: мемоїзує функцію для стабільного посилання між рендерами - Lodash `_.curry` / `_.partial`: будують спеціалізовані функції із загальних ### Питання на співбесіді **Q:** Яка різниця між HOF і callback? **A:** Callback - це будь-яка функція передана як аргумент. HOF - це функція що її приймає. `map` є HOF; `num => num * 2` є callback. **Q:** Реалізуй `map` самостійно. **A:** ```javascript function myMap(arr, fn) { const result = []; for (let i = 0; i < arr.length; i++) { result.push(fn(arr[i], i, arr)); } return result; } ``` **Q:** Як замикання (closure) пов'язані з HOF? **A:** Більшість HOF що повертають функції, покладаються на замикання. Повернута функція зберігає доступ до змінних зовнішньої області видимості - наприклад `factor` всередині `multiplier` - навіть після завершення зовнішнього виклику. **Q:** Чи є у HOF вплив на продуктивність? **A:** У важких циклах - так. Повернуті функції створюють closure-алокації в купі (heap). Для масивів від 1M+ елементів ручний цикл може бути в 2-3 рази швидшим у V8. У більшості прикладного коду ця різниця не має значення. **Q:** Чому не мемоїзований HOF у deps масиві `useEffect` в React 18 спричиняє нескінченний цикл? **A:** Кожен рендер створює нове посилання на функцію. React порівнює залежності за посиланням, тому нова функція виглядає як змінена залежність. Ефект повторно запускається, тригерить рендер, який створює ще одне нове посилання. `useCallback` з правильним масивом залежностей розриває цей цикл. ## Приклади ### Базовий: логер для будь-якої функції ```javascript function withLogging(fn) { return (...args) => { console.log('Викликається:', fn.name, 'з аргументами:', args); const result = fn(...args); console.log('Результат:', result); return result; }; } function add(a, b) { return a + b; } const loggedAdd = withLogging(add); loggedAdd(3, 4); // Викликається: add з аргументами: [3, 4] // Результат: 7 ``` `withLogging` повертає нову функцію що обгортає будь-яку передану функцію. Оригінальний `add` залишається незмінним. Це патерн декоратор: поведінка розширюється без змін у вихідній функції. ### Середній рівень: валідація маршруту в Express ```javascript const express = require('express'); const app = express(); app.use(express.json()); // HOF: приймає обробник, повертає новий обробник з вбудованою валідацією function validateUser(fn) { return (req, res, next) => { if (!req.body.username) { return res.status(400).send('Missing username'); } return fn(req, res, next); }; } const userHandler = (req, res) => res.json({ user: req.body.username }); app.post('/user', validateUser(userHandler)); // POST /user { username: 'alice' } => 200 { user: 'alice' } // POST /user {} => 400 "Missing username" ``` `validateUser` обгортає будь-який обробник маршруту перевіркою. Сам обробник нічого не знає про валідацію. Заміни `validateUser` на `requireAdmin` або `checkRateLimit` - патерн залишається ідентичним.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.