тема
На страницу за один раз выводится массив из 10 записей, как с этим справиться, не зависая при этом в интерфейсе?
материализованный
На странице есть пустой узел ненумерованного списка
ul
,Этоid
дляlist-with-big-data
, теперь нужно вставить 10w в списокli
, текстовое содержимое каждого элемента списка может быть определено само по себе, и требуется, чтобы при каждом элементе спискаli
При нажатии пройтиalert
Отображает текстовое содержимое в элементе списка.
<!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>页面加载海量数据</title>
</head>
<body>
<ul id="list-with-big-data">100000 数据</ul>
<script>
// 此处添加你的代码逻辑
</script>
</body>
</html>
анализировать
Может увидеть проблему на первый взгляд, мы могли бы подумать о таком решении: получитьul
Элемент, затем построитьli
элемент и наборli
Текстовое содержимое привязывается к слушателю, а затем в циклеul
провестиappend
операция, т. е. то, что может прийти на ум, это следующая реализация кода.
(function() {
const ulContainer = document.getElementById("list-with-big-data");
// 防御性编程
if (!ulContainer) {
return;
}
for (let i = 0; i < 100000; i++) {
const liItem = document.createElement("li");
liItem.innerText = i + 1;
// EventListener 回调函数的 this 默认指向当前节点,若使用箭头函数,得谨慎
liItem.addEventListener("click", function() {
alert(this.innerText);
});
ulContainer.appendChild(liItem);
}
})();
Практикуя приведенный выше код, мы обнаружили, что интерфейс очень недружественный, а ощущение заикания серьезное. Основная причина зависания заключается в том, что в каждом цикле изменяется структура DOM, и из-за большого количества данных время выполнения цикла слишком велико, а частота кадров рендеринга в браузере слишком низкая.
На самом деле он содержит 100 000li
длинных списков, пользователи не увидят их все сразу, а лишь некоторые из них. Поэтому для большинстваli
Рендеринг работы, мы можем отложить завершение.
мы можем начать сУменьшите количество манипуляций с DOMа такжеСокращение времени циклаДва аспекта уменьшают время блокировки основного потока.
DocumentFragment
The DocumentFragment interface represents a minimal document object that has no parent. It is used as a lightweight version of
Document
that stores a segment of a document structure comprised of nodes just like a standard document. The key difference is that because the document fragment isn't part of the active document tree structure, changes made to the fragment don't affect the document, cause reflow, or incur any performance impact that can occur when changes are made.
существуетMDNВо введении мы знаем, что это можно сделать с помощьюDocumentFragment
Использование DOM сокращает количество операций DOM и снижает влияние перекомпоновки на производительность.
requestAniminationFrame
The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.
С точки зрения сокращения времени цикла, мы можемразделяй и властвуйдумал, будет 100000li
вставляются на страницу пачками, и мы передаемrequestAniminationFrame
Новый узел вставляется перед перерисовкой страницы.
привязка события
Если мы хотим прослушивать большое количество элементов, рекомендуется использовать механизм событий JavaScript для реализации делегирования событий, что может значительно сократить количество регистраций событий DOM.
решение
После вышеизложенного у нас есть следующее решение.
(function() {
const ulContainer = document.getElementById("list-with-big-data");
// 防御性编程
if (!ulContainer) {
return;
}
const total = 100000; // 插入数据的总数
const batchSize = 4; // 每次批量插入的节点个数,个数越多,界面越卡顿
const batchCount = total / batchSize; // 批处理的次数
let batchDone = 0; // 已完成的批处理个数
function appendItems() {
// 使用 DocumentFragment 减少 DOM 操作次数,对已有元素不进行回流
const fragment = document.createDocumentFragment();
for (let i = 0; i < batchSize; i++) {
const liItem = document.createElement("li");
liItem.innerText = batchDone * batchSize + i + 1;
fragment.appendChild(liItem);
}
// 每次批处理只修改 1 次 DOM
ulContainer.appendChild(fragment);
batchDone++;
doAppendBatch();
}
function doAppendBatch() {
if (batchDone < batchCount) {
// 在重绘之前,分批插入新节点
window.requestAnimationFrame(appendItems);
}
}
// kickoff
doAppendBatch();
// 使用 事件委托 ,利用 JavaScript 的事件机制,实现对海量元素的监听,有效减少事件注册的数量
ulContainer.addEventListener("click", function(e) {
const target = e.target;
if (target.tagName === "LI") {
alert(target.innerText);
}
});
})();