面試:頁面載入海量資料

Jiahonzheng發表於2018-04-26

題目

10w 條記錄的陣列,一次性渲染到頁面上,如何處理可以不凍結UI?

具體化

頁面上有個空的無序列表節點 ul ,其 idlist-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 結構,並且由於資料量大,導致迴圈執行時間過長,瀏覽器的渲染幀率過低。

事實上,包含 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 操作次數,降低迴流對效能的影響。

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);

}
});

})();
複製程式碼

來源:https://juejin.im/post/5ae17a386fb9a07abc299cdd?utm_medium=fe&utm_source=weixinqun

相關文章