使用JavaScript对HTML表格进行排序

128
我需要一个表格排序的解决方案(使用JavaScript),但我似乎还没有找到合适的。我只是需要按字母顺序对每一列进行排序。它不需要忽略任何代码或数字,也不需要处理货币。当点击列标题时,它只需从 A 到 Z / 从Z到A 进行切换排序。
请问有人知道这样一个非常简单的解决方案吗?
20个回答

274

重新审视一个旧解决方案,我想为它的五周年庆典提供一次面貌更新!

  • 纯Javascript(ES6)
  • 进行字母和数字排序-升序和降序
  • 适用于ChromeFirefoxSafari(以及IE11,见下文)

快速解释

  1. 为所有标题(th)单元格添加click事件...
  2. 对于当前table,查找所有行(除了第一行)...
  3. 根据所点击列的值对行进行排序...
  4. 按新顺序将行插入表格中。

const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;

const comparer = (idx, asc) => (a, b) => ((v1, v2) => 
    v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
    )(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));

// do the work...
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
    const table = th.closest('table');
    Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
        .sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
        .forEach(tr => table.appendChild(tr) );
})));
table, th, td {
    border: 1px solid black;
}
th {
    cursor: pointer;
}
<table>
    <tr><th>Country</th><th>Date</th><th>Size</th></tr>
    <tr><td>France</td><td>2001-01-01</td><td><i>25</i></td></tr>
    <tr><td><a href=#>spain</a></td><td><i>2005-05-05</i></td><td></td></tr>
    <tr><td><b>Lebanon</b></td><td><a href=#>2002-02-02</a></td><td><b>-17</b></td></tr>
    <tr><td><i>Argentina</i></td><td>2005-04-04</td><td><a href=#>100</a></td></tr>
    <tr><td>USA</td><td></td><td>-6</td></tr>
</table>


IE11支持(非ES6)

如果你想要支持IE11,你需要放弃ES6语法,并使用Array.fromElement.closest的替代方案。

即:

var getCellValue = function(tr, idx){ return tr.children[idx].innerText || tr.children[idx].textContent; }

var comparer = function(idx, asc) { return function(a, b) { return function(v1, v2) {
        return v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2);
    }(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}};

// do the work...
Array.prototype.slice.call(document.querySelectorAll('th')).forEach(function(th) { th.addEventListener('click', function() {
        var table = th.parentNode
        while(table.tagName.toUpperCase() != 'TABLE') table = table.parentNode;
        Array.prototype.slice.call(table.querySelectorAll('tr:nth-child(n+2)'))
            .sort(comparer(Array.prototype.slice.call(th.parentNode.children).indexOf(th), this.asc = !this.asc))
            .forEach(function(tr) { table.appendChild(tr) });
    })
});

比较函数分解

为了简洁起见,我压缩了comparer()函数。它有点复杂/难以阅读,因此在此再次展开/格式化/加注释。

// Returns a function responsible for sorting a specific column index 
// (idx = columnIndex, asc = ascending order?).
var comparer = function(idx, asc) { 

    // This is used by the array.sort() function...
    return function(a, b) { 

        // This is a transient function, that is called straight away. 
        // It allows passing in different order of args, based on 
        // the ascending/descending order.
        return function(v1, v2) {

            // sort based on a numeric or localeCompare, based on type...
            return (v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2)) 
                ? v1 - v2 
                : v1.toString().localeCompare(v2);
        }(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
    }
};

12
请注意,如果表头行被包含在< thead >元素中,则语句th.parentNode.parentNode会失败。我建议使用“ var table = th.closest('table');”。请注意,Element.closest()似乎没有在IE11 / Edge中实现。 - Ed Griebel
21
作为一个像我一样的 JavaScript 新手,我希望你能提供一些建议,因为我浪费了很多时间才明白为什么它对我不起作用。如果你只是把这个脚本放在 <script> 标签中的 HTML 中,那么什么也不会发生。你需要将这个函数链接到某个事件上,例如加载窗口: window.onload=function(){ /* 这个答案中的脚本 */},然后它就可以正常工作了! - Vladimir
2
@NickGrealy 感谢您优美的代码!这个指定叫什么名字:this.asc = !this.asc - krolovolk
7
如果您的行位于一个<tbody>标签内,而这个答案无法正常工作,请参考这个微调 - jedwards
2
非常好!我不得不查看https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild,才能理解为什么table.appendChild(tr)不会使当前的tr加倍。MDN指出:“如果给定的子节点是文档中现有节点的引用,则appendChild()将其从当前位置移动到新位置”。 - Donapieppo
显示剩余24条评论

87

我写了一些代码,可以按行对表格进行排序,假设只有一个<tbody>,并且单元格没有colspan属性。

function sortTable(table, col, reverse) {
    var tb = table.tBodies[0], // use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
        tr = Array.prototype.slice.call(tb.rows, 0), // put rows into array
        i;
    reverse = -((+reverse) || -1);
    tr = tr.sort(function (a, b) { // sort rows
        return reverse // `-1 *` if want opposite order
            * (a.cells[col].textContent.trim() // using `.textContent.trim()` for test
                .localeCompare(b.cells[col].textContent.trim())
               );
    });
    for(i = 0; i < tr.length; ++i) tb.appendChild(tr[i]); // append each row in order
}
// sortTable(tableNode, columId, false);

如果您不想做上述假设,您需要考虑在每种情况下如何处理。(例如,将所有内容放入一个中或将所有前面的colspan值相加等)。然后,您可以将此附加到每个表格中,例如假设标题位于中。
function makeSortable(table) {
    var th = table.tHead, i;
    th && (th = th.rows[0]) && (th = th.cells);
    if (th) i = th.length;
    else return; // if no `<thead>` then do nothing
    while (--i >= 0) (function (i) {
        var dir = 1;
        th[i].addEventListener('click', function () {sortTable(table, i, (dir = 1 - dir))});
    }(i));
}

function makeAllSortable(parent) {
    parent = parent || document.body;
    var t = parent.getElementsByTagName('table'), i = t.length;
    while (--i >= 0) makeSortable(t[i]);
}

然后在onload事件上调用makeAllSortable函数。


这是一个在表格上运行的示例fiddle


@PaulS。非常感谢,非常有用。我在所有浏览器上进行了测试,除了IE10之外,它都运行良好,您有任何想法吗? - user2517028
@user2517028使用_IE11_来模拟旧版本,这对我有效,直到IE8中addEventListener失败(String.prototype.trim也会失败)。为了尝试找出问题出在哪里,请按F12并查看_Console_中出现/过的错误。 - Paul S.
谢谢,代码写得很好。有没有想法让它适用于数字呢?因为实际上,例如9被认为比42大。如果可能的话,我想避免填充数字... - Fla
另外,为什么需要使用trim()函数来处理字符串? - Fla
13
在MDN上找到.localeCompare()方法的用法,使用numeric: true选项即可在字符串中正确比较数字。将.localeCompare(b.cells[col].textContent.trim())替换为.localeCompare(b.cells[col].textContent.trim(), undefined, {numeric: true})即可。 - Fla
显示剩余5条评论

70

Nick Grealy's accepted answer 很好,但如果您的行在一个 <tbody> 标记内部,则会有一些奇怪的行为(第一行永远不会排序,排序后的行最终会在 tbody 标记外部,并可能丢失格式)。

然而这是一个简单的修复方法:

只需更改:

document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
  const table = th.closest('table');
  Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
    .sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
    .forEach(tr => table.appendChild(tr) );

收件人:

document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
  const table = th.closest('table');
  const tbody = table.querySelector('tbody');
  Array.from(tbody.querySelectorAll('tr'))
    .sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
    .forEach(tr => tbody.appendChild(tr) );

5
非常好的修复!因为我的表格中有 <tbody>,所以出现了问题,但这个修复解决了它。 - T-101
你绝对救了我!我一直在尝试解决同样的问题,但都没有成功。这是一个非常干净的解决tbody标签/固定第一行问题的方法。 - k0rnik
如果我在 to_html 中使用了格式化程序,我的浮点数已被转换为字符串,这使得上面的排序函数无法正常工作。是否有可能升级代码,以便将 lambda x: '{0:.3f}%'.format(x*100) 生成的字符串转换回浮点数,以便正确排序? - k0rnik
我可以使用parseFloat来修复浮点数的字符串格式问题,在这种情况下,我已经修改了您的代码中的一行:(parseFloat(getCellValue(asc ? a : b, idx)), parseFloat(getCellValue(asc ? b : a, idx))。问题在于,对于非浮点数字符串列,parseFloat将返回NaN,这将导致无法进行排序。我正在努力修复您的代码,以便在解析之前检查parseFloat是否isNaN。 - k0rnik
这适用于除了我的日期字段之外的所有字段。有任何想法为什么它不能与日期字段一起使用吗? - user3520445

8
我知道用Javascript对HTML表格进行排序的最佳方法是使用以下函数。
只需将要排序的表格的id和行上的列号传递给它。它假设您正在排序的列是数字或其中包含数字,并将进行正则表达式替换以获取数字本身(非常适合带有符号的货币和其他数字) 。
function sortTable(table_id, sortColumn){
    var tableData = document.getElementById(table_id).getElementsByTagName('tbody').item(0);
    var rowData = tableData.getElementsByTagName('tr');            
    for(var i = 0; i < rowData.length - 1; i++){
        for(var j = 0; j < rowData.length - (i + 1); j++){
            if(Number(rowData.item(j).getElementsByTagName('td').item(sortColumn).innerHTML.replace(/[^0-9\.]+/g, "")) < Number(rowData.item(j+1).getElementsByTagName('td').item(sortColumn).innerHTML.replace(/[^0-9\.]+/g, ""))){
                tableData.insertBefore(rowData.item(j+1),rowData.item(j));
            }
        }
    }
}

使用示例:

$(function(){
    // pass the id and the <td> place you want to sort by (td counts from 0)
    sortTable('table_id', 3);
});

我喜欢这个解决方案,但使用sort()不是更快吗? - Martin Zvarík
为什么你需要在jQuery中放置一个匿名函数来调用你的函数?难道你不能直接调用 sortTable('table_id', 3) 吗? - payne

7
它不仅仅是“排序”,但dataTables.net可以满足你的需求。我每天都在使用它,它得到了很好的支持,并且非常快速(需要jQuery)。

http://datatables.net/

DataTables是jQuery JavaScript库的插件。它是一个高度灵活的工具,基于渐进增强的基础,可以为任何HTML表格添加高级交互控件。
Google可视化是另一个选项,但需要比dataTables更多的设置,但不需要任何特定的框架/库(除了google.visualizations)。

http://code.google.com/apis/ajax/playground/?type=visualization#table

如果您使用其他JavaScript框架,还有其他选项可供选择。Dojo、Prototype等都有可用的“表格增强”插件,可以提供至少表格排序功能。许多插件提供更多功能,但是我要重申一下... 我还没有遇到过像datatables.net这样功能强大且快速的插件。


1
嗯,我会看一下的。虽然我希望能有一个JavaScript的解决方案,但是如果这个方法可行的话,那也不错!我的唯一担心是,当表格中有HTML时,有些人可能会感到困惑,这个方法是否只是读取HTML而不关心它正在查看什么?还是会尝试忽略它? - lukeseager
严格来说,它是JavaScript。jQuery只是JS框架...并非魔法。DataTables在处理一些HTML方面非常出色,但是您可能需要编写自定义排序器(记录在他的网站上)。 - BLSully
4
@BLSully:http://listjs.com/怎么样?我看到datatables有点笨重,在某些情况下杀鸡焉用牛刀。 - Adriano
@AdrienBe:不错的建议!以前没有遇到过这个。需要看看它在处理大数据集时的表现如何。我之前在 DataTables 中运行了 15,000+ 行,性能在现代浏览器中仍然可以接受。但我绝对同意 DataTables 可能对某些问题来说太过笨重。ListJS 看起来是一个很好的替代方案(还可以使用除表格以外的 HTML)。 - BLSully
@BLSully:今天我用了它,感觉还不错。这里有一个非常简单的例子,可以帮助你入门https://dev59.com/A3rZa4cB1Zd3GeqPyybK#23078200 - Adriano

6

使用:hover箭头效果进行表格排序。只需将类.order添加到需要排序的每个列的<th>元素中。

function table_sort() {
  const styleSheet = document.createElement('style')
  styleSheet.innerHTML = `
        .order-inactive span {
            visibility:hidden;
        }
        .order-inactive:hover span {
            visibility:visible;
        }
        .order-active span {
            visibility: visible;
        }
    `
  document.head.appendChild(styleSheet)

  document.querySelectorAll('th.order').forEach(th_elem => {
    let asc = true
    const span_elem = document.createElement('span')
    span_elem.style = "font-size:0.8rem; margin-left:0.5rem"
    span_elem.innerHTML = "▼"
    th_elem.appendChild(span_elem)
    th_elem.classList.add('order-inactive')

    const index = Array.from(th_elem.parentNode.children).indexOf(th_elem)
    th_elem.addEventListener('click', (e) => {
      document.querySelectorAll('th.order').forEach(elem => {
        elem.classList.remove('order-active')
        elem.classList.add('order-inactive')
      })
      th_elem.classList.remove('order-inactive')
      th_elem.classList.add('order-active')

      if (!asc) {
        th_elem.querySelector('span').innerHTML = '▲'
      } else {
        th_elem.querySelector('span').innerHTML = '▼'
      }
      const arr = Array.from(th_elem.closest("table").querySelectorAll('tbody tr'))
      arr.sort((a, b) => {
        const a_val = a.children[index].innerText
        const b_val = b.children[index].innerText
        return (asc) ? a_val.localeCompare(b_val) : b_val.localeCompare(a_val)
      })
      arr.forEach(elem => {
        th_elem.closest("table").querySelector("tbody").appendChild(elem)
      })
      asc = !asc
    })
  })
}

table_sort()
<table>
  <thead>
    <tr>
      <th class="order">Country</th>
      <th class="order">Date</th>
      <th class="order">Size</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>France</td>
      <td>2001-01-01</td>
      <td><i>25</i></td>
    </tr>
    <tr>
      <td><a href=#>spain</a></td>
      <td><i>2005-05-05</i></td>
      <td></td>
    </tr>
    <tr>
      <td><b>Lebanon</b></td>
      <td><a href=#>2002-02-02</a></td>
      <td><b>-17</b></td>
    </tr>
    <tr>
      <td><i>Argentina</i></td>
      <td>2005-04-04</td>
      <td><a href=#>100</a></td>
    </tr>
    <tr>
      <td>USA</td>
      <td></td>
      <td>-6</td>
    </tr>
  </tbody>
</table>


修复标题排序后,运行良好。使用:const arr = Array.from(th_elem.closest("table").querySelectorAll('tbody tr')).slice(1) - WinEunuuchs2Unix
在循环中查询closest表格及其tbody是低效的。你应该将tbody缓存到一个变量中。此外,你可以用单个replaceChildren调用来替换循环中的appendChildtbody.replaceChildren(...arr) - JukkaP

4

这里是使用纯JavaScript的完整示例。用于排序的算法基本上是冒泡排序(BubbleSort)。这里有一个Fiddle

   <!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">

<script type="text/javascript">
    function sort(ascending, columnClassName, tableId) {
        var tbody = document.getElementById(tableId).getElementsByTagName(
                "tbody")[0];
        var rows = tbody.getElementsByTagName("tr");

        var unsorted = true;

        while (unsorted) {
            unsorted = false

            for (var r = 0; r < rows.length - 1; r++) {
                var row = rows[r];
                var nextRow = rows[r + 1];

                var value = row.getElementsByClassName(columnClassName)[0].innerHTML;
                var nextValue = nextRow.getElementsByClassName(columnClassName)[0].innerHTML;

                value = value.replace(',', '.'); // in case a comma is used in float number
                nextValue = nextValue.replace(',', '.');

                if (!isNaN(value)) {
                    value = parseFloat(value);
                    nextValue = parseFloat(nextValue);
                }

                if (ascending ? value > nextValue : value < nextValue) {
                    tbody.insertBefore(nextRow, row);
                    unsorted = true;
                }
            }
        }
    };
</script>
</head>
<body>
    <table id="content-table">
        <thead>
            <tr>
                <th class="id">ID <a
                    href="javascript:sort(true, 'id', 'content-table');">asc</a> <a
                    href="javascript:sort(false, 'id', 'content-table');">des</a>
                </th>
                <th class="country">Country <a
                    href="javascript:sort(true, 'country', 'content-table');">asc</a> <a
                    href="javascript:sort(false, 'country', 'content-table');">des</a>
                </th>
                <th class="some-fact">Some fact <a
                    href="javascript:sort(true, 'some-fact', 'content-table');">asc</a>
                    <a href="javascript:sort(false, 'some-fact', 'content-table');">des</a>
                <th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td class="id">001</td>
                <td class="country">Germany</td>
                <td class="some-fact">16.405</td>
            </tr>
            <tr>
                <td class="id">002</td>
                <td class="country">France</td>
                <td class="some-fact">10.625</td>
            </tr>
            <tr>
                <td class="id">003</td>
                <td class="country">UK</td>
                <td class="some-fact">15.04</td>
            </tr>
            <tr>
                <td class="id">004</td>
                <td class="country">China</td>
                <td class="some-fact">13.536</td>
            </tr>
        </tbody>
    </table>
</body>
</html>

你可以从这里检查源代码:https://github.com/wmentzel/table-sort

4
你可以处理一个json数组和sort函数。这是一种相当易于维护的结构,可以进行操作(例如:排序)。
以下是未经测试的想法。如果你传入一个按顺序放置列的数组,它将支持多重排序和顺序排序。
var DATA_TABLE = {
    {name: 'George', lastname: 'Blarr', age:45},
    {name: 'Bob', lastname: 'Arr', age: 20}
    //...
};

function sortDataTable(arrayColNames, asc) { // if not asc, desc
    for (var i=0;i<arrayColNames.length;i++) {
        var columnName = arrayColNames[i];
        DATA_TABLE = DATA_TABLE.sort(function(a,b){
            if (asc) {
                return (a[columnName] > b[columnName]) ? 1 : -1;
            } else {
                return (a[columnName] < b[columnName]) ? 1 : -1;
            }
        });
    }
}

function updateHTMLTable() {
    // update innerHTML / textContent according to DATA_TABLE
    // Note: textContent for firefox, innerHTML for others
}

现在让我们想象一下,您需要按姓氏、名字,最后按年龄排序。

var orderAsc = true;
sortDataTable(['lastname', 'name', 'age'], orderAsc);

这应该会产生类似于以下内容的结果:
{name: 'Jack', lastname: 'Ahrl', age: 20},
{name: 'Jack', lastname: 'Ahrl', age: 22},
//...

我有一个相当长的表格,大约有200行并且还在增长。您认为这种方法比其他答案更快吗? - posfan12

3

按单元格排序表格行。 1. 更简单,而且有一些功能。 2. 在排序时区分“数字”和“字符串”。 3. 添加切换以按升序或降序排序。

var index;      // cell index
var toggleBool; // sorting asc, desc 
function sorting(tbody, index){
    this.index = index;
    if(toggleBool){
        toggleBool = false;
    }else{
        toggleBool = true;
    }

    var datas= new Array();
    var tbodyLength = tbody.rows.length;
    for(var i=0; i<tbodyLength; i++){
        datas[i] = tbody.rows[i];
    }

    // sort by cell[index] 
    datas.sort(compareCells);
    for(var i=0; i<tbody.rows.length; i++){
        // rearrange table rows by sorted rows
        tbody.appendChild(datas[i]);
    }   
}

function compareCells(a,b) {
    var aVal = a.cells[index].innerText;
    var bVal = b.cells[index].innerText;

    aVal = aVal.replace(/\,/g, '');
    bVal = bVal.replace(/\,/g, '');

    if(toggleBool){
        var temp = aVal;
        aVal = bVal;
        bVal = temp;
    } 

    if(aVal.match(/^[0-9]+$/) && bVal.match(/^[0-9]+$/)){
        return parseFloat(aVal) - parseFloat(bVal);
    }
    else{
          if (aVal < bVal){
              return -1; 
          }else if (aVal > bVal){
                return 1; 
          }else{
              return 0;       
          }         
    }
}

以下是HTML示例

            <table summary="Pioneer">

                <thead>
                    <tr>
                        <th scope="col"  onclick="sorting(tbody01, 0)">No.</th>
                        <th scope="col"  onclick="sorting(tbody01, 1)">Name</th>
                        <th scope="col"  onclick="sorting(tbody01, 2)">Belong</th>
                        <th scope="col"  onclick="sorting(tbody01, 3)">Current Networth</th>
                        <th scope="col"  onclick="sorting(tbody01, 4)">BirthDay</th>
                        <th scope="col"  onclick="sorting(tbody01, 5)">Just Number</th>
                    </tr>
                </thead>

                <tbody id="tbody01">
                    <tr>
                        <td>1</td>
                        <td>Gwanshic Yi</td>
                        <td>Gwanshic Home</td>
                        <td>120000</td>
                        <td>1982-03-20</td>
                        <td>124,124,523</td>
                    </tr>
                    <tr>
                        <td>2</td>
                        <td>Steve Jobs</td>
                        <td>Apple</td>
                        <td>19000000000</td>
                        <td>1955-02-24</td>
                        <td>194,523</td>
                    </tr>
                    <tr>
                        <td>3</td>
                        <td>Bill Gates</td>
                        <td>MicroSoft</td>
                        <td>84300000000</td>
                        <td>1955-10-28</td>
                        <td>1,524,124,523</td>
                    </tr>
                    <tr>
                        <td>4</td>
                        <td>Larry Page</td>
                        <td>Google</td>
                        <td>39100000000</td>
                        <td>1973-03-26</td>
                        <td>11,124,523</td>
                    </tr>
                </tbody>
            </table>


2

另一个简洁但易读的解决方案: 只需要将类.order添加到每个要排序的列的<th>元素中。

document.querySelectorAll('th.order').forEach(th_elem => {
    let asc=true
    const index = Array.from(th_elem.parentNode.children).indexOf(th_elem)          
    th_elem.addEventListener('click', (e) => {              
        const arr = [... th_elem.closest("table").querySelectorAll('tbody tr')]
        arr.sort( (a, b) => {
            const a_val = a.children[index].innerText
            const b_val = b.children[index].innerText                   
            return (asc) ? a_val.localeCompare(b_val) : b_val.localeCompare(a_val)
        })
        arr.forEach(elem => {                   
            th_elem.closest("table").querySelector("tbody").appendChild(elem)
        })
        asc = !asc
    })
})

1
非常感谢这个漂亮的解决方案! 我只需要切掉表格的第一行,以防止TH被排序...const arr = [... th_elem.closest("table").querySelectorAll('tbody tr')].slice(1) - T.M.

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