首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何让排序HTML表格更快?

如何让排序HTML表格更快?
EN

Stack Overflow用户
提问于 2019-12-11 17:44:53
回答 3查看 1.7K关注 0票数 10

我是一个Javascript新手。我写的代码是来自W3Schools的更新。

代码语言:javascript
复制
function sortFunctionNumeric(n) {
  var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
  table = document.getElementById("reportingTable");
  switching = true;
  //Set the sorting direction to ascending:
  dir = "asc";
  /*Make a loop that will continue until
  no switching has been done:*/
  while (switching) {
    //start by saying: no switching is done:
    switching = false;
    rows = table.rows;
    /*Loop through all table rows (except the
    first, which contains table headers):*/
    for (i = 1; i < (rows.length - 1); i++) {
      //start by saying there should be no switching:
      shouldSwitch = false;
      /*Get the two elements you want to compare,
      one from current row and one from the next:*/
      x = rows[i].getElementsByTagName("TD")[n];
      y = rows[i + 1].getElementsByTagName("TD")[n];
      /*check if the two rows should switch place,
      based on the direction, asc or desc:*/
      if (dir == "asc") {
        if (Number(x.innerHTML) > Number(y.innerHTML)) {
          //if so, mark as a switch and break the loop:
          shouldSwitch = true;
          break;
        }
      } else if (dir == "desc") {
        if (Number(x.innerHTML) < Number(y.innerHTML)) {
          //if so, mark as a switch and break the loop:
          shouldSwitch = true;
          break;
        }
      }
    }
    if (shouldSwitch) {
      /*If a switch has been marked, make the switch
      and mark that a switch has been done:*/
      rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
      switching = true;
      //Each time a switch is done, increase this count by 1:
      switchcount++;
    } else {
      /*If no switching has been done AND the direction is "asc",
      set the direction to "desc" and run the while loop again.*/
      if (switchcount == 0 && dir == "asc") {
        dir = "desc";
        switching = true;
      }
    }
  }
}

现在,排序工作得非常好。然而,它是非常慢的!

我处理了很多daqta行(根据项目的不同,它可以高达9000行)。有没有办法加速我的Javascript代码?

EN

回答 3

Stack Overflow用户

发布于 2019-12-11 18:06:47

它有助于避免在浏览器JavaScript中实现排序算法,因为即使您最终实现了相同的排序算法,JavaScript内置的Array.prototype.sort方法也会快得多(IIRC大多数JS引擎可能都会使用QuickSort )。

我是这样做的:

  • 获取JavaScript Array中的所有<tr>元素。
    • 您需要将querySelectorAllArray.from结合使用,因为querySelectorAll 不返回数组,它实际上返回一个NodeListOf<T> -但您可以将其传递到Array.from以将其转换为数组

  • 一旦有了Array,就可以使用带有自定义回调的Array.prototype.sort(comparison)从要比较的两个<tr>元素的<td>子元素中提取数据,然后进行数据比较(在比较数值时使用x - y技巧。对于string值,您将希望使用String.prototype.localeCompare,例如,对Array进行排序(即使对于包含数万行的表,这也不会超过几毫秒,as QuickSort is really quick!)使用父<tbody>.

appendChild重新添加每个<tr>

我在TypeScript中的实现如下所示,以及位于其下的脚本运行器中包含有效JavaScript的工作示例:

代码语言:javascript
复制
// This code has TypeScript type annotations, but can be used directly as pure JavaScript by just removing the type annotations first.

function sortTableRowsByColumn( table: HTMLTableElement, columnIndex: number, ascending: boolean ): void {

    const rows = Array.from( table.querySelectorAll( ':scope > tbody > tr' ) );

    rows.sort( ( x: HTMLtableRowElement, y: HTMLtableRowElement ) => {
        const xValue: string = x.cells[columnIndex].textContent;
        const yValue: string = y.cells[columnIndex].textContent;

        // Assuming values are numeric (use parseInt or parseFloat):
        const xNum = parseFloat( xValue );
        const yNum = parseFloat( yValue );

        return ascending ? ( xNum - yNum ) : ( yNum - xNum ); // <-- Neat comparison trick.
    } );

    // There is no need to remove the rows prior to adding them in-order because `.appendChild` will relocate existing nodes.
    for( let row of rows ) {
        table.tBodies[0].appendChild( row );
    }
}

function onColumnHeaderClicked( ev: Event ): void {

    const th = ev.currentTarget as HTMLTableCellElement;
    const table = th.closest( 'table' );
    const thIndex: number = Array.from( th.parentElement.children ).indexOf( th );

    const ascending = ( th.dataset as any ).sort != 'asc';

    sortTableRowsByColumn( table, thIndex, ascending );

    const allTh = table.querySelectorAll( ':scope > thead > tr > th' );
    for( let th2 of allTh ) {
        delete th2.dataset['sort'];
    }

    th.dataset['sort'] = ascending ? 'asc' : 'desc';
}

我的sortTableRowsByColumn函数假设如下:

  • 您的<table>元素使用<thead>,并具有使用现代浏览器的单个<tbody>
  • You're,该浏览器支持=>Array.fromfor( x of y ):scope.closest()和D53(即不是Internet Explorer11)。您的数据以D58的D56(D57)单元格的形式存在。表中没有D61或D62单元格。H263 F264

这是一个可运行的示例。只需单击列标题即可按升序或降序进行排序:

代码语言:javascript
复制
function sortTableRowsByColumn( table, columnIndex, ascending ) {

    const rows = Array.from( table.querySelectorAll( ':scope > tbody > tr' ) );
    
    rows.sort( ( x, y ) => {
    
        const xValue = x.cells[columnIndex].textContent;
        const yValue = y.cells[columnIndex].textContent;
        
        const xNum = parseFloat( xValue );
        const yNum = parseFloat( yValue );

        return ascending ? ( xNum - yNum ) : ( yNum - xNum );
    } );

    for( let row of rows ) {
        table.tBodies[0].appendChild( row );
    }
}

function onColumnHeaderClicked( ev ) {
    
    const th = ev.currentTarget;
    const table = th.closest( 'table' );
    const thIndex = Array.from( th.parentElement.children ).indexOf( th );

    const ascending = !( 'sort' in th.dataset ) || th.dataset.sort != 'asc';
    
    const start = performance.now();

    sortTableRowsByColumn( table, thIndex, ascending );

    const end = performance.now();
    console.log( "Sorted table rows in %d ms.",  end - start );

    const allTh = table.querySelectorAll( ':scope > thead > tr > th' );
    for( let th2 of allTh ) {
        delete th2.dataset['sort'];
    }
 
    th.dataset['sort'] = ascending ? 'asc' : 'desc';
}

window.addEventListener( 'DOMContentLoaded', function() {
    
    const table = document.querySelector( 'table' );
    const tb = table.tBodies[0];

    const start = performance.now();

    for( let i = 0; i < 9000; i++ ) {
        
        let row = table.insertRow( -1 );
        row.insertCell( -1 ).textContent = Math.ceil( Math.random() * 1000 );
        row.insertCell( -1 ).textContent = Math.ceil( Math.random() * 1000 );
        row.insertCell( -1 ).textContent = Math.ceil( Math.random() * 1000 );
    }

    const end = performance.now();
    console.log( "IT'S OVER 9000 ROWS added in %d ms.", end - start );
    
} );
代码语言:javascript
复制
html { font-family: sans-serif; }

table {
    border-collapse: collapse;
    border: 1px solid #ccc;
}
    table > thead > tr > th {
        cursor: pointer;
    }
    table > thead > tr > th[data-sort=asc] {
        background-color: blue;
        color: white;
    }
    table > thead > tr > th[data-sort=desc] {
        background-color: red;
        color: white;
    }
    table th,
    table td {
        border: 1px solid #bbb;
        padding: 0.25em 0.5em;
    }
代码语言:javascript
复制
<table>
    <thead>
        <tr>
            <th onclick="onColumnHeaderClicked(event)">Foo</th>
            <th onclick="onColumnHeaderClicked(event)">Bar</th>
            <th onclick="onColumnHeaderClicked(event)">Baz</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>9</td>
            <td>a</td>
        </tr>
        <!-- 9,000 additional rows will be added by the DOMContentLoaded event-handler when this snippet is executed. -->
    </tbody>
</table>

关于性能的一句话:

根据Chrome78的开发人员工具的性能分析器,在我的电脑上,performance.now()调用表明行在大约300ms内进行了排序,但是在JavaScript停止运行后发生的“重新计算样式”和“布局”操作分别花费了240ms和450ms (690ms的总重新布局时间,加上300ms的排序时间意味着从点击排序到排序需要整整一秒(1000ms))。

当我更改脚本,将<tr>元素添加到中间DocumentFragment而不是<tbody> (这样可以保证每个.appendChild调用都不会导致回流/布局,而不是仅仅假设.appendChild不会触发回流),并重新运行性能测试时,我的结果计时数字大致相同(5次重复后,它们实际上总共稍微高出约120ms,平均时间为1120ms)-但我会将其归因于浏览器即时播放。

下面是sortTableRowsByColumn中更改后的代码

代码语言:javascript
复制
    function sortTableRowsByColumn( table, columnIndex, ascending ) {

        const rows = Array.from( table.querySelectorAll( ':scope > tbody > tr' ) );

        rows.sort( ( x, y ) => {

            const xValue = x.cells[columnIndex].textContent;
            const yValue = y.cells[columnIndex].textContent;

            const xNum = parseFloat( xValue );
            const yNum = parseFloat( yValue );

            return ascending ? ( xNum - yNum ) : ( yNum - xNum );
        } );

        const fragment = new DocumentFragment();
        for( let row of rows ) {
            fragment.appendChild( row );
        }

        table.tBodies[0].appendChild( fragment );
    }

我认为性能相对较慢是因为自动的表格布局算法。我敢打赌,如果我把我的CSS改成使用table-layout: fixed;,布局时间就会缩短。(更新:我用table-layout: fixed;测试了它,令人惊讶的是,它根本没有提高性能-我似乎无法获得比1000ms更好的时间-哦,好吧)。

票数 8
EN

Stack Overflow用户

发布于 2019-12-11 20:34:48

代码语言:javascript
复制
<!DOCTYPE html>
<html>

<head>
    <script>
        function sort_table(tbody, index, sort = (a, b) => {
            if(a < b) return -1; if(a > b) return 1; return 0;}) 
        {
            var list = []
            for (var i = 0; i < tbody.children.length; i++)
                list.push([tbody.children[i].children[index].innerText, tbody.children[i]]);
            list.sort((a, b) => sort(a[0], b[0]));
            var newtbody = document.createElement('tbody');
            for (var i = 0; i < list.length; i++)
                newtbody.appendChild(list[i][1]);
            tbody.parentNode.replaceChild(newtbody, tbody);
            return newtbody;
        }
    </script>
</head>

<body>
    <h2>Unsorted</h2>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Last Name</th>
                <th>Nationality</th>
                <th>Born</th>
            </tr>
        </thead>
        <tbody>
            <tr><td>Henry</td><td>Cavill</td>
                <td>British</td><td>5 May 1983</td></tr>
            <tr><td>Gal</td><td>Gadot</td>
                <td>Israeli</td><td>30 April 1985</td></tr>
            <tr><td>Olga</td><td>Kurylenko</td>
                <td>Ukrainian</td><td>14 November 1979</td></tr>
            <tr><td>Vincent</td><td>Cassel</td>
                <td>French</td><td>23 November 1966</td></tr>
        </tbody>
    </table>
    <script>
        var table = document.getElementsByTagName('table')[0];
        var named = table.cloneNode(true);
        var dated = table.cloneNode(true);
        document.body.innerHTML += "<h2>Sorted by name</h2>";
        document.body.appendChild(named);

        sort_table(named.children[1], 0); //by name

        document.body.innerHTML += "<h2>Sorted by date</h2>";
        document.body.appendChild(dated);

        sort_table(dated.children[1], 3, (a, b) => { //by date
            if (new Date(a) < new Date(b)) return -1;
            if (new Date(a) > new Date(b)) return 1;
            return 0;
        });
    </script>
</body>

</html>

156 ms - 190 ms中的9000行(数量)

票数 1
EN

Stack Overflow用户

发布于 2021-05-13 08:01:14

我需要根据一列地址对一个表进行排序:秋季街3号,布兰登路1号,自然巷10号,原始广场100号……想要的是街名和门牌号。

为此,我修改了sortTableRowsByContent函数,将数字从文本中分离出来。另外,因为我的一些列有空值,所以我在排序时隐藏它们:

代码语言:javascript
复制
function sortTableRowsByColumn( table, columnIndex, ascending ) {

const rows = Array.from( table.querySelectorAll( ':scope > tbody > tr' ) );

rows.sort( ( a, b ) => {

  const aValue = a.cells[columnIndex].textContent;
  const bValue = b.cells[columnIndex].textContent;
  const aNum = parseFloat( aValue );
  const bNum = parseFloat( bValue );

  const aNew = aValue.replace(/^\d{1,3} /, ""); 
  const bNew = bValue.replace(/^\d{1,3} /, "");

  if (ascending){
    return aNew.localeCompare(bNew) || parseInt(aNum) - parseInt(bNum);
  } else {
    return bNew.localeCompare(aNew) || parseInt(bNum) - parseInt(aNum);
  }

} );

for ( i=0; i < rows.length; i++) {
  if (rows[i].getElementsByTagName("td")[columnIndex].textContent == '') {
// this hides the blank rows
    rows[i].style.display = "none";
  } else {
// this restores blanks if a different column is sorted
    rows[i].style.display = "";
  }
};

for( let row of rows ) {
  table.tBodies[0].appendChild( row );
}
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59282842

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档