10w 條記錄的數組,一次性渲染到頁面上,如何處理能夠不凍結UI?javascript
頁面上有個空的無序列表節點
ul
,其id
爲list-with-big-data
,現須要往列表插入 10w 個li
,每一個列表項的文本內容可自行定義,且要求當每一個li
被單擊時,經過alert
顯示列表項內的文本內容。html
<!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
操做,便可能想到的是如下代碼實現。java
(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 結構,而且因爲數據量大,致使循環執行時間過長,瀏覽器的渲染幀率太低。node
事實上,包含 100000 個 li
的長列表,用戶不會當即看到所有,只會看到少部分。所以,對於大部分的 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 操做次數,下降回流對性能的影響。app
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.函數
在縮短循環時間方面,咱們能夠經過 分治 的思想,將 100000 個 li
分批插入到頁面中,而且咱們經過 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);
}
});
})();
複製代碼