HTML Table 表格结构
表格是 HTML 中用于展示结构化数据的核心元素。虽然现代布局已不推荐用表格实现,但在数据展示、报表呈现等场景中,语义化的表格依然不可替代。
表格的基本组成
一个完整的 HTML 表格由以下核心标签构成:
| 标签 | 说明 | 是否必需 |
|---|---|---|
<table> | 表格的根容器 | 是 |
<caption> | 表格标题,紧跟 <table> 之后 | 否(推荐) |
<thead> | 表头分组 | 否(推荐) |
<tbody> | 表体分组 | 否(推荐) |
<tfoot> | 表尾分组 | 否 |
<tr> | 表格行(table row) | 是 |
<th> | 表头单元格(table header) | 否 |
<td> | 数据单元格(table data) | 是 |
<colgroup> / <col> | 列分组与列样式控制 | 否 |
最简表格
最基础的表格只需要 <table>、<tr> 和 <td> 三个标签:
<table>
<tr>
<td>姓名</td>
<td>年龄</td>
</tr>
<tr>
<td>小明</td>
<td>18</td>
</tr>
</table>
渲染效果:
| 姓名 | 年龄 |
|---|---|
| 小明 | 18 |
虽然浏览器允许省略 <thead> 和 <tbody>,但缺少语义分组的表格不利于无障碍访问和样式控制。实际开发中应始终使用完整的语义结构。
语义化完整表格
一个规范的语义化表格示例:
<table>
<caption>2024 年第一季度销售数据</caption>
<thead>
<tr>
<th>月份</th>
<th>销售额(万元)</th>
<th>同比增长</th>
</tr>
</thead>
<tbody>
<tr>
<td>1 月</td>
<td>320</td>
<td>+12%</td>
</tr>
<tr>
<td>2 月</td>
<td>280</td>
<td>+8%</td>
</tr>
<tr>
<td>3 月</td>
<td>350</td>
<td>+15%</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>合计</td>
<td>950</td>
<td>+11.7%</td>
</tr>
</tfoot>
</table>
各分区的作用
<caption>:为表格提供可见标题,屏幕阅读器会优先朗读它,是表格无障碍的重要组成。<thead>:语义化地标识表头行,在打印长表格时浏览器会在每页重复显示表头。<tbody>:包裹数据主体,当表格内容很长时可以独立滚动(配合 CSS)。<tfoot>:放置汇总行。HTML 规范允许<tfoot>写在<tbody>之前,浏览器会自动渲染在底部。
即使你不写 <tbody>,浏览器也会自动插入一个。你可以用开发者工具查看 DOM,会发现 <tr> 被包裹在自动生成的 <tbody> 里。
<th> 与 <td> 的区别
| 特性 | <th> | <td> |
|---|---|---|
| 语义 | 表头单元格 | 数据单元格 |
| 默认样式 | 粗体 + 居中 | 正常字重 + 左对齐 |
scope 属性 | 支持(标识作用范围) | 不支持 |
| 使用位置 | 通常在 <thead> 中,也可在 <tbody> 中作为行标题 | <tbody> 和 <tfoot> 中 |
scope 属性
scope 用于明确 <th> 的作用范围,帮助屏幕阅读器正确关联表头与数据:
<table>
<thead>
<tr>
<th scope="col">科目</th>
<th scope="col">成绩</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">数学</th>
<td>95</td>
</tr>
<tr>
<th scope="row">英语</th>
<td>88</td>
</tr>
</tbody>
</table>
scope="col":表示该表头对整列生效scope="row":表示该表头对整行生效scope="colgroup"/scope="rowgroup":对列组/行组生效
单元格合并
表格支持通过 colspan(列合并)和 rowspan(行合并)来创建跨越多个单元格的复杂布局。
colspan —— 跨列合并
colspan 让一个单元格横跨多列:
<table border="1">
<tr>
<th colspan="3">学生成绩表</th>
</tr>
<tr>
<th>姓名</th>
<th>语文</th>
<th>数学</th>
</tr>
<tr>
<td>小明</td>
<td>90</td>
<td>95</td>
</tr>
</table>
合并效果示意:
┌──────────────────────────┐
│ 学生成绩表 │ ← colspan="3",横跨 3 列
├────────┬────────┬────────┤
│ 姓名 │ 语文 │ 数学 │
├────────┼────────┼────────┤
│ 小明 │ 90 │ 95 │
└────────┴────────┴────────┘
rowspan —— 跨行合并
rowspan 让一个单元格纵跨多行:
<table border="1">
<tr>
<th>类别</th>
<th>名称</th>
<th>价格</th>
</tr>
<tr>
<td rowspan="2">水果</td>
<td>苹果</td>
<td>5 元/斤</td>
</tr>
<tr>
<!-- 这里不需要再写"水果"的 <td>,因为上面已经 rowspan 占位 -->
<td>香蕉</td>
<td>3 元/斤</td>
</tr>
<tr>
<td rowspan="2">蔬菜</td>
<td>西红柿</td>
<td>4 元/斤</td>
</tr>
<tr>
<td>黄瓜</td>
<td>2 元/斤</td>
</tr>
</table>
合并效果示意:
┌────────┬────────┬────────┐
│ 类别 │ 名称 │ 价格 │
├────────┼────────┼────────┤
│ │ 苹果 │ 5元/斤 │
│ 水果 ├────────┼────────┤ ← rowspan="2"
│ │ 香蕉 │ 3元/斤 │
├────────┼────────┼────────┤
│ │ 西红柿 │ 4元/斤 │
│ 蔬菜 ├────────┼────────┤ ← rowspan="2"
│ │ 黄瓜 │ 2元/斤 │
└────────┴────────┴────────┘
混合使用 colspan 和 rowspan
<table border="1">
<tr>
<th colspan="2" rowspan="2">课程安排</th>
<th colspan="2">上午</th>
<th colspan="2">下午</th>
</tr>
<tr>
<th>第一节</th>
<th>第二节</th>
<th>第三节</th>
<th>第四节</th>
</tr>
<tr>
<td rowspan="2">周一</td>
<td>上</td>
<td>语文</td>
<td>数学</td>
<td>英语</td>
<td>体育</td>
</tr>
<tr>
<td>下</td>
<td>物理</td>
<td>化学</td>
<td>音乐</td>
<td>自习</td>
</tr>
</table>
- 多写了单元格:被合并掉的位置不应再写
<td>或<th>,否则会导致表格变形。 - 数量不匹配:每行的"有效列数"(包括被 colspan/rowspan 占据的)必须一致。
- 调试技巧:先在纸上画出表格网格,标注每个单元格的位置,再编写 HTML。
列分组 <colgroup> 与 <col>
<colgroup> 和 <col> 用于对列进行分组和统一设置样式,避免为每个单元格重复添加样式:
<table>
<colgroup>
<col />
<col style="background-color: #f0f8ff;" />
<col style="background-color: #fff0f5;" />
</colgroup>
<thead>
<tr>
<th>姓名</th>
<th>语文</th>
<th>数学</th>
</tr>
</thead>
<tbody>
<tr>
<td>小明</td>
<td>90</td>
<td>95</td>
</tr>
<tr>
<td>小红</td>
<td>88</td>
<td>92</td>
</tr>
</tbody>
</table>
<col> 的 span 属性可以让一个 <col> 元素覆盖多列:
<colgroup>
<col /> <!-- 第 1 列 -->
<col span="2" class="score-cols" /> <!-- 第 2~3 列共享样式 -->
</colgroup>
<colgroup> 适合对整列设置背景色、宽度等样式。但列样式的优先级较低,单元格本身的样式会覆盖它。
表格的无障碍(Accessibility)
无障碍是表格开发中容易被忽视但非常重要的方面。
基本原则
- 始终提供
<caption>:让用户在"进入"表格之前了解表格的内容。 - 使用
<th>搭配scope:明确表头与数据的关联关系。 - 复杂表格使用
headers+id:当表头结构复杂(多级表头)时,用id和headers属性手动关联。
复杂表头的 headers 关联
<table>
<caption>各班各科平均成绩</caption>
<thead>
<tr>
<th id="class" rowspan="2">班级</th>
<th id="lang" colspan="2">语文</th>
<th id="math" colspan="2">数学</th>
</tr>
<tr>
<th id="lang-avg" headers="lang">平均分</th>
<th id="lang-pass" headers="lang">及格率</th>
<th id="math-avg" headers="math">平均分</th>
<th id="math-pass" headers="math">及格率</th>
</tr>
</thead>
<tbody>
<tr>
<th id="c1" headers="class">一班</th>
<td headers="c1 lang lang-avg">85</td>
<td headers="c1 lang lang-pass">92%</td>
<td headers="c1 math math-avg">90</td>
<td headers="c1 math math-pass">95%</td>
</tr>
</tbody>
</table>
简单表格(只有一行表头)使用 scope 即可。当表格存在多级表头或合并单元格的表头时,才需要 headers + id 做精确关联。
表格常用 CSS 样式
边框与间距
/* 合并边框(去除双线) */
table {
border-collapse: collapse; /* 关键属性 */
}
/* 分离边框(保留间距) */
table {
border-collapse: separate;
border-spacing: 8px 4px; /* 水平间距 垂直间距 */
}
th, td {
border: 1px solid #ccc;
padding: 8px 12px;
}
border-collapse 是表格样式中最重要的属性:
border-collapse: separate(默认) border-collapse: collapse
┌───┐ ┌───┐ ┌───┐ ┌───┬───┬───┐
│ A │ │ B │ │ C │ │ A │ B │ C │
└───┘ ┌───┐ ┌───┐ ├───┼───┼───┤
│ D │ │ E │ │ D │ E │ F │
└───┘ └───┘ └───┴───┴───┘
↑ 每个单元格独立边框 ↑ 相邻边框合并为一条线
斑马纹效果
/* 偶数行添加背景色 */
tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
/* 悬停高亮 */
tbody tr:hover {
background-color: #e8f4fd;
}
固定表头(可滚动表体)
.table-wrapper {
max-height: 400px;
overflow-y: auto;
}
.table-wrapper thead th {
position: sticky;
top: 0;
background-color: #fff;
z-index: 1;
}
响应式表格
在小屏幕上,宽表格容易溢出。常见的处理方式:
/* 方式一:水平滚动 */
.table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* 方式二:让表格自适应宽度 */
table {
width: 100%;
table-layout: fixed; /* 列宽平均分配 */
}
td {
word-wrap: break-word;
overflow-wrap: break-word;
}
table-layout 属性
table-layout 控制浏览器如何计算列宽:
| 值 | 行为 | 特点 |
|---|---|---|
auto(默认) | 根据所有单元格内容计算列宽 | 灵活但渲染慢,需读取全部内容 |
fixed | 只根据第一行和 <col> 的宽度计算 | 渲染快,适合大数据量表格 |
/* 大数据表格推荐 */
table {
table-layout: fixed;
width: 100%;
}
当表格行数很多时(数百行以上),使用 table-layout: fixed 可以显著提升渲染性能,因为浏览器不需要遍历所有行来确定列宽。
表格布局 vs CSS 布局
早期的网页大量使用表格进行页面布局,这种做法如今已被淘汰。
何时使用表格
应该使用表格的场景:
- 数据报表、统计表
- 价格对比表
- 课程表、日程表
- 表单中的表格式布局(如设置页面的键值对展示)
不应该使用表格的场景:
- 页面整体布局(头部、侧栏、内容区)
- 导航菜单
- 图片画廊
- 卡片列表
- 语义错误:表格是"数据"标签,用于布局会误导屏幕阅读器。
- 灵活性差:表格布局难以实现响应式设计。
- 代码冗余:需要大量嵌套标签,维护成本高。
- 渲染性能:表格需要全部内容加载后才能计算布局。
实战示例:完整的数据表格
下面是一个综合运用上述知识的完整示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>员工信息表</title>
<style>
.table-container {
overflow-x: auto;
}
table {
border-collapse: collapse;
width: 100%;
font-family: -apple-system, sans-serif;
}
caption {
font-size: 1.2em;
font-weight: bold;
padding: 12px;
caption-side: top; /* 标题在上方 */
text-align: left;
}
th, td {
border: 1px solid #ddd;
padding: 10px 14px;
text-align: left;
}
thead th {
background-color: #4a90d9;
color: #fff;
position: sticky;
top: 0;
}
tbody tr:nth-child(even) {
background-color: #f5f7fa;
}
tbody tr:hover {
background-color: #e8f0fe;
}
tfoot td {
font-weight: bold;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<div class="table-container">
<table>
<caption>技术部员工信息表</caption>
<colgroup>
<col style="width: 60px;" />
<col style="width: 100px;" />
<col />
<col />
<col style="width: 120px;" />
</colgroup>
<thead>
<tr>
<th scope="col">工号</th>
<th scope="col">姓名</th>
<th scope="col">职位</th>
<th scope="col">部门</th>
<th scope="col">入职日期</th>
</tr>
</thead>
<tbody>
<tr>
<td>001</td>
<td>张三</td>
<td>前端工程师</td>
<td>技术部</td>
<td>2022-03-15</td>
</tr>
<tr>
<td>002</td>
<td>李四</td>
<td>后端工程师</td>
<td>技术部</td>
<td>2021-07-20</td>
</tr>
<tr>
<td>003</td>
<td>王五</td>
<td>测试工程师</td>
<td>技术部</td>
<td>2023-01-10</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">共 3 名员工</td>
</tr>
</tfoot>
</table>
</div>
</body>
</html>
面试高频问答
Q1:<th> 和 <td> 有什么区别?
答:两者都是表格单元格,区别在于语义和默认样式。<th> 表示表头单元格,默认粗体居中,支持 scope 属性来声明作用范围(列或行);<td> 表示数据单元格,默认正常字重左对齐。在无障碍方面,屏幕阅读器会利用 <th> 的信息来为用户朗读对应的列名或行名。
Q2:colspan 和 rowspan 分别是什么?使用时需要注意什么?
答:colspan 让单元格横跨多列,rowspan 让单元格纵跨多行。使用时需要注意:
- 被合并的位置不要再写多余的
<td>/<th>。 - 每行的有效列数(包括被 span 占据的)必须保持一致,否则表格会错位。
- 可以同时使用
colspan和rowspan,但要仔细计算每个单元格的坐标。
Q3:border-collapse: collapse 和 separate 有什么区别?
答:
separate(默认值):每个单元格有独立的边框,单元格之间有间距(可用border-spacing控制)。collapse:相邻单元格的边框合并为一条线,没有间距。实际开发中几乎总是使用collapse,因为它更美观且避免了双线问题。
Q4:为什么不推荐用表格做页面布局?
答:四个原因:
- 语义不正确:
<table>在 HTML 规范中用于展示数据,用于布局会误导辅助技术(如屏幕阅读器)。 - 响应式困难:表格的行列结构是固定的,难以在不同屏幕尺寸下灵活调整。
- 代码维护难:表格布局需要大量嵌套的
<tr><td>,代码冗余可读性差。 - 性能问题:浏览器必须加载完整个表格内容后才能计算布局(而 CSS 布局可以渐进式渲染)。 现代开发应使用 Flexbox 或 CSS Grid 来做布局。
Q5:如何实现表格的固定表头效果?
答:使用 CSS position: sticky 实现:
thead th {
position: sticky;
top: 0;
background-color: #fff; /* 必须设置背景色,否则滚动时内容会透出 */
z-index: 1;
}
外层容器需要设置 max-height 和 overflow-y: auto 来触发滚动。注意:如果祖先元素有 overflow: hidden,sticky 会失效。
Q6:table-layout: fixed 有什么作用?
答:table-layout: fixed 让浏览器只根据第一行(或 <col> 定义的宽度)来确定列宽,而不是遍历所有行的内容。优点是渲染性能更好(尤其是大数据量表格),缺点是内容可能溢出单元格(需要配合 overflow 和 word-wrap 处理)。
Q7:如何提升表格的无障碍性?
答:关键措施包括:
- 使用
<caption>提供表格标题。 - 使用
<th>标识表头,并添加scope="col"或scope="row"声明作用方向。 - 对于复杂的多级表头表格,使用
id和headers属性精确关联表头与数据单元格。 - 保持表格结构简单——如果一个表格需要非常复杂的合并,考虑拆分成多个简单表格。