Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Методи масиву проти циклів». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Методи масивів, це абстракції над циклами, і за зручність вони платять накладними витратами: виклик callback на кожній ітерації, передача трьох аргументів `(element, index, array)`, внутрішні перевірки та, для `map` чи `filter`, виділення нового масиву.** Звичайний `for` не робить нічого з цього: тіло циклу рушій вбудовує (inline) і JIT-компілятор оптимізує його легше, бо код передбачуваний і без обгорток. На малих даних різниця вимірюється частками мілісекунди й нею можна знехтувати, а на мільйонах елементів або в «гарячому» коді (рендеринг, парсинг, canvas, агрегації) `for` буває у 2-5 разів швидшим. Додаткові аллокації також навантажують garbage collector. ```javascript // Slower: callback call per item plus a freshly allocated array const doubled1 = arr.map(x => x * 2); // Faster: no callback, result array preallocated const doubled2 = new Array(arr.length); for (let i = 0; i < arr.length; i++) doubled2[i] = arr[i] * 2; ``` **Ключове:** методи масивів повільніші через callback, аллокації та внутрішні перевірки, але обирати `for` варто лише там, де профайлер показав вузьке місце.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Методи масивів (`map`, `filter`, `forEach`, `reduce` тощо), це абстракції над циклами, реалізовані всередині рушія JavaScript.** Вони зручніші, коротші й читабельніші, але мають додаткові накладні витрати, через які можуть бути повільнішими за прості цикли `for`, `for...of` і `while`, особливо на великих масивах. ## Теорія ### TL;DR - Метод масиву на кожній ітерації викликає callback і передає йому три аргументи, а `for` просто виконує тіло циклу. - `map`, `filter`, `slice`, `concat` додатково виділяють новий масив, тобто нову пам'ять і роботу для GC. - «Голий» `for` передбачуваний, тому JIT-компілятор (V8, SpiderMonkey) оптимізує його легше. - На мільйоні елементів `for` буває у 2-5 разів швидшим; на 10 тисячах різниця це частки мілісекунди. - Для звичайного продуктового коду читабельність важливіша за таку різницю, тому `.map()` у JSX цілком доречний. - Оптимізувати варто лише «гарячі» ділянки: рендеринг, парсинг, обробку бінарних даних, canvas, важкі агрегації. ### Швидкий приклад ```javascript const arr = Array.from({ length: 1_000_000 }, (_, i) => i); // map console.time('map'); const doubled1 = arr.map(x => x * 2); console.timeEnd('map'); // for console.time('for'); const doubled2 = new Array(arr.length); for (let i = 0; i < arr.length; i++) { doubled2[i] = arr[i] * 2; } console.timeEnd('for'); ``` На більшості рушіїв результат буде приблизно такий: ```text map: 30-60 ms for: 10-20 ms ``` ### Що відбувається під капотом Коли ви пишете: ```javascript arr.map(x => x * 2); ``` рушій робить приблизно таке: 1. Перевіряє, що `arr` справді масив. 2. Створює новий масив для результатів. 3. На кожній ітерації: - викликає callback-функцію; - передає їй три аргументи `(element, index, array)`; - зберігає результат у новий масив. 4. Повертає підсумковий масив. Це купа кроків, які додають накладні витрати порівняно зі звичайним `for`-циклом, де ви просто виконуєте інструкції без обгорток і перевірок. ### Чому саме повільніше | Причина | Що відбувається | | --- | --- | | **Callback-функція** | На кожній ітерації викликається функція, тобто зайві виклики на стеку | | **Створення нового масиву** | `map`, `filter`, `slice`, `concat` створюють копії, це додаткова пам'ять | | **Перевірки та контекст** | Метод проходить валідації: довжина, holes (дірки в масиві), прототип, тип | | **Неоптимізовані замикання** | Якщо callback захоплює зовнішні змінні, накладних витрат ще більше | | **Функціональні принципи** | Ці методи «чисті» й не мутують дані, тому потрібно більше аллокацій | | **Цикли простіше оптимізувати JIT-компілятором** | Рушій (V8, SpiderMonkey) швидше оптимізує «голий» `for` | ### Чому це помітно саме в «гарячих» ділянках > Якщо цикл виконується мільйони разів (рендеринг, сортування, агрегації даних, парсинг), накладні витрати на callback починають дорого коштувати. У таких місцях: - кожен виклик callback, це новий запис у call stack; - зайві аллокації, це більше роботи для GC (garbage collector); - додаткові аргументи (`index`, `array`) означають більше об'єктів у пам'яті. ### Коли це не має значення Для більшості бізнес-задач (списки, фільтри, маппінг до 10 тисяч елементів): - різниця між `for` і `map`, це частки мілісекунди; - читабельність і чистота коду важливіші. Саме тому в React/Vue-коді використовують `.map()` для JSX: це декларативно й зрозуміло. ```javascript {items.map(item => <Card key={item.id} {...item} />)} ``` Але якщо у вас масив на мільйони елементів або цикл у «гарячому місці» (рендеринг, обробка бінарних даних, canvas, парсер), краще взяти: ```javascript for (let i = 0; i < n; i++) { /* ... */ } ``` ### Що реально швидше, за порядком | Цикл | Швидкість | Особливості | | --- | --- | --- | | `for (let i = 0; i < n; i++)` | Найшвидший | Без перевірок, inline, передбачуваний | | `for...of` | Швидкий, але з ітератором | Трохи накладних витрат | | `while` | Приблизно як `for` | Залежить від рушія | | `forEach()` | Повільніший (callback) | Не повертає новий масив | | `map()` | Повільніший | Створює новий масив | | `filter()`, `reduce()` | Ще повільніші | Аллокації, додаткові операції | ### Як пришвидшити методи масивів | Метод | Оптимізація | | --- | --- | | `.map()` | Використовувати чисту стрілкову функцію без зовнішніх замикань | | `.filter()` | Не будувати ланцюжки `.map().filter().reduce()`, а об'єднати їх в один прохід | | `.reduce()` | Для складних операцій винести накопичення у `for` | | `.forEach()` | Замінити на `for` або `for...of` у «гарячому» коді | | `.concat()` / spread (`[...a, ...b]`) | На великих масивах замінити на `push.apply()` або цикл | Коротке резюме причин: | Причина | Чому повільніше | | --- | --- | | Callback-функції | Створюють додаткові виклики й контекст | | Новий масив | Виділяється нова пам'ять | | Валідації та ітерація | Вбудовані перевірки та протоколи | | Навантаження на GC | Створюються тимчасові об'єкти | | Цикли простіші | Легше оптимізувати JIT-компілятору | Висновок: для критичних за продуктивністю задач беріть `for`, для зрозумілого декларативного коду, `map`, `filter`, `reduce`. ### Типові помилки - **Переписувати весь код на `for` «заради швидкості».** Без вимірювання це втрата читабельності без виграшу: у 99% місць різниця непомітна. - **Ланцюжки `.map().filter().reduce()` на великих масивах.** Кожна ланка проходить масив повністю й виділяє проміжний масив. Один цикл або один `reduce` робить те саме за один прохід. - **Плутати `forEach` і `for` у питанні виходу з циклу.** З `forEach` не можна зробити `break` чи `return` назовні; спроба обійти це через `throw` дорожча за звичайний цикл. - **Думати, що `for...of` такий самий швидкий, як класичний `for`.** Він працює через протокол ітератора й на кожному кроці створює об'єкт `{ value, done }`. - **Забувати попередньо виділити масив.** `new Array(n)` з присвоєнням за індексом дешевше, ніж `push()` у цикл на мільйони елементів. - **Мікрооптимізувати замість того, щоб змінити алгоритм.** Заміна O(n²) на O(n) дає більше, ніж будь-яка заміна `map` на `for`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.