Javascript / HTML:优化大型表/大量元素

时间:2012-05-11 11:25:26

标签: javascript performance html-table

我需要显示由某些数字配置创建的视觉模式,以及它们之间的关系。为此,我创建了一个表,并使用行/列作为x / y坐标(以ID编码)。对于许多TD元素,我给出了一个有意义的背景颜色并附加了鼠标悬停/输出处理程序,以显示一个弹出窗口,其中包含该TD指定的编号和相关信息。 (单元太小,实际上不包含数字;它们更像是一个可视设备,我使用所说的背景颜色,因此可以很容易地看到图案。)

我需要能够放大和缩小此表,以及以其他方式管理它。但是,在处理大型表(1000行和多列或更多)时,浏览器需要花费大量时间在每个新的缩放级别进行渲染。做了一点研究,我看到TABLE元素需要多一点,因为浏览器必须根据表格中的单元格内容和可用宽度重新计算。将'table-layout'设置为'fixed'并为表提供宽度应该可以防止这种情况发生,但它仍然需要永远。

然后我认为用DIV元素制作一个表可能会起作用,因为这需要更少的总元素,并且没有像表那样的自动调整大小 - 我可以硬编码尺寸。我尝试了一些我在网上找到的代码,使用“display:table”(以前从未听说过),以及非表格式的DIV(喜欢编写单词......)。

这是我创建的测试页面,用于测试三个并计算它们的时间(view it here):

<!--
  Only tested in Google Chrome on Windows 7!
  I have not bothered testing in other browsers as this is a private project
  and all members of the project use Chrome.
-->
<html>
<head>
<title>Table Test</title>
<style>
#controls td {
  font-family: Courier New, monospace;
}
input[type=button] {
  width: 100%;
}
input[type=text] {
  width: 50px;
}
/* styles the pure table version */
table {
  table-layout: fixed;
  border-collapse: collapse;
}
.td {
  border: 1px solid black;
  width: 48px;
  height: 48px;
  vertical-align: top;
}
/* styles the DIV version using "display: table" */
.div-table {
  display: table;
  border: solid black;
  border-width: 1px 0px 0px 1px;
}
.div-table-row {
  display: table-row;
  width: auto;
  clear: both;
}
.div-table-col {
  float: left;
  display: table-column;
  width: 48px;
  height: 48px;
  border: solid black;
  border-width: 0px 1px 1px 0px;
}
/* styles the pure DIV version */
div#grid {
  border: solid black;
  border-width: 1px 0px 0px 1px;
}
.cell {
  display: inline-block;
  width: 49px;
  height: 49px;
  padding: 0px;
  margin: 0px;
  border: solid black;
  border-width: 0px 1px 1px 0px;
}
</style>

<script type="text/javascript">
function Stopwatch() {   // basic Stopwatch object to time functions
  var startTime = null; 
  var stopTime = null; 
  var running = false; 

  function getTime() {
    var day = new Date();
    return day.getTime();
  }

  this.start = function() { 
    if (running == true)
      return;
    else if (startTime != null) 
      stopTime = null; 
    running = true;    
    startTime = getTime();
    return startTime;
  }
  this.stop = function() { 
    if (running == false)
      return;    
    stopTime = getTime();
    running = false; 
    return stopTime;
  }
  this.duration = function() { 
    if (startTime == null || stopTime == null)
      return 'Undefined';
    else
      return (stopTime - startTime) / 1000;
  }
}
var stopwatch = new Stopwatch();

var x = 50;  // global dimension variables
var y = 100;

function updateDims() { // updates the global dimension variables
                        // returns true/false to indicate success
  x = parseInt(document.getElementById('x').value);
  y = parseInt(document.getElementById('y').value);
  if (typeof x == 'number' && x > 0 && typeof y == 'number' && y > 0) return true;
  return false;
}

function makeTableTable() {        // creates the pure table version
  if (!updateDims()) return false; // grab desired x/y dimensions

// grab count of how many times function has been run to calculate average
  var count = document.getElementById('count1');
  count = count.value = parseInt(count.value) + 1;
// grab previous average time to calculate new average
  var avg = parseFloat(document.getElementById('avg1').innerHTML);

// start the stopwatch, grabbing initial start time, and begin concatenating
  var start = stopwatch.start();
  var html = '<table id="grid" cellpadding="0" cellspacing="0">';
  for (var i = 0; i < y; i++) {  // for each row...
    html += '<tr>';
    for (var j = 0; j < x; j++) {  // build desired number of cells
      html += '<td class="td" id="'+j+'/'+[y-[i+1]]+'">' + [1+j+i*x] + '</td>';
    }
    html += '</tr>';  // close row
    j = 0;
  }
  html += '</table>';

// stop the watch and record duration it took to concatenate
  stopwatch.stop();
  document.getElementById('concat1').innerHTML = stopwatch.duration();

// start the watch again and insert HTML
  stopwatch.start();
  document.getElementById('output').innerHTML = html;

// stop the watch, grabbing the final end time, and record duration for innerHTML
  var end = stopwatch.stop();
  document.getElementById('insert1').innerHTML = stopwatch.duration();

// find total time from initial start time and final end time
  document.getElementById('total1').innerHTML = (end - start) / 1000;

// calculate average time
  if (count > 1)
    document.getElementById('avg1').innerHTML = Math.round(100000*(avg*(count-1)+(end-start)/1000)/count)/100000;
  else
    document.getElementById('avg1').innerHTML = (end-start) / 1000;
}

function makeDivTable() {          // creates the DIV version using "display: table"
  if (!updateDims()) return false;

  var count = document.getElementById('count2');
  count = count.value = parseInt(count.value) + 1;
  var avg = parseFloat(document.getElementById('avg2').innerHTML);

  var start = stopwatch.start();
  var html = '<div id="grid" class="div-table">';
  for (var i = 0; i < y; i++) {
    html += '<div class="div-table-row">';
    for (var j = 0; j < x; j++) {
      html += '<div class="div-table-col" id="'+j+'/'+[y-[i+1]]+'">' + [1+j+i*x] + '</div>';
    }
    html += '</div>';
    j = 0;
  }
  html += '</div>';

  stopwatch.stop();
  document.getElementById('concat2').innerHTML = stopwatch.duration();

  stopwatch.start();
  document.getElementById('output').innerHTML = html;

  var end = stopwatch.stop();
  document.getElementById('insert2').innerHTML = stopwatch.duration();
  document.getElementById('total2').innerHTML = (end - start) / 1000;

  if (count > 1)
    document.getElementById('avg2').innerHTML = Math.round(100000*(avg*(count-1)+(end-start)/1000)/count)/100000;
  else
    document.getElementById('avg2').innerHTML = (end-start) / 1000;

// update width of outer DIV
  document.getElementById('grid').style.width = 50 * x + 'px';
}

function makeDivDiv() {            // creates the pure DIV version
  if (!updateDims()) return false;
  var cells = x*y;   // will iterate through total number of cells,
                     // rather than by row and column
  var count = document.getElementById('count3');
  count = count.value = parseInt(count.value) + 1;
  var avg = parseFloat(document.getElementById('avg3').innerHTML);

  var start = stopwatch.start();
  var html = '<div id="grid">';
  for (var i = 0; i < cells; i++) {
    var id = [i-x*Math.floor(i/x)+1] + '/' + [y-Math.floor(i/x)];
    html += '<div class="cell" id="' + id + '">' + [i+1] + '</div>';
  }
  html += '</div>';

  stopwatch.stop();
  document.getElementById('concat3').innerHTML = stopwatch.duration();

  stopwatch.start();
  document.getElementById('output').innerHTML = html;

  var end = stopwatch.stop();
  document.getElementById('insert3').innerHTML = stopwatch.duration();
  document.getElementById('total3').innerHTML = (end - start) / 1000;

  if (count > 1)
    document.getElementById('avg3').innerHTML = Math.round(100000*(avg*(count-1)+(end-start)/1000)/count)/100000;
  else
    document.getElementById('avg3').innerHTML = (end-start) / 1000;

  document.getElementById('grid').style.width = 50 * x + 'px';
}

</script>

</head>
<body>

<table id="controls" border="1">
  <tr>
    <td>x: <input type="text" id="x" value="50"  /></td>
    <td>y: <input type="text" id="y" value="100" /></td>
    <td colspan="7"></td>
  </tr>
  <tr>
    <td><input type="button" onclick="makeTableTable()" value="Standard table" />
        <input type="hidden" id="count1" value="0" /></td>
    <td>Concatenation:</td>
    <td id="concat1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td> 
    <td>Insertion:</td>
    <td id="insert1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
    <td>Total:</td>
    <td id="total1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
    <td>Average:</td>
    <td id="avg1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
  </tr>
  <tr>
    <td><input type="button" onclick="makeDivTable()" value="Div w/ 'display:table'" />
        <input type="hidden" id="count2" value="0" /></td>
    <td>Concatenation:</td>
    <td id="concat2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td> 
    <td>Insertion:</td>
    <td id="insert2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
    <td>Total:</td>
    <td id="total2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
    <td>Average:</td>
    <td id="avg2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
  </tr>
  <tr>
    <td><input type="button" onclick="makeDivDiv()" value="Div Table" />
        <input type="hidden" id="count3" value="0" /></td>
    <td>Concatenation:</td>
    <td id="concat3">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
    <td>Insertion:</td>
    <td id="insert3">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
    <td>Total:</td>
    <td id="total3">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
    <td>Average:</td>
    <td id="avg3">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
  </tr>
</table><br />

<div id="output"></div>
</body>
</html>

首先尝试使用较小的值,例如默认值。然后尝试将x和y更改为1000,并尝试默认的浏览器缩放功能。生成表格以及放大或缩小都应该花费很长时间。

值得注意的是,据报道,所采取的时间显然不是它实际需要的时间 - 人们可以看到,仅仅是通过球场计算第二次计算(好的“一个密西西比”)。当几次运行后报告的平均时间分别为2.3,2.7和2.4时,400x400的网格分别持续大约13秒,5秒和7秒。所有与计时有关的代码都不会对它产生太大影响。您可以将其全部删除或全部注释(view it)并且它是相同的(对我来说是13,5和7)。

所以有两个问题:

首先,为什么这些数字存在差异?计时器应该计时函数中的所有内容。除了第一次调用updateDims之外,发生的两件事主要是字符串连接和innerHTML的赋值。 (网格的宽度也有变化,但我不知道这会花多少时间。)它是否只是浏览器渲染时间,一旦功能完成就会发生,所以没有定时?一些有利于此的证据是纯表格版本的显着时间要长得多,这意味着浏览器可能正在重新计算所有TD宽度等等。

其次,可以做些什么呢?什么?串联是它的一小部分,所以这不是问题。 (而且我已经尝试过进入阵列并加入,并且连接速度始终更快,至少对我而言。)此外,如果要相信秒表,将其插入innerHTML并不需要那么长时间。这是事后发生的一切。我曾经考虑过使用Backgrounder技术,所以即使需要一段时间来更新,至少它不会一直锁定浏览器,并且会发生一些明显的活动。但是如果锁定点发生在函数的“外部”,我看不出它是如何工作的。我可以设计自己的缩放功能,但我不相信它会比浏览器更快。初步测试说没有。

我正在尝试做的任何其他替代方案(虽然它可能对你很模糊;对不起)当然感激不尽!

StackOverflow上的其他一些页面:

Are large html tables slow?让人觉得桌子通常可以快速渲染。但是,这可能只适用于已经硬编码到HTML文件中的表,而不是像我的那样生成。

Javascript performance with creating large tables与我的情况非常相似,只是具有分页表的已接受解决方案对我来说不是一个选项,因为这会破坏目的。而我的表格不包含数据。然而,其中一位受访者确实暗示,使用如此庞大的表格,DOM方法可能比HTML解析更快(尽管我已经在其他地方看到反之亦然;可能含有大量元素)。当我有机会时,我可以测试一下。你觉得怎么样?

0 个答案:

没有答案