簡介
事情的由來是這樣紫的,目前我負責公司內部的核心業務Gis天眼系統開發,遇到一個問題就是:後端返回幾千條資料導致瀏覽器渲染失敗,瀏覽器幾乎是停滯狀態。後來沒有想到合適的解決方案,臨時渲染少量資料解決了。我記得清清楚楚,我已經看過關於這樣的問題怎麼解決,可惜我沒有使用,原因是學而不思,看而不用。後來由於家裡有些事情,我請假回家休假休息了一段時間。回來之後開了一次會議,說我同事 實現了一個上述問題,用到了js執行緒。然後我就針對此問題開始了二次思考。就有了本文。
JS執行緒
瀏覽器內分js執行緒、GUI渲染執行緒、事件觸發執行緒、等。大家都知道JS是單執行緒,但是問題來了,單執行緒如何實現非同步,比如說我們經常使用的Ajax是怎麼實現的呢?當你真正瞭解JS的Event Loop你就會明白!哦:原來如此。這裡我就對執行緒進行拋磚引玉,如果想深入學習可以看一下這篇文章:https://segmentfault.com/a/1190000012806637
如何渲染大量資料
渲染大量資料肯定會涉及到GUI渲染執行緒與js執行緒。如下簡單的程式碼:
<!--
dom 節點
-->
<div id="app">
</div>
//js程式碼
var app=document.getElementById("app");
for(var i=0;i<100;i++){
var span=document.createElement("span");
span.innerHTML = i;
app.appendChild(span);//例子才會這樣寫的
/**
*工作中會 這樣寫,拼接字串,for迴圈結束後一次性appendChild
*/
}
複製程式碼
從上面程式碼可以分析、每次for迴圈使用dom進行渲染。瀏覽器是怎麼渲染的呢?JS執行緒是單執行緒,它如果執行js執行緒,GUI渲染執行緒肯定會等候,這樣一來渲染大量資料就會造成頁面卡頓,甚至停滯、奔潰。頁面顯示效果就是一下子這些dom節點全部渲染出來。知道了這一點,我們就可以想辦法解決它(渲染大量資料)。
初探程式碼執行方式
如下程式碼:
console.log(1);
setTimeout(function(){
console.log(2);
},100);
console.log(3);
複製程式碼
大家肯定會說這個很簡單,輸出1 3 2。我想說的是大家看JS的Event Loop了嗎?看了肯定知道其原理。
- 首先判斷JS是同步還是非同步,同步就進入主程式,非同步就進入event table
- 非同步任務在event table中註冊函式,當滿足觸發條件後,被推入event queue
- 同步任務進入主執行緒後一直執行,直到主執行緒空閒時,才會去event queue中檢視是否有可執行的非同步任務,如果有就推入主程式中。
第一版本
![渲染大量資料我是這樣操作的](https://i.iter01.com/images/16b1f5bdf8795663f249d4e86a67bfc21446d935bc2c6eb57f1f492349e8b1ea.png)
var app=document.getElementById("app");
var j=1;
/**
* 渲染方式
*
* @number {number} 數量
* */
function showDom(number){
console.log('渲染'+(j++)+"次");
for(var i=0;i<number;i++){
var span=document.createElement("span");
span.innerHTML = i;
app.appendChild(span);//例子才會這樣寫的
/**
*工作中會 這樣寫,拼接字串,for迴圈結束後一次性appendChild
*/
}
}
/**
* 渲染大資料量的dom節點
*
* @count {number} 總數量
*
* */
function init(count){
if(typeof count!=="number") {
console.warn(count+"型別不是:Number");
return;
}
if(count>500){
setTimeout(function(){
showDom(500);
init(count-500);
},200);
}else{
showDom(count);
}
}
init(4000);
複製程式碼
可以看出利用上述方式可以簡單輕鬆實現渲染大量資料,給使用者的感覺是,當前資料很多,我需要一步一步渲染。比之前一下子渲染幾千條資料導致GUI渲染引擎卡頓、甚至停滯強多啦。
第二版本
接下來我又參考書籍使用了下面的程式碼。
/**
* 分時函式
* @ary {Arry} 資料
* @callback {Function} 回掉函式,一個引數,當前資料項
* @count {Number} 數量
*
* */
function timeChunk(ary,callback,count){
var objTs=Object.prototype.toString,//檢測型別
t;//定時器
if(objTs.call(ary)!=="[object Array]"){
return console.warn(ary+"---》應該是Arry型別");
}
if(objTs.call(callback)!=="[object Function]"){
return console.warn(callback+"---》應該是回掉函式");
}
if(objTs.call(count)!=="[object Number]"){
return console.warn(count+"---》應該是Number型別");
}
//開始執行函式
function start(){
for(var i=0;i<Math.min(count||1,ary.length);i++){
callback(ary.shift());
}
}
return function(){
t=setInterval(function(){
if(ary.length===0){
return clearInterval(t);
}
start();
},200);
}
}
//後端返回資料
var ayy=[];
for (var a=0;a<50000;a++) {
ayy.push(a);
}
//開始使用 分時函式
var init=timeChunk(ayy,function(i){
var span=document.createElement("span");
span.innerHTML = i;
app.appendChild(span);
},500);
//開始渲染大資料
init();
複製程式碼
參考demo
dome1 http://sandbox.runjs.cn/show/154bzaip
dome2 http://sandbox.runjs.cn/show/hne29nn0
2018-02-06更新
-
document.createDocumentFragment是最完美的選擇。
-
for迴圈內appendChild到底會不會引起瀏覽器渲染----我的回答:不會。 因為js執行緒一直佔用,GUI渲染執行緒等待,只有js執行緒空閒,GUI渲染執行緒才會渲染。
-
setInterval不是最優選擇,應該使用setTimeout實現setInterval
-
什麼時候會出現記憶體洩漏?
這是兩種不同的渲染方式
var app=document.getElementById("app");
var fragment=document.createDocumentFragment();
/**
* 渲染方式1
* 使用createDocumentFragment
*
* @number {number} 數量
* */
function showDom(number){
for(var i=0;i<number;i++){
var span=document.createElement("span");
span.innerHTML = i;
//app.appendChild(span);
fragment.appendChild(span);
}
app.appendChild(fragment);
}
showDom(10000);
/**
* 渲染方式2
* 使用appendChild
*
* @number {number} 數量
* */
function showDom1(number){
for(var i=0;i<number;i++){
var span=document.createElement("span");
span.innerHTML = i;
app.appendChild(span);
//fragment.appendChild(span);
}
//app.appendChild(fragment);
}
//showDom1(10000);
//可以使用Chrome瀏覽器 TimeLine檢視js執行緒與渲染執行緒佔用時間。
複製程式碼
這是直接使用appendChild方法
![渲染大量資料我是這樣操作的](https://i.iter01.com/images/4a3bb7b67b7a4698ff1a0afcb91abc7fa830771acd709f9f40f38fa0eeef5bd9.png)
![渲染大量資料我是這樣操作的](https://i.iter01.com/images/c628ccf8c58dedfab5d8b6f5c394f75af45746ca15eb70a01e408c0dc62b0fe8.png)
![渲染大量資料我是這樣操作的](https://i.iter01.com/images/a82282075e67f087937eb0a6d36b4e5d348b3a7da168da13b7af86b614be6d6b.png)
![渲染大量資料我是這樣操作的](https://i.iter01.com/images/6613efc7cc940217beef977152d6f4285eee6048fc2a23c0f18c29e54939bfab.png)
謝謝大家的寶貴意見
謝謝大家能夠提出這些寶貴的意見,希望大家能夠理性評論、互動。每個人掌握的知識點都是有限的,只有大家一起討論才會發現問題,解決問題。希望大家能夠理解。
總結
要在學習中思考,在專案中實戰。總有一天你會變得更加厲害!