本文探讨前端一次性渲染数万条数据导致的页面卡顿问题。通过对比全量渲染与分批异步渲染,指出DOM操作是性能瓶颈。利用setTimeout将大批量数据分割为小批次,在异步队列中逐步插入DOM,从而避免主线程阻塞,实现页面即时响应。
大数据量渲染的性能问题分析
曾有同事分享一道面试题:当后端返回数万条数据时,前端应如何高效渲染?这一话题引发了热烈讨论,大家提出了多种方案,包括直接循环生成HTML或使用分页。由于直接操作DOM必然影响页面性能,为此我编写了一个测试Demo进行验证,代码如下
$.get("data.json", function (response) {
//response里大概有13万条数据
loadAll( response );
});
function loadAll(response) {
var html = "";
for (var i = 0; i < response.length; i++) {
var item = response[i];
html += "<li>title:" + item.title + " content:" + item.content + "</li>";
}
$("#content").html(html);
}
测试使用的data.json包含约13万条数据。通过Ajax获取数据后,采用最基础的方式直接展示,在Chrome浏览器中观察发现,从刷新页面到数据显示完成耗时约5秒,卡顿现象显著。分析代码执行时间后得知,循环拼接字符串的过程耗时并不长,真正的性能瓶颈在于将HTML字符串插入文档的操作,即执行$("#content").html(html)时,由于需将13万个li元素插入文档,导致渲染缓慢。
基于setTimeout的分批异步渲染方案
既然一次性渲染13万条数据会导致加载缓慢,可以尝试分批渲染,例如每次处理10000条,分13次完成。然而,若这13次操作在同一执行流程中同步运行,不仅无法解决卡顿,反而会增加代码复杂度。在其他语言中,多线程是解决此类问题的最佳方案;虽然JavaScript没有真正的多线程,但setTimeout和setInterval能模拟类似效果。因此,利用setTimeout函数成为解决该问题的关键,其功能可理解为在指定时间后启动新任务。
$.get("data.json", function (response) {
//response里大概有13万条数据
loadAll( response );
});
function loadAll(response) {
//将13万条数据分组, 每组500条,一共260组
var groups = group(response);
for (var i = 0; i < groups.length; i++) {
//闭包, 保持i值的正确性
window.setTimeout(function () {
var group = groups[i];
var index = i + 1;
return function () {
//分批渲染
loadPart( group, index );
}
}(), 1);
}
}
//数据分组函数(每组500条)
function group(data) {
var result = [];
var groupItem;
for (var i = 0; i < data.length; i++) {
if (i % 500 == 0) {
groupItem != null && result.push(groupItem);
groupItem = [];
}
groupItem.push(data[i]);
}
result.push(groupItem);
return result;
}
var currIndex = 0;
//加载某一批数据的函数
function loadPart( group, index ) {
var html = "";
for (var i = 0; i < group.length; i++) {
var item = group[i];
html += "<li>title:" + item.title + index + " content:" + item.content + index + "</li>";
}
//保证顺序不错乱
while (index - currIndex == 1) {
$("#content").append(html);
currIndex = index;
}
}
上述代码的大致执行流程如下
1. 通过Ajax获取待处理数据,总量为13万条
2. 将数组进行分组,每组包含500条数据,共计260组
3. 遍历这260组数据,分别处理每一组。利用setTimeout函数开启新的执行线程(异步),防止主线程因渲染大量数据而阻塞。
在loadPart函数中包含以下代码片段
while (index - currIndex == 1) {
$("#content").append(html);
currIndex = index;
}采用这种方式执行后,页面瞬间完成渲染,无需等待。虽然从同步改为异步增加了整体资源消耗,但页面响应变得即时。考虑到前端运行环境为用户电脑,这种轻微的性能损失换取用户体验的提升是值得的。
虽然示例中的极端情况在现实中罕见,但日常工作中常遇到类似场景。借鉴其中的处理思路,或许能为解决其他问题提供帮助。
注:setTimeout并非真正的多线程,此处借用线程一词以便表述
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持本站!