关于javascript:精读高性能表格

8次阅读

共计 3847 个字符,预计需要花费 10 分钟才能阅读完成。

每个前端都想做一个完满的表格,业界也在继续摸索不同的思路,比方钉钉表格、语雀表格。

笔者所在数据中台团队也对表格有着极高的要求,尤其是自助剖析表格,须要兼顾性能与交互性能,本文便是记录自助剖析表格高性能的研发思路。

精读

要做表格首先要抉择基于 DOM 还是 Canvas,这是技术选型的第一步。比方钉钉表格就是 基于 Canvas 实现的,当然这不代表 Canvas 实现就比 DOM 实现要好,从技术上各有利弊:

  • Canvas 渲染效率比 DOM 高,这是浏览器实现导致的。
  • DOM 可拓展性比 Canvas 好,渲染自定义内容首选 DOM 而非 Canvas。

技术选型要看具体的业务场景,钉钉表格其实就是在线 Excel,Excel 这种状态决定了单元格内肯定是简略文本加一些简略图标,因而不必思考渲染自定义内容的场景,所以抉择 Canvas 渲染在将来也不会遇到不好拓展的麻烦。

而自助剖析表格人造可能拓展图形、图片、操作按钮到单元格中,对轴的拖拽响应交互也非常复杂,为了不让 Canvas 成为当前拓展的瓶颈,还是抉择 DOM 实现比拟得当。

那问题来了,既然 DOM 渲染效率人造比 Canvas 低,咱们应该如何用 DOM 实现一个高性能表格呢?

其实业界曾经有许多 DOM 表格优化计划了,次要以按需渲染、虚构滚动为主,即预留一些 Buffer 区域用于滑动时填充,表格仅渲染可视区域与 Buffer 区域局部。但这些计划都不可避免的存在疾速滑动时白屏问题,笔者通过一直尝试终于发现了一种完满解决的计划,咱们一起往下看吧!

单元格应用 DIV 相对定位

即每个单元格都是用相对定位的 DIV 实现,整个表格都是有独立计算地位的 DIV 拼接而成的:

这样做的前提是:

  1. 所有单元格地位都要提前计算,这里能够利用 web worker 做并行计算。
  2. 单元格合并仅是产生一个更大的单元格,它的定位形式与小单元格并无差别。

带来的益处是:

  1. 滚动时,单元格能够最大水平实现复用。
  2. 对于合并的单元格,只会让可视区域渲染的总单元格数更小,更利于性能晋升,而不是带来性能累赘。

如图所示有 16 个单元格,当咱们向右下滑动一格时,两头 3×3 即 9 个格子的区域是齐全不会从新渲染的,这样零散的相对定位散布能够最大水平维持单元格原本的地位。咱们能够认为,任何一格单元格只有本身不超出屏幕范畴,就不会随着滚动而重渲染。

如果你采纳 React 框架来实现,只有将每个格子的 key 设置为惟一的即可,比方以后行列号。

模仿滚动而非原生滚动

一般来说,轴因为逻辑非凡,其渲染逻辑和单元格会离开保护,因而咱们将表格分为三个区域:横轴、纵轴、单元格。

显然,常识是横轴只能纵向滚动,纵轴只能横向滚动,单元格能够横纵向滚动,那么横向和纵向滚动条就只能呈现在单元格区域:

这样会存在三个问题:

  1. 单元格应用原生滚动,横纵轴只能在单元格区域监听滚动后,通过 .scroll 模仿滚动,这必然会导致单元格与轴滚动有肯定错位,即轴的滚动有几毫秒的滞后感。
  2. 鼠标放在轴上时无奈滚动,因为只有单元格是 overflow: auto 的,而轴区域 overflow: hidden 无奈触发滚动。
  3. 疾速滚动呈现白屏,即使留了 Buffer 区域,在疾速滚动时也无能为力,这是因为渲染速度跟不上滚动导致的。

通过一番思考,咱们只有将计划稍作调整,就能同时解决下面三个问题:即不要应用原生的滚动条,而是应用 .scroll 代替滚动,用 mousewheel 监听滚动的触发:

这样做带来什么变动呢?

  1. 轴、单元格区域都应用 .scroll 触发滚动,使得轴和单元格不会呈现错位,因为轴和单元格都是用 .scroll 触发的滚动。
  2. 任何地位都能监听滚动,使得轴上也能滚动了,咱们不再依赖 overflow 属性。
  3. 疾速滚动时惊喜的发现不会白屏了,起因是用 js 管制触发的滚动产生在渲染实现之后,所以浏览器会在滚动产生前现实现渲染,这相当乏味。

模仿滚动时,实际上整个表格都是 overflow: hidden 的,浏览器就不会给出自带滚动条了,咱们须要用 DIV 做出虚构滚动条代替,这个绝对容易。

零 buffer 区域

当咱们采纳模仿滚动计划时,相当于采纳了在滚动时“高频渲染”的计划,因而不须要应用截留,更不要应用 Buffer 区域,因为更大的 Buffer 区域意味着更大的渲染开销。

当咱们把 Buffer 区域移除时,发现整个屏幕内渲染单元格在 1000 个以内时,古代浏览器甚至配合 Windows 都能疾速实现滚动前刷新,并不会影响滚动的流畅性。

当然,滚动过快仍然不是一件坏事,既然滚动是由咱们管制的,能够稍许管制下滚动速度,管制在每次触发 mousewheel 位移不超过 200 左右最佳。

预计算

像单元格合并、行列暗藏、单元格格式化等计算逻辑,最好在滚动前提前算掉,否则在疾速滚动时实时计算必然会带来额定的计算成本损耗。

然而这种预计算也有弊病,当单元格数量超过 10w 时,计算耗时个别会超过 1 秒,单元格数量超过 100w 时,计算耗时个别会超过 10 秒,用预计算的就义换来滚动的晦涩,还是有些遗憾,咱们能够再思考以下,是否升高预计算的损耗?

部分预计算

部分预计算就是一种解决方案,即使单元格数量有一千万个,但咱们如果仅计算前 1w 个单元格呢?那无论数据量有多大,都不会呈现丝毫卡顿。

但部分预计算有着显著毛病,即表格渲染过程中,部分计算结果并不总等价于全局计算结果,典型的有列宽、行高、跨行跨列的计算字段。

咱们须要针对性解决,对于单元格宽高计算,必须采纳部分计算,因为全量计算的损耗十分大。但部分计算必定是不精确的,如下图所示:

但出于性能思考,咱们初始化可能仅能计算前三行的高度,此时,咱们须要在滚动时做两件事件:

  1. 在疾速滚动的时候,向 web worker 发送预计要滚动到的地位,增量计算这些地位文字宽度,并实时修改列总宽。(因为列总宽算完只有存储最大值,所以已计算的数量级会被压缩为 O(1))。
  2. 宽度计算结束后,疾速刷新以后屏幕单元格宽度,但在宽度校准的同时,维持可视区域内左对齐不变,如下图所示:

这样滚动过程中尽管单元格会被忽然撑开,但地位并不会产生绝对挪动,与提前全量撑开后视觉内容雷同,因而用户体验并不会有理论影响,但计算工夫却由 O(row * column) 降落到 O(1),只有计算一个常数量级的单元格数目。

计算字段也是同理,能够在滚动时按片预计算,但要留神仅能在计算波及部分单元格的状况下进行,如果这个计算是全局性质的,比方排名,那么部分排序的排名必定是谬误的,咱们必须进行全量计算。

好在,即使是全量计算,咱们也只须要思考一部分数据,假如行列数量都是 n,能够将计算复杂度由 O(n²) 升高为 O(n):

这种计算字段的解决无奈保障反对有限数量级的数据,但能够大大降低计算工夫,假如 1000w 单元格计算工夫开销是 60s,这是一个简直不能忍耐的工夫,假如 1000w 单元格是 1w 行 * 1k 列造成的,咱们部分计算的开销是 1w 行(100ms)+ 1k 列 (10ms) = 0.1s,对用户来说简直感触不到 1000w 单元格的卡顿。

在 10w 行 * 10w 列的状况下,等待时间是 1+1 = 2s,用户会感触到显著卡顿,但总单元格数量可是惊人的 100 亿,光数据可能就几 TB 了,不可能呈现这种规模的聚合数据。

Map Reduce

前端计算还能够采纳多个 web worker 减速,总之不要让用户电脑的 CPU 闲置。咱们能够通过 window.navigator.hardwareConcurrency 获取硬件并行能反对的最大 web worker 数量,咱们就实例化等量的 web worker 并行计算。

拿方才排名的例子来说,同样 1000w 单元格数量,如果只有一列呢?那行数就是扎扎实实的 1000w,这种状况下,即使 O(n) 复杂度计算耗时也可能冲破 60s,此时咱们就能够分段计算。我的电脑 hardwareConcurrency 值为 8,那么就实例化 8 个 web worker,别离并行计算第 0 ~ 125w, 125w ~ 250w …, 875w ~ 1000w 段的数据别离进行排序,最初失去 8 段有序序列,在主 worker 线程中进行合并。

咱们能够采纳分治合并,即针对顺次收到的排序后果 x1, x2, x3, x4…,将收到的后果两两合并成 x12, x34, …,再次合并为 x1234 直到合并为一个数组为止。

当然,Map Reduce 并不能解决所有问题,假如 1000w 数据计算耗时 60s,咱们分为 8 段并行,每一段均匀耗时 7.5s,那么第一轮排序总耗时为 7.5s。分治合并工夫复杂度为 O(kn logk),其中 k 是分段数,这里是 8 段,logk 约等于 3,每段长度 125w 是 n,那么一个 125w 数量级的二分排序耗时大略是 4.5s,工夫复杂度是 O(n logn),所以等价为 logn = 4.5s, k x logk 等于几?这里因为 k 远小于 n,所以工夫耗费会远小于 4.5s,加起来耗时不会超过 10s。

总结

如果你想打造高性能表格,DIV 性能足够了,只有留神实现的时候稍加技巧即可。你能够用 DIV 实现一个兼顾性能、拓展性的表格,是时候从新置信 DOM 了!

笔者倡议读完本文的你,依照这样的思路做一个小 Demo,同时思考,这样的表格有哪些通用性能能够形象?如何设计 API 能力成为各类业务表格的基座?如何设计性能能力满足业务层表格繁多的拓展诉求?

探讨地址是:精读《高性能表格》· Issue #309 · dt-fe/weekly

如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。

关注 前端精读微信公众号

版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)

正文完
 0