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