独家 Figma 面经深度解析:Document Layer 核心状态管理与算法复盘
目录
背景介绍
近年来,随着前端与协同设计领域的不断深耕,大厂对于工程师的代码底子和系统架构设计要求越来越高。最近,有不少同学向我们打听如何准备Figma面试。为了帮助大家精准狙击考点,今天我们来深度复盘一道非常经典的Figma高频题目:Document Layer 的状态管理与撤销重做机制(Undo/Redo)。
这篇硬核的Figma面经将带你从零推导这道算法题的最优解,不仅有详细的设计思路,还有面试级别的可运行代码。无论你是在冲刺 Offer,还是想精进数据结构的实战能力,都能从中获益。
核心考点拆解
题目要求我们实现一个文档图层(Document Layer)系统,包含基础的 apply(应用修改)操作,以及进阶的 commit_batch(批量提交)和 redo(重做)的 follow up 功能。
其中有三个极为关键的陷阱与细节:
- Apply 的即时性:在执行
commit_batch之前,每次调用的apply必须已经生效(take effect)。这意味着文档的当前状态必须是实时更新的。 - Commit Batch 的语义:
commit_batch的作用是打包最近的一批修改,它仅影响undo(撤销)操作的逻辑。撤销时,需要将整个 batch 里的修改一次性回退。 - 状态合并与空间优化:绝对不能无脑记录所有的
apply历史流水。系统需要进行状态合并。例如:连续执行了apply(1, color, red)和apply(1, color, blue),我们只需要记录对这个图层的最终改动逻辑,从而节省大量内存。
算法设计思路
为了完美避开上述陷阱并满足空间优化要求,我们可以采用双栈(Undo Stack / Redo Stack)加上一个暂存区(Staging Area)的架构设计:
- 文档状态(Document State):用一个字典维护每个 Layer 的当前最新属性。每次调用
apply时,直接更新这个字典,保证修改立即生效。 - 暂存区(Staging Area):用来存放当前正在进行但还未被
commit_batch打包的改动。当执行apply时,不仅更新当前状态,同时也要把“修改前”的旧值记录到暂存区中。关键点在于:如果暂存区中已经记录过该 Layer 的该属性,则不再覆盖旧值。这样就能完美实现题目要求的状态合并操作。 - Undo 栈与 Redo 栈:
- 当调用
commit_batch时,将整个暂存区打包成一个事务(Transaction)压入 Undo 栈,并清空暂存区。同时,必须清空 Redo 栈(因为新的操作打断了可重做的未来分支)。 - 执行
undo时,从 Undo 栈弹出一个事务,根据里面保存的旧值恢复文档状态,同时将恢复前的值(即撤销时的当前值)打包压入 Redo 栈。 - 执行
redo时,从 Redo 栈弹出一个事务,应用其中的新值,同时将其压回 Undo 栈。
Python 面试级代码实现
下面是满足所有细节要求的核心 Python 实现,代码经过严格排版,可直接用于面试白板或代码评测系统中:
class DocumentLayer:
def __init__(self):
# 存储当前文档层的真实状态: {layer_id: {property: value}}
self.state = {}
# 暂存区:记录当前未提交批次中,属性被修改【前】的原始状态
# 格式: {layer_id: {property: old_value}}
self.staging = {}
# 撤销与重做栈
self.undo_stack = []
self.redo_stack = []
def apply(self, layer_id, prop, new_value):
if layer_id not in self.state:
self.state[layer_id] = {}
old_value = self.state[layer_id].get(prop, None)
# 状态合并:如果这是该批次中第一次修改此属性,记录其最原始的值
if layer_id not in self.staging:
self.staging[layer_id] = {}
if prop not in self.staging[layer_id]:
self.staging[layer_id][prop] = old_value
# 修改立即生效
self.state[layer_id][prop] = new_value
def commit_batch(self):
if not self.staging:
return
# 打包暂存区的原始状态,压入 undo 栈
self.undo_stack.append(self.staging)
self.staging = {}
# 发生新提交后,未来的重做分支失效
self.redo_stack.clear()
def undo(self):
if not self.undo_stack:
return
batch_to_undo = self.undo_stack.pop()
redo_batch = {}
for layer_id, props in batch_to_undo.items():
if layer_id not in redo_batch:
redo_batch[layer_id] = {}
for prop, old_value in props.items():
# 记录当前状态,供 redo 使用
redo_batch[layer_id][prop] = self.state[layer_id].get(prop, None)
# 回滚到旧状态
if old_value is None:
if prop in self.state.get(layer_id, {}):
del self.state[layer_id][prop]
else:
self.state[layer_id][prop] = old_value
self.redo_stack.append(redo_batch)
def redo(self):
if not self.redo_stack:
return
batch_to_redo = self.redo_stack.pop()
undo_batch = {}
for layer_id, props in batch_to_redo.items():
if layer_id not in undo_batch:
undo_batch[layer_id] = {}
for prop, new_value in props.items():
# 记录重做前的状态,供再次 undo 使用
undo_batch[layer_id][prop] = self.state[layer_id].get(prop, None)
# 应用重做状态
if new_value is None:
if prop in self.state.get(layer_id, {}):
del self.state[layer_id][prop]
else:
if layer_id not in self.state:
self.state[layer_id] = {}
self.state[layer_id][prop] = new_value
self.undo_stack.append(undo_batch)
2026 真人上岸案例分享
就在 2026 年初,我们的学员 Alex 面临了同样的挑战。Alex 之前一直在做传统的后端业务,面对复杂的状态机管理和前端底层逻辑时总是心里没底。为了把握住这次难得的机遇,他预约了我们的高级面试辅导。
在辅导过程中,我们的导师一针见血地指出了他的薄弱环节,针对性地带他手撕了三遍这道状态管理题目,反复强调了 apply 的即时性原则以及对状态合并空间优化的深入理解。在真实面试中,当面试官抛出 commit_batch 与 redo 的进阶问题时,Alex 从容不迫地在白板上画出双栈加 Staging 区的架构图,并流畅写出了 O(1) 状态合并的代码逻辑。最终,他不仅当场拿到了面试官的 Strong Hire 评价,更是凭借出色的表现顺利Figma上岸,斩获了令同龄人艳羡的顶配包裹!
面试救急与冲刺
技术面试越往深处走,越考校的是你对系统细节的把控力与解决极端场景的思维深度。刷题虽然重要,但在关键时刻如果有行业专家的精准点拨,往往能让你事半功倍。
如果你正在备战,或者已经收到了心仪公司的面试邀请,但面对像 Document Layer 这样复杂的算法与设计题仍然感到吃力,不要犹豫,点击下方链接联系我们!我们提供顶级名企专家的 1v1 指导,助你在竞争激烈的环境中脱颖而出。
遇到突发面试?拿捏不准高频题?点击联系我们的 面试救急通道 获取你的专属突击方案!