树扁平化可用递归(直观易读)、栈模拟(防栈溢出)、增强版(支持自定义字段和过滤)及一行式(简易场景)。核心是统一处理空children、多根节点与字段灵活性,确保健壮性与复用性。

直接用递归或栈结构遍历树节点,把每个节点推入新数组即可,不需要额外库。
用递归方式扁平化(最直观)
适合层级不深、代码易读的场景。定义一个空数组,递归访问每个节点,先推入当前节点,再递归处理子节点(children)。
- 确保节点有 children 属性(常见命名,可按实际字段调整)
- 子节点数组为空或不存在时跳过递归,避免报错
- 保持原数据不变,返回全新一维数组
function flattenTree(data) {
const result = [];
function traverse(node) {
if (!node) return;
result.push(node);
if (Array.isArray(node.children)) {
node.children.forEach(traverse);
}
}
Array.isArray(data) ? data.forEach(traverse) : traverse(data);
return result;
}
用栈模拟深度优先(避免递归栈溢出)
适合深层嵌套或不确定层级的树(如组织架构、评论楼中楼),用循环+栈替代递归,更健壮。
- 把根节点(或根节点数组)先压入栈
- 每次弹出一个节点,推入结果数组,再将其子节点逆序压栈(保证顺序与递归一致)
- 比递归略复杂,但无调用栈限制
function flattenTreeByStack(data) {
const result = [];
const stack = Array.isArray(data) ? [...data] : [data];
while (stack.length) {
const node = stack.pop();
if (!node) continue;
result.push(node);
if (Array.isArray(node.children)) {
// 逆序压栈,使 children[0] 先被处理(维持 DFS 前序顺序)
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
}
}
return result;
}
支持自定义子字段名和过滤条件
真实项目中,子节点字段可能叫 subs、nodes 或 children;也可能需排除某些节点(如 disabled: true)。
- 把子字段名作为参数传入,提升复用性
- 增加 predicate 函数,在推入前判断是否保留该节点
- 默认行为保持兼容,不传参即按 children 处理
function flattenTree(data, { childrenKey = 'children', filter = () => true } = {}) {
const result = [];
function traverse(node) {
if (!node || !filter(node)) return;
result.push(node);
const children = node[childrenKey];
if (Array.isArray(children)) {
children.forEach(traverse);
}
}
(Array.isArray(data) ? data : [data]).forEach(traverse);
return result;
}
<p>// 使用:只保留 enabled 节点,子字段为 'subs'
flattenTree(treeData, { childrenKey: 'subs', filter: n => n.enabled });</p>
一行式写法(适合简单场景)
如果树只有单根且结构固定,可用 reduce + 扩展运算符快速实现,但注意性能和可读性平衡。
- 仅适用于浅层或已知结构,深层嵌套会因展开过多影响性能
- 无法过滤或适配不同字段名,属于“能用但不推荐长期维护”的快捷写法
const flatten = (node) => [node, ...(node.children?.flatMap(flatten) || [])];
不复杂但容易忽略细节,关键是统一处理空 children、多根情况和字段灵活性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











