本文主要是針對JavaScript(以下簡稱JS)處理大批量數據時,產生性能問題的簡要分析,以及如何優雅的演化提升性能的解決方案;
場景:假設有10W條數據量,需要相應業務處理並綁定渲染在html頁面,如何一步一步的分析並優化 JS 的性能,提升數據處理速度;
假設我們需要處理的需求如下,從0到10W條數據通過ul>li的方式顯示在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>JS大批量數據渲染測試Demo</title>
</head>
<body>
<!-- <ul id="container">
<li>1</li>
<li>2</li>
<li>3</li>
<li>...</li>
<li>10000</li>
</ul> -->
<ul id="container">
</ul>
<script type="text/javascript" src="../js/scheme1.js"> //依次修改後面演化的方案【scheme2.js】,【scheme3.js】,【scheme4.js】
</body>
</html>
我們該如何讓 JS 優雅的處理這10W條數據呢?
1.常規方式 =>【一次性渲染】:scheme1.js
let now = Date.now();
const total = 100000; // 插入的數據總數
let oContainer = document.getElementById('container');
// DOM:document object model 文檔對象模型
for (let i = 0; i < total; i++) {
let oLi = document.createElement('li'); // 創建li元素
oLi.innerHTML = i; //注意區分:innerHTML ,innerText ??
oContainer.appendChild(oLi);
}
console.log('js運行時間:' , Date.now() - now);
// 渲染頁面
setTimeout(()=>{
console.log('總的運行時間:' , Date.now() - now);
}, 0);
測試瀏覽器:【QQ瀏覽器極速內核】
測試結果:
2.【定時器分批加載】:scheme2.js
const total = 100000;
let oContainer = document.getElementById('container');
const once = 2000; // 一次插入的條數
const page = total / once; // 總的頁數
const index = 0; // 數據的索引
function insert (curTotal, curIndex){
if(curTotal <= 0){
return; // 遞歸的出口
}
setTimeout(()=>{
for(let i=0; i<once; i++){
let oLi = document.createElement('li');
oLi.innerHTML = curIndex + i;
oContainer.appendChild(oLi);
}
// 遞歸的入口
insert(curTotal - once, curIndex + once); //[curTotal - once]:剩餘的條數,[curIndex + once]:開始的索引
}, 0);
}
insert(total, index); //遞歸調用
測試結果:
3.告知瀏覽器動畫處理 =》【window.requestAnimationFrame】:scheme3.js
const total = 100000;
let oContainer = document.getElementById('container');
const once = 2000; // 一次插入的條數
const page = total / once; // 總的頁數
const index = 0; // 數據的索引
function insert (curTotal, curIndex){
if(curTotal <= 0){
return; // 遞歸的出口
}
window.requestAnimationFrame(()=>{
for(let i=0; i<once; i++){
let oLi = document.createElement('li');
oLi.innerHTML = curIndex + i;
oContainer.appendChild(oLi);
}
// 遞歸的入口
insert(curTotal - once, curIndex + once); //[curTotal - once]:剩餘的條數,[curIndex + once]:開始的索引
});
}
insert(total, index); // 遞歸調用
測試結果:
4.文檔片段化 =》【document.createDocumentFragment】:scheme4.js
const total = 100000;
let oContainer = document.getElementById('container');
const once = 2000; // 一次插入的條數
const page = total / once; // 總的頁數
const index = 0; // 數據的索引
function insert (curTotal, curIndex){
if(curTotal <= 0){
return;// 遞歸的出口
}
window.requestAnimationFrame(()=>{
let fragment = document.createDocumentFragment(); // 文檔碎片
for(let i=0; i<once; i++){
let oLi = document.createElement('li');
oLi.innerHTML = curIndex + i;
// oContainer.appendChild(oLi);
fragment.appendChild(oLi);
}
oContainer.appendChild(fragment);
// 遞歸的入口
insert(curTotal - once, curIndex + once); //[curTotal - once]:剩餘的條數,[curIndex + once]:開始的索引
});
}
insert(total, index); // 遞歸調用
測試結果:
備註:以上測試受瀏覽器(型號,版本)影響,電腦內存,硬件以及【FPS=》是圖像領域中的定義,是指畫面每秒傳輸幀數,通俗來講就是指動畫或視頻的畫面數】,大多數顯示顯示器60Hz;本人同一個瀏覽器每個方案測試三次,基本都有一定的時間偏差,以上只是個基本的參考。
總結:
a.瀏覽器是順序解析,JS是阻塞加載,會阻塞DOM樹或阻塞渲染樹的構建;
b.JS中setTimeout()執行的時間並不唯一準確;
c.windows.requestAnimationFrame() =》告訴瀏覽器——你希望執行一個動畫,並且要求瀏覽器在下次重繪之前調用指定的回調函數更新動畫;
d.重排【Reflow】必定會引發重繪,但重繪【Repaint】不一定會引發重排;https://www.cnblogs.com/yadongliang/p/10677589.html
e.document.createDocumentFragment() =》他們是DOM節點,並不是主DOM樹的一部分。通常的用例是創建文檔片段,將元素附加到文檔片段,然後將文檔片段附加到DOM樹。在DOM樹中,文檔片段被其所有的子元素所代替。因爲文檔片段存在於內存中,並不在DOM樹中,所以將子元素插入到文檔片段時不會引起頁面迴流(對元素位置和幾何上的計算)。因此,使用文檔片段通常會帶來更好的性能。