渲染大量數據我是這樣操做的

簡介

事情的由來是這樣紫的,目前我負責公司內部的核心業務Gis天眼系統開發,遇到一個問題就是:後端返回幾千條數據致使瀏覽器渲染失敗,瀏覽器幾乎是停滯狀態。後來沒有想到合適的解決方案,臨時渲染少許數據解決了。我記得清清楚楚,我已經看過關於這樣的問題怎麼解決,惋惜我沒有使用,緣由是學而不思,看而不用。後來因爲家裏有些事情,我請假回家休假休息了一段時間。回來以後開了一次會議,說我同事 實現了一個上述問題,用到了js線程。而後我就針對此問題開始了二次思考。就有了本文。segmentfault

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節點所有渲染出來。知道了這一點,咱們就能夠想辦法解決它(渲染大量數據)。bash

初探代碼執行方式

以下代碼:app

console.log(1);
setTimeout(function(){
	console.log(2);
},100);
console.log(3);
複製代碼

你們確定會說這個很簡單,輸出1 3 2。我想說的是你們看JS的Event Loop了嗎?看了確定知道其原理。dom

  1. 首先判斷JS是同步仍是異步,同步就進入主進程,異步就進入event table
  2. 異步任務在event table中註冊函數,當知足觸發條件後,被推入event queue
  3. 同步任務進入主線程後一直執行,直到主線程空閒時,纔會去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);//例子纔會這樣寫的
		/**
    	*工做中會 這樣寫,拼接字符串,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/154bzaipoop

dome2 http://sandbox.runjs.cn/show/hne29nn0學習

2018-02-06更新

  1. document.createDocumentFragment是最完美的選擇。

  2. for循環內appendChild到底會不會引發瀏覽器渲染----個人回答:不會。 由於js線程一直佔用,GUI渲染線程等待,只有js線程空閒,GUI渲染線程纔會渲染。

  3. setInterval不是最優選擇,應該使用setTimeout實現setInterval

  4. 何時會出現內存泄漏?

這是兩種不一樣的渲染方式

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方法

這是結果
這是使用createDocumentFragment方法

這是結果

謝謝你們的寶貴意見

謝謝你們可以提出這些寶貴的意見,但願你們可以理性評論、互動。每一個人掌握的知識點都是有限的,只有你們一塊兒討論纔會發現問題,解決問題。但願你們可以理解。

總結

要在學習中思考,在項目中實戰。總有一天你會變得更加厲害!

相關文章
相關標籤/搜索