簡介
事情的由來是這樣紫的,目前我負責公司內部的核心業務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");
var Fragment=document.createDocumentFragment(); for(var i=0;i<100;i++){ var span=document.createElement("span"); span.innerHTML = i; app.appendChild(span); }
從上面代碼可以分析、每次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中查看是否有可執行的異步任務,如果有就推入主進程中。
第一版本
我使用了遞歸調用實現如下代碼:
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); } } /** * 渲染大數據量的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
總結
要在學習中思考,在項目中實戰。總有一天你會變得更加厲害!
