JavaScript: Wie kann ich eine große Tabelle schneller sortieren?
Hallo zusammen,
ich habe eine große Tabelle in meiner ASP.NET Core Razor Pages Anwendung, die ich clientseitig mit JavaScript sortieren möchte.
Allerdings dauert die Sortierung bei einer großen Datenmenge zu lange, und ich suche nach einer performanten Lösung.
Meine aktuelle Implementierung:
Ich nutze Array.sort() und vergleiche Strings und Zahlen entsprechend, das funktioniert aber das ist nicht effizient genug.
Besonders wenn die Tabelle viele Zeilen (z. B. >10.000) hat, wird die Sortierung langsam.
Meine Anforderungen:
✅ Die Sortierung muss komplett in JavaScript (clientseitig) erfolgen
✅ Sie muss sowohl Texte (Strings mit Umlaute, Sonderzeichen, etc.) als auch Zahlen (inkl. Währungen mit €) schnell sortieren
✅ Die Tabelle wird nachträglich dynamisch mit Daten befüllt (also keine initiale Sortierung im Backend möglich)
✅ Die Lösung soll sehr schnell auch bei großen Datenmengen sein
Ich habe bereits probiert:
❌ Array.sort() (wird langsam bei vielen Zeilen)
❌ localeCompare() (korrekt für Strings, aber langsam)
❌ TypedArrays für Zahlenwerte (bringt nicht genug Speed)
Gibt es eine bessere Möglichkeit, eine HTML-Tabelle performant zu sortieren?
Vielleicht mit Web Worker, einer anderen Datenstruktur oder anderen Algorithmen?
oder gibt es andere Funktionen/Möglichkeiten, die ich noch probieren kann?
hier mein aktueller Ansatz:
Tabelle:
<div class="col-lg-9 col-md-7 col-12">
<div class="table-container">
<table class="table-modern table-hover w-100" id="statistikTable">
<thead>
<tr>
<th onclick="sortTable(0, this)">Datum <img class="sort-icon" src="/img/dgvsort.png" style="display: none;"></th>
<th onclick="sortTable(1, this)">PID <img class="sort-icon" src="/img/dgvsort.png" style="display: none;"></th>
<th onclick="sortTable(2, this)">Nachname <img class="sort-icon" src="/img/dgvsort.png" style="display: none;"></th>
<th onclick="sortTable(3, this)">Vorname <img class="sort-icon" src="/img/dgvsort.png" style="display: none;"></th>
<th onclick="sortTable(4, this)">Rechnungsnetto <img class="sort-icon" src="/img/dgvsort.png" style="display: none;"></th>
<th onclick="sortTable(5, this)">Belegnummer <img class="sort-icon" src="/img/dgvsort.png" style="display: none;"></th>
</tr>
</thead>
<tbody>
@if (Model.StatistikData != null && Model.StatistikData.Rows.Count > 0)
{
foreach (DataRow row in Model.StatistikData.Rows)
{
<tr>
<td>@(((DateTime)row["Datum"]).ToString("dd.MM.yyyy"))</td>
<td>@row["PID"]</td>
<td>@row["KundeNachname"]</td>
<td>@row["KundeVorname"]</td>
<td>@Convert.ToDecimal(row["Rechnungsnetto"]).ToString("N2")</td>
<td>@row["Belegnummer"]</td>
</tr>
}
}
else
{
<tr>
<td colspan="6" class="text-center">Keine Daten gefunden</td>
</tr>
}
</tbody>
</table>
</div>
</div>
meine sort Funktion:
function sortTable(columnIndex, header) {
const table = document.getElementById("statistikTable");
const tbody = table.tBodies[0];
const rows = Array.from(tbody.rows);
const ascending = table.dataset.sortOrder !== "asc";
const isNumericColumn = !isNaN(parseFloat(rows[0].cells[columnIndex].textContent.replace("€", "").replace(",", ".").trim()));
let sortedRows;
if (isNumericColumn) {
sortedRows = rows
.map(row => ({
element: row,
value: parseFloat(row.cells[columnIndex].textContent.replace("€", "").replace(",", ".").trim()) || 0
}))
.sort((a, b) => ascending ? a.value - b.value : b.value - a.value);
} else {
const collator = new Intl.Collator("de", { numeric: true, sensitivity: "base" });
sortedRows = rows
.map(row => ({
element: row,
value: row.cells[columnIndex].textContent.trim()
}))
.sort((a, b) => ascending ? collator.compare(a.value, b.value) : collator.compare(b.value, a.value));
}
const fragment = document.createDocumentFragment();
sortedRows.forEach(({ element }) => fragment.appendChild(element));
tbody.appendChild(fragment);
table.dataset.sortOrder = ascending ? "asc" : "desc";
updateSortIcons(header, ascending);
}
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";
}
}
Freue mich über jede Hilfe! 😊
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.
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.
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);
};
}
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