Сначала по-настоящему честно: setInterval? Выкини его.
Если тебе не нужно показывать таймер обратного отсчёта на глазах у пользователя, нафиг эти интервалы. Особенно если они гоняются каждую секунду ради какой-нибудь ерунды – типа анимации, которую никто не видит. Был у меня кейс: проект на React, где в фоне постоянно тикал setInterval на проверку позиции скролла. И да – ноут у меня гудел, как будто майнил биткоин. Удалили – и вдруг всё стало… тише. Как будто сняли наушники с шумом моря.
Просто замени на requestAnimationFrame
Ну или лучше вообще пересмотри, нужен ли там этот цикл. requestAnimationFrame – не панацея, но если тебе реально надо что-то постоянно считать (например, обновлять прогресс в игре), оно работает аккуратнее. Не так топорно, не так шумно.
Ограничь область действия, и всё станет проще
Был у нас лендинг с кнопками “добавить в избранное”. Красивые, анимации, сердечки. Всё отлично… пока не обнаружили, что при наведении на каждую такую кнопку запускается слушатель, который проверяет все кнопки на странице. Каждое. Блин. Наведение. 200 обработчиков, 200 проверок, 200 хлопков вентилятором в ноутбуке. Просто ограничили делегирование по ближайшему контейнеру – и вся эта вакханалия закончилась.
Не трогай весь документ
document.querySelectorAll(‘a’) – это не шутка, это беда. Особенно, если ты вызываешь это в цикле при каждом клике. Лучше один раз закешируй нужные элементы и работай с этим массивом. Или вообще – используй делегирование. Удивительно, но оно ещё работает!
Прокрутка? О, это отдельный ад
Если ты ловишь scroll и каждый раз вызываешь какую-то логику – поздравляю, ты устроил себе DDoS. Один пользователь, один тачпад, один админский ноут – и ты сам себе враг. Решение? throttle и debounce. Просто попробуй их – lodash, underscore, своя функция – неважно. Главное – не вызывать scrollHandler тысячу раз в секунду.
Конкретный пример
На одном проекте был “липкий” хедер, который должен был исчезать при прокрутке вниз и появляться при прокрутке вверх. Логика работала… почти. Только вот скролл фризился каждые пару секунд. Всё из-за того, что scroll-обработчик пересчитывал координаты и состояния слишком часто. Поставили throttle на 100мс – и как будто натянули масло на шестерёнки.
Проверяй, кто виноват
DevTools тебе в помощь. Performance → Record, погоняй пару секунд – и смотри, кто жрёт процессор. Я, честно, раньше думал: “Да ну, это не про меня”. А потом увидел, как один заброшенный listener блокировал главный поток на полсекунды. На мобильном это казалось вечностью.
И если ты думаешь, что у тебя всё нормально – проверь ещё раз
Особенно на медленных устройствах. Прямо открой эмулятор Galaxy A51, и погоняй сайт. Если всё тормозит – это не “у всех тормозит”, это у тебя. И это чинится. Почти всегда.
Да, можно жить без лагов. Но для этого надо немного покопаться в мусоре. Не ленись – выгреби старое, отключи лишнее, закрой лейку, из которой льются миллисекунды. Оно того стоит. Твой пользователь – это не бетономешалка. Не надо его грузить всем подряд.
Сокращение времени выполнения скриптов через устранение блокирующих зависимостей
Выкинь всё, что мешает дышать
Первое, что стоит сделать – выдрать из головы мысль, что “если подключено – значит нужно”. Нет. Почти всегда половина библиотек живёт у тебя в коде просто потому, что когда-то кто-то из команды не хотел копаться. Или потому что “ну мало ли, вдруг пригодится”.
Всё, что грузится синхронно, безоговорочно тормозит загрузку и выполнение – да, даже твоя любимая jQuery, которая до сих пор прячется где-то между chart.js и lodash. И тут нет никакой драмы – просто грустная реальность: всё, что грузится <script>
без async
или defer
, стопорит страницу так, будто это поезд в метро, который забыл закрыть двери.
async или defer? Или сразу руки в ноги?
Ты удивишься, но 95% случаев можно обойтись без “синхронных” скриптов. Ставь defer
по умолчанию. Вот прям всегда. Почему? Потому что он держит слово. Скрипт грузится параллельно с HTML, но запускается только когда весь DOM собрался как пазл. Удобно. С async
сложнее – он дергается сам по себе, без оглядки. Иногда это полезно, особенно для аналитики или всяких внешних трекеров.
Пример? Ну вот у нас был клиент, у которого перед каждым скроллом подгружался какой-то совершенно бесполезный мониторинг прокрутки. Через script
без defer. Меняли на async, запихнули его в самый конец – и время отрисовки сократилось на 1.3 секунды. 1.3, Карл. Из-за одного левого трекера.
Динамическая загрузка – как запасной парашют
Иногда вообще не нужно тащить скрипты сразу. Они нужны, когда пользователь что-то сделал. Ну, типа кликнул “открыть чат” – только тогда и загрузи SDK чата. Да, руками. Через createElement('script')
, appendChild и всё такое. Это не извращение. Это разум.
Так же делали на проекте с кастомной картой – пока пользователь не откроет вкладку “Где мы находимся”, никакой Leaflet не грузится. Открывает – только тогда прилетает. 2.4 МБ JS-а? Спасибо, не надо. Только по требованию. Как пицца ночью.
А можно вообще без них?
Ну, да. Иногда можно. Вот честно – перепиши ты этот свайпер на CSS scroll snap, и никакой JavaScript тебе не понадобится. Или формочку отправляй через form
и action
, как в старые добрые. Без всякого “fetch и spinner и loader и ещё что-нибудь”.
Нет, я не за ретроградство. Я за здравый смысл. Чем меньше кода – тем меньше шансов, что он поломает тебе UX.
Пробегись по зависимостям – прямо сейчас
- Открой DevTools, вкладка Network, фильтр – JS
- Смотри, что грузится синхронно
- Проверь: это точно нужно именно на первой загрузке?
- Можно ли отложить? Или выкинуть совсем?
- Если нельзя – async или defer. Точка.
Эти пару шагов – и ты уже бежишь быстрее. Как будто снял рюкзак с кирпичами. Приятно же, когда скролл не тормозит, клики не залипают, а CLS – почти ноль.
Просто начни выкидывать лишнее
Иногда улучшение не в том, чтобы что-то добавлять. А наоборот. Убрал – стало легче. Это как с людьми, если честно.
А с кодом – тем более. Всё, что блокирует выполнение, – твой враг. Убери врага. И станет дышать проще.
Уменьшение объёма JavaScript-бандла с помощью модульной загрузки
Забудь про один гигантский файл. Серьёзно
Если всё валишь в один бандл – ты буквально стреляешь себе в ногу. Особенно, если у тебя там графики, формы, чаты, куча компонентов, которые юзер даже не видит сразу. Зачем загружать редактор, если человек просто листает блог?
Вот тебе идея: разбей всё на модули. Не надо грузить то, что не нужно прямо сейчас. Пусть куски приходят по требованию – когда реально пригодятся. Это называется модульная загрузка, или lazy loading. Не новость, конечно, но почему-то до сих пор у кучи людей всё собирается в один безумный мешок.
Конкретика: что можно отложить
- галереи с модальными окнами (просто ссылки хватит, пока не кликнули)
- чаты поддержки (зачем грузить скрипт, если никто не пишет?)
- калькуляторы, фильтры, выпадашки – всё, что живёт «где-то потом»
- админские панели (особенно если они вообще спрятаны от обычных юзеров)
Я как-то подключил целую библиотеку визуализации данных, чтобы показать один-единственный график, который юзер видел через три клика. Результат – +1.2MB к начальному бандлу. Ну типа… глупо было, не повторяй моих ошибок.
Как сделать по уму
Если работаешь с Webpack – там есть import()
. Реально просто: пишешь import('./Chart.js')
внутри функции, и бандл сам порежется. Vite, Rollup, ESBuild – все поддерживают эту историю. Никаких плясок с бубном.
Пример из жизни
Проект на React. Главная – обычный лендинг. В футере – кнопка «Открыть чат». Раньше бандл подтягивал туда ещё и движок чата. После lazy loading – он подгружается только при клике. Заметно? Ещё как. FCP – быстрее, LCP – тоже. Lighthouse стал улыбаться. Google вообще как будто перестал злиться.
Но будь осторожен…
Если переусердствуешь – получишь фрагментацию. Много маленьких чанков, постоянные сетевые запросы. Особенно на медленном интернете – это беда. Поэтому, да, тестируй. Группируй модули логически. Всё, что нужно вместе – загружай вместе.
Контекстный пример
Вот здесь https://auslander.ru/kak-sozdavat-prompty/ авторы грамотно применили модульность: интерфейс лёгкий, интерактивы не мешают первичной загрузке, всё подгружается ровно тогда, когда нужно – и ни секунды раньше.
И это работает не только в “фронте”
Хочешь двинуть проект за пределы своего региона? Проверь, насколько быстро он открывается в других странах. Там и сети другие, и телефоны слабее. А вот тут уже пригодится опыт продвижения в других странах. Лёгкий интерфейс, быстрая загрузка – и ты уже на шаг впереди.
Финал не будет
Потому что это не рассказ со счастливым концом. Это просто мысль: чем меньше хлама в момент первой загрузки, тем быстрее пользователь получит то, зачем он пришёл. Всё остальное – потом. Когда (и если) ему это реально понадобится.
Повышение производительности интерфейса за счёт делегирования событий
Сразу к делу: забудь про миллион обработчиков
Ставить обработчик на каждый элемент – это как печь пирожки по одному. Работает? Ну, типа да. Но когда их сотни – становится тяжело дышать. Просто тяжело. Браузер начинает заикаться, скролл дергается, кнопки жмутся с лагом, как будто всё в киселе.
И вот тут делегирование – это как включить духовку на весь поддон сразу. Вместо того чтобы понавесить сотни функций, кидаешь одну – на родителя. Всё. Всё остальное делается «по ходу».
Пример из жизни: список дел и адское мерцание
Был у нас проект – таск-менеджер. Список задач на 800+ элементов. Каждый чекбокс – с отдельным `addEventListener`. Страница подтормаживала так, что люди реально думали, что это баг. Мы заменили всё на делегирование через `ul`, один клик – и внутри него ловим таргет. Производительность выросла, как в RPG после прокачки: прям цифры – минус 70% нагрузки. Легенда, а не приём.
Как это работает – быстро и без соплей
Ты вешаешь `click` на контейнер. Внутри `event.target`, смотришь, кто там в тебя кликнул, и действуешь. Это даже звучит логичнее, чем пытаться предсказать все возможные нажатия. Пример? Вот:
document.querySelector('.todo-list').addEventListener('click', (e) => {
if (e.target.matches('.todo-item input[type="checkbox"]')) {
e.target.closest('.todo-item').classList.toggle('done');
}
});
Всё. Одна строчка, ноль боли. Не надо чистить обработчики при удалении. Не надо ничего пересоздавать при добавлении. Оно просто работает. Как хороший бутерброд.
Но есть нюанс – не лезь, если не надо
Если у тебя пять кнопок и они статичны – ну не надо ничего городить. Делегирование для динамики. Когда кнопки прилетают с сервера, таски добавляются по клику, или у тебя в DOM что-то постоянно крутится, как Юла в TikTok – тогда да. Тогда оно рулит.
Не ставь 300 обработчиков. Поставь один, но умный. Делегирование – это как один глаз, но зоркий. Видит всё, реагирует только на нужное. Спокойствие интерфейсу и браузеру. И тебе.
Если тебе надо вдохновиться ещё – глянь, как это делают в старом добром jQuery. Там делегирование – часть ДНК. А если ты всё ещё добавляешь `onclick` в каждый элемент руками… ну, мы просто помолчим. Хотя нет, не помолчим: бро, остановись.