Статьи

Как избежать блокировки DOM в JavaScript

Программы JavaScript запускаются в браузере в одном потоке и во время выполнения, например, Node.js. Когда код выполняется на вкладке браузера, все остальное останавливается: команды меню, загрузки, рендеринг, обновления DOM и даже анимация GIF.

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

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

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

// raise an event when a button is clicked
document.getElementById('clickme').addEventListener('click', handleClick);

// handle button click event
function handleClick(e) {

  // get element to animate
  let sprite = document.getElementById('sprite');
  if (!sprite) return;

  // remove 'animate' class when animation ends
  sprite.addEventListener('animationend', () => {
    sprite.classList.remove('animate');
  });

  // add 'animate' class
  sprite.classList.add('animate');
}

ES2015 предоставил Promisesasync Для получения дополнительной информации см. « Управление потоком в современном JS ».

Блокирующие бандиты

К сожалению, некоторые операции JavaScript всегда будут синхронными, в том числе:

  • текущие расчеты
  • обновление DOM
  • используя localStorage или IndexedDB для хранения и извлечения данных.

Следующее перо показывает захватчик, который использует комбинацию CSS-анимации для перемещения и JavaScript для перемещения конечностей. Изображение справа — это основной анимированный GIF. Нажмите кнопку записи с 100 000 await

Обновления DOM блокируются во время этой операции. Захватчик останавливается или заикается в большинстве браузеров. Анимированная GIF-анимация в некоторых приостановится. На более медленных устройствах может отображаться предупреждение «скрипт не отвечает» .

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

Веб-работники

Одним из решений для длительных процессов являются веб-работники . Это позволяет основному браузерному приложению запускать фоновый скрипт и обмениваться данными с использованием событий сообщений. Например:

 sessionStorage operations

Скрипт веб-работника:

 // main.js
// are web workers supported?
if (!window.Worker) return;

// start web worker script
let myWorker = new Worker('myworker.js');

// message received from myWorker
myWorker.onmessage = e => {
  console.log('myworker sent:', e.data);
}

// send message to myWorker
myWorker.postMessage('hello');

Рабочий может даже порождать других рабочих для эмуляции сложных потоковых операций. Тем не менее, работники намеренно ограничены, и работник не может напрямую получить доступ к DOM или // myworker.js
// start when a message is received
onmessage = e => {
console.log('myworker received:', e.data);
// ... long-running process ...
// post message back
postMessage('result');
};
(это фактически сделает многопоточный JavaScript-код и нарушит стабильность браузера.) Поэтому все сообщения отправляются в виде строк, что позволяет передавать объекты в кодировке JSON, но не DOM-узлы.

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

(Node.js предлагает дочерние процессы, которые похожи на веб-работников, но имеют опции для запуска исполняемых файлов, написанных на других языках.)

Аппаратно-ускоренная анимация

Большинство современных браузеров не блокируют CSS-анимацию с аппаратным ускорением, которая запускается в их собственном слое.

По умолчанию, приведенный выше пример перемещает захватчик, изменяя window Это и подобные свойства, такие как left-marginleft

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

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

Хранение в памяти

Обновление объекта в памяти происходит значительно быстрее, чем при использовании механизма хранения, который записывает на диск. Выберите тип хранилища объектов на ручке и нажмите « написать» . Результаты могут отличаться, но они должны быть примерно в 10 раз быстрее, чем эквивалентная операция sessionStorage

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

 // get previously-saved data
var store = JSON.parse(localStorage.getItem('store'));

// initialise an empty store
if (!store || !store.initialized) {
  store = {
    initialized: true,
    username: 'anonymous'
    score: 0,
    best: { score: 1000, username: 'Alice' }
  }
};

// save to localStorage on page unload
window.addEventListener('unload', () => {
  localStorage.setItem('store', JSON.stringify(store));
});

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

  • нет активности пользователя (события мыши, касания или клавиатуры) в течение нескольких секунд
  • игра приостановлена ​​или вкладка приложения находится в фоновом режиме (см. API видимости страницы )
  • есть естественная пауза — например, когда игрок умирает, завершает уровень, перемещается между основными экранами и так далее.

Веб-производительность

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

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

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