Điều này giúp tránh thực hiện các thuật toán sắp xếp trong JavaScript của trình duyệt vì Array.prototype.sortphương thức tích hợp sẵn của JavaScript sẽ nhanh hơn nhiều ngay cả khi bạn kết thúc thực hiện cùng một thuật toán sắp xếp ( dù sao hầu hết các công cụ JS có thể sẽ sử dụng QuickSort ).
Đây là cách tôi làm:
- Nhận tất cả các
<tr>yếu tố trong một JavaScript Array.
- Bạn cần sử dụng
querySelectorAllkết hợp với Array.fromvì querySelectorAll không trả về một mảng , nó thực sự trả về một NodeListOf<T>- nhưng bạn có thể chuyển nó vào Array.fromđể chuyển đổi nó thành một mảngArray .
- Khi bạn có
Array, bạn có thể sử dụng Array.prototype.sort(comparison)với một cuộc gọi lại tùy chỉnh để trích xuất dữ liệu từ phần tử <td>con của hai <tr>phần tử được so sánh, sau đó so sánh dữ liệu (sử dụng x - ymẹo khi so sánh các giá trị số. Đối với stringcác giá trị bạn sẽ muốn sử dụng String.prototype.localeCompare, ví dụ: return x.localeCompare( y ).
- Sau khi
Arrayđược sắp xếp (không mất quá vài mili giây cho ngay cả một bảng có hàng chục nghìn hàng, vì QuickSort thực sự nhanh chóng !) Thêm lại mỗi lần <tr>sử dụng appendChildcủa cha mẹ <tbody>.
Việc triển khai của tôi trong TypeScript nằm bên dưới, cùng với một mẫu đang hoạt động với JavaScript hợp lệ trong trình chạy tập lệnh nằm bên dưới:
// 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';
}
sortTableRowsByColumnHàm của tôi giả sử như sau:
<table>Phần tử của bạn sử dụng <thead>và có một<tbody>
- Bạn đang sử dụng một trình duyệt hiện đại hỗ trợ
=>, Array.from, for( x of y ), :scope, .closest(), và .remove()(tức là không Internet Explorer 11).
- Dữ liệu của bạn tồn tại dưới dạng
#text( .textContent) của các <td>yếu tố.
- Không có
colspanhoặc rowspancác ô trong bảng.
Đây là một mẫu có thể chạy được. Chỉ cần nhấp vào tiêu đề cột để sắp xếp theo thứ tự tăng dần hoặc giảm dần:
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>
Một từ về hiệu suất:
Theo công cụ phân tích Hiệu suất của Công cụ dành cho nhà phát triển của Chrome 78, trên máy tính của tôi, các performance.now()cuộc gọi cho biết các hàng được sắp xếp trong khoảng 300ms, tuy nhiên các thao tác "Tính toán lại kiểu" và "Bố cục" xảy ra sau khi JavaScript ngừng chạy lần lượt mất 240ms và 450ms ( Tổng thời gian chuyển tiếp 690ms, cộng với thời gian sắp xếp 300ms có nghĩa là phải mất toàn bộ giây (1.000ms) từ lần nhấp để sắp xếp).
Khi tôi thay đổi tập lệnh sao cho các <tr>phần tử được thêm vào một trung gian DocumentFragmentthay vì <tbody>(để mỗi .appendChildcuộc gọi được đảm bảo không gây ra phản xạ / bố cục, thay vì chỉ giả sử rằng .appendChildsẽ không kích hoạt phản xạ) và chạy lại hiệu suất kiểm tra các số liệu thời gian kết quả của tôi giống hệt nhau hoặc ít hơn (chúng thực sự cao hơn một chút khoảng 120ms sau 5 lần lặp lại, trong thời gian trung bình là (1.120ms) - nhưng tôi sẽ đưa nó xuống trình phát JIT của trình duyệt .
Đây là mã đã thay đổi bên trong 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 );
}
Tôi cho rằng hiệu suất tương đối chậm do thuật toán Bố trí bảng tự động. Tôi sẽ đặt cược nếu tôi thay đổi CSS của mình để sử dụng table-layout: fixed;thời gian bố trí sẽ co lại. (Cập nhật: Tôi đã thử nghiệm nó table-layout: fixed;và thật ngạc nhiên là không cải thiện hiệu suất chút nào - tôi dường như không thể có được thời gian tốt hơn 1.000ms - ồ tốt).
document.createDocumentFragement()