如何用纯JavaScript实现HTML表格按行/列双向排序?
纯JavaScript实现表格行/列双向排序方案
我刚好折腾过类似的需求,纯JS完全能搞定,不用依赖任何库。咱们先把核心逻辑拆解清楚,再给你上完整可运行的代码:
核心思路
- 列排序(点击表头):点击任意列标题时,提取该列所有行的单元格值,根据值的类型(字符串/数字/日期)对表格行进行升序/降序排序,然后重新渲染表格。
- 行排序(点击第一列单元格):点击第一列的普通单元格时,提取该行所有列的单元格值,把表格的列转换成可排序的数组,根据该行的值对列进行排序,再重构表格结构。
- 排序状态切换:给表头和第一列单元格添加状态标记,记录当前排序方向,点击时自动切换升序/降序。
完整实现代码
<!DOCTYPE html> <html> <head> <style> table { border-collapse: collapse; width: 80%; margin: 20px auto; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; cursor: pointer; } td:first-child { cursor: pointer; background-color: #f9f9f9; } .sort-asc::after { content: " ↑"; } .sort-desc::after { content: " ↓"; } </style> </head> <body> <table id="sortableTable"> <thead> <tr> <th>姓名</th> <th>年龄</th> <th>薪资</th> <th>入职日期</th> </tr> </thead> <tbody> <tr> <td>张三</td> <td>28</td> <td>8500</td> <td>2021-03-15</td> </tr> <tr> <td>李四</td> <td>35</td> <td>12000</td> <td>2019-07-22</td> </tr> <tr> <td>王五</td> <td>22</td> <td>6000</td> <td>2022-11-01</td> </tr> </tbody> </table> <script> const table = document.getElementById('sortableTable'); const thead = table.querySelector('thead'); const tbody = table.querySelector('tbody'); // 列排序:点击表头触发 thead.addEventListener('click', (e) => { if (e.target.tagName !== 'TH') return; const th = e.target; const columnIndex = Array.from(thead.querySelectorAll('th')).indexOf(th); const isAsc = !th.classList.contains('sort-asc'); // 清除其他表头的排序状态 thead.querySelectorAll('th').forEach(t => t.classList.remove('sort-asc', 'sort-desc')); th.classList.add(isAsc ? 'sort-asc' : 'sort-desc'); // 获取所有行并排序 const rows = Array.from(tbody.querySelectorAll('tr')); rows.sort((a, b) => { const valA = a.querySelectorAll('td')[columnIndex].textContent.trim(); const valB = b.querySelectorAll('td')[columnIndex].textContent.trim(); // 判断数据类型,数字/日期转成对应类型比较 if (!isNaN(valA) && !isNaN(valB)) { return isAsc ? Number(valA) - Number(valB) : Number(valB) - Number(valA); } else if (!isNaN(Date.parse(valA)) && !isNaN(Date.parse(valB))) { return isAsc ? new Date(valA) - new Date(valB) : new Date(valB) - new Date(valA); } else { // 字符串按字典序比较 return isAsc ? valA.localeCompare(valB) : valB.localeCompare(valA); } }); // 重新插入排序后的行 tbody.innerHTML = ''; rows.forEach(row => tbody.appendChild(row)); }); // 行排序:点击第一列单元格触发 tbody.addEventListener('click', (e) => { if (e.target.tagName !== 'TD' || e.target.cellIndex !== 0) return; const td = e.target; const rowIndex = Array.from(tbody.querySelectorAll('tr')).indexOf(td.parentElement); const isAsc = !td.classList.contains('sort-asc'); // 清除第一列其他单元格的排序状态 tbody.querySelectorAll('td:first-child').forEach(t => t.classList.remove('sort-asc', 'sort-desc')); td.classList.add(isAsc ? 'sort-asc' : 'sort-desc'); // 把表格转换成列数组:[[列1所有值], [列2所有值], ...] const columns = []; const headerCells = Array.from(thead.querySelectorAll('th')); const bodyRows = Array.from(tbody.querySelectorAll('tr')); // 初始化列数组,包含表头 headerCells.forEach((th, idx) => { columns.push([th.textContent.trim()]); }); // 填充每行的单元格到对应列 bodyRows.forEach(row => { Array.from(row.querySelectorAll('td')).forEach((cell, idx) => { columns[idx].push(cell.textContent.trim()); }); }); // 根据目标行的值排序列 columns.sort((colA, colB) => { const valA = colA[rowIndex + 1]; // +1是因为第一元素是表头 const valB = colB[rowIndex + 1]; // 同样处理数据类型 if (!isNaN(valA) && !isNaN(valB)) { return isAsc ? Number(valA) - Number(valB) : Number(valB) - Number(valA); } else if (!isNaN(Date.parse(valA)) && !isNaN(Date.parse(valB))) { return isAsc ? new Date(valA) - new Date(valB) : new Date(valB) - new Date(valA); } else { return isAsc ? valA.localeCompare(valB) : valB.localeCompare(valA); } }); // 重构表格:先清空,再生成新的表头和行 thead.innerHTML = ''; tbody.innerHTML = ''; // 生成新表头 const newHeaderRow = document.createElement('tr'); columns.forEach(col => { const th = document.createElement('th'); th.textContent = col[0]; newHeaderRow.appendChild(th); }); thead.appendChild(newHeaderRow); // 生成新行(从第1个索引开始,跳过表头) for (let i = 1; i < columns[0].length; i++) { const newRow = document.createElement('tr'); columns.forEach(col => { const td = document.createElement('td'); td.textContent = col[i]; newRow.appendChild(td); }); tbody.appendChild(newRow); } }); </script> </body> </html>
关键细节说明
- 数据类型自动识别:排序时会自动判断值是数字、日期还是字符串,用对应的规则比较,避免字符串数字排序出错(比如"12000"和"8500"的字符串排序会有问题,转成数字就正常了)。
- 排序状态可视化:通过
sort-asc和sort-desc类给表头/第一列单元格添加箭头标记,用户能直观看到当前排序方向。 - 事件委托:用事件委托代替给每个单元格绑定事件,性能更好,而且动态生成的行/列也能触发排序。
- 双向切换:每次点击都会切换升序/降序,不用额外按钮。
你可以直接把这段代码复制到HTML文件里运行,点击表头就能按列排序,点击第一列的姓名单元格就能按该行的值对所有列排序,完全符合你的需求~
内容的提问来源于stack exchange,提问作者lalengua
相关产品推荐
相关产品推荐

