Ключевые моменты: отсутствие задержек, плавное взаимодействие
1. Самый традиционный, самый простой и грубый способ
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>如何渲染10000条数据在dom节点上</title>
</head>
<body>
<ul id="root">
</div>
<script>
function createOneHundredThousandData(){
let arr = [];
for(let i=0;i<100000;i++){
arr.push({
imgUrl:'https://zos.alipayobjects.com/rmsportal/hfVtzEhPzTUewPm.png',
key:i
})
}
return arr;
}
var beginTime = performance.now();
console.log('beginTime',beginTime);
let h = [];
let data = createOneHundredThousandData()
// 写法1 原生js 的 for循环
for(let i =0;i<data.length;i++){
h.push('<li>' + '<img src="'+ data[i].imgUrl +'" \/>'+ 'current index ' + data[i].key + '<\/li>');
}
// 写法2 数组自带的map方法
// h = data.map((item,index)=>'<li>' + '<img src="'+ item.imgUrl +'" \/>'+ 'current index ' + item.key + '<\/li>');
document.getElementById('root').innerHTML = h.join('');
document.addEventListener('DOMContentLoaded',function(){
var endTime = performance.now();
console.log('DOMContentLoaded endTime',endTime);
var total = ((endTime - beginTime)/1000).toFixed(5);
console.log('DOMContentLoaded render 100000 items takes ' + total + ' 秒');
});
window.onload = function(){
var endTime = performance.now();
console.log('window.onload endTime',endTime);
var total = ((endTime - beginTime)/1000).toFixed(5);
console.log('window.onload render 100000 items takes ' + total + ' 秒');
}
</script>
</body>
</html>
Браузер Chrome (версия 74.0.3729.169 (официальная версия) (64-разрядная версия)) результаты работы консоли следующие
beginTime 398.8050000043586
DOMContentLoaded endTime 9032.814999984112
DOMContentLoaded render 100000 items takes 8.63401 秒
window.onload endTime 17766.104999987874
window.onload render 100000 items takes 17.36730 秒
Другими словами, рендеринг содержит 100 000 записей, и каждый фрагмент данных представляет собой простую комбинацию изображений и текста, и это занимает почти 17 секунд. Перед завершением рендеринга страницы считается, что пользователь уже давно нетерпелив и закрыл страницу. Это все еще более новая версия браузера Chrome. Переключение на другие браузеры может быть еще хуже. Очевидно, что традиционный способ определенно не квалифицирован.
Что касается приведенной выше демонстрации, можно добавить несколько вопросов:
-
1. InnerHTML вставить DOM вместо использования документа .Createelement, Document.appendChild, как преимуществ InnerHTML.
-
2. Используйте массив [], чтобы кэшировать строку DOM, в первую очередь нажимайте его, а затем напрямую jion ('') для объединения каждого элемента в массиве в строку, которая гораздо более исполнила, чем строки сплавов один за другим.
-
3, объект кругового массива может использоваться для циркуляции, может использовать карту, например, forEach, небольшое количество данных, когда разница невелика, в этом варианте карты видно, что данные времени циркулируют на сто тысяч немного меньше производительность обычного цикла for.
Судя по языку JavaScript, он однопоточный, предназначен для обработки только одной задачи одновременно, а следующая задача может быть обработана после завершения любой обработки, можно понимать это как последовательное выполнение. (Более подробно и строго, вы можете углубиться в цикл событий JavaScript). Поэтому, когда страница выполняет рендеринг или операция JavaScript занимает много времени, операция не была завершена, и в это время вы инициируете взаимодействие на странице, вы не получите своевременный ответ.
Возвращаясь к этому вопросу, когда мы рендерим 100 000 фрагментов данных, нам нужно использовать слайсы (что-то вроде идеи React Fiber). Как понять это? Он заключается в пакетном рендеринге 100 000 сброшенных данных на страницу, этот пакет задач необходимо поместить в асинхронные callback'и (первый пакет задач не используется), чтобы при последующих рендерингах приоритет мог быть отдан потоку очереди выполнения , Когда очередь простаивает, возвращайтесь назад и продолжайте извлекать слайсы в асинхронном обратном вызове для выполнения.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>如何渲染10000条数据在dom节点上</title>
</head>
<body>
<ul id="root">
</div>
<script>
function createOneHundredThousandData(){
let arr = [];
for(let i=0;i<100000;i++){
arr.push({
imgUrl:'https://zos.alipayobjects.com/rmsportal/hfVtzEhPzTUewPm.png',
key:i
})
}
return arr;
}
var beginTime = performance.now();
console.log('beginTime',beginTime);
let h = [];
let data = createOneHundredThousandData();
// 先渲染100条数据
let firstScreenData = data.splice(0,100); // 用数组的splice方法,截取后并修改原数组
for(let i=0;i<100;i++){
let li = document.createElement('li');
let img = document.createElement('img');
img.src = firstScreenData[i].imgUrl;
li.appendChild(img);
let text = document.createTextNode(firstScreenData[i].key);
// console.log('partialData[i].key',partialData[i].key);
li.appendChild(text);
document.getElementById('root').appendChild(li);
}
// setTimeout 中的回调会在主线程空闲时被执行
setTimeout(()=>{
function renderHundred(n){
// console.log('n=',n);
// 每次渲染100条
let partialData = data.splice(0,100);
for(let i=0;i<100 && partialData.length>0;i++){
let li = document.createElement('li');
let img = document.createElement('img');
img.src = partialData[i].imgUrl;
li.appendChild(img);
let text = document.createTextNode(partialData[i].key);
// console.log('partialData[i].key',partialData[i].key);
li.appendChild(text);
document.getElementById('root').appendChild(li);
}
if(n){
setTimeout(()=>{
renderHundred(n-1);
},50)
}
}
renderHundred(999);// 渲染除了首屏数据外的数据
},1000);
document.addEventListener('DOMContentLoaded',function(){
var endTime = performance.now();
console.log('DOMContentLoaded endTime',endTime);
var total = ((endTime - beginTime)/1000).toFixed(5);
console.log('DOMContentLoaded render 100000 items takes ' + total + ' 秒');
});
window.onload = function(){
var endTime = performance.now();
console.log('window.onload endTime',endTime);
var total = ((endTime - beginTime)/1000).toFixed(5);
console.log('window.onload render 100000 items takes ' + total + ' 秒');
}
</script>
</body>
</html>
Результаты приведены ниже:
beginTime 139.08000002265908
DOMContentLoaded endTime 193.2200000155717
DOMContentLoaded render 100000 items takes 0.05414 秒
window.onload endTime 207.63000001898035
window.onload render 100000 items takes 0.06855 秒
Эти данные малоинформативны, их можно понять как трудоемкую статистику реализации первого слайса (всего 999 слайсов, которые потом не отражаются), но опыт взаимодействия значительно улучшается. Что касается поддержки данных конкретной карты или нет, вы можете просмотреть ее в модуле производительности консоли Chrome. Если вам интересно, скопируйте этот код и попробуйте. Вывод довольно оптимистичный.
3. Есть ли другие варианты кроме setTimeout?
Ответ: должно быть. То естьrequestAnimationFrame
window.requestAnimationFrame() сообщает браузеру, что вы хотите выполнить анимацию, и просит браузер вызвать указанную функцию обратного вызова, чтобы обновить анимацию перед следующей перерисовкой. Этот метод должен пройти вПерезвонитеВ качестве параметра функция обратного вызова будет выполнена до следующего перекраски браузера
Примечание. Если вы хотите продолжить обновление следующего кадра анимации перед следующей перерисовкой браузера, сама функция обратного вызова должна снова вызвать window.requestAnimationFrame().
Как видно из официальной документации, эти ключевые слова:"передать функцию обратного вызова". Итак, вы можете использовать эту замену setTimeout?
Некоторые фрагменты кода ниже:
let data = createOneHundredThousandData();
let count = 0;
let totalLoop = 1000;// 渲染1000
function animatonCb(){
console.log(count);
let partialData = data.splice(0,100); // 用数组的splice方法,截取后并修改原数组
for(let i=0;i<100 && partialData.length >=1;i++){
let li = document.createElement('li');
let img = document.createElement('img');
img.src = partialData[i].imgUrl;
li.appendChild(img);
let text = document.createTextNode(partialData[i].key);
// console.log('partialData[i].key',partialData[i].key);
li.appendChild(text);
document.getElementById('root').appendChild(li);
}
if(count < totalLoop){
count ++;
requestAnimationFrame(animatonCb)
}
}
requestAnimationFrame(animatonCb);
Взгляните на данные консоли:
beginTime 249.32000000262633
0
DOMContentLoaded endTime 279.33499999926426
DOMContentLoaded render 100000 items takes 0.03001 秒
1
2
window.onload endTime 308.28500000643544
window.onload render 100000 items takes 0.05897 秒
В-четвертых, после завершения загрузки сто тысяч данных?
Приведенные выше два решения решают проблему рендеринга без замораживания. В этом примере DOM-структура каждой записи несложная, и может показаться, что с эффектом все в порядке. Но реальный бизнес-сценарий определенно сложнее, чем этот. При каждом изменении DOM будет переупорядочиваться 100 000 кусков данных, так что проблемы с производительностью точно будут.
Решение состоит в том, чтобы следить за тем, находится ли элемент в видимом окне.IntersectionObserver
Интерфейс IntersectionObserver (часть API Intersection Observer) предоставляет способ асинхронного наблюдения за пересечением целевого элемента с его предками или окнами просмотра документа верхнего уровня. Элемент-предок и окно просмотра называются корнем. Когда создается объект IntersectionObserver, он настраивается на прослушивание заданной доли видимой области в корне. После создания IntersectionObserver его конфигурацию нельзя изменить, поэтому данный объект наблюдателя можно использовать только для прослушивания определенных изменений в видимой области; однако вы можете настроить несколько целевых элементов в одном и том же объекте наблюдателя.
Общая идея такова:
- Общий набор источников данных, контейнеры хранения данных содержимого страницы
- Форма страницы Содержание данных Содержание данных Содержимое правила контейнера (при условии, что контейнер для хранения установлен на 200, один экран до 20. Затем контейнер для хранения может отображать данные 10 экрана.
- Когда пользователь сдвигает на землю 6 экранов данных, очевидно, что первых 5 экранов данных нет в видимом окне, тогда можно удалить первые 3 экрана данных в контейнере хранения. При этом данные с 11-го по 13-й экраны получаются из общего источника данных.
Я изучу это подробно позже, когда у меня будет время.
V. Заключение
Такие вопросы часто задают такие крупные производители, как bat, а знания охватывают широкий спектр, после освоения которого понимание производительности фронтенда и Caton будет более глубоким.