Оптимизировать выполнение JavaScript

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

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

Профилирование производительности JavaScript может быть своего рода искусством, поскольку написанный вами JavaScript не имеет ничего общего с кодом, который фактически выполняется. Современные браузеры используют JIT-компиляторы и всевозможные оптимизации и трюки, чтобы обеспечить максимально быстрое выполнение, и это существенно меняет динамику кода.

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

Краткое содержание

  • Избегайте setTimeout или setInterval для визуальных обновлений; вместо этого всегда используйте requestAnimationFrame.
  • Переместите долго выполняющийся JavaScript из основного потока в Web Workers.
  • Используйте микрозадачи, чтобы вносить изменения в DOM в нескольких кадрах.
  • Используйте временную шкалу Chrome DevTools и профилировщик JavaScript, чтобы оценить влияние JavaScript.

Используйте requestAnimationFrame для визуальных изменений.

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

/**
    * If run as a requestAnimationFrame callback, this
    * will be run at the start of the frame.
    */
function updateScreen(time) {
    // Make visual updates here.
}

requestAnimationFrame(updateScreen);

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

setTimeout приводит к тому, что браузер пропускает кадр.

Фактически, jQuery использовал setTimeout для своего animate поведения. В версии 3 он был изменен для использования requestAnimationFrame . Если вы используете более старую версию jQuery, вы можете исправить ее, чтобы использовать requestAnimationFrame , что настоятельно рекомендуется.

Уменьшите сложность или используйте Web Workers

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

Вам следует тактично подходить к тому, когда и как долго будет запускаться JavaScript. Например, если вы используете анимацию, подобную прокрутке, в идеале вам следует стремиться поддерживать время JavaScript в районе 3–4 мс . Если дольше, вы рискуете отнять слишком много времени. Если у вас период простоя, вы можете позволить себе более спокойно относиться к затраченному времени.

Во многих случаях вы можете перенести чистую вычислительную работу в Web Workers , если, например, для нее не требуется доступ к DOM. Манипулирование данными или их обход, например сортировка или поиск, часто хорошо подходят для этой модели, равно как и загрузка и генерация модели.

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = evt.data;
    // Update data on screen...
});

Не всякая работа может соответствовать этой модели: у веб-работников нет доступа к DOM. Если ваша работа должна выполняться в основном потоке, рассмотрите пакетный подход, при котором вы сегментируете более крупную задачу на микрозадачи, каждая из которых занимает не более нескольких миллисекунд, и выполняется внутри обработчиков requestAnimationFrame для каждого кадра.

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

Знайте «налог на фреймы» вашего JavaScript

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

Панель «Производительность» Chrome DevTools — лучший способ измерить стоимость вашего JavaScript. Обычно вы получаете записи низкого уровня следующим образом:

Запись производительности в Chrome DevTools

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

Вооружившись этой информацией, вы сможете оценить влияние JavaScript на производительность вашего приложения и начать находить и исправлять любые «горячие точки», в которых выполнение функций занимает слишком много времени. Как упоминалось ранее, вам следует попытаться либо удалить долго выполняющийся JavaScript, либо, если это невозможно, переместить его в веб-воркер, освободив основной поток для продолжения выполнения других задач.

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

Избегайте микрооптимизации вашего JavaScript

Возможно, было бы здорово узнать, что браузер может выполнить одну версию вещи в 100 раз быстрее, чем другую, например, запрос offsetTop элемента выполняется быстрее, чем вычисление getBoundingClientRect() , но почти всегда верно, что вы будете вызывать только функции подобное происходит небольшое количество раз за кадр, поэтому обычно бесполезно концентрироваться на этом аспекте производительности JavaScript. Обычно вы сэкономите лишь доли миллисекунд.

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

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