Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Вузькі місця в JS-коді». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Основні вузькі місця, це блокування головного потоку, надлишкові операції з DOM, витоки пам'яті, неефективні цикли та структури даних, зайві мережеві запити, погана оптимізація відмальовування, важкий бандл і відсутність профілювання.** JavaScript однопотоковий, тому будь-яка довга синхронна операція, великий цикл, `JSON.parse()` на мегабайтах чи важкий алгоритм заморожує інтерфейс: лікується розбиттям на чанки, Web Workers, debounce і throttle. DOM, це найдорожчі операції, тому зміни групують, кешують посилання на елементи й уникають читання `offsetHeight` чи `getComputedStyle` у циклі, бо це викликає reflow. Витоки дають неочищені таймери, «висячі» слухачі подій, замикання на великі об'єкти та глобальні змінні. На великих масивах шкодять вкладені цикли `O(n^2)`, `splice()` і `shift()`, а замість лінійного пошуку краще взяти `Set` або `Map`. Наостанок, будь-яку оптимізацію починають з вимірювання в Chrome DevTools, а не навмання. ```javascript // Пошук за O(n) у циклі перетворює задачу на O(n^2) const ids = new Set(activeIds); // O(1) на перевірку const active = users.filter((u) => ids.has(u.id)); ``` **Ключове:** головні вороги, це довгі синхронні обчислення в головному потоці, зайві reflow та витоки; знаходять їх профілюванням, а не здогадками.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Вузьке місце, це ділянка коду, яка обмежує продуктивність усього застосунку, і в JavaScript таких ділянок вісім типових: головний потік, DOM, пам'ять, цикли, мережа, відмальовування, бандл і алгоритми.** Дев'яте, найпоширеніше, це відсутність профілювання, коли оптимізують навмання. ## Теорія ### TL;DR - JavaScript однопотоковий: будь-яка важка синхронна операція блокує UI, рендеринг і обробку подій. - Операції з DOM найдорожчі; читання геометрії всередині циклу спричиняє reflow. - Витоки пам'яті дають таймери, слухачі подій, замикання та глобальні змінні. - На великих масивах вирішують алгоритм і структура даних: `Set` і `Map` замість лінійного пошуку. - Мережа і розмір бандла часто важать більше, ніж швидкість самого коду. - Анімації мають жити на GPU (`transform`, `opacity`, `requestAnimationFrame`). - Спочатку профілювання, потім оптимізація. ### Швидкий приклад ```javascript // Погано: читання геометрії в циклі змушує браузер перераховувати layout щоразу items.forEach((el) => { el.style.height = el.offsetHeight + 10 + 'px'; }); // Добре: спочатку читаємо все, потім пишемо все const heights = items.map((el) => el.offsetHeight); items.forEach((el, i) => { el.style.height = heights[i] + 10 + 'px'; }); ``` ### Блокування головного потоку JavaScript однопотоковий. Будь-яка важка операція блокує UI, рендеринг і обробку подій. **Приклади:** - великі цикли (`for`, `while`, `forEach`) без `setTimeout` чи `requestIdleCallback`; - парсинг або обробка великих JSON (`JSON.parse(hugeData)`); - обчислювально важкі алгоритми (сортування, рекурсії, шифрування); - синхронні запити (`XMLHttpRequest` без async); - великі зміни DOM в одному кадрі. **Як виправити:** - розбивати роботу на чанки (`setTimeout`, `requestAnimationFrame`); - використовувати **Web Workers** для фонових обчислень; - застосовувати **debounce** і **throttle** для подій. ### Надлишкові рендери, робота з DOM і відмальовування Операції з DOM найдорожчі з погляду продуктивності. **Проблеми:** - надто часті вставки та видалення елементів; - перерахунок стилів і layout при кожній зміні; - звернення до `offsetHeight`, `getComputedStyle`, `scrollTop` викликає **reflow**; - цикли, всередині яких змінюється DOM. **Як виправити:** - використовувати **DocumentFragment**, **virtual DOM**, batch-оновлення; - кешувати посилання на елементи; - мінімізувати `reflow`, змінюючи клас одразу для групи елементів; - у React: мемоізація, `PureComponent`, `React.memo`, `useMemo`, `useCallback`. #### Слабка оптимізація відмальовування Інтерфейс смикається, анімації лагають, FPS падає. **Причини:** - надто часта зміна `style` і `transform`; - важкі тіні, фільтри, `border-radius`; - невикористання GPU (CSS-анімації без `transform: translateZ(0)`); - перерахунок layout у кожному кадрі анімації. **Рішення:** - виносити анімації на GPU; - об'єднувати зміни DOM за один кадр; - використовувати `will-change`, `transform`, `opacity`; - `requestAnimationFrame` замість таймерів для анімацій. ### Надлишкове споживання пам'яті Витоки пам'яті призводять до того, що вкладка важчає, а інтерфейс зависає. **Причини:** - неочищені таймери (`setInterval`, `setTimeout`); - «висячі» слухачі подій, які не знімаються при видаленні елемента; - замикання, що утримують посилання на великі об'єкти; - глобальні змінні, які ніколи не очищаються; - кеші, які не очищаються. **Рішення:** - чистити таймери (`clearInterval`, `clearTimeout`); - знімати обробники (`removeEventListener`); - використовувати `WeakMap` і `WeakSet`; - аналізувати через Chrome DevTools, вкладка Memory. ### Неефективні цикли, колекції та алгоритми Особливо помітно на великих масивах. **Типові помилки:** - вкладені цикли `O(n^2)` без потреби; - використання `.map()`, `.filter()`, `.reduce()` на великих масивах без оптимізації; - створення нових масивів чи об'єктів на кожній ітерації; - часті виклики `Array.splice()` та `Array.shift()`, це дорогі операції. **Що робити:** - використовувати доречніші структури: `Set`, `Map`, `WeakMap`; - використовувати ітератори, генератори та `for...of` замість `forEach` на великих обсягах; - профілювати ділянки через `performance.now()`. #### Неоптимальні структури даних Приклад: сортування масиву на 100 000 елементів через `sort()` без компаратора або пошук через `filter()` замість `Set`. **Що робити:** - обирати структуру під задачу: `Set` для унікальності, `Map` для швидкого lookup; - уникати лінійного пошуку там, де можна використати хеш-пошук; - використовувати бінарний пошук і кешування обчислень. ### Мережа, бандл і профілювання Навіть швидкий JS-код не врятує, якщо мережа перевантажена. **Проблеми:** - повторні запити без кешу; - дублювальні виклики API на кожному рендері; - завантаження величезних бандлів JS і CSS; - немає стиснення gzip чи brotli; - немає lazy loading. **Як виправити:** - кешування (HTTP-кеш, IndexedDB, Service Worker, мемоізація); - об'єднання запитів (batching); - React Query, SWR, заголовок `Cache-Control`; - код-сплітинг і динамічний імпорт (`import()`). #### Важкий бандл Чим більше JavaScript, тим довше завантаження і парсинг. **Причини:** - підключення зайвих бібліотек; - дублювання залежностей; - невикористання tree-shaking; - inline JSON, великі іконки та зображення в коді. **Рішення:** - аналіз бандла (`webpack-bundle-analyzer`, `next build --analyze`); - tree-shaking, code splitting, динамічний `import()`; - використання CDN, HTTP/2, ESM-бандлів; - мініфікація (Terser, SWC). #### Відсутність профілювання та метрик Найчастіша помилка, це оптимізація навмання. **Рішення:** - Chrome DevTools (Performance, Memory, Coverage); - `console.time()`, `performance.mark()` і `performance.measure()`; - Lighthouse, Web Vitals; - Sentry Performance, New Relic, Datadog. Стисле резюме: | Категорія | Приклад проблеми | Як виправити | | --- | --- | --- | | Блокування потоку | Довгий цикл | Розбити, Worker | | DOM | Багато reflow | Batch update | | Пам'ять | Витоки | WeakMap, очищення | | Цикли | O(n^2) | Оптимізувати | | Мережа | Повторні запити | Кешувати | | Рендер | FPS < 60 | GPU-анімації | | Бандл | 2 МБ JS | Tree-shaking | | Алгоритми | Хибна структура | Підбір під задачу | ### Типові помилки - **Оптимізувати без вимірювання.** Без профілю в DevTools час іде на ділянки, що не впливають на результат. - **Вважати `async` рятунком від блокування.** `async/await` не переносить обчислення в інший потік: важкий цикл усередині `async`-функції так само морозить UI. Потрібен Web Worker. - **Чергувати читання і запис DOM.** Патерн «прочитав геометрію, записав стиль, знову прочитав» спричиняє layout thrashing. Спочатку всі читання, потім усі записи. - **Мемоізувати все підряд.** `useMemo` і `useCallback` самі коштують пам'яті та порівнянь; на дешевих обчисленнях вони лише додають накладні витрати. - **Забувати прибирати за собою.** Слухач події чи `setInterval`, створений при монтуванні й не знятий при розмонтуванні, це готовий витік. - **Плутати розмір бандла з часом парсингу.** Навіть закешований файл треба розпарсити й скомпілювати, тому обсяг коду важливий і на повторних візитах. - **Мікрооптимізувати цикли замість алгоритму.** Заміна `forEach` на `for` не врятує `O(n^2)`, а перехід на `Map` врятує.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.