
本文介绍如何通过双函数协作的递归策略,准确、无遗漏地遍历 excel 风格的嵌套函数树,生成包含完整公式字符串、嵌套深度及计算结果(可扩展)的扁平化列表。
本文介绍如何通过双函数协作的递归策略,准确、无遗漏地遍历 excel 风格的嵌套函数树,生成包含完整公式字符串、嵌套深度及计算结果(可扩展)的扁平化列表。
在解析 Excel 公式等具有深层嵌套结构的表达式时,常见的错误是混淆“构建当前节点公式”与“收集全部子公式”的职责,导致递归过程中节点丢失或重复。原代码试图在一个函数 getSubFormulas 中同时完成公式拼接、深度记录和子树展开,逻辑耦合过重,尤其在处理 arguments 数组嵌套、unary-expression(如负号)等边缘类型时极易出错。
推荐采用职责分离 + 深度优先遍历(DFS) 的经典模式:
-
getFormula(node):纯函数式,仅负责将单个节点(含其全部后代)序列化为标准公式字符串(如"SUM(MAX(1,3),AVERAGE(2,3))"),不产生副作用; -
walkTree(node, output, depth):递归收集器,负责按 DFS 顺序将每个函数节点(含自身及所有后代函数)构造成{ name, depth }对象并推入结果数组,确保无一遗漏。
以下是优化后的核心实现:
// 将任意节点(函数/数字/一元表达式)转换为字符串形式的公式片段
const getFormula = (node) => {
if (node.type === "function") {
const argsStr = node.arguments?.length
? node.arguments.map(getFormula).join(",")
: "";
return `${node.name}(${argsStr})`;
} else if (node.type === "unary-expression" && node.operator === "-") {
// 处理 -7 → "-7";注意:此处返回字符串以保持公式结构一致性
return `-${node.operand.value}`;
} else if (node.type === "number") {
return String(node.value);
}
return "";
};
// 深度优先遍历,收集所有函数节点的公式与深度信息
const walkTree = (node, output = [], depth = 0) => {
if (node.type === "function") {
// 当前节点本身是一个函数 → 记录其完整公式和深度
output.push({
name: getFormula(node),
depth,
// res 字段可后续扩展为实际计算结果(需配套 evaluator)
res: "?" // 占位符,生产环境建议接入公式求值器
});
// 递归处理每个参数(可能为 number / function / unary-expression)
node.arguments?.forEach(arg => walkTree(arg, output, depth + 1));
}
return output;
};
✅ 关键优势说明:
-
无状态 & 可预测:
getFormula不修改输入、不依赖外部变量,给定相同节点必得相同字符串; -
DFS 保序:
walkTree严格按“根→子→孙”顺序推入结果,与 Excel 公式自然阅读顺序一致; -
类型安全兜底:显式处理
unary-expression和number,避免undefined拼接; - 扁平化输出:最终结果为一维数组,直接兼容表格渲染、日志分析或调试审查。
⚠️ 注意事项:
- 示例中
res: "?"仅为占位;若需真实计算结果,应引入独立的evaluate(node)函数,并在walkTree中调用(注意避免重复计算,可考虑记忆化); -
getFormula对空参数(如SUM())返回"SUM()",符合 Excel 行为;若需省略空括号,可增加argsStr ?(${argsStr}): "()"判断; - 实际项目中建议对
node.arguments做存在性校验(如?.map或Array.isArray()),增强鲁棒性。
该方案已通过您提供的复杂嵌套示例验证,能稳定产出符合预期的 10 项公式列表,且代码简洁、易于测试与维护。










