news 2026/4/17 15:58:39

表格这玩意儿,是怎么越搞越复杂的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
表格这玩意儿,是怎么越搞越复杂的

1995 年:原始的 HTML 表格

网页里只有<table><tr><td>。后台系统还没出现,表格就是用来展示一些静态数据的。

<tableborder="1"><tr><td>张三</td><td>90</td></tr><tr><td>李四</td><td>85</td></tr></table>

没有任何高级名词,一切都赤裸裸的 DOM。


2005 年:数据变多了,手写 HTML 行不通了

痛点:后台系统需要展示 500 条员工记录,手写 500 行<tr>不现实。

解决方案:用 JavaScript 循环数组,动态生成 DOM。

constdata=[/* 500 条数据 */];consttbody=document.querySelector('tbody');data.forEach(item=>{consttr=document.createElement('tr');tr.innerHTML=`<td>${item.name}</td><td>${item.score}</td>`;tbody.appendChild(tr);});

出现的概念:数据驱动渲染。表格不再是静态的,而是由数据生成的。


2010 年:列太多,横向滚动时关键列消失了

痛点:员工表有 30 列,屏幕只能显示 10 列。用户向右滚动时,最左边的“姓名”列滚出了视野,导致不知道每一行是谁的工资。

尝试用 CSS 解决position: sticky。但当时浏览器支持差,而且一旦表头有合并单元格,sticky 的计算经常错位。

最终方案多表叠加(渲染三张独立的表格)。

具体怎么做?
  1. 左侧固定表格:只包含需要固定的列(比如姓名),宽度固定,绝对定位在左边,禁止横向滚动。
  2. 右侧固定表格:只包含右侧需要固定的列(比如操作列),绝对定位在右边。
  3. 中间滚动表格:包含所有列,宽度设为所有列宽之和,放在一个overflow-x: auto的容器里。

垂直滚动同步:用户滚动中间表格的垂直滚动条时,通过 JS 将scrollTop值同步设置给左右两张固定表格的容器。

// 监听中间表格的滚动容器middleWrapper.addEventListener('scroll',()=>{constscrollTop=middleWrapper.scrollTop;leftWrapper.scrollTop=scrollTop;rightWrapper.scrollTop=scrollTop;});

行高对齐:因为三张表格独立渲染,同一行的高度可能因为内容不同而不一致。所以需要在每次渲染后,用ResizeObservergetBoundingClientRect获取每一行的真实高度,然后强制同步三张表中对应行的高度。

// 简化的对齐逻辑:获取所有行高,取最大值应用到三张表的对应行constleftRows=leftTable.querySelectorAll('tr');constmiddleRows=middleTable.querySelectorAll('tr');for(leti=0;i<leftRows.length;i++){constmaxHeight=Math.max(leftRows[i].getBoundingClientRect().height,middleRows[i].getBoundingClientRect().height);leftRows[i].style.height=maxHeight+'px';middleRows[i].style.height=maxHeight+'px';}

出现的新名词:固定列(Fixed Columns)、多表叠加、滚动同步、行高对齐。


2015 年:数据量爆炸,一次性渲染 10 万行直接卡死

痛点:运营需要查看 10 万条日志。如果用 2005 年的循环渲染方法,浏览器会生成 10 万个<tr>节点,内存占用数百 MB,页面直接白屏或卡死。

解决方案虚拟滚动(Virtual Scrolling)。

核心思想:既然用户屏幕一次只能看到 30 行,那我就只渲染这 30 行,其余的行用两个空白的<tr>占位,撑出滚动条的高度。

具体实现步骤(以定高模式为例):
  1. 设定固定行高:比如每行高度rowHeight = 48像素。

  2. 计算可视区能容纳的行数

    constvisibleCount=Math.ceil(container.clientHeight/rowHeight);
  3. 监听滚动事件,计算起始索引

    constscrollTop=container.scrollTop;conststartIndex=Math.floor(scrollTop/rowHeight);constendIndex=startIndex+visibleCount;
  4. 只渲染startIndexendIndex的数据

  5. 用占位行撑开滚动条高度

    • 在渲染的真实行上方,放一个<tr>,其高度为startIndex * rowHeight
    • 在真实行下方,放一个<tr>,其高度为(totalCount - endIndex) * rowHeight

这样,滚动条的总高度等于totalCount * rowHeight,用户感觉就像真的滚动了 10 万行,但实际上浏览器里只有 30 多个<tr>

出现的新名词:虚拟滚动、视口裁剪、起始索引、占位行。


2018 年:表头需要像 Excel 一样多层嵌套

痛点:财务要求表头为“2024 年第一季度”,下面分“1月”、“2月”、“3月”,每月再分“收入”、“支出”。如果用原生<th>手动写colspanrowspan,不仅计算繁琐,而且一旦列顺序调整,所有合并数字都要重算。

解决方案列配置树 + 自动计算跨度

只需用嵌套标签描述表头结构,组件内部自动算出每个<th>colspanrowspan

具体算法:
  1. 将嵌套的列配置拍平为一维数组(深度优先遍历 DFS),同时记录每个节点的层级(level)。

  2. 自底向上计算colspan

    • 叶子节点(没有子列)的colspan = 1
    • 父节点的colspan= 其所有子节点colspan之和。
    • 递归实现,从最深层往回算。
  3. 自顶向下计算rowspan

    • 先算出整棵树的最大深度maxDepth
    • 叶子节点的rowspan = maxDepth - 当前层级
    • 非叶子节点的rowspan = 1
  4. 生成表头二维网格:创建一个maxDepth行的二维数组,根据每个节点的levelcolspanrowspan放入对应位置,并用一个occupied布尔数组标记已被合并占用的格子,避免重复放置。

出现的新名词:多级表头、深度优先遍历(DFS)、自底向上/自顶向下计算、占位标记。


2020 年:跨页全选的带宽和内存危机

痛点:用户想全选所有“已离职”员工(共 5000 人)并发送问卷。初级做法是把 5000 个 ID 全传到前端存储。如果数据是 50 万呢?内存和带宽都会爆炸。

解决方案差量状态管理(只存例外)。

具体做法:
  1. 前端状态设计

    interfaceSelectState{mode:'none'|'page'|'all';// 全选模式filters:Record<string,any>;// 当前筛选条件total:number;// 符合条件的总数据量exceptions:Set<number>;// 取消勾选的 ID 集合}
  2. 用户点击全选时:设置mode = 'all',记录当前filters,清空exceptions

  3. 用户手动取消某行(ID=123)时exceptions.add(123)

  4. 提交给后端的数据结构

    {"operation":"delete","scope":"filtered","filters":{"status":"离职"},"excludeIds":[123,456]}
  5. 后端直接执行 SQL

    DELETEFROMusersWHEREstatus='离职'ANDidNOTIN(123,456);

带宽消耗从传输 5000 个 ID(约 100KB)降为几十字节。

出现的新名词:跨页全选、差量状态管理、例外集合、条件批量操作。


总结:表格技术演进地图

时间痛点解决方案核心名词
1995无,静态展示<table>标签DOM
2005数据量大,手写 HTML 繁琐JS 循环生成 DOM数据驱动
2010列太多,横向滚动丢失上下文三张表格叠加 + 滚动同步固定列、多表叠加
2015数据量极大(10 万+),渲染卡死只渲染可视区 + 占位行虚拟滚动、视口裁剪
2018表头复杂,手动计算跨度易错树形配置 + 自动计算 colspan/rowspan多级表头、DFS 拍平
2020跨页全选大数据量时带宽内存爆炸差量状态管理,只传例外集合跨页全选、差量模式
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 15:57:57

如何高效使用Qsign签名服务:5个实战技巧与深度解析

如何高效使用Qsign签名服务&#xff1a;5个实战技巧与深度解析 【免费下载链接】Qsign Windows的一键搭建签名api 项目地址: https://gitcode.com/gh_mirrors/qs/Qsign Qsign签名服务是解决QQ机器人开发中签名验证难题的本地化解决方案。这款基于Unidbg虚拟化技术的签名…

作者头像 李华
网站建设 2026/4/17 15:56:17

FireRed-OCR 5分钟快速上手:像玩GBA一样搞定复杂文档解析

FireRed-OCR 5分钟快速上手&#xff1a;像玩GBA一样搞定复杂文档解析 1. 引言&#xff1a;当GBA遇上文档解析 还记得小时候玩《口袋妖怪&#xff1a;火红》时&#xff0c;那个能神奇识别各种宝可梦信息的图鉴吗&#xff1f;现在&#xff0c;同样的黑科技被应用到了文档处理领…

作者头像 李华
网站建设 2026/4/17 15:53:47

如何快速配置Photon光影包:面向技术玩家的完整指南

如何快速配置Photon光影包&#xff1a;面向技术玩家的完整指南 【免费下载链接】photon A gameplay-focused shader pack for Minecraft 项目地址: https://gitcode.com/gh_mirrors/photon3/photon Photon光影包是一款专注于游戏体验的Minecraft着色器包&#xff0c;通过…

作者头像 李华
网站建设 2026/4/17 15:48:35

告别多个Keil图标!教你合并MDK/C51/C251开发环境(Windows版)

告别多个Keil图标&#xff01;三合一开发环境整合实战指南 每次打开电脑看到桌面上排排站的uVision图标&#xff0c;是不是觉得既占空间又影响效率&#xff1f;作为嵌入式开发者&#xff0c;我们经常需要同时处理51单片机、ARM核MCU和251架构项目&#xff0c;但Keil官方默认安装…

作者头像 李华
网站建设 2026/4/17 15:41:23

加州总检察长:新解封记录揭示亚马逊价格操纵策略

新解封记录曝光独家报道显示&#xff0c;在亚马逊与加利福尼亚州的反垄断诉讼中&#xff0c;一批此前被编辑的文件被提交。亚马逊否认存在价格操纵行为。证据细节披露加州当局称&#xff0c;数百份此前被编辑的记录显示&#xff0c;亚马逊施压其平台上的独立卖家&#xff0c;让…

作者头像 李华
网站建设 2026/4/17 15:41:19

掌握nginx-proxy-manager-zh:从零到精通的完整实战指南

掌握nginx-proxy-manager-zh&#xff1a;从零到精通的完整实战指南 【免费下载链接】nginx-proxy-manager-zh 基于nginx-proxy-manager翻译的中文版本 项目地址: https://gitcode.com/gh_mirrors/ng/nginx-proxy-manager-zh nginx-proxy-manager-zh作为一款中文界面的Ng…

作者头像 李华