JavaScript: Wie kann ich eine große Tabelle schneller sortieren?

3 Antworten

ASP.NET Core Razor Pages ist nicht meine Baustelle aber ich befürchte, dass sich solche Datenmengen schwer performant auf dem Client sortieren lassen.

Auf jeden Fall solltest du erst mal ermitteln, was bei dir der limitierende Faktor ist. Ich denke nämlich nicht, dass es die Sortierung als solche ist. Meist ist es eher die Darstellung bzw. die extrem vielen Änderungen am DOM.

In deinem Fall vermute ich, dass deine Tabelle bei jedem Zwischenschritt bei der Sortierung schon neu gerendert wird. Wenn es so ist, musst du das auf jeden Fall ausschließen und sicherstellen, dass alle Daten erst sortiert und danach neu gerendert werden. Dazu würde ich erst mal im Model eine extra Variable .isSorting (default false) einbauen und die beim Sortieren auf true setzen. In der Schleife der Tabelle wird dann !Model.isSorting als zusätzliche Bedingung mit eingebaut.

So würde ich das Problem zumindest angehen.

Woher ich das weiß:Berufserfahrung – Entwickle Webseiten seit über 25 Jahren.

EchoTech 
Beitragsersteller
 17.02.2025, 13:53

Habe jetzt eine Variable isSorting deklariert. Außerdem wird die Sortierung jetzt in einem separaten Thread ausgeführt, sodass die Hauptseite nicht blockiert wird (Stichwort Web Worker).

Das Rendering habe ich angepasst, also alle Ansätze kombiniert, die ich hier bekommen habe. Jetzt geht die Sortierung blitzschnell.

Also war einfach der DOM überfordert, da ich zu viel JavaScript in einem gehabt habe.

So sieht die Funktion jetzt aus:

let isSorting = false;
let worker;

if (window.Worker) {
  worker = new Worker('/js/sortWorker.js'); 
} else {
  console.error("Web Worker werden von diesem Browser nicht unterstützt.");
}

function sortTable(columnIndex, header) {
  if (isSorting) return;
  isSorting = true;

  const table = document.getElementById("statistikTable");
  const tbody = table.tBodies[0];

  const rows = Array.from(tbody.rows).map(row =>
    Array.from(row.cells).map(cell => cell.textContent.trim())
  );

  const ascending = header.dataset.asc !== "true";

  worker.postMessage({ rows, columnIndex, ascending });

  worker.onmessage = function (event) {
    const sortedRows = event.data;
    const fragment = document.createDocumentFragment();

    sortedRows.forEach(rowData => {
      const row = document.createElement("tr");
      rowData.forEach(cellText => {
        const td = document.createElement("td");
        td.textContent = cellText;
        row.appendChild(td);
      });
      fragment.appendChild(row);
    });

    tbody.innerHTML = "";
    tbody.appendChild(fragment);

    header.dataset.asc = ascending;
    updateSortIcons(header, ascending);
    isSorting = false;
  };

  worker.onerror = function (error) {
    console.error("❌ Fehler im Web Worker:", error);
    isSorting = false;
  };
}

function updateSortIcons(header, ascending) {
  document.querySelectorAll(".sort-icon").forEach(icon => {
    icon.src = "/img/dgvsort.png";
    icon.style.display = "inline";
  });

  const icon = header.querySelector(".sort-icon");
  if (icon) {
    icon.src = ascending ? "/img/dgvsortup.png" : "/img/dgvsortdown.png";
    icon.style.display = "inline";
  }
}

Die Fehlermeldungen/errors habe ich nur für mich reingemacht zum testen. Mache ich natürlich wenn die Anwendung Online geht raus

Danke für den Tipp

Ich frage mich ob wirklich die Sortierung so langsam ist, oder auch das bearbeiten des DOMs.

Miss das mal genauer. Auch das Mappen und so. Vergleiche welcher Teil wie lange dauert, mit console.time, und schau, wie schnell die verschiedenen Lösungen zur Sortierung wirklich sind.

Mir fällt noch indexedDB ein, habe ich aber selber nie genutzt. Ich glaube das supported indices.

Ich würde auf jeden Fall nicht die komplette Tabelle rendern, das letzte mal als ich einen ähnlichen Use Case hatte, habe ich eine Buffer Funktion von dem Framework genutzt, das hat sehr viel gebracht. Alternativ Pagination.

Ich würde außerdem einfach die Sortierung einschränken. Nach Vornamen wird hier nicht sortiert, wer damit ein Problem hat, soll eine Beschwerde unter /dev/null ablegen. Man kann die wichtigsten Eigenschaften eventuell vorsortieren. Bspw. die top 10% Rechnungsbeträge. Die kann man dann schon mal anzeigen, und den Rest im Hintergrund weiter sortieren (oder am besten nie, weil normalerweise schaut sich niemand 10k Einträge manuell an).

Muss aber zugeben, hab mir deine Lösung nicht genau angeschaut.

Außerdem verstehe ich deine Anforderungen nicht. Prüfe deine Anforderungen noch mal genau. Die hören sich für mich lost an.

Woher ich das weiß:Berufserfahrung – Software Entwickler / Devops

EchoTech 
Beitragsersteller
 17.02.2025, 13:54

Habe jetzt eine Variable isSorting deklariert. Außerdem wird die Sortierung jetzt in einem separaten Thread ausgeführt, sodass die Hauptseite nicht blockiert wird (Stichwort Web Worker).

Das Rendering habe ich angepasst, also alle Ansätze kombiniert, die ich hier bekommen habe. Jetzt geht die Sortierung blitzschnell.

Also war einfach der DOM überfordert, da ich zu viel JavaScript in einem gehabt habe.

So sieht die Funktion jetzt aus:

let isSorting = false;
let worker;

if (window.Worker) {
  worker = new Worker('/js/sortWorker.js'); 
} else {
  console.error("Web Worker werden von diesem Browser nicht unterstützt.");
}

function sortTable(columnIndex, header) {
  if (isSorting) return;
  isSorting = true;

  const table = document.getElementById("statistikTable");
  const tbody = table.tBodies[0];

  const rows = Array.from(tbody.rows).map(row =>
    Array.from(row.cells).map(cell => cell.textContent.trim())
  );

  const ascending = header.dataset.asc !== "true";

  worker.postMessage({ rows, columnIndex, ascending });

  worker.onmessage = function (event) {
    const sortedRows = event.data;
    const fragment = document.createDocumentFragment();

    sortedRows.forEach(rowData => {
      const row = document.createElement("tr");
      rowData.forEach(cellText => {
        const td = document.createElement("td");
        td.textContent = cellText;
        row.appendChild(td);
      });
      fragment.appendChild(row);
    });

    tbody.innerHTML = "";
    tbody.appendChild(fragment);

    header.dataset.asc = ascending;
    updateSortIcons(header, ascending);
    isSorting = false;
  };

  worker.onerror = function (error) {
    console.error("❌ Fehler im Web Worker:", error);
    isSorting = false;
  };
}

function updateSortIcons(header, ascending) {
  document.querySelectorAll(".sort-icon").forEach(icon => {
    icon.src = "/img/dgvsort.png";
    icon.style.display = "inline";
  });

  const icon = header.querySelector(".sort-icon");
  if (icon) {
    icon.src = ascending ? "/img/dgvsortup.png" : "/img/dgvsortdown.png";
    icon.style.display = "inline";
  }
}

Die Fehlermeldungen/errors habe ich nur für mich reingemacht zum testen. Mache ich natürlich wenn die Anwendung Online geht raus

Danke für den Tipp

Ich habe eine Tabelle, die ich über die th Elemente sortiere. Da ich allerdings viel mit jQuery mache, sind die Funktionen für Vanilla etwas Anders. die kannst du aber relativ einfach umbauen.

Dies funktioniert sehr gut mit folgendem Code:

Listener für die Tabellenköpfe und Sort-Icon (Über Iconify):

$('#datasource-table').on('click', 'th.filter', function () {
    // Remove the sort Icon
    $('.sort_icon').html('');
    const table = $(this).closest('table');
    let rows = table
        .find('tr:gt(0)')
        .toArray()
        .sort(dataSourceComparer($(this).index()));

    // Change the Icon
    $(this)
        .find('.sort_icon')
        .html('<span class="iconify" data-icon="mdi:sort-alphabetical-ascending" data-height="18">');
    if (!this.asc) {
        rows = rows.reverse();
        $(this)
            .find('.sort_icon')
            .html('<span class="iconify" data-icon="mdi:sort-alphabetical-descending" data-height="18">');
    }
    for (var i = 0; i < rows.length; i++) {
        table.append(rows[i]);
    }
    this.asc = !this.asc;
});

Zwei Funktionen zum Sortieren:

function getdataSourceCellValue(row, index) {
    return $(row).children('td').eq(index).text();
}

function dataSourceComparer(index) {
    return function (a, b) {
        var valA = getdataSourceCellValue(a, index),
            valB = getdataSourceCellValue(b, index);
        return $.isNumeric(valA) && $.isNumeric(valB) ? valA - valB : valA.toString().localeCompare(valB);
    };
}
Woher ich das weiß:Studium / Ausbildung – Begeisterter Beweger der 0 und 1

EchoTech 
Beitragsersteller
 17.02.2025, 13:55

Habe jetzt eine Variable isSorting deklariert. Außerdem wird die Sortierung jetzt in einem separaten Thread ausgeführt, sodass die Hauptseite nicht blockiert wird (Stichwort Web Worker).

Das Rendering habe ich angepasst, also alle Ansätze kombiniert, die ich hier bekommen habe. Jetzt geht die Sortierung blitzschnell.

Also war einfach der DOM überfordert, da ich zu viel JavaScript in einem gehabt habe.

So sieht die Funktion jetzt aus:

let isSorting = false;
let worker;

if (window.Worker) {
  worker = new Worker('/js/sortWorker.js'); 
} else {
  console.error("Web Worker werden von diesem Browser nicht unterstützt.");
}

function sortTable(columnIndex, header) {
  if (isSorting) return;
  isSorting = true;

  const table = document.getElementById("statistikTable");
  const tbody = table.tBodies[0];

  const rows = Array.from(tbody.rows).map(row =>
    Array.from(row.cells).map(cell => cell.textContent.trim())
  );

  const ascending = header.dataset.asc !== "true";

  worker.postMessage({ rows, columnIndex, ascending });

  worker.onmessage = function (event) {
    const sortedRows = event.data;
    const fragment = document.createDocumentFragment();

    sortedRows.forEach(rowData => {
      const row = document.createElement("tr");
      rowData.forEach(cellText => {
        const td = document.createElement("td");
        td.textContent = cellText;
        row.appendChild(td);
      });
      fragment.appendChild(row);
    });

    tbody.innerHTML = "";
    tbody.appendChild(fragment);

    header.dataset.asc = ascending;
    updateSortIcons(header, ascending);
    isSorting = false;
  };

  worker.onerror = function (error) {
    console.error("❌ Fehler im Web Worker:", error);
    isSorting = false;
  };
}

function updateSortIcons(header, ascending) {
  document.querySelectorAll(".sort-icon").forEach(icon => {
    icon.src = "/img/dgvsort.png";
    icon.style.display = "inline";
  });

  const icon = header.querySelector(".sort-icon");
  if (icon) {
    icon.src = ascending ? "/img/dgvsortup.png" : "/img/dgvsortdown.png";
    icon.style.display = "inline";
  }
}

Die Fehlermeldungen/errors habe ich nur für mich reingemacht zum testen. Mache ich natürlich wenn die Anwendung Online geht raus

Danke für den Tipp