表格行排序和字符串性能

4
我有一个拥有大量行的表格,不适合分页。这个表格中的行可以通过点击列标题进行排序,触发基于http://www.exforsys.com/tutorials/jquery/jquery-basic-alphabetical-sorting.html的客户端排序算法。该函数会为每一行动态添加一个“expando”属性,从而缓存键的预排序状态:
row.sortKey = $(row).children('td').eq(column).text().toUpperCase();

正如您所看到的,属性值只是简单地设置为点击列的内容,排序完成后它们被丢弃(置空)。实际上,性能表现出奇的好 - 但包含更多文本的列似乎排序较慢。
由于排序仅是为了使用户更容易找到他们正在查找的行,我想可以通过使用substr(0,7)或类似方法来裁剪键值以加快速度(八个字符应该提供足够的精度)。然而,我发现进行substr()会产生更多的性能成本,而不是节省,如果有什么问题,它会使排序变慢。
有谁知道可以应用于此方法的任何(其他)优化吗?
这是一个更完整的示例:
var rows = $table.find('tbody > tr').get();
$.each(rows, function(index, row) {
    row.sortKey = $(row).children('td').eq(column).text().toUpperCase()
})
rows.sort(function(a, b) {
    if (a.sortKey < b.sortKey) return -1
    if (a.sortKey > b.sortKey) return 1
    return 0
})
$.each(rows, function(index, row) {
    $table.children('tbody').append(row)
    row.sortKey = null
})

编辑:这是我的代码的最终版本,包括下面答案中提供的许多优化:

$('table.sortable').each(function() {
    var $table = $(this);
    var storage = new Array();
    var rows = $table.find('tbody > tr').get();
    $('th', $table).each(function(column) {
        $(this).click(function() {
            var colIndex = this.cellIndex;
            for(i=0;i<rows.length;i++) {
                rows[i].sortKey = $(rows[i].childNodes[colIndex]).text().toUpperCase();
            }
            rows.sort(function(a, b) {
                if (a.sortKey < b.sortKey) return -1;
                if (a.sortKey > b.sortKey) return 1;
                return 0;
            });
            for(i=0;i<rows.length;i++) {
                storage.push(rows[i]);
                rows[i].sortKey = null;
            }
            $table.children('tbody').append(storage);
        });
    });
});
6个回答

5
你提供的示例存在几个问题。第一个问题是你在循环内部使用jquery选择列,这会导致严重的性能损失。如果你可以控制html代码,我建议你使用普通的DOM方法来获取所需的列进行排序。请注意,有时你可能期望得到一个表格单元格节点,但实际上可能得到一个文本节点。稍后我会回来解释这个问题。使用for循环要比$.each更快,但我建议你进行基准测试。
我使用你提供的示例创建了一个包含1000行的表格,在我的电脑上需要大约750毫秒才能完成排序。我进行了一些优化(见下面的代码),并将其缩短到200毫秒左右。排序本身只需要大约20毫秒(不错)。
var sb = [];
sb.push("<table border='1'>");
var x;
for (var i = 0; i < 1000; i++) {
    x = Math.floor(Math.random() * 1000);
    sb.push("<tr><td>data");
    sb.push(x);
    sb.push("</td></tr>");
}
sb.push("</table>");

document.write(sb.join(""));

$table = $("table");
var rows = $table.find('tbody > tr').get();
var columnIndex = 0;

var t = new Date();

$.each(rows, function(index, row) {
    row.sortKey = $(row.childNodes[columnIndex]).text();
});
alert("Sort key: " + (new Date() - t) + "ms");
t = new Date();

rows.sort(function(a, b) {
        return a.sortKey.localeCompare(b.sortKey);
});
alert("Sort: " + (new Date() - t) + "ms");
t = new Date();
var tbody = $table.children('tbody').get(0);

$.each(rows, function(index, row) {
    tbody.appendChild(row);
    delete row.sortKey;
})

alert("Table: " + (new Date() - t) + "ms");

当您追求速度写代码时,希望每次迭代都尽可能快,因此不要在循环中执行可以在循环外执行的操作。例如,将$table.children('tbody').get(0);移动到最后一个循环之外,可以极大地提速。
至于使用DOM方法访问列,您需要的是列索引,因此可以遍历th列直到找到正确的列(前提是th标记和td标记的html格式相同)。然后,可以使用该索引获取正确的行子节点。
另外,如果表格是静态的且用户可能对其进行更多排序,则应缓存行并不删除sortKey属性。这样可以节省约30%的排序时间。还有表格内容的问题。如果内容是文本,则此排序方法就可以了。如果它包含数字等,则需要考虑一下,因为我使用的是String类型的localeCompare方法。

通过索引访问列而不是作为行的<td>子元素,对性能和排序时间产生了显着影响,1500行现在只需要约2秒。这绝对足够适用于我的应用程序,谢谢! - Ola Tuvesson

2

我能想到的一种优化方法是修改这段代码:

$.each(rows, function(index, row) {
        $table.children('tbody').append(row)
        row.sortKey = null
})

不要逐行添加,改为一次添加更多行甚至全部。为此,您需要首先创建所有行的字符串,然后一次性添加。

使用 array.push 和 array.join 来连接字符串。


听起来是个好主意,我会试一试看能做出什么! - Ola Tuvesson
我将授予您“最佳答案”奖项,因为您提供的两个答案都显著提高了性能。 - Ola Tuvesson

1

0

在mkoryak的建议下,我现在已经修改了代码,将行组装成一个数组,然后一次性追加它们:

$.each(rows, function(index, row) {
    storage.push(row);
    row.sortKey = null;
});
$table.children('tbody').append(storage);

这似乎提高了性能约25% - 现在对1500行进行排序需要大约3秒,而以前需要大约4秒。


我认为应该是 $table.children('tbody').append(storage.join("")); - mkoryak
1
既然数组将行存储为对象而不是字符串,如果我执行 storage.join(''),输出中只会得到一堆 [object HTMLTableRowElement]... - Ola Tuvesson
是的,那很糟糕。如果您将HTML字符串推入数组而不是行元素会怎样呢?我不知道append如何使用该数组,但它可能需要迭代该数组,并按顺序将HTML连接起来以进行附加。 - mkoryak
Ola,你的修改很好。 "append" 在HTML中不起作用(你可能已经知道了),而且尝试使用innerHTML解决方案也没有意义,因为对于table、tbody和tr来说,innerHTML是只读的。http://support.microsoft.com/kb/239832 - Prestaul
抱歉,我应该说在IE中innerHTML是只读的...虽然这没有意义,但事实就是如此。 - Prestaul

0

我曾经使用DataTables jQuery插件来完成这种工作,以前我也用过YUI DataTable组件。jQuery DataTables 1.5 beta版本包括从HTML代码实例化或使用AJAX数据源。它的设置非常简单,对于筛选和排序数据也非常快。

我发现一旦我的代码超过了1600行,唯一的方法就是使用服务器端数据加载。


-4

嗨,欧拉!

这可能并不是与您的问题相关的内容,但我们在谈论多少行?对于非常大量的行 - 数万行 - 表格非常慢。更轻量级的解决方案是使用div和css来模拟表格。它可能不是语义上正确的(哈!表格和语义在同一个短语中,谁会想到呢?),但速度要快得多。


通常情况下,行数不超过几百行(在<0.5秒内排序),最多约为1500行(在约4秒内排序)。是的,有趣的是,在这种情况下,<table>确实是100%合理的选择! - Ola Tuvesson
使用表格来存储数据没有任何问题,这也是它们存在的原因。事实上,表格是在表格中显示数据的语义适当的解决方案。并不是所有的表格都是不好的! - cgp
我同意!从上世纪九十年代基于表格的Photoshop“切片”到新千年完全基于CSS的语义正确的可伸缩性,现在我每次都会利用适当的<table>机会。而<colgroup>、<caption>、<thead>等都非常棒! - Ola Tuvesson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接