Array.prototype.sort同じソートアルゴリズムを実装しても、JavaScriptの組み込みメソッドの方がはるかに高速になるため、ブラウザーのJavaScriptにソートアルゴリズムを実装しないようにするのに役立ちます(IIRCほとんどのJSエンジンはおそらくQuickSortを使用します)。
ここに私がそれをする方法があります:
<tr>JavaScriptのすべての要素を取得しますArray。
- は配列を返さないため
querySelectorAll、と組み合わせて使用する必要があります。これは実際には-を返しますが、これをに渡してに変換できます。Array.fromquerySelectorAll NodeListOf<T>Array.fromArray
- を取得し
ArrayたらArray.prototype.sort(comparison)、カスタムコールバックを使用して、比較される<td>2つの<tr>要素の子からデータを抽出し、データを比較できます(x - y数値を比較するときのトリックを使用します。string使用する値にはString.prototype.localeCompare、たとえば、return x.localeCompare( y )。
Arrayがソートされた後(QuickSortは非常に高速なので、数万行のテーブルでも数ミリ秒以上かかることはありません)、それぞれの親<tr>を使用して再度追加します。appendChild<tbody>
TypeScriptでの私の実装を以下に示します。その下にあるスクリプトランナーに有効な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>
- あなたはサポートされている最新のブラウザを使用している
=>、Array.from、for( x of y )、:scope、.closest()、および.remove()(すなわちないのInternet Explorer 11)。
- データは要素の
#text(.textContent)として存在し<td>ます。
- 何もありません
colspanまたはrowspanテーブル内のセル。
これは実行可能なサンプルです。列ヘッダーをクリックするだけで、昇順または降順で並べ替えられます。
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 );
} );
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;
}
<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>
パフォーマンスについて一言:
Chrome 78のデベロッパーツールのパフォーマンスアナライザーによると、私のコンピューターでは、performance.now()呼び出しは行が約300ミリ秒でソートされたことを示していますが、JavaScriptの実行が停止した後に発生する「スタイルの再計算」および「レイアウト」操作は、それぞれ240ミリ秒と450ミリ秒かかりました(合計690msの再レイアウト時間と300msのソート時間は、クリックからソートまでに1秒(1,000ms)かかったことを意味します)。
<tr>要素のDocumentFragment代わりに中間要素に要素が追加されるようにスクリプトを変更したとき<tbody>(各.appendChild呼び出しがリフロー/レイアウトを引き起こさないことが保証されているため、リフロー.appendChildがトリガーされないことを前提としています)、パフォーマンスを再実行しました私の結果のタイミングの数値がほぼ同じであることをテストします(実際には、5回の繰り返しの後、平均時間(1,120ミリ秒)で合計約120ミリ秒わずかに高くなりましたが、それをブラウザーのJITプレイアップに当てはめます。
変更されたコードはsortTableRowsByColumn次のとおりです。
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 );
}
自動テーブルレイアウトアルゴリズムのため、パフォーマンスが比較的遅いと思います。table-layout: fixed;レイアウト時間を使用するようにCSSを変更すると、時間が短縮されます。(更新:私はそれをテストしましたがtable-layout: fixed;、驚くべきことに、パフォーマンスはまったく向上しませんでした。1,000ミリ秒よりも良い時間をとることはできないようです-まあ)。
document.createDocumentFragement()