Главная / Статьи / Почему микрооптимизации не помогают улучшить производительность настоящего JavaScript

Почему микрооптимизации не помогают улучшить производительность настоящего JavaScript

Объясняется, почему сосредоточение внимания на измеримых показателях, таких как размер пакета и количество повторных отрисовок, часто приводит к упуску настоящих причин медленной работы пользовательского интерфейса в приложениях на JavaScript.

3977 слов

На бумаге пет-риквест казался надежным.

Размер пакета сократился на 18 процентов. Было устранено несколько ненужных зависимостей. Несколько компонентов были обернуты в механизмы мемоизации. Несколько операций с массивами были заменены на, как утверждалось, более эффективные циклы. Оценка Lighthouse повысилась. Все показатели в описании пет-риквеста указывали в правильном направлении.

Команда одобрила его без колебаний.

Через две недели пользователи по-прежнему жаловались на медленную работу приложения.

Не та медлительность, когда «бенчмарк показывает несколько дополнительных миллисекунд».

Не проблема, связанная с цифрами на панели управления.

Та самая медлительность, которая действительно влияет на людей.

Они нажимали кнопку и не были уверены, что она сработала.

Они открывали экран и сидели, ожидая появления чего-то полезного.

Они меняли фильтр и наблюдали, как интерфейс на мгновение замирает, прежде чем снова начать работу.

Команда вложила огромные усилия в работу над производительностью.

Однако они нацелились не на те показатели.

Такая ситуация постоянно встречается в современных проектах на JavaScript.

Несмотря на улучшенные инструменты анализа производительности, более быстрые среды выполнения, умные инструменты сборки кода, более мощные фреймворки и постоянно усиливающиеся браузеры, разработчики продолжают придерживаться одной и той же привычки:

Оптимизация того, что легче всего измерить, а не того, что действительно важно для пользователей.

JavaScript предлагает бесконечный список элементов, которые можно оптимизировать.

Компонент перерисовывается четыре тысячи раз — вы это исправляете.

Размер пакета составляет 300 КБ — вы его сокращаете.

Функция выполняется за 12 миллисекунд — вы её переписываете.

Зависимость занимает 40 КБ — вы её удаляете.

Микротест показывает, что подход А превосходит подход Б на 7 процентов.

Значит, вы выбираете вариант А.

Каждый из этих вариантов может считаться законным способом работы.

Но ни один из них автоматически не приводит к лучшему пользовательскому опыту для тех, кто использует ваше приложение.

Иногда самый быстрый написанный вами код решает проблему, которая никогда никого не беспокоила.

Тем временем медленный запрос к базе данных, избыточный сетевой вызов, неэффективная последовательность загрузки, перегруженный пакет данных API или плохо спроектированное взаимодействие тихо стоят пользователей реального времени.

Именно это несоответствие является настоящей проблемой, которую стоит рассмотреть.

Производительность — это не то же самое, что скорость

Одним из наиболее полезных уроков работы с производственными системами является то, что производительность нельзя свести к одному показателю.

Приложение может быть высокоэффективным с точки зрения вычислений даже на уровне микросекунд, но при этом оставаться крайне неудобным в использовании.

Представьте страницу, которая выполняет следующую последовательность действий:

  1. Загрузка оболочки приложения.
  2. Загрузка нескольких фрагментов JavaScript.
  3. Запуск фреймворка.
  4. Загрузка данных конфигурации.
  5. Получение информации о текущем пользователе.
  6. Загрузка данных разрешений.
  7. Загрузка содержимого панели управления.
  8. Отображение панели управления.
  9. Загрузка уведомлений.
  10. Отображение уведомлений.

Каждый отдельный шаг может быть совершенно быстрым по отдельности.

Настоящая проблема заключается в самой цепочке операций.

Пользователей не волнует, насколько хорошо отрегулирован каждый элемент по отдельности.

Их волнует то, что они смотрели на пустой экран до тех пор, пока что-либо не появилось.

Поэтому любое серьезное исследование производительности должно начинаться с одного вопроса:

Чего на самом деле ждет человек по другую сторону?

А не:

Как сократить время выполнения этой функции?

Это совершенно разные направления исследований.

Разработчик может потратить три часа на то, чтобы сократить время выполнения функции с 8 мс до 3 мс.

Тем временем страница простаивает в течение 700 мс, ожидая запроса, который вообще не был нужен.

Это не настоящая оптимизация.

Это всего лишь уборка мебели, пока в стене за ней протекает труба.

Одержимость 5 миллисекундами

У разработчиков JavaScript есть слабость к микрооптимизациям.

Часть этого интереса действительно оправдана.

Люди спорят о циклах for против метода map().

Они обсуждают стратегии выделения памяти.

Они изучают скрытые классы, поведение сборки мусора, замыкания, затраты на деструктуризацию, накладные расходы на вызов функций и оптимизации JIT.

За всем этим стоит реальные, ценные знания.

Проблема не в том, чтобы понимать эти механизмы.

Проблема в том, что к ним обращаются без каких-либо доказательств их значимости в данном случае.

Предположим, функция выполняется 100 раз в ходе одного взаимодействия с пользователем.

В настоящее время каждый её вызов занимает 2 мс.

Вы тратите полдня, чтобы сократить это время до 1 мс.

Общая экономия: 100 миллисекунд.

Это может иметь какую-то ценность.

Но предположим, что в ходе того же взаимодействия происходит также ненужный вызов API, на обработку которого уходит 600 мс.

Удаление этого вызова займёт пять минут и принесёт в шесть раз большую экономию по времени, чем вся ваша работа над оптимизацией за полдня.

Говоря просто, это кажется очевидным.

Тем не менее во время обсуждений кода внимание сосредотачивается на первом типе проблем, потому что он виден непосредственно в отчёте о различиях.

Ненужный сетевой вызов, напротив, может находиться за тремя уровнями абстракции.

Это приводит к предсказуемой и опасной предвзятости:

Команды в итоге оптимизируют код, видимый им, а не систему, которую на самом деле используют их пользователи.

Браузер — это не ваша функция

Ещё одной распространенной ошибкой является предположение, что время выполнения JavaScript рассказывает всё о производительности.

Это далеко не так.

Приложение на базе браузера — это целая система, а не просто один вызов функции.

Эта система включает в себя:

  • Разрешение DNS
  • Установление соединения
  • Обмен данными по протоколу TLS
  • Обработка на стороне сервера
  • Запросы к базе данных
  • Сериализация ответов API
  • Время передачи данных по сети
  • Разбор HTML
  • Разбор JavaScript
  • Выполнение JavaScript
  • Отрисовка
  • Расчет макета
  • Отображение элементов
  • Составление изображения
  • и реальное взаимодействие пользователя со всем этим
  • Каждый из этих этапов может влиять на скорость восприятия процесса.

    Предположим, вам удается сократить время отрисовки компонента React с 15 мс до 8 мс.

    Это действительно хороший результат.

    Но если бэкенд требует 900 мс для генерации данных, необходимых этому компоненту, ваше улучшение практически не заметно.

    Или, возможно, сервер отвечает быстро, но отправляет 2 МБ JSON для страницы, которой нужно всего 20 КБ.

    Теперь вы тратите процессорные ресурсы и пропускную способность на передачу данных, которые вообще не должны существовать в таком виде.

    Или, возможно, страница скачивает огромную библиотеку на стороне клиента прежде, чем сможет отрисовать что-либо значимое.

    Ни одна из этих проблем не связана с самим компонентом.

    Это проблемы архитектуры.

    Именно поэтому серьезная работа над производительностью часто больше похожа на следственные действия, чем на простое ускорение JavaScript.

    Вы выясняете причину задержки и следуете за ней до самого источника, куда бы она ни привела.

    Самая дорогостоящая операция часто — это та, которую стоит пропустить

    При решении задач, связанных с производительностью, существует полезный порядок приоритетов.

    Ускорение операции — это хороший результат.

    Однако обычно лучше вовсе пропустить эту операцию.

    Посмотрите на этот фрагмент кода:

    const results = expensiveTransform(items);
    

    Анализ производительности показывает, что эта трансформация занимает 40 мс.

    Ваш первый порыв — оптимизировать её.

    Возможно, вы введёте кэш.

    Возможно, замените её на более эффективный алгоритм.

    Возможно, перенесёте выполнение этой операции в совершенно другое место.

    Но прежде чем что-либо делать, спросите себя:

    Почему вообще происходит эта трансформация?

    Возможно, результат меняется только при настройке фильтра.

    Возможно, вы запускаете его заново при каждой отрисовке в любом случае.

    Возможно, бэкенд может передать вам уже обработанную версию.

    Возможно, интерфейс на самом деле не требует всех 10 000 строк.

    Возможно, вы загружаете данные, которые пользователь никогда не откроет.

    Настоящее решение может вообще не находиться внутри expensiveTransform().

    Возможно, достаточно просто убрать этот вызов.

    Такой подход хорошо применим и к другим примерам.

    Пропускайте запросы, которые вам не нужно отправлять.

    Пропускайте отрисовку UI, которое остается скрытым.

    Пропускайте вычисление значений, которые никто не будет использовать.

    Пропускайте выполнение кода, который пользователи никогда не активируют.

    Пропускайте обработку данных, которые можно было отфильтровать на ранних этапах.

    Пропускайте повторную работу, результат которой на самом деле не изменился.

    Самая быстрая операция — это та, которая вообще не выполняется.

    Размер пакета — не всё

    Размер пакета действительно заслуживает внимания. В этом нет сомнений.

    Но поскольку это показатель, который тщательно отслеживается, команды иногда начинают рассматривать его как замену самой производительности.

    Команда уменьшает размер своего JavaScript-пакета на 50 КБ и считает это успехом.

    Тем временем приложение всё равно отправляет шесть запросов подряд, прежде чем пользователь сможет что-либо с ним сделать.

    Конечно, пакет стал меньше.

    Но это ещё не означает автоматически улучшения пользовательского опыта.

    Всё это не говорит о том, что размер пакета неважен.

    Это значит, что необходимо выяснить когда именно этот JavaScript-код фактически используется.

    Скрипт размером 100 КБ, который мешает начальной интерактивности, может оказаться гораздо важнее, чем скрипт размером 300 КБ, загружаемый позже для функции, с которой пользователь может воспользоваться раз в месяц.

    Важна временная составляющая.

    Важно также то, когда выполняется код.

    Важно, на каком устройстве это работает.

    Важна сеть, по которой происходит передача данных.

    Важно наличие кэша.

    И, что не менее важно, важно то, что на самом деле пытается сделать пользователь.

    Если какая-то функция используется редко, загрузка всего необходимого сразу при запуске может оказаться плохим решением.

    Разделение кода помогает с этим.

    То же самое касается отложенной загрузки.

    Но оба подхода могут превратиться в бесполезные ритуалы, если применять их без понимания того, как на самом деле загружается страница.

    Вопрос, который стоит задать, не таков:

    Как нам ещё сократить размер этого пакета?

    А вот какой:

    Что нужно конкретному пользователю в данный момент, и как быстро мы можем сделать это конкретное решение доступным?

    Такой подход приводит к гораздо лучшим результатам.

    Возможно, проблема не в компоненте, на который вы смотрите

    Каждый, кто работал с React, знаком с этим циклом.

    Компонент перерисовывается чаще, чем следует.

    Кто-то прибегает к использованию useMemo.

    Одно лишнее перерисовывание исчезает.

    Все довольны.

    Затем аналогичная ситуация возникает с следующим компонентом.

    Вскоре кодовая база наполняется вызовами мемоизации:

    const filtered = useMemo(
      () => expensiveFilter(items, query),
      [items, query]
    );
    
    const handleClick = useCallback(() => {
      doSomething(id);
    }, [id]);
    
    const value = useMemo(
      () => ({ user, permissions }),
      [user, permissions]
    );
    

    Иногда это действительно правильный шаг.

    В других случаях это лишь затрудняет понимание кода, не решая по сути никаких проблем.

    Оптимизация не бесплатна.

    Мемоизация в частности сопряжена с реальными затратами.

    Она увеличивает нагрузку на память, требует поддержки массивов зависимостей, создает дополнительную когнитивную нагрузку и предоставляет новые места для скрытия тонких ошибок.

    Сначала измерьте, прежде чем прибегать к ней.

    Если какой-то расчёт занимает 0,2 мс и выполняется редко, его оптимизация не принесёт никакой пользы.

    Если же он занимает 50 мс и выполняется при каждом нажатии клавиши, это совершенно другая ситуация.

    Цель не в том, чтобы выявлять каждую перерисовку.

    Цель — сделать важные интеракции достаточно быстрыми.

    Эти две цели не идентичны.

    Сосредоточьтесь на интеракциях, а не на отдельных компонентах

    Именно здесь многим командам может помочь смена подхода.

    Пользователи не воспринимают компоненты как отдельные единицы.

    Они ощущают то, что делают.

    Они вводят запрос к поиску.

    Они заполняют поля.

    Они переходят между страницами.

    Они отправляют форму.

    Они открывают выпадающий список.

    Они переключаются между вкладками.

    Они загружают файл.

    Они прокручивают ленту новостей.

    Они сидят и ждут, пока что-то появится.

    Вместо того чтобы спрашивать:

    Хорошо ли оптимизирован этот компонент?

    Попробуйте спросить:

    Кажется ли ввод текста в это поле поиска мгновенным?

    Вместо того чтобы спрашивать:

    Этот список отображается эффективно?

    Лучше спросить:

    Может ли человек плавно прокручивать этот список, без задержек в интерфейсе?

    Вместо того чтобы спрашивать:

    Сократили ли мы количество итераций React?

    Лучше спросить:

    Дает ли нажатие на эту кнопку пользователю немедленную и понятную обратную связь?

    Вторая группа вопросов гораздо точнее отражает то, что действительно важно для продукта.

    Умная оптимизация, при которой важное взаимодействие остается таким же медленным, как раньше, не обязательно является ценной инженерной решением, как бы изящно она ни выглядела в отчете об изменениях.

    Вкладка «Сеть» обычно превосходит переписываемый цикл

    Прежде чем трогать хотя бы один цикл, откройте панель сети своего браузера.

    Это не случайное предложение.

    Часто там вы найдете больше возможностей для улучшений, чем в сотнях строк ручно настроенного JavaScript.

    Обращайте внимание на такие моменты:

    • одна и та же заявка отправляется более одного раза
    • заявки выполняются поочередно, хотя могли бы работать параллельно
    • заявки отправляются до того, как появляется реальная потребность в их данных
    • ответы слишком объемные по сравнению с тем, что отображается
    • кэширование, которое должно существовать, но отсутствует
    • запросы данных, происходящие чаще, чем необходимо
  • Полезная нагрузка, заполненная метаданными, которые никто не читает
  • Конечные точки возвращают полные записи, хотя интерфейсу нужны всего три поля
  • Запросы отправляются при каждом нажатии клавиши
  • Данные загружаются компонентами, которые даже не видны
  • Ресурсы загружаются глобально, хотя ими пользуется лишь небольшая часть приложения
  • Один единственный ненужный запрос может превзойти по влиянию десятки незначительных изменений в JavaScript.

    Возьмем поиск в качестве примера.

    Вот наивный подход:

    User types "j"
    → request
    
    User types "ja"
    → requestUser types "jav"
    → requestUser types "java"
    → request
    

    Команда может потратить много усилий на ускорение отображения результатов.

    Но на самом деле проблемой может быть то, что приложение отправляет четыре отдельных запроса там, где достаточно одного.

    Использование техник таких как дебаунсинг, отмена запросов, кэширование и более эффективно спроектированные запросы обычно приносят гораздо большую пользу.

    Именно такие улучшения происходят на уровне всей системы, а не внутри отдельной функции.

    Не оптимизируйте неправильное устройство

    Проведение тестов на собственном разработческом компьютере почти ничего не говорит о том, как приложение будет работать на устаревшем телефоне с ограниченной производительностью CPU и нестабильным соединением.

    Это имеет огромное значение для JavaScript.

    Современное оборудование может обрабатывать огромные объемы кода, при этом разработчик даже не замечает замедления.

    Мощный ноутбук может скрыть проблемы.

    Флагманский телефон может скрыть проблемы.

    Быстрая офисная сеть может скрыть проблемы.

    Работа локально может скрыть проблемы.

    Затем кто-то реальный открывает приложение на бюджетном устройстве с нестабильным соединением.

    Вдруг это тщательно настроенное приложение становится медленным и нереагирующим.

    Именно поэтому так важны тестирование в реалистичных условиях.

    Вам не нужно делать это постоянно.

    Но вам необходимо делать это достаточно часто, чтобы команда могла реально понять, как ведет себя приложение вне комфортной среды разработки.

    Правильный вопрос не таков:

    Работает ли это быстро на моей конфигурации?

    А таков:

    Достаточно ли это быстро для тех, кто на самом деле им пользуется?

    Архитектура обычно превосходит микрооптимизацию

    Это, пожалуй, самый важный урок из всех.

    Архитектура определяет потолок производительности.

    Если ваше приложение должно выполнить пять вызовов API подряд, прежде чем сможет отобразить главный экран, никакие уловки с массивами не помогут улучшить этот опыт.

    Если каждый маршрут загружает весь пакет приложения, удаление нескольких вспомогательных функций не решит основной проблемы.

    Если клиент загружает огромный набор данных, а затем фильтрует его в браузере, усовершенствование логики фильтрации, скорее всего, менее важно, чем исправление самого контракта API.

    Если действие пользователя удаляет большую часть кэшированного состояния, использование мемоизации для компонентов не поможет исправить сломанную стратегию аннулирования кэша.

    Если вы отрисовываете тысячи узлов DOM одновременно, изменение способа использования функции map() не спасет ситуацию.

    Улучшения, которые действительно приносят результат, обычно связаны с изменением того, где выполняется работа, когда она выполняется или должна ли она вообще выполняться.

    Это архитектурные решения, а не корректировки на уровне кода.

    Что я на самом деле проверяю при анализе производительности

    Когда что-то кажется медленным, следует сопротивляться инстинкту сразу приступить к изучению кода.

    Начните с воспроизведения проблемы.

    Затем спросите, чего именно пользователь там ждет.

    После этого расследование обычно следует определенной последовательности.

    1. Загрузка

    Что должно произойти, прежде чем пользователь сможет увидеть и использовать важные элементы страницы?

    Определите критический путь.

    Какие ресурсы действительно необходимы?

    Какие запросы мешают продвижению процесса?

    Что загружается там, где этого не нужно?

    2. Сеть

    Откройте панель «Сеть».

    Ищите закономерности в формате «водопада».

    Цепочки последовательных запросов требуют пристального внимания.

    Так же как и дублирующиеся вызовы и данные, объем которых превышает норму.

    3. Отрисовка

    Далее посмотрите, что делает сам браузер.

    Предоставляет ли страница огромное количество данных DOM?

    Являются ли шаги формирования макета и отрисовки ресурсов трудоемкими?

    Происходит ли дорогостоящая обработка в ответ на действия пользователя?

    4. JavaScript

    Только на этом этапе важны детали на уровне функций.

    Какие операции фактически потребляют время процессора?

    Какие из них выполняются повторно?

    Какие связаны с действием, только что совершенным пользователем?

    5. Память

    Если приложение работает хуже по мере увеличения времени его работы, стоит проверить память.

    Утечки памяти и неконтролируемое её увеличение могут маскироваться под общую медлительность.

    6. Реальное влияние на пользователя

    Наконец, свяжите всё, что вы обнаружили, с реальным опытом использования.

    Стало ли быстрее запуск приложения?

    Стала ли поисковая функция быстрее отвечать?

    Улучшилась ли навигация?

    Стало ли проще выполнять некоторые задачи?

    Если ничего из этого не изменилось, стоит задуматься, действительно ли была выявлена основная проблема.

    Бюджеты на производительность полезны — если они связаны с реальностью

    Команды часто устанавливают такие правила:

    Размер пакета JavaScript должен быть менее 300 КБ.

    Это разумная отправная точка, лучше, чем совсем отсутствие ограничений.

    Однако бюджет, основанный на реальном опыте использования, обычно более полезен:

    • Основной контент должен отображаться быстро
    • Поиск не должен казаться медленным
    • Навигация должна давать немедленную обратную связь
    • Ключевые страницы не должны зависеть от последовательности запросов
    • JavaScript, необходимый для первого взаимодействия, должен быть сведён к минимуму
    • Большие наборы данных не должны отображаться на экране сразу всеми

    Эти показатели сложнее свести к одному чёткому числу.

    Но они гораздо точнее отражают то, что действительно замечают и важно для пользователей.

    Показатели становятся полезными, когда помогают понять, что на самом деле происходит.

    Они превращаются в опасность в тот момент, когда достижение определенных цифр становится основной целью.

    Ловушка оптимизации

    Существует тонкое психологическое влияние, которое заставляет разработчиков идти по этому пути.

    Оптимизация чего-либо кажется проявлением прогресса.

    Можно указать на конкретную правку и сказать:

    Размер пакета сокращен на 14%.

    Или указать на тесты производительности и сказать:

    Эта функция теперь работает на 32% быстрее.

    Или показать кому-то скриншот профиляровщика в качестве доказательства.

    Такие успехи приносят удовольствие.

    Но некоторые из самых значимых улучшений производительности, честно говоря, довольно скучны.

    Устранение ненужного вызова API не делает демонстрацию захватывающей.

    Изменение формата ответа сервера тоже не выглядит впечатляюще.

    Это также не сводится к устранению зависимостей в цепочке.

    Это также не сводится к исправлению запроса, который возвращает 5 000 строк, хотя достаточно 50.

    Это также не сводится к добавлению кэша.

    Работа, которую вы избегаете выполнять, по своей природе остается незаметной.

    Именно поэтому её так легко упустить из виду.

    Лучшее улучшение производительности может привести к меньшему объёму кода, меньшему количеству запросов, меньшему числу вычислений и вообще к меньшему количеству работающих компонентов.

    Возможно, не будет ничего, что стоило бы сделать скриншотом.

    Просто приложение становится удобнее в использовании.

    Оптимизация должна начинаться с доказательств

    Вот правило, которое стоит применять везде:

    Не оптимизируйте код. Оптимизируйте подтверждённые проблемы.

    Для этого не обязательно создавать сложные процессы инженерии производительности для каждой новой функции.

    Достаточно просто собрать достаточно доказательств, чтобы понять, куда на самом деле уходит время.

    Используйте инструменты профилирования.

    Воспользуйтесь встроенными панелями мониторинга производительности браузера.

    Записывайте трассировки сетевых запросов.

    Получайте данные телеметрии из продакшена.

    Настройте мониторинг реальных пользователей там, где это целесообразно.

    Тестируйте на устройствах, соответствующих вашей реальной аудитории.

    И прежде всего, воссоздайте ситуацию, при которой поступила жалоба.

    Если заинтересованная сторона говорит, что «панель управления работает медленно», сдерживайте желание сразу переходить к коду компонентов и начинать удалять элементы отрисовки.

    Сначала выясните, на что именно намекает термин «медленно».

    Медленна ли первоначальная реакция сервера?

    Является ли проблемой определенный запрос API?

    Слишком ли большой пакет JavaScript?

    Слишком долго идет парсинг?

    Этап отрисовки ли является трудоемким?

    Есть ли длительная задача, блокирующая основной поток?

    Недостаточно ли эффективен запрос к базе данных?

    Есть ли задержки из-за накопления запросов?

    Браузер ли простаивает, ожидая чего-то, что могло бы начаться раньше?

    Или приложение технически реагирует, но не предоставляет пользователю никакой визуальной обратной связи?

    Отладка производительности — это работа детектива.

    Это не соревнование в том, кто сможет удалить больше всего JavaScript-кода.

    Лучшая оптимизация JavaScript — это, возможно, меньше JavaScript

    Это может показаться странным заявлением от разработчика JavaScript.

    Но по мере роста приложений это становится всё более важным.

    Каждый фрагмент кода, выполняемый на клиенте, имеет свою стоимость.

    Его нужно скачивать.

    Возможно, его нужно парсить.

    Возможно, его нужно компилировать.

    Он должен выполняться.

    Он потребляет память.

    Он конкурирует с браузером за время отрисовки.

    Он может создавать препятствия во время взаимодействия с пользователем.

    Всё это не означает, что JavaScript по своей природе плох.

    Это означает, что любые вычисления, происходящие на клиенте, должны иметь обоснование для своего существования.

    Иногда лучшим решением является отрисовка на сервере.

    Иногда — стриминг контента вместо его блокировки.

    Иногда — полное перемещение логики на серверную часть.

    Иногда — введение кэша.

    Иногда — сокращение объёма данных, возвращаемых API.

    Иногда — постепенная загрузка элементов вместо их одновременной загрузки.

    Иногда — простое удаление функции, которой на самом деле никто не пользуется.

    А иногда существующий JavaScript вполне подходит без изменений.

    Главный вывод заключается в том, чтобы перестать автоматически предполагать, что оптимизация должна происходить непосредственно внутри самого JavaScript.

    Чему в конечном итоге учатся опытные разработчики

    На ранних этапах карьеры разработчика оптимизация обычно означает ускорение работы существующего кода.

    При наличии большего опыта внимание смещается на устранение действий, которые вообще не были нужны.

    В конечном итоге вопросы становятся более глубокими, касаясь причин существования этих действий вообще.

    Это значимая эволюция мышления.

    Вместо того чтобы спрашивать:

    Можно ли сделать этот цикл быстрее?

    Вопрос становится:

    Почему вообще в браузере обрабатываются 20 000 записей?

    Вместо того чтобы спрашивать:

    Как можно предотвратить повторную отрисовку этого компонента?

    Вопрос становится:

    Почему одно взаимодействие вызывает изменения во всем состоянии страницы?

    Вместо того чтобы спрашивать:

    Как сделать этот пакет меньше?

    Вопрос становится таким:

    Почему пользователь должен скачивать этот код перед тем, как что-либо сделать?

    Вместо того чтобы спрашивать:

    Как сделать этот запрос более эффективным?

    Вопрос становится таким:

    Действительно ли этот запрос необходим?

    Именно задавание таких более глубоких вопросов приводит к по-настоящему лучшей архитектуре.

    Цель — не быстрый код

    Этот урок требует наибольше времени, чтобы он по-настоящему усвоился.

    Работа над производительностью не заключается в создании самого быстрого возможного JavaScript.

    Речь идет о создании продукта, который кажется достаточно быстрым для тех, кто им фактически пользуется.

    Эти две цели — не одно и то же.

    Прекрасно оптимизированный алгоритм бесполезен, если пользователь вынужден ждать две секунды на обработку запроса, запускающего его.

    Идеально мемизированный компонент не помогает, если страница отрисовывает 40 компонентов, которые пользователь никогда даже не увидит.

    Меньший размер пакета автоматически не означает улучшения, если приложение по-прежнему блокирует основные действия из-за бесполезных операций.

    А улучшение показателей тестирования ничего не значит, если реальный пользователь вообще не ощущает разницы.

    Сегодня у разработчиков JavaScript есть доступ к большему количеству методов оптимизации, чем когда-либо раньше.

    Поэтому ещё важнее знать, что не стоит оптимизировать.

    Начните с акцента на пользователе.

    Определите истинную причину задержки.

    Правильно её измерьте.

    Проследите за ней по всей системе от начала до конца.

    Удалите всё, что не является необходимым.

    Только тогда стоит заняться ускорением оставшегося.

    Эта последовательность имеет значение.

    Потому что самая ценная оптимизация не обязательно является наиболее технически впечатляющей.

    Это та оптимизация, которая заставляет пользователя даже не замечать, что приложение изначально было медленным.

    Связанные статьи

    • Native Browser APIs Replacing Popular npm Packages in 2026 — Рассказывает о том, как встроенные функции JavaScript и CSS, такие как Signals, оператор pipeline, Temporal и Anchor Positioning, заменяют популярные npm-пакеты.
  • Настоящая сложность современного JavaScript происходит от инструментов, а не от самого языка — В этой статье объясняется, как такие основные функции JavaScript, как async/await и optional chaining, упрощают код, в то время как избыточные инструменты и зависимости создают ненужную сложность.
  • Как DataLayer GA4 определяет, являются ли ваши аналитические данные достоверными — Рассматривается, как DataLayer связывает веб-сайты и GTM с GA4, почему простые события нарушают отчетность в электронной коммерции, и как правильно структурировать и отлаживать передачу данных.