Дело не в том, что написание исходного кода не имеет значения, но обычно оно мало что дает или вообще не влияет на общую схему вещей, особенно когда речь идет о микрооптимизации. Поэтому перед тем, как приступить к аргументу переполнения стека о .forEach
.map
против .forEach
против for
, сравнивая результаты с JSperf.com, убедитесь, что вы видите лес, а не только деревья. 50 кбит / с может звучать в 50 раз лучше, чем 1 кбит / с на бумаге, но в большинстве случаев это не будет иметь значения.
Разбор, компиляция и выполнение
По сути, проблема большинства неработоспособных JS заключается не в запуске самого кода, а во всех шагах, которые необходимо предпринять, прежде чем код даже начнет выполняться.
Мы говорим об уровнях абстракции здесь. Процессор в вашем компьютере работает машинный код. Большая часть кода, который вы запускаете на своем компьютере, находится в скомпилированном двоичном формате. (Я сказал код, а не программы , рассматривая все приложения Electron в наши дни.) То есть, за исключением всех абстракций на уровне ОС, он изначально работает на вашем оборудовании, никакой подготовительной работы не требуется.
JavaScript не предварительно скомпилирован. Он поступает (через относительно медленную сеть) как читаемый код в вашем браузере, который, по сути, является «ОС» для вашей программы JS.
Этот код сначала должен быть проанализирован, то есть прочитан и превращен в индексируемую компьютером структуру, которую можно использовать для компиляции. Затем он компилируется в байт-код и, наконец, машинный код, прежде чем он может быть выполнен вашим устройством / браузером.
Еще одна очень важная вещь, которую стоит упомянуть, это то, что JavaScript является однопоточным и работает в основном потоке браузера. Это означает, что одновременно может выполняться только один процесс. Если ваша временная шкала производительности DevTools заполнена желтыми пиками, когда ваш процессор загружен на 100%, у вас будут длинные / пропущенные кадры, резкая прокрутка и другие неприятные вещи.
Итак, есть вся эта работа, которую необходимо выполнить, прежде чем ваш JS начнет работать. Разбор и компиляция занимает до 50% общего времени выполнения JS в движке Chrome V8.
Есть две вещи, которые вы должны убрать из этого раздела:
- Хотя это не обязательно линейно, время разбора JS масштабируется с размером пакета. Чем меньше JS вы отправляете, тем лучше.
- Каждый используемый вами JS-фреймворк (React, Vue, Angular, Preact …) — это еще один уровень абстракции (если только он не скомпилирован, как Svelte ). Это не только увеличит размер пакета, но и замедлит ваш код, поскольку вы не общаетесь напрямую с браузером.
Есть способы смягчить это, например, использование сервисных работников для выполнения заданий в фоновом режиме и в другом потоке, использование asm.js для написания кода, который легче компилировать в машинные инструкции, но это целая «другая тема».
Однако вы можете избегать использования каркасов анимации JS для всего и читать о том, что вызывает рисование и макеты . Используйте библиотеки только тогда, когда нет абсолютно никакой возможности реализовать анимацию, используя обычные CSS-переходы и анимации.
Даже если они используют CSS-переходы, составные свойства и requestAnimationFrame()
, они все еще работают в JS в основном потоке. Они в основном просто забивают вашу модель DOM встроенными стилями каждые 16 мс, так как они мало что могут сделать. Вы должны убедиться, что все ваши JS будут выполняться менее чем за 8 мс на кадр, чтобы анимации были плавными.
CSS-анимация и переходы, с другой стороны, выполняются вне основного потока — на графическом процессоре, если реализованы качественно, не вызывая ретрансляции / перекомпоновки.
Учитывая, что большинство анимаций запускаются либо во время загрузки, либо во время взаимодействия с пользователем, это может дать вашим веб-приложениям необходимую возможность для дыхания.
Web Animations API — это новый набор функций, который позволит вам создавать производительные JS-анимации из основного потока, но пока придерживайтесь CSS-переходов и методов, таких как FLIP .
Размеры комплекта — все
Сегодня это все о связках. Прошли времена Бауэра и десятки тегов <script>
перед закрывающим </body>
.
Теперь все дело в npm install
любой новой блестящей игрушке, которую вы найдете в NPM, объединяющей их вместе с Webpack в один большой JS-файл объемом 1 МБ и заставляющем браузер ваших пользователей сканировать свои данные.
Попробуйте отправить меньше JS. Вам может не понадобиться вся библиотека Lodash для вашего проекта. Вам абсолютно необходимо использовать JS Framework? Если да, рассматривали ли вы возможность использования чего-либо, кроме React, например, Preact или HyperHTML , которые меньше чем 1/20 размера React? Вам нужен TweenMax для анимации прокрутки вверх? Удобство npm и изолированных компонентов в фреймворках имеет недостаток: первым ответом разработчиков на проблему стало использование JS. Когда у тебя есть только молоток, все выглядит как гвоздь.
Когда вы закончите обрезку сорняков и отгрузите меньше JS, попробуйте отправлять это умнее . Отправляйте то, что вам нужно, когда вам это нужно.
Webpack 3 имеет удивительные функции, называемые разделением кода и динамическим импортом . Вместо объединения всех ваших модулей JS в монолитный пакет app.js
он может автоматически разбивать код с помощью синтаксиса import()
и загружать его асинхронно.
Вам также не нужно использовать каркасы, компоненты и маршрутизацию на стороне клиента, чтобы получить выгоду от этого. Допустим, у вас есть сложный кусок кода, который .mega-widget
ваш .mega-widget
, который может быть на любом количестве страниц. Вы можете просто написать следующее в своем основном файле JS:
if (document.querySelector('.mega-widget')) { import('./mega-widget'); }
Если ваше приложение находит виджет на странице, оно динамически загружает необходимый вспомогательный код. В остальном все хорошо.
Кроме того, для работы Webpack требуется собственная среда выполнения, и он внедряет ее во все генерируемые им файлы .js. Если вы используете плагин commonChunks
, вы можете использовать следующее для извлечения среды выполнения в ее собственный блок :
new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', }),
Он извлечет среду выполнения из всех ваших других блоков в свой собственный файл, в данном случае с именем runtime.js
. Просто убедитесь, что он загружен до вашего основного пакета JS. Например:
<script src="runtime.js"> <script src="main-bundle.js">
Тогда есть тема переданного кода и полифилов. Если вы пишете современный (ES6 +) JavaScript, вы, вероятно, используете Babel для преобразования его в ES5-совместимый код. Транспортировка не только увеличивает размер файла из-за всей многословности, но также и сложности, и это часто имеет регрессии производительности по сравнению с собственным кодом ES6 +.
Наряду с этим вы, вероятно, используете пакет babel-polyfill
и whatwg-fetch
для исправления отсутствующих функций в старых браузерах. Затем, если вы пишете код с использованием async/await
, вы также переносите его с помощью генераторов, необходимых для включения regenerator-runtime
…
Дело в том, что вы добавляете почти 100 килобайт в свой пакет JS, который имеет не только огромный размер файла, но также огромные затраты на синтаксический анализ и выполнение для поддержки старых браузеров.
Однако нет смысла наказывать людей, использующих современные браузеры. Подход, который я использую, и который Филипп Уолтон рассмотрел в этой статье , заключается в создании двух отдельных пакетов и их условной загрузке. Babel делает это легко с помощью babel-preset-env
. Например, у вас есть один пакет для поддержки IE 11, а другой без полизаполнения для последних версий современных браузеров.
Грязный, но эффективный способ — поместить следующее во встроенный скрипт:
(function() { try { new Function('async () => {}')(); } catch (error) {
Если браузер не может оценить async
функцию, мы предполагаем, что это старый браузер, и просто отправляем полизаполненный пакет. В противном случае пользователь получает аккуратный и современный вариант.
Вывод
Мы хотели бы, чтобы вы извлекли пользу из этой статьи, что JS стоит дорого, и его следует использовать с осторожностью.
Убедитесь, что вы тестируете производительность вашего сайта на бюджетных устройствах в реальных условиях сети. Ваш сайт должен загружаться быстро и быть интерактивным как можно скорее. Это означает доставку меньше JS и доставку быстрее любыми необходимыми способами. Ваш код всегда должен быть уменьшен, разбит на более мелкие, управляемые пакеты и загружаться асинхронно, когда это возможно. На стороне сервера убедитесь, что в нем включен HTTP / 2 для более быстрой параллельной передачи, а также сжатие gzip / Brotli для существенного сокращения размеров передачи вашего JS.
И с этим сказал, я хотел бы закончить со следующим твитом: