Оптимизация производительности — это палка о двух концах, и хорошая, и плохая. Хорошая сторона заключается в том, что это может повысить производительность веб-сайта, а плохая сторона заключается в том, что настройка вызывает затруднения или слишком много правил, которым нужно следовать. А некоторые правила оптимизации производительности применимы не ко всем сценариям и должны использоваться с осторожностью Читателям предлагается прочитать эту статью критически.
Источники цитат, связанных с предложениями по оптимизации в этой статье, будут даны после предложений или в конце текста.
1. Уменьшите HTTP-запросы
Полный HTTP-запрос должен пройти поиск DNS, рукопожатие TCP, браузер отправляет HTTP-запрос, сервер получает запрос, сервер обрабатывает запрос и отправляет ответ, а браузер получает ответ. Давайте рассмотрим конкретный пример, чтобы помочь понять HTTP:
Это HTTP-запрос, и размер запрошенного файла составляет 28,4 КБ.
Глоссарий:
- Очередь: время в очереди запросов.
- Задержка: разница во времени между моментом установления TCP-соединения и моментом фактической передачи данных, включая время согласования прокси-сервера.
- Согласование прокси: время, затраченное на согласование с подключением к прокси-серверу.
- DNS-поиск: время, необходимое для выполнения DNS-поиска, каждый отдельный домен на странице должен выполнять DNS-поиск.
- Начальное соединение/подключение: время, затраченное на установление соединения, включая подтверждение/повторные попытки TCP и согласование SSL.
- SSL: время, затраченное на рукопожатие SSL.
- Отправленный запрос: время, затраченное на отправку сетевого запроса, обычно одна миллисекунда.
- Ожидание (TFFB): TFFB — это время от выдачи запроса страницы до получения первого байта данных ответа.
- Загрузка содержимого: время, затраченное на получение данных ответа.
Из этого примера видно, что доля времени, затрачиваемая на фактическую загрузку данных, равна13.05 / 204.16 = 6.39%
, чем меньше файл, тем меньше коэффициент, а чем больше файл, тем выше коэффициент. Вот почему рекомендуется объединять несколько небольших файлов в один большой файл, тем самым уменьшая количество HTTP-запросов.
Использованная литература:
2. Использование HTTP2
По сравнению с HTTP1.1, HTTP2 имеет следующие преимущества:
Быстрый парсинг
Когда сервер анализирует запрос HTTP 1.1, он должен продолжать считывать байты, пока не встретит разделитель CRLF. И анализ запросов HTTP2 не должен быть таким хлопотным, потому что HTTP2 — это протокол, основанный на кадрах, и каждый кадр имеет поле, указывающее длину кадра.
мультиплексирование
HTTP1.1 Если вы хотите инициировать несколько запросов одновременно, вам необходимо установить несколько соединений TCP, поскольку соединение TCP может одновременно обрабатывать только один запрос HTTP1.1.
В HTTP2 несколько запросов могут совместно использовать одно соединение TCP, что называется мультиплексированием. Один и тот же запрос и ответ представлены потоком и идентифицированы уникальным идентификатором потока. Несколько запросов и ответов могут быть отправлены не по порядку в TCP-соединении, а затем восстановлены по идентификатору потока по прибытии в пункт назначения.
сжатие заголовка
HTTP2 обеспечивает сжатие заголовков.
Например, есть следующие два запроса:
:authority: unpkg.zhimg.com
:method: GET
:path: /za-js-sdk@2.16.0/dist/zap.js
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
:authority: zz.bdstatic.com
:method: GET
:path: /linksubmit/push.js
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
Как видно из двух приведенных выше запросов, много данных дублируется. Если вы можете хранить один и тот же заголовок и отправлять между ними только разные части, вы можете сэкономить много трафика и ускорить время запроса.
HTTP/2 использует «таблицу заголовков» на стороне клиента и сервера для отслеживания и хранения ранее отправленных пар ключ-значение и больше не отправляет каждый запрос и ответ для одних и тех же данных.
Давайте рассмотрим упрощенный пример, предполагая, что клиент отправляет следующие заголовки запроса по порядку:
Header1:foo
Header2:bar
Header3:bat
Когда клиент отправляет запрос, он создает таблицу на основе значений заголовка:
показатель | имя заголовка | стоимость |
---|---|---|
62 | Header1 | foo |
63 | Header2 | bar |
64 | Header3 | bat |
Если сервер получит запрос, он создаст таблицу, как обычно. Когда клиент отправляет следующий запрос, если заголовки совпадают, он может напрямую отправить блок заголовка следующим образом:
62 63 64
Сервер будет искать ранее созданную таблицу и восстанавливать числа до полного заголовка, соответствующего индексу.
приоритет
HTTP2 может устанавливать более высокий приоритет для более срочных запросов, и сервер может отдавать приоритет обработке таких запросов после их получения.
управление потоком
Поскольку пропускная способность трафика TCP-подключения (в зависимости от пропускной способности сети от клиента к серверу) фиксирована, при наличии нескольких одновременных запросов один запрос будет занимать больше трафика, а другой запрос будет занимать меньше трафика. Управление потоком может точно контролировать поток различных потоков.
пуш сервера
Новая мощная функция, добавленная в HTTP2, заключается в том, что сервер может отправлять несколько ответов на запрос клиента. Другими словами, в дополнение к ответу на первоначальный запрос сервер может передавать дополнительные ресурсы клиенту без явного запроса клиента.
Например, когда браузер запрашивает веб-сайт, сервер не только возвращает HTML-страницу, но и заранее передает ресурсы в соответствии с URL-адресом ресурса на HTML-странице.
Теперь многие веб-сайты начали использовать HTTP2, например, Zhihu:
Где h2 относится к протоколу HTTP2, а http/1.1 относится к протоколу HTTP1.1.
Использованная литература:
3. Используйте рендеринг на стороне сервера
Рендеринг на стороне клиента: получите файл HTML, при необходимости загрузите файл JavaScript, запустите файл, сгенерируйте DOM и выполните рендеринг.
Рендеринг на стороне сервера: сервер возвращает файл HTML, а клиенту нужно только проанализировать HTML.
- Преимущества: быстрый рендеринг в верхней части страницы, хорошее SEO.
- Недостатки: Конфигурация сложна, что увеличивает вычислительную нагрузку сервера.
Ниже я использую Vue SSR в качестве примера, чтобы кратко описать процесс SSR.
Процесс рендеринга клиента
- Получите доступ к клиентскому веб-сайту.
- Сервер возвращает оператор, содержащий оператор импорта ресурсов и
<div id="app"></div>
HTML-файл. - Клиент запрашивает ресурсы с сервера через HTTP, и когда необходимые ресурсы загружены, выполняет
new Vue()
Начните создание и визуализацию страницы.
Процесс рендеринга на стороне сервера
- Посетите веб-сайт, созданный сервером.
- Сервер просматривает, какие файлы ресурсов необходимы текущему компоненту маршрутизации, и заполняет HTML-файл содержимым этих файлов. Если есть запрос ajax, он будет выполнен для предварительной выборки данных и заполнения их в файле HTML и, наконец, возврата страницы HTML.
- Когда клиент получает эту HTML-страницу, он может немедленно начать визуализацию страницы. При этом страница также будет загружать ресурсы, когда необходимые ресурсы будут загружены, начнется выполнение.
new Vue()
Начните создание экземпляра и захватите страницу.
Как видно из двух вышеперечисленных процессов, разница заключается во втором шаге. Веб-сайт, визуализируемый на стороне клиента, возвращает HTML-файл напрямую, а веб-сайт, отображаемый на стороне сервера, возвращает HTML-файл после рендеринга страницы.
Какая польза от этого? более быстрое время до контента.
Предположим, вашему веб-сайту необходимо загрузить четыре файла abcd перед рендерингом. И размер каждого файла составляет 1M.
Рассчитайте следующим образом: веб-сайт, отображаемый на стороне клиента, должен загрузить 4 файла и HTML-файлы для завершения рендеринга домашней страницы, а общий размер составляет 4 МБ (без учета размера HTML-файла). Веб-сайту, визуализируемому на стороне сервера, нужно только загрузить визуализированный HTML-файл для завершения рендеринга домашней страницы, а общий размер — это HTML-файл, который был визуализирован (этот тип файла не будет слишком большим, обычно несколько сотен КБ, загруженный HTML-файл моего личного веб-сайта блога (SSR) составляет 400 КБ).Вот почему рендеринг на стороне сервера быстрее.
Использованная литература:
4. Используйте CDN для статических ресурсов
Сеть доставки контента (CDN) — это группа веб-серверов, распределенных в разных географических точках. Все мы знаем, что чем дальше сервер от пользователя, тем выше задержка. CDN предназначены для решения этой проблемы, развертывая серверы в нескольких местах, приближая пользователей к серверу, тем самым сокращая время запросов.
Принцип CDN
Когда пользователь посещает веб-сайт, если CDN отсутствует, процесс выглядит следующим образом:
- Браузер должен преобразовать доменное имя в IP-адрес, поэтому ему необходимо сделать запрос к локальному DNS.
- Локальный DNS поочередно отправляет запросы на корневой сервер, сервер доменных имен верхнего уровня и авторитетный сервер, чтобы получить IP-адрес сервера веб-сайта.
- Локальный DNS отправляет IP-адрес обратно в браузер, который делает запрос к IP-адресу веб-сервера и получает ресурс.
Если на веб-сайте, который посещает пользователь, развернута CDN, процесс выглядит следующим образом:
- Браузер должен преобразовать доменное имя в IP-адрес, поэтому ему необходимо сделать запрос к локальному DNS.
- Локальный DNS по очереди отправляет запросы на корневой сервер, сервер доменных имен верхнего уровня и уполномоченный сервер, чтобы получить IP-адрес глобальной системы балансировки нагрузки (GSLB).
- Локальный DNS отправляет запрос в GSLB.Основная функция GSLB — определить местоположение пользователя на основе IP-адреса локального DNS, отфильтровать локальную систему балансировки нагрузки (SLB), которая находится ближе к пользователю, и вернуть пользователю IP-адрес SLB в качестве результата Локальный DNS.
- Локальный DNS отправляет IP-адрес SLB обратно в браузер, который делает запрос к SLB.
- В соответствии с ресурсами и адресами, запрошенными браузером, SLB выбирает оптимальный сервер кэширования и отправляет его обратно в браузер.
- Затем браузер перенаправляется на сервер кеша в соответствии с адресом, отправленным обратно SLB.
- Если у кэш-сервера есть ресурс, который нужен браузеру, он отправляет ресурс обратно в браузер. Если нет, запросите ресурс с исходного сервера, отправьте его в браузер и кэшируйте локально.
Использованная литература:
5. Поместите CSS вверху файла, а файлы JavaScript внизу.
- Выполнение CSS блокирует рендеринг, блокируя выполнение JS
- Загрузка и выполнение JS блокирует синтаксический анализ HTML, предотвращая сборки CSSOM
Если эти теги CSS и JS помещены в тег HEAD и их загрузка и анализ занимают много времени, страница будет пустой. Поэтому файл JS следует поместить внизу (не блокирует синтаксический анализ DOM, но будет блокировать рендеринг), а затем загружать файл JS после синтаксического анализа HTML, чтобы как можно скорее представить содержимое страницы пользователю.
Так почему же файл CSS все еще находится в заголовке?
Поскольку загрузка сначала HTML, а затем загрузка CSS сделает страницу, которую пользователь видит в первый раз нестилизованной и «уродливой», во избежание этого файл CSS должен быть помещен в голову.
Кроме того, не исключено, что JS-файлы будут размещены в голове, если вы добавите атрибут defer к тегу script, асинхронную загрузку и отложенное выполнение.
Использованная литература:
6. Используйте значок шрифта iconfont вместо значка изображения
Значок шрифта предназначен для превращения значка в шрифт, который при использовании совпадает с шрифтом, и вы можете установить свойства, такие как размер шрифта, цвет и т. Д., Что очень удобно. Иконка шрифта является векторной иллюстрацией и не будет искажена. Еще одним преимуществом является то, что полученные файлы имеют особенно маленький размер.
Сжать файлы шрифтов
использоватьfontmin-webpackПлагин сжимает файлы шрифтов (спасибоФронтенд Сяовэйпоставка).
Использованная литература:
7. Эффективно используйте кеш и не загружайте одни и те же ресурсы повторно
Чтобы пользователям не приходилось запрашивать файлы каждый раз, когда они посещают веб-сайт, мы можем контролировать это поведение, добавляя Expires или max-age. Expires устанавливает время, пока до этого времени браузер не будет запрашивать файл, а будет напрямую использовать кеш. Хотя max-age — это относительное время, рекомендуется использовать max-age вместо Expires.
Но это создаст проблему, что делать при обновлении файла? Как я могу сказать браузеру повторно запросить файл?
Обновляя адрес ссылки на ресурс, указанный на странице, браузер может активно отказаться от кеша и загрузить новый ресурс.
Конкретный метод заключается в том, чтобы связать изменение URL-адреса ресурса с содержимым файла, то есть только изменение содержимого файла приведет к изменению соответствующего URL-адреса, чтобы обеспечить точное управление кешем в файле. уровень. Что связано с содержимым файла? Мы, естественно, подумаем об использованииДайджест данных в алгоритмСводная информация получается для файла, и сводная информация соответствует содержимому файла один за другим, и существует основа управления кэшем, которая может быть точной до детализации одного файла.
Использованная литература:
- webpack + express обеспечивает точное кэширование файлов
- webpack-кеш
- Чжан Юньлун — Как разработать и внедрить интерфейсный код в крупной компании?
8. Сжать файл
Сжатие файлов может сократить время загрузки файлов и улучшить взаимодействие с пользователем.
Благодаря развитию webpack и node сжимать файлы стало очень удобно.
В webpack вы можете использовать следующие плагины для сжатия:
- JavaScript: UglifyPlugin
- CSS: MiniCssExtractPlugin
- HTML: HtmlWebpackPlugin
На самом деле, мы можем сделать лучше. То есть использовать сжатие gzip. Это можно включить, добавив флаг gzip в заголовок Accept-Encoding в заголовках HTTP-запросов. Разумеется, сервер также должен поддерживать эту функцию.
gzip на сегодняшний день является самым популярным и эффективным методом сжатия. Например, размер файла app.js, созданного после сборки проекта, который я разработал с помощью Vue, составляет 1,4 МБ, а после сжатия gzip — всего 573 КБ, что уменьшает размер почти на 60%.
Прикрепите использование веб-пакета и конфигурации узла gzip.
Скачать плагин
npm install compression-webpack-plugin --save-dev
npm install compression
конфигурация веб-пакета
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [new CompressionPlugin()],
}
конфигурация узла
const compression = require('compression')
// 在其他中间件前使用
app.use(compression())
9. Оптимизация изображения
(1) Отложенная загрузка изображения
На странице путь к изображению не задается первым, а реальное изображение загружается только тогда, когда изображение появляется в видимой области браузера, что является ленивой загрузкой. Для веб-сайтов с большим количеством изображений одновременная загрузка всех изображений сильно повлияет на взаимодействие с пользователем, поэтому необходимо использовать отложенную загрузку изображений.
Во-первых, вы можете установить изображение так, изображение не будет загружаться, когда страница не видна:
<img data-src="https://avatars0.githubusercontent.com/u/22117876?s=460&u=7bd8f32788df6988833da6bd155c3cfbebc68006&v=4">
Когда страница видна, используйте JS для загрузки изображения:
const img = document.querySelector('img')
img.src = img.dataset.src
Таким образом картинка загружается, и полный код можно увидеть в справочных материалах.
Использованная литература:
(2) Адаптивные изображения
Преимущество адаптивных изображений заключается в том, что браузер может автоматически загружать подходящее изображение в зависимости от размера экрана.
пройти черезpicture
выполнить
<picture>
<source srcset="banner_w1000.jpg" media="(min-width: 801px)">
<source srcset="banner_w800.jpg" media="(max-width: 800px)">
<img src="banner_w800.jpg" alt="">
</picture>
пройти через@media
выполнить
@media (min-width: 769px) {
.bg {
background-image: url(bg1080.jpg);
}
}
@media (max-width: 768px) {
.bg {
background-image: url(bg768.jpg);
}
}
(3) Отрегулируйте размер изображения
Например, у вас есть изображение размером 1920*1080, которое отображается пользователю в виде миниатюры, а полное изображение отображается только тогда, когда пользователь наводит на него курсор мыши. Если пользователь никогда не наводит указатель мыши на миниатюру, загрузка изображения тратится впустую.
Таким образом, мы можем использовать два изображения для оптимизации. Изначально загружаются только миниатюры, а когда пользователь наводит курсор на изображение, загружается более крупное изображение. Существует также способ отложить загрузку большого изображения и вручную изменить src большого изображения для загрузки после загрузки всех элементов.
(4) Уменьшите качество изображения.
Например, изображения JPG с качеством 100% и качеством 90% обычно неразличимы, особенно при использовании в качестве фонового изображения. Я часто использую PS для вырезания фонового изображения, вырезаю изображение в формате JPG и сжимаю его до качества 60%, в основном не вижу разницы.
Есть два метода сжатия, один через плагин webpackimage-webpack-loader
, второй — сжать через онлайн-сайт.
Плагин webpack прикреплен нижеimage-webpack-loader
Применение.
npm i -D image-webpack-loader
конфигурация веб-пакета
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000, /* 图片大小小于1000字节限制时会自动转成 base64 码引用*/
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
/*对图片进行压缩*/
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
}
(5) Максимально используйте эффекты CSS3 вместо изображений.
Есть много изображений, которые можно нарисовать с помощью CSS-эффектов (градиентов, теней и т. д.), и в этом случае лучше использовать CSS3. Потому что размер кода обычно составляет долю или даже десятую часть размера изображения.
Использованная литература:
(6) Изображения в формате webp
Преимущество WebP заключается в том, что он имеет лучший алгоритм сжатия данных изображения, который может привести к уменьшению размера изображения, и качество изображения, неразличимое невооруженным глазом, а также режимы сжатия без потерь и с потерями, альфа-прозрачность и анимацию. , эффект преобразования в JPEG и PNG довольно хороший, стабильный и равномерный.
Использованная литература:
10. Загружайте код по запросу через веб-пакет, извлекайте код третьей библиотеки и уменьшайте избыточный код с ES6 до ES5.
Отложенная загрузка или загрузка по запросу — отличный способ оптимизировать веб-страницы или приложения. Этот метод фактически разделяет ваш код в некоторых логических точках останова, а затем ссылается или собирается ссылаться на другие новые блоки кода после выполнения определенных операций в некоторых блоках кода. Это ускоряет первоначальную загрузку приложения и уменьшает его общий размер, поскольку некоторые блоки кода могут никогда не загрузиться.
Создавайте имена файлов на основе содержимого файлов и динамически импортируйте компоненты с помощью функции импорта для обеспечения загрузки по требованию.
Это требование может быть достигнуто путем настройки свойства имени файла вывода. В параметре значения атрибута имени файла есть [contenthash], который создаст уникальный хэш на основе содержимого файла. Когда содержимое файла изменяется, [contenthash] также изменяется.
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, '../dist'),
},
Извлечь сторонние библиотеки
Поскольку представленные сторонние библиотеки, как правило, относительно стабильны, они не будут часто меняться. Поэтому лучше извлекать их отдельно в качестве долгосрочного кеша. Здесь вам нужно использовать опцию cacheGroups плагина webpack4 splitChunk.
optimization: {
runtimeChunk: {
name: 'manifest' // 将 webpack 的 runtime 代码拆分为一个单独的 chunk。
},
splitChunks: {
cacheGroups: {
vendor: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
},
}
},
- test: используется для контроля того, какие модули соответствуют этой группе кеша. Если передано как есть, по умолчанию будут выбраны все модули. Типы значений, которые можно передавать: RegExp, String и Function;
- приоритет: указывает вес извлечения, чем больше число, тем выше приоритет. Поскольку модуль может удовлетворять условиям нескольких групп cacheGroups, последнее слово остается за модулем с наибольшим весом;
- ReuseExistingchunk: указывает, использовать ли существующий фрагмент, если true, если текущий фрагмент содержит извлеченный модуль, а затем новый.
- minChunks (по умолчанию 1): минимальное количество ссылок на этот блок кода перед разделением.
- куски (по умолчанию асинхронные): начальные, асинхронные и все
- имя (имя упакованных чанков): строка или функция (функция может настроить имя по условиям)
Сокращение избыточного кода с ES6 до ES5
Преобразованный код Babel должен использовать некоторые вспомогательные функции для достижения той же функции, что и исходный код, например:
class Person {}
будет преобразовано в:
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Person = function Person() {
_classCallCheck(this, Person);
};
здесь_classCallCheck
только одинhelper
функция, если классы объявлены во многих файлах, то таких будет многоhelper
функция.
здесь@babel/runtime
Пакет объявляет все вспомогательные функции, которые необходимо использовать, и@babel/plugin-transform-runtime
Эффект заключается в объединении всехhelper
функциональный файл из@babel/runtime包
Внесите:
"use strict";
var _classCallCheck2 = require("@babel/runtime/helpers/classCallCheck");
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
var Person = function Person() {
(0, _classCallCheck3.default)(this, Person);
};
здесь не компилируетсяhelper
функцияclassCallCheck
, а прямо цитируется@babel/runtime
серединаhelpers/classCallCheck
.
Установить
npm i -D @babel/plugin-transform-runtime @babel/runtime
использоватьсуществует.babelrc
в файле
"plugins": [
"@babel/plugin-transform-runtime"
]
Использованная литература:
- В Babel 7.1 представлена оболочка полифилла во время выполнения преобразования.
- ленивая загрузка
- Отложенная загрузка маршрутизации Vue
- кеш веб-пакета
- Шаг за шагом, чтобы понять плагин splitChunk для webpack4
11. Уменьшить перерисовку при перерисовке
процесс рендеринга в браузере
- Разобрать HTML для создания дерева DOM.
- Разобрать CSS для создания дерева правил CSSOM.
- Разбирать JS, манипулировать деревом DOM и деревом правил CSSOM.
- Объедините дерево DOM с деревом правил CSSOM, чтобы создать дерево рендеринга.
- Пройдите по дереву рендеринга, чтобы начать компоновку, и рассчитайте информацию о положении и размере каждого узла.
- Браузер отправляет данные для всех слоев на графический процессор, который компилирует и отображает слои на экране.
переставлять
При изменении положения или размера элемента DOM браузер повторно создает дерево рендеринга, этот процесс называется перекомпоновкой.
перерисовать
Когда дерево рендеринга регенерируется, каждый узел дерева рендеринга рисуется на экране.Этот процесс называется перерисовкой. Не все действия вызовут перекомпоновку, например изменение цвета шрифта, что вызовет только перерисовку. Помните, что перекомпоновка вызывает перерисовку, а перерисовка не вызывает перерисовку.
И перекомпоновка, и перерисовка — очень затратные операции, потому что поток движка JavaScript и поток рендеринга графического интерфейса являются взаимоисключающими, и только один из них может работать одновременно.
Какая операция вызовет перестановку?
- Добавить или удалить видимые элементы DOM
- изменение позиции элемента
- изменение размера элемента
- Изменения содержания
- Изменение размера окна браузера
Как уменьшить ОТМУТ И REDRAW?
- При изменении стиля с JavaScript лучше не писать стиль напрямую, а заменить класс, чтобы изменить стиль.
- Если вы хотите выполнить ряд операций с элементом DOM, вы можете вывести элемент DOM из потока документа и вернуть его в документ после завершения модификации. Рекомендуется использовать скрытые элементы (display:none) или фрагменты документа (DocumentFragement), которые могут хорошо реализовать это решение.
12. Использование делегирования событий
Делегирование событий использует всплывающую подсказку событий и может управлять всеми событиями определенного типа, указав только один обработчик событий. Все события, в которых используются кнопки (большинство событий мыши и событий клавиатуры), подходят для использования технологии делегирования событий. Использование делегирования событий может сэкономить память.
<ul>
<li>苹果</li>
<li>香蕉</li>
<li>凤梨</li>
</ul>
// good
document.querySelector('ul').onclick = (event) => {
const target = event.target
if (target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}
// bad
document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
})
13. Помните о местоположении программы
Хорошо написанная компьютерная программа часто имеет хорошую локальность.Они имеют тенденцию ссылаться на элементы данных рядом с последним элементом данных, на который ссылались, или на сам недавно процитированный элемент данных.Эта тенденция называется принципом локальности. Программа с хорошей локализацией работает быстрее, чем программа с плохой локальностью.
Локальность обычно принимает две разные формы:
- Временная локальность: в программе с хорошей временной локальностью ячейка памяти, на которую ссылаются один раз, вероятно, будет использоваться много раз в недалеком будущем.
- Пространственная локальность: в программе с хорошей пространственной локальностью, если на ячейку памяти ссылаются один раз, программа, вероятно, будет ссылаться на ближайшую ячейку памяти в не слишком отдаленном будущем.
Пример временной локализации
function sum(arry) {
let i, sum = 0
let len = arry.length
for (i = 0; i < len; i++) {
sum += arry[i]
}
return sum
}
В этом примере на переменную sum ссылаются один раз за итерацию цикла, поэтому для суммы имеется хорошее локальное время.
Пример пространственной местности
Программы с хорошей пространственной локацией
// 二维数组
function sum1(arry, rows, cols) {
let i, j, sum = 0
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
sum += arry[i][j]
}
}
return sum
}
Пространственная местность плохой программы
// 二维数组
function sum2(arry, rows, cols) {
let i, j, sum = 0
for (j = 0; j < cols; j++) {
for (i = 0; i < rows; i++) {
sum += arry[i][j]
}
}
return sum
}
Глядя на два приведенных выше примера пространственной локальности, способ последовательного доступа к каждому элементу массива, начиная с каждой строки в примере, называется эталонным шаблоном с шагом 1. Если в массиве осуществляется доступ ко всем k элементам, он называется эталонным шаблоном с шагом k. В общем, с увеличением размера шага пространственная локальность уменьшается.
В чем разница между этими двумя примерами? Отличие состоит в том, что в первом примере массив просматривается построчно и после каждой строки просматривается следующая строка; во втором примере массив просматривается по столбцам, просматривается элемент в строке и сразу же просматривается тот же элемент в следующей строке. элемент столбца.
Массивы хранятся в памяти в порядке строк, и в результате пример сканирования массива построчно получает эталонный шаблон с шагом 1, который имеет хорошую пространственную локальность; в то время как другой пример имеет шаг строк, который имеет крайне плохую пространственную локализацию.
Тестирование производительности
Рабочая среда:
- cpu: i5-7400
- Браузер: хром 70.0.3538.110
Выполните 10 пространственных испытаний на местность на двухмерном массиве длительности 9000 (длина подпараметрического массива также 9000), а время (миллисекунды) усредняется. Результаты следующие:
Как использовано в качестве примера выше, два примера пространственной локальности
размер шага 1 | Размер шага 9000 |
---|---|
124 | 2316 |
Судя по приведенным выше результатам теста, время выполнения массива с шагом 1 на порядок меньше, чем у массива с шагом 9000.
Суммировать:
- Программы, которые неоднократно обращаются к одной и той же переменной, имеют хорошую временную локальность.
- Для программы с эталонным шаблоном с размером шага k чем меньше размер шага, тем лучше пространственная локальность; в то время как программа, скачущая в памяти с большим размером шага, будет иметь плохую пространственную локальность.
Использованная литература:
14. если-иначе против переключателя
Когда количество условий суждения увеличивается, более склонны использовать switch вместо if-else.
if (color == 'blue') {
} else if (color == 'yellow') {
} else if (color == 'white') {
} else if (color == 'black') {
} else if (color == 'green') {
} else if (color == 'orange') {
} else if (color == 'pink') {
}
switch (color) {
case 'blue':
break
case 'yellow':
break
case 'white':
break
case 'black':
break
case 'green':
break
case 'orange':
break
case 'pink':
break
}
В приведенном выше случае лучше использовать переключатель с точки зрения удобочитаемости (оператор переключателя js реализован не на основе хэша, а на циклической оценке, поэтому if-else и переключатель одинаковы с точки зрения производительности).
15. Таблица поиска
Если использование switch и if-else не лучший выбор, когда слишком много условных операторов, попробуйте таблицу поиска. Таблицы поиска могут быть созданы с использованием массивов и объектов.
switch (index) {
case '0':
return result0
case '1':
return result1
case '2':
return result2
case '3':
return result3
case '4':
return result4
case '5':
return result5
case '6':
return result6
case '7':
return result7
case '8':
return result8
case '9':
return result9
case '10':
return result10
case '11':
return result11
}
Этот оператор switch можно превратить в таблицу поиска.
const results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11]
return results[index]
Если условный оператор является не числом, а строкой, объект можно использовать для построения таблицы поиска.
const map = {
red: result0,
green: result1,
}
return map[color]
16. Избегайте зависаний страницы
60 кадров в секунду и частота обновления устройства
Большинство устройств в настоящее время имеют частоту обновления экрана 60 раз в секунду. Следовательно, если на странице присутствует анимация или эффект градиента, или пользователь прокручивает страницу, скорость, с которой браузер отображает каждый кадр анимации или страницы, также должна соответствовать частоте обновления экрана устройства.
Бюджетное время для каждого кадра составляет чуть более 16 мс (1 секунда / 60 = 16,66 мс). Но на самом деле у браузера есть кропотливая работа, поэтому вся ваша работа должна быть выполнена за 10 мс. Если этот бюджет не может быть выполнен, частота кадров упадет, а контент будет дрожать на экране. Это явление, обычно называемое заиканием, может негативно повлиять на работу пользователя.
Предположим, вы используете JavaScript для модификации DOM и изменения стиля, выполняете перестановку и перерисовку и, наконец, рисуете ее на экране. Если какое-либо из них выполняется слишком долго, рендеринг кадра займет слишком много времени, и средняя частота кадров упадет. Предположим, что этот кадр занял 50 мс, тогда частота кадров в это время составляет 1 с / 50 мс = 20 кадров в секунду, и страница выглядит так, как будто она зависла.
Для некоторого продолжительного выполнения JavaScript мы можем использовать таймеры для разделения и задержки выполнения.
for (let i = 0, len = arry.length; i < len; i++) {
process(arry[i])
}
Предположим, что приведенная выше структура цикла возникает из-за высокой сложности process() или слишком большого количества элементов массива, или даже того и другого, вы можете попробовать сегментацию.
const todo = arry.concat()
setTimeout(function() {
process(todo.shift())
if (todo.length) {
setTimeout(arguments.callee, 25)
} else {
callback(arry)
}
}, 25)
Если вам интересно узнать больше, вы можете проверить этоВысокопроизводительный JavaScriptГлава 6 иЭффективный интерфейс: практика веб-эффективного программирования и оптимизацииГлава 3.
Использованная литература:
17. Используйте requestAnimationFrame для реализации визуальных изменений
Из пункта 16 мы знаем, что у большинства устройств частота обновления экрана составляет 60 раз в секунду, что означает, что среднее время на кадр составляет 16,66 миллисекунды. При использовании JavaScript для создания анимации в лучшем случае каждый код выполняется в начале кадра. И единственный способ гарантировать, что 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
Для анимации функция обратного вызова будет запускаться в какой-то момент кадра, возможно, в самом конце, что часто может привести к тому, что мы пропустим кадры и вызовем заикание.
Использованная литература:
18. Использование веб-воркеров
Web Worker не зависит от основного потока, так как использует другие рабочие потоки и может выполнять задачи, не мешая пользовательскому интерфейсу. Рабочий процесс может отправлять сообщения коду JavaScript, который его создал, отправляя сообщения обработчикам событий, указанным в этом коде (и наоборот).
Веб-воркеры подходят для длительных сценариев, которые работают с чистыми данными или не имеют ничего общего с пользовательским интерфейсом браузера.
Создать нового рабочего потока так же просто, как указать URI скрипта для выполнения рабочего потока (main.js):
var myWorker = new Worker('worker.js');
// 你可以通过postMessage() 方法和onmessage事件向worker发送消息。
first.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
second.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
Получив сообщение в воркере, мы можем написать в ответ код обработчика события (worker.js):
onmessage = function(e) {
console.log('Message received from main script');
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
console.log('Posting message back to main script');
postMessage(workerResult);
}
Обработчик onmessage выполняется сразу после получения сообщения, а само сообщение в коде используется как атрибут данных события. Здесь мы просто умножаем эти 2 числа и снова используем метод postMessage(), чтобы отправить результат обратно в основной поток.
Вернувшись в основной поток, мы снова используем onmessage, чтобы ответить на сообщение работника:
myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Message received from worker');
}
Здесь мы получаем данные события сообщения и устанавливаем их как textContent результата, чтобы пользователь мог напрямую видеть результат операции.
Однако внутри рабочего процесса вы не можете напрямую манипулировать узлами DOM и не можете использовать методы и свойства объекта окна по умолчанию. Тем не менее, вы можете использовать ряд вещей в объекте окна, включая WebSockets, IndexedDB и механизмы хранения данных, такие как API хранилища данных FireFox для конкретной ОС.
Использованная литература:
19. Использование битовых манипуляций
Все числа в JavaScript хранятся в 64-битном формате с использованием стандарта IEEE-754. Но при битовых манипуляциях числа преобразуются в 32-битный формат со знаком. Даже если требуется преобразование, битовые операции выполняются намного быстрее, чем другие математические и логические операции.
по модулю
Поскольку младший бит четных чисел равен 0, а нечетных чисел равен 1, операция по модулю может быть заменена операцией с битами.
if (value % 2) {
// 奇数
} else {
// 偶数
}
// 位操作
if (value & 1) {
// 奇数
} else {
// 偶数
}
Округление
~~10.12 // 10
~~10 // 10
~~'1.5' // 1
~~undefined // 0
~~null // 0
битовая маска
const a = 1
const b = 2
const c = 4
const options = a | b | c
Определив эти опции, можно использовать побитовую операцию И, чтобы определить, есть ли a/b/c в опциях.
// 选项 b 是否在选项中
if (b & options) {
...
}
20. Не переопределяйте нативные методы
Независимо от того, насколько оптимизирован ваш код JavaScript, он не может сравниться с нативными методами. Поскольку нативные методы написаны на низкоуровневом языке (C / C ++) и скомпилированы в машинный код, который становится частью браузера. Когда нативные методы доступны, попробуйте использовать их, особенно манипуляцию математики и дома.
21. Уменьшите сложность селекторов CSS
(1) Браузер читает селектор, следуя принципу чтения справа налево от селектора.
посмотреть пример
#block .text p {
color: red;
}
- Найдите все элементы P.
- Определяет, имеет ли элемент в результате 1 родительский элемент с текстом имени класса.
- Определяет, имеет ли элемент в результате 2 родительский элемент с блоком id
(2) Приоритет селектора CSS
内联 > ID选择器 > 类选择器 > 标签选择器
На основании двух приведенных выше сведений можно сделать вывод.
- Чем короче селектор, тем лучше.
- Попробуйте использовать селекторы с высоким приоритетом, такие как селекторы идентификаторов и классов.
- Избегайте подстановочных знаков *.
В заключение, из того, что я обнаружил, селекторы CSS не нуждаются в оптимизации, потому что разница в производительности между самым медленным и самым медленным селекторами очень мала.
Использованная литература:
22. Используйте flexbox вместо старых моделей макетов
В первые дни макета CSS мы могли бы позиционировать элементы абсолютно, относительно или плавать. И теперь у нас есть новый макетflexbox, у него есть преимущество перед более ранним методом компоновки, то есть производительность выше.
На приведенном ниже снимке экрана показаны накладные расходы макета при использовании поплавков на 1300 полях:
Затем мы используем flexbox для воспроизведения примера:
Теперь при том же количестве элементов и том же внешнем виде время компоновки намного меньше (3,5 мс против 14 мс в этом примере).
Тем не менее, совместимость с flexbox по-прежнему является проблемой, не все браузеры ее поддерживают, поэтому используйте ее с осторожностью.
Совместимость с браузером:
- Chrome 29+
- Firefox 28+
- Internet Explorer 11
- Opera 17+
- Safari 6.1+ (prefixed with -webkit-)
- Android 4.4+
- iOS 7.1+ (prefixed with -webkit-)
Использованная литература:
23. Анимируйте, используя изменения свойств преобразования и непрозрачности
В CSS изменения свойств transform и opacity не вызывают перекомпоновку и перерисовку, это свойства, которые могут обрабатываться композитом независимо.
Использованная литература:
24. Используйте правила с умом и избегайте чрезмерной оптимизации
Оптимизация производительности в основном делится на две категории:
- Оптимизация времени загрузки
- оптимизация времени выполнения
Среди приведенных выше 23 предложений первые 10 предложений относятся к оптимизации времени загрузки, а последние 13 предложений относятся к оптимизации времени выполнения. Вообще говоря, нет необходимости использовать все правила оптимизации производительности 23. Лучше всего делать целевые настройки в соответствии с группой пользователей сайта, что экономит энергию и время.
Прежде чем решить проблему, нужно сначала обозначить проблему, иначе никак не начать. Поэтому, прежде чем приступать к оптимизации производительности, лучше всего изучить производительность загрузки и производительность веб-сайта.
Проверить производительность загрузки
Производительность загрузки веб-сайта в основном зависит от времени белого экрана и времени первого экрана.
- Время белого экрана: относится ко времени с момента ввода URL-адреса до момента, когда страница начинает отображать содержимое.
- Время в верхней части экрана: относится ко времени от ввода URL-адреса до полного отображения страницы.
Поместите следующий скрипт в</head>
Время белого экрана можно получить спереди.
<script>
new Date() - performance.timing.navigationStart
// 通过 domLoading 和 navigationStart 也可以
performance.timing.domLoading - performance.timing.navigationStart
</script>
существуетwindow.onload
выполнить в случаеnew Date() - performance.timing.navigationStart
Вы можете получить первое экранное время.
Проверить производительность запуска
С помощью инструментов разработчика Chrome мы можем просматривать производительность веб-сайта во время выполнения.
Откройте сайт, нажмите F12, чтобы выбрать исполнение, нажмите на серую точку в левом верхнем углу, она станет красной, чтобы начать запись. В это время вы можете имитировать пользователя для использования веб-сайта.После его использования нажмите «Стоп», после чего вы сможете увидеть отчет о производительности во время работы веб-сайта. Если есть красный блок, значит, есть просадка кадров, если зеленый, значит, FPS хороший. Для конкретного использования производительности, пожалуйста, используйте поисковую систему для поиска, в конце концов, пространство ограничено.
Я полагаю, что, проверяя производительность загрузки и работы, вы получили общее представление о производительности веб-сайта. Итак, что нужно сделать в настоящее время, так это использовать приведенные выше 23 предложения, чтобы максимально оптимизировать свой веб-сайт.
Использованная литература:
Другие ссылки
- Почему важна производительность
- Руководство по созданию высокопроизводительных веб-сайтов
- Полное руководство по веб-производительности
- Высокопроизводительный JavaScript
- Эффективный интерфейс: практика веб-эффективного программирования и оптимизации