树形结构 的一些操作技巧和问题

发布于:2025-07-12 ⋅ 阅读:(41) ⋅ 点赞:(0)
const buildJsonWithFlatList = (flatList) => {
  const json = {};
  flatList?.forEach(({ key }) => {
    const paths = key.split('&&');
    fillJSONWithPropertyPaths(paths, json);
  });
  return json;
};
const fillJSONWithPropertyPaths = (propertyPaths, json) => {
  let obj = json;
  while (propertyPaths?.length) {
    const property = propertyPaths.shift();
    if (!obj['children']) {
      obj = obj[property] = { children: {} };
      continue;
    }
    if (obj['children'][property]) {
      obj = obj['children'][property];
    } else {
      obj = obj['children'][property] = { children: {} };
    }
  }
};

输入:['a', 'b', 'c']

输出

  {
    a: {
      children: {
        b: {
          children: {
            c: { children: {} }
          }
        }
      }
    }
  }

输入

const flatList = [
  { key: 'a&&b&&c' },
  { key: 'a&&b&&d' },
  { key: 'a&&e' }
];

输出

{
  a: {
    children: {
      b: {
        children: {
          c: { children: {} },
          d: { children: {} }
        }
      },
      e: {
        children: {}
      }
    }
  }
}

这里有一段代码

fillJSONWithPropertyPaths 函数中始终操作的是同一个 json 对象 所以结果只会保留最后一个结果

修改建议
1. 修改 fillJSONWithPropertyPaths 函数
创建一个全新的对象来存储当前路径的结果。
将新对象合并到最终的 JSON 结构中。3. 新增 mergeJson 函数
用于递归合并两个 JSON 对象。

const fillJSONWithPropertyPaths = (propertyPaths) => {
  const result = {};
  let obj = result;

  while (propertyPaths?.length) {
    const property = propertyPaths.shift();
    obj[property] = { children: {} };
    obj = obj[property].children;
  }

  return result;
};
const buildJsonWithFlatList = (flatList) => {
  const json = {};

  flatList?.forEach(({ key }) => {
    const paths = key.split('&&');
    const subJson = fillJSONWithPropertyPaths(paths);

    // 合并 subJson 到最终的 json 中
    mergeJson(json, subJson);
  });

  return json;
};
const mergeJson = (target, source) => {
  for (const key in source) {
    if (!target[key]) {
      target[key] = source[key];
    } else {
      mergeJson(target[key].children, source[key].children);
    }
  }
};

3. 新增 mergeJson 函数
用于递归合并两个 JSON 对象。

fillJSONWithPropertyPaths

这里有一个思考

const fillJSONWithPropertyPaths = (propertyPaths) => {
  const result = {};
  let obj = result;

  while (propertyPaths?.length) {
    const property = propertyPaths.shift();
    if (!obj[property]) {
      obj[property] = { children: {} };
    }
    obj = obj[property].children;
  }

  return result;
};

这里 方法独立出一个 let obj = result; 而不是直接修改result的值 

我们知道 对象之间的赋值是存在浅拷贝的 直接修改副本 原本也会改变 那为什么还要这么做呢

是为了在不破坏对象引用的前提下,逐层深入嵌套结构

我们来看一个反例:直接操作 result

const fillJSONWithPropertyPaths = (propertyPaths) => {
  let result = {};
  while (propertyPaths?.length) {
    const property = propertyPaths.shift();
    if (!result[property]) {
      result[property] = { children: {} };
    }
    result = result[property].children; // ❌ 问题在这里
  }
  return result;
};

❌ 问题分析
初始时 result 是 {}。
第一次循环后,result[property] = { children: {} },然后你把 result = result[property].children。
这意味着你完全丢弃了原来的 result 引用,现在 result 指向的是 children 对象。
最终返回的是最内层的 children,而不是整个树结构。
所以最终返回的只是一个空对象 {},而不是完整的嵌套结构

 使用 let obj = result 的好处

let obj = result;

while (...) {
  ...
  obj = obj[property].children; // 修改的是 obj,不是 result
}

obj 是对 result 的引用副本。
在循环中不断更新 obj,可以逐层深入嵌套结构。
原始的 result 始终保留着整个树的根节点引用,最后可以正确返回完整结构。

🧠 类比理解
你可以把它想象成“指针”操作:
result 是根目录。
obj 是一个游标(cursor),用来一层一层地往下走。
如果你把 result 当游标用了,你就找不到根目录了。


所以,引入 obj 是为了保持对原始对象的引用,同时又能灵活地进行嵌套操作。这是构建树形结构的一种常见技巧。

继续深入

第一步:初始化

const result = {};
let obj = result;

result ──┐
           ▼
         { }     ← obj

第二步:进入循环

检查 obj['a'] 是否存在? ❌ 不存在
创建一个新的节点:

obj['a'] = { children: {} };

更新

obj = obj['a'].children;

result ──┐
           ▼
         { a: { children: {} } }   ← obj 现在指向 children

🔄 第二次循环

检查 obj['b'] 是否存在? ❌ 不存在
创建新节点:

obj['b'] = { children: {} };

obj = obj['b'].children;

result ──┐
           ▼
         { a: { children: { b: { children: {} } } } }   ← obj 现在指向 b.children
第三步:退出循环,返回结果

{
  a: {
    children: {
      b: {
        children: {
          c: { children: {} }
        }
      }
    }
  }
}


✅ 结论
obj['a'] = { children: {} } 会直接修改 obj 的内容。
因为 obj 和 result 指向同一个对象,所以 result 也被修改了。
后续的 obj = obj['a'].children 是为了继续构建下一层结构,但不会影响 result 的结构。


网站公告

今日签到

点亮在社区的每一天
去签到