Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Throttle у JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`throttle`, це обгортка, яка гарантує, що функція виконається не частіше ніж раз на задану кількість мілісекунд, хоч би скільки разів спрацювала подія.** Перший виклик проходить одразу, час його виконання запам'ятовується, і всі наступні виклики ігноруються, доки не мине `delay`. Це саме те, що потрібно для подій, які летять сотнями за секунду: `mousemove`, `scroll`, `resize`. На відміну від debounce, throttle не чекає кінця серії подій, а рівномірно пропускає по одному виклику через кожен інтервал, тому прогрес-бар чи позиція курсора оновлюються під час дії, а не після неї. Готова версія з Lodash додатково вміє керувати викликом на початку і в кінці інтервалу через опції `leading` і `trailing`. ```javascript function throttle(fn, delay) { let lastCall = 0; return function (...args) { const now = Date.now(); if (now - lastCall >= delay) { lastCall = now; fn.apply(this, args); } }; } ``` **Ключове:** throttle означає «не частіше ніж раз на N мс» і дає регулярні оновлення під час події, тоді як debounce означає «не раніше ніж через N мс після останньої події» і дає один виклик у кінці.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`throttle`, це техніка, яка обмежує частоту викликів функції: навіть якщо подія відбувається дуже часто, функція виконується не частіше ніж раз на заданий інтервал часу.** Простими словами, правило звучить так: «викликай функцію не частіше ніж раз на N мілісекунд». ## Теорія ### TL;DR - Правило throttle: «не частіше ніж раз на N мс», незалежно від кількості подій. - Реалізація тримає час останнього виклику (`lastCall`) і пропускає новий, лише коли минув інтервал. - Перший виклик проходить одразу, наступні ігноруються до кінця вікна затримки. - Сценарії: `scroll`, `mousemove`, `resize`, нескінченний скрол, оновлення прогрес-бара. - Throttle, це «не частіше ніж»; debounce, це «не раніше ніж». Перший оновлює під час дії, другий, після неї. - Версія з Lodash додає опції `leading` і `trailing`, тобто виклик на початку та в кінці інтервалу. ### Швидкий приклад ```javascript function throttle(fn, delay) { let lastCall = 0; return function (...args) { const now = Date.now(); if (now - lastCall >= delay) { lastCall = now; fn.apply(this, args); } }; } const handleMove = throttle(() => { console.log('Рух миші оброблено'); }, 500); window.addEventListener('mousemove', handleMove); ``` ### Проблема: обробник без throttle Припустімо, ви хочете стежити за тим, як користувач рухає мишею: ```javascript window.addEventListener('mousemove', () => { console.log('рух миші'); }); ``` У чому проблема: - `mousemove` може спрацьовувати **десятки разів на секунду** (до 100-200 разів на секунду); - функція викликатиметься надто часто; - навантаження на процесор зростає, і продуктивність падає. Якщо всередині обробника є читання геометрії (`getBoundingClientRect`) або зміна стилів, кожне зайве спрацювання ще й провокує додатковий layout, тож сторінка починає смикатися. ### Як throttle працює всередині ```javascript function throttle(fn, delay) { let lastCall = 0; return function (...args) { const now = Date.now(); if (now - lastCall >= delay) { lastCall = now; fn.apply(this, args); } }; } const handleMove = throttle(() => { console.log('Рух миші оброблено'); }, 500); window.addEventListener('mousemove', handleMove); ``` Тепер функція `handleMove` викликається **не частіше ніж 1 раз на 500 мс**, навіть якщо `mousemove` спрацьовує сотні разів. Покроково: 1. На першому виклику функція виконується одразу. 2. Зберігається час останнього виклику (`lastCall`). 3. Усі наступні виклики **ігноруються**, доки не мине `delay` мілісекунд. 4. Після закінчення тайм-вікна функцію знову можна викликати. Зверніть увагу: ця найпростіша реалізація має лише `leading`-поведінку. Остання подія серії, яка потрапила всередину інтервалу, буде втрачена, бо `trailing`-виклику тут немає. ### Приклади з реального коду 1. **Оновлення прогрес-бара при скролі:** ```javascript window.addEventListener( 'scroll', throttle(() => { progressBar.value = window.scrollY; }, 100) ); ``` 2. **Відстеження позиції миші для анімації:** ```javascript window.addEventListener( 'mousemove', throttle(updateCursorPosition, 50) ); ``` 3. **Перерахунок розмірів вікна (`resize`):** ```javascript window.addEventListener( 'resize', throttle(updateLayout, 200) ); ``` У багатьох проєктах беруть готовий варіант з Lodash: ```javascript import { throttle } from 'lodash'; const throttled = throttle(() => { console.log('scroll!'); }, 300); window.addEventListener('scroll', throttled); ``` Версія з Lodash просунутіша: вона вміє викликати функцію **на початку і (або) в кінці** інтервалу через опції `leading` і `trailing`, а також має метод `cancel()` для скасування відкладеного виклику. ### Відмінність від debounce | Особливість | **Throttle** | **Debounce** | | --- | --- | --- | | Мета | Обмежити частоту викликів | Відкласти виконання, доки користувач не завершить дію | | Викликається | Регулярно, але не частіше ніж раз на N мс | Лише один раз після паузи | | Приклад | `scroll`, `mousemove`, `resize` | `input`, `search`, `autocomplete` | | Поведінка | «Не частіше ніж» | «Не раніше ніж» | Наочно: - **Throttle**: під час скролу сторінки оновлюємо позицію кожні 100 мс. - **Debounce**: чекаємо, доки користувач закінчить друкувати, і лише потім робимо запит. Підсумок: | Пункт | Опис | | --- | --- | | Що робить | Обмежує частоту виклику функції | | Коли використовувати | При частих подіях: `scroll`, `resize`, `mousemove` | | Аргументи | Функція і затримка (мс) | | Повертає | Нову обгорнуту функцію | | Користь | Знижує навантаження на CPU, підвищує продуктивність | | Відмінність від debounce | Виконується регулярно, але не частіше ніж заданий інтервал | ### Типові помилки - Створювати обгортку на кожне спрацювання: `addEventListener('scroll', throttle(fn, 100))` усередині іншого обробника чи рендера щоразу дає нову функцію з нульовим `lastCall`, і обмеження не працює. - Забувати про втрачений `trailing`-виклик: у наївній реалізації остання подія серії не обробляється, тому фінальна позиція скролу може лишитися незафіксованою. Для цього беруть Lodash або додають `setTimeout` на хвіст інтервалу. - Плутати throttle з debounce: для живого пошуку throttle надішле запит кожні N мс просто під час набору тексту, а потрібен один запит після паузи. - Використовувати throttle для анімації замість `requestAnimationFrame`: для візуальних оновлень rAF синхронізований з кадрами браузера і дає плавніший результат. - Брати `Date.now()` там, де потрібна монотонність: системний годинник може перевестися, тому для точних інтервалів надійніше `performance.now()`. - Не знімати обробник при розмонтуванні компонента: throttle не скасовує підписку, і обгортка далі смикає код уже неіснуючого екрана.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.