LayoutNG 是如何优化 Flex 的
# 一、背景:为什么需要 LayoutNG?
在 2020 年前,Chrome 使用旧版 Blink Layout。
问题:
- 布局代码 20+ 年历史包袱
- Block / Table / Flex / Grid 各自实现
- 回流传播不可控
- 嵌套 flex 容易重复计算
- 浮点误差导致反复 layout
为了解决这些问题,Chromium 重写了布局引擎:
LayoutNG
它是 Chromium/Blink 的下一代布局系统。
目标:
- 更高性能
- 更少重复 layout
- 可预测的复杂度
- 更精确的规范实现
# 二、旧版 Flex 的性能问题
旧算法的问题主要有 4 类:
# 1️⃣ 重复测量(Repeated Measurement)
旧实现里:
- flex-basis:auto
- min-content
- shrink
都会导致:
👉 子元素被测量多次
流程类似:
测量 → 分配空间 → 不够 → 重新测量 → 再分配
嵌套 flex 会指数级放大。
# 2️⃣ 回流传播不可控
在旧 Blink:
- 父容器尺寸变化
- 可能触发所有子元素重新 layout
- 子元素变化又可能影响父元素
形成:
Layout Ping-Pong
# 3️⃣ 不统一的 Layout Tree
旧系统中:
- Block 用一套逻辑
- Flex 用另一套
- Table 用另一套
共享能力弱 → 代码重复 → 优化困难
# 4️⃣ 浮点误差问题
空间分配后出现:
- 1px 偏差
- 再次 reflow 修正
导致额外 layout pass
# 三、LayoutNG 的核心设计思想
LayoutNG 引入了三大核心结构:
# 1️⃣ Constraint Space(约束空间)
核心思想:
子元素不再“自己猜尺寸” 而是父元素给一个明确约束空间
它包含:
- 可用宽度
- 可用高度
- writing mode
- min/max
这样:
子元素 layout 是纯函数:
输入:Constraint Space
输出:Fragment
2
# 性能意义:
- 避免反复测量
- 同样输入不会重复计算
- 结果可缓存
# 2️⃣ Fragment Tree(片段树)
旧版 layout 直接修改 render tree。
LayoutNG:
- 不直接修改节点
- 生成 Fragment
- 所有 layout 结果写入 fragment tree
好处:
- 不污染原始结构
- 可以回滚
- 可以缓存
- 避免重复遍历 DOM
# 3️⃣ 单向数据流
旧版:
父 → 子
子 → 父
父再 → 子
2
3
LayoutNG:
父传约束 → 子返回结果
结束
2
强制单向流动。
这和 Transformer 的单向 attention 有点类似 —— 避免循环依赖。
# 四、LayoutNG 如何优化 Flex ?
现在讲重点。
# 1️⃣ 减少测量次数
旧版:
for each item:
measure min-content
measure max-content
2
3
LayoutNG:
- 只在真正需要时测量
- 结果缓存
- Constraint 相同直接复用
等价于:
memoization
复杂度从可能 O(n²) 降为接近 O(n)
# 2️⃣ 更聪明的 Free Space 分配算法
Flex grow/shrink 是最复杂部分。
LayoutNG 做了:
- 预计算冻结项
- 分批分配
- 避免反复回退
旧版可能:
分配 → 溢出 → 回退 → 再分配
新版:
计算一次权重 → 批量分配
避免多轮 pass
# 3️⃣ 避免嵌套爆炸
旧版嵌套:
Flex
└ Flex
└ Flex
2
3
每层都重新 layout 子树。
LayoutNG:
- 子树 layout 结果封装为 Fragment
- 父层只读取尺寸
- 不重新展开子树
这极大降低嵌套成本。
# 4️⃣ 更精准的 Dirty 标记
旧系统 dirty 粒度粗:
一个属性变 → 整个 subtree layout
LayoutNG:
- 精确记录受影响节点
- 使用 LayoutInvalidationReason
- 只重新 layout 必要节点
例如:
改变某个 flex item 的 margin:
只影响:
- 该 item
- 该 flex container
而不是整个页面
# 5️⃣ 改进 min/max content 计算
min-content 是 Flex 性能杀手。
LayoutNG:
- 使用统一 intrinsic sizing 算法
- 避免重复测量
- 使用更高精度 float 运算
减少回流次数。
# 五、性能对比(真实趋势)
在 Chromium 团队发布的测试中:
| 场景 | 旧 Blink | LayoutNG |
|---|---|---|
| 复杂嵌套 flex | 明显卡顿 | 显著下降 |
| 大量 shrink | 多次 layout | 单轮 |
| 精度误差修正 | 多次回流 | 基本消除 |
| 复杂 writing-mode | 很慢 | 稳定 |
典型提升:
20% ~ 40% 某些极端场景 > 2x
# 六、LayoutNG 的一个关键突破
它把 Layout 变成了:
“函数式布局系统”
Layout(node, constraint) → fragment
无副作用
这意味着:
- 更容易并行化(未来)
- 更容易缓存
- 更容易调试
# 七、为什么现在 Flex 不再是性能黑洞?
因为:
- 单向约束流
- Fragment 缓存
- 精准 invalidation
- 避免多轮 shrink/grow
- 统一 intrinsic 计算
Flex 仍然比 block 重,
但已经:
从“不确定复杂度”变成“可预测复杂度”
# 八、你可以如何验证?
打开 Chrome DevTools:
Performance → Record
看:
- Layout 时间
- Layout invalidation
- Forced reflow
你会发现:
现代 Chrome 中 Flex 的 layout 次数明显少于 2018 年以前版本。
# 九、终极总结
旧版 Blink Flex:
状态驱动 + 双向传播 + 多轮测量
LayoutNG Flex:
约束驱动 + 单向数据流 + 函数式输出
这是一次架构级优化。