渲染大量数据我是这样操作的

简介

事情的由来是这样紫的,目前我负责公司内部的核心业务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了吗?看了肯定知道其原理。

  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/154bzaip

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

这是结果

谢谢大家的宝贵意见

谢谢大家能够提出这些宝贵的意见,希望大家能够理性评论、互动。每个人掌握的知识点都是有限的,只有大家一起讨论才会发现问题,解决问题。希望大家能够理解。

总结

要在学习中思考,在项目中实战。总有一天你会变得更加厉害!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值