Чем больше вы знаете, тем больше вы не знаете
点赞
Посмотри еще раз, аромат остался в руке, и слава
предисловие
В практической работе мы редко сталкиваемся с ситуациями, когда на страницу за один раз нужно вставить большой объем данных, но для обогащения нашей системы знаний нам необходимо понимать и четко понимать, как избежать главной страницы при столкновение с большим объемом данных Рендеринг данных в случае лиц и принципы, лежащие в его основе.
В случае одновременной вставки большого количества данных обычно существует два подхода:
- разрезание времени
- виртуальный список
В начале этой статьи мы сосредоточимся на том, как использовать时间分片
способ рендеринга больших объемов данных,虚拟列表
Связанный контент см.«Передний расширенный» высокопроизводительный рендеринг 100 000 элементов данных (виртуальный список)
Самый грубый подход (однократный рендеринг)
Давайте сначала рассмотрим самый грубый подход, вставляя сразу большой объем данных на страницу:
<ul id="container"></ul>
// 记录任务开始时间
let now = Date.now();
// 插入十万条数据
const total = 100000;
// 获取容器
let ul = document.getElementById('container');
// 将数据插入容器中
for (let i = 0; i < total; i++) {
let li = document.createElement('li');
li.innerText = ~~(Math.random() * total)
ul.appendChild(li);
}
console.log('JS运行时间:',Date.now() - now);
setTimeout(()=>{
console.log('总运行时间:',Date.now() - now);
},0)
// print: JS运行时间: 187
// print: 总运行时间: 2844
Мы зацикливаем более 100 000 записей, а время работы JS составляет187ms
, что по-прежнему довольно быстро, но общее время после окончательного рендеринга составляет2844ms
.
Кратко объясните, почему дваждыconsole.log
Результаты времени сильно разнятся, и как просто их посчитатьJS运行时间
а также总渲染时间
:
- в JS
Event Loop
, когда все события в стеке выполнения, управляемом движком JS, и все события микрозадач выполняются, поток рендеринга будет запущен для рендеринга страницы. - Первый
console.log
Время триггера — до отображения страницы, а интервал времени, полученный в это время, — это время, необходимое для запуска JS. - второй
console.log
Он помещается в setTimeout, и его время срабатывания — когда рендеринг завершен, а в следующий разEvent Loop
казнен в
Подробнее о цикле событий см. в этой статье -->
согласно дваждыconsole.log
, можно сделать вывод, что:
Когда рендерится большой объем данных, JS-операция не является узким местом производительности, узким местом производительности в основном является этап рендеринга.
Используйте таймер
Из вышеприведенного примера мы уже знаем, что отставание страницы вызвано одновременным рендерингом большого количества DOM, поэтому рассматриваем процесс рендеринга пакетами.
Здесь мы используемsetTimeout
для пакетного рендеринга
<ul id="container"></ul>
//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
let index = 0;
//循环加载数据
function loop(curTotal,curIndex){
if(curTotal <= 0){
return false;
}
//每页多少条
let pageCount = Math.min(curTotal , once);
setTimeout(()=>{
for(let i = 0; i < pageCount; i++){
let li = document.createElement('li');
li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
ul.appendChild(li)
}
loop(curTotal - pageCount,curIndex + pageCount)
},0)
}
loop(total,index);
Используйте гифку, чтобы увидеть эффект
Мы видим, что время загрузки страницы очень быстрое, и мы можем быстро видеть все данные на первом экране при каждом обновлении, но когда мы быстро прокручиваем страницу, мы обнаружим, что на странице есть заставка или белый экран. экран Феномен
Почему появляется заставка?
Во-первых, проясните некоторые понятия.FPS
Указывает количество обновлений экрана в секунду. Непрерывные изображения, которые мы обычно видим, состоят из неподвижных изображений, и каждое изображение называется帧
,FPS
это описание帧
Физическая величина, меняющая скорость.
Большинство компьютерных мониторов имеют частоту обновления 60 Гц, что примерно эквивалентно 60 перерисовкам в секунду.FPS
Это 60 кадров/с, настройка этого значения зависит от разрешения экрана, размера экрана и видеокарты.
Поэтому большинство мониторов постоянно обновляют изображение на экране с частотой 60 раз в секунду, когда вы ничего не делаете за экраном компьютера.
Почему ты не чувствуешь перемен?
Это потому, что человеческий глаз имеет эффект визуальной остановки, то есть впечатление от предыдущей картинки, оставшееся в мозгу, не исчезло, и последует следующая картинка. Этот интервал составляет всего 16,7 мс (1000/60≈16,7), поэтому он заставит вас ошибочно подумать, что изображение на экране неподвижно.
И ощущение, которое дает экран, правильное, только представьте, если частота обновления станет 1 раз/сек, то изображение на экране будет серьезно мерцать. Это может легко привести к таким симптомам, как усталость глаз, болезненность и головокружение.
Большинство браузеров ограничивают операцию перерисовки частотой, не превышающей частоту перерисовки дисплея, потому что пользовательский опыт не улучшится, даже если эта частота будет превышена. Поэтому оптимальный интервал цикла для максимально плавной анимации — 1000 мс/60, что примерно равно 16,6 мс.
Интуитивное ощущение, опыт работы с разной частотой кадров:
- Анимации с частотой кадров от 50 до 60 FPS будут достаточно плавными и комфортными;
- Для анимаций с частотой кадров от 30 до 50 кадров в секунду уровень комфорта варьируется от человека к человеку из-за их разных уровней чувствительности;
- Анимации с частотой кадров ниже 30 кадров в секунду заставляют людей чувствовать явные заикания и дискомфорт;
- Анимации с большими колебаниями частоты кадров также могут заставить людей чувствовать себя застрявшими.
Коротко о setTimeout и феномене заставки
-
setTimeout
Время выполнения не является детерминированным. В JS,setTimeout
Задача помещается в очередь событий, и только после выполнения основного потока он проверяет, нужно ли выполнять задачу в очереди событий, поэтомуsetTimeout
Фактическое время выполнения может быть позже установленного времени. - На частоту обновления влияет разрешение экрана и размер экрана, поэтому она может варьироваться от устройства к устройству, в то время как
setTimeout
Можно установить только фиксированный интервал времени, который не обязательно совпадает со временем обновления экрана.
Две вышеупомянутые ситуации приведут к тому, что темп выполнения setTimeout будет несовместим с темпом обновления экрана.
существуетsetTimeout
Дом, чтобы работать, мы должны дождаться обновления экрана, когда экран рядом с рисунком, если два не в ногу, это может привести к работе через середину кадра в прошлом, и непосредственно обновить элементы следующего кадра, что приводит к пропущенным кадрам.
Используйте запросanimationFrame.
а такжеsetTimeout
в сравнении с,requestAnimationFrame
Самым большим преимуществом является то, что система решает, когда выполняется функция обратного вызова.
Если частота обновления экрана 60 Гц, то функция обратного вызова будет выполняться каждые 16,7 мс, если частота обновления 75 Гц, то временной интервал становится равным 1000/75=13,3 мс, другими словами,requestAnimationFrame
Темп соответствует темпу обновления системы. Это может гарантировать, что функция обратного вызова выполняется только один раз в каждом интервале обновления экрана, чтобы не вызвать потерю кадров.
Мы используемrequestAnimationFrame
Чтобы выполнить пакетный рендеринг:
<ul id="container"></ul>
//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
let index = 0;
//循环加载数据
function loop(curTotal,curIndex){
if(curTotal <= 0){
return false;
}
//每页多少条
let pageCount = Math.min(curTotal , once);
window.requestAnimationFrame(function(){
for(let i = 0; i < pageCount; i++){
let li = document.createElement('li');
li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
ul.appendChild(li)
}
loop(curTotal - pageCount,curIndex + pageCount)
})
}
loop(total,index);
увидеть эффект
Мы видим, что скорость загрузки страницы очень высокая, а при прокрутке она также очень плавная, без мерцания и пропуска кадров.
Это конец, можно снова оптимизировать?
Конечно~~
Использование фрагментов документа
Сначала объясните, что такое документфрагмент, ссылки на литературуMDN
DocumentFragment
, Интерфейс фрагментов документа представляет собой минимальный объект документа, не имеющий родительского файла. Используется как облегченная версияDocument
Используется для хранения форматированных или неформатированных фрагментов XML. Самая большая разница в том, чтоDocumentFragment
Не являясь частью реального дерева DOM, его изменения не вызовут (повторного рендеринга) дерева DOM и не вызовут проблем с производительностью и т. д.
можно использоватьdocument.createDocumentFragment
метод или конструктор для создания пустогоDocumentFragment
Из описания MDN мы знаем, чтоDocumentFragments
Это узел DOM, но он не является частью дерева DOM и может считаться находящимся в памяти, поэтому вставка дочерних элементов во фрагмент документа не приведет к переформатированию страницы.
когдаappend
элемент кdocument
середина, поappend
Вычисление таблицы стилей входящего элемента происходит синхронно, и вычисленное значение стиля можно получить, вызвав в это время getComputedStyle.
а такжеappend
элемент кdocumentFragment
В таблице стилей таблица стилей элемента не рассчитывается, поэтомуdocumentFragment
Лучшая производительность. Конечно, оптимизация браузера сейчас сделана очень хорошо,
когдаappend
элемент кdocument
После середины современный браузер также может откладывать вычисление таблицы стилей после выполнения скрипта, когда нет возможности получить доступ к getComputeStyle.
Последний измененный код выглядит следующим образом:
<ul id="container"></ul>
//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
let index = 0;
//循环加载数据
function loop(curTotal,curIndex){
if(curTotal <= 0){
return false;
}
//每页多少条
let pageCount = Math.min(curTotal , once);
window.requestAnimationFrame(function(){
let fragment = document.createDocumentFragment();
for(let i = 0; i < pageCount; i++){
let li = document.createElement('li');
li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
fragment.appendChild(li)
}
ul.appendChild(fragment)
loop(curTotal - pageCount,curIndex + pageCount)
})
}
loop(total,index);
наконец
Эта статья больше предназначена для того, чтобы дать представление о том, как загружать большое количество простых DOM одновременно с помощью разделения времени. Для сложных случаев DOM обычно используются виртуальные списки, что касается этого вопроса, мы продолжим их организовывать, так что следите за обновлениями.
Рекомендуемая серия статей
- «Расширенный внешний интерфейс» включает в себя всестороннее сочетание многопоточности и цикла событий.
- Как изящно обрабатывать исключения изображений в «Advanced Front-end»
- Анализ и реализация одностраничной маршрутизации «переднего плана»
- «Front-end Advanced» полностью понимает каррирование функций.
- Память кучи памяти стека "Front-end advanced" в JS
- «Расширенное переднее» управление памятью в JS
- Массив "Front-end advanced" вышел из строя