You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JS树形结构添加子节点触发无限循环问题求助

问题分析与解决方案

核心问题:浅拷贝导致所有节点共享同一个children数组

你的代码中创建新元素时使用了Object.assign({}, divBase),这是浅拷贝操作。由于divBase里的children是数组类型,浅拷贝只会复制数组的引用地址,导致所有新建的Div元素的children属性都指向同一个数组。当你调用parent.children.push(child)时,所有节点的children数组都会同步更新,看起来就像子节点被添加到了所有节点中,进而可能引发递归遍历的无限循环。

其他待修复问题

  1. displayList函数未递归渲染子节点,页面无法展示树形结构的层级关系;
  2. removeItem函数存在逻辑冗余,外层循环和内部findAndRemove函数的遍历逻辑重复。

修复步骤

1. 修复元素创建的深拷贝问题

替换add_element中的浅拷贝逻辑,确保每个新元素的children是独立数组:

function add_element(element) {
  if (element === "div") {
    // 手动创建对象,避免浅拷贝共享children数组
    let newElement = {
      Name: "",
      Type: "div",
      children: [], // 每个元素拥有独立的children数组
      open: false
    };
    newElement.Name = "Div " + getElementNum();
    elements.push(newElement);
    displayList(elements, document.getElementById("Explorer_contaner"));
    console.log(elements);
    return true;
  }
  return false;
}

如果后续需要更复杂的基对象,也可以使用深拷贝函数:

function deepCopy(obj) {
  return JSON.parse(JSON.stringify(obj));
}

// 在add_element中调用:
let newElement = deepCopy(divBase);

2. 修复displayList函数,递归渲染子节点

修改displayList,使其递归遍历子节点并渲染,同时支持节点的展开/折叠:

function displayList(list, root) {
  root.innerHTML = "";
  for (const item of list) {
    const itemEl = document.createElement("div");
    itemEl.className = "eitem";
    itemEl.id = item.Name;
    itemEl.draggable = true;
    itemEl.addEventListener("click", function() {
      selectItem(item.Name);
    });

    // 处理展开/折叠按钮
    if (item.children.length != 0) {
      const button = document.createElement("button")
      const img = document.createElement("img")

      button.className = "eButton"
      img.className = "eImg"

      img.src = item.open ? "Icons/down.svg" : "Icons/right.svg";

      // 按钮点击切换展开状态
      button.addEventListener("click", (e) => {
        e.stopPropagation(); // 避免触发父元素的选中事件
        item.open = !item.open;
        displayList(elements, document.getElementById("Explorer_contaner"));
      });

      button.appendChild(img)
      itemEl.appendChild(button)
    }

    const itemText = document.createTextNode(item.Name);
    const itemP = document.createElement("p");
    const itemb = document.createElement("b");

    itemP.className = "Etext";

    itemb.appendChild(itemText);
    itemP.appendChild(itemb);
    itemEl.appendChild(itemP);

    // 拖拽事件逻辑保持不变
    itemEl.addEventListener("dragstart", function(event) {
      event.dataTransfer.setData("text/plain", item.Name);
    });

    itemEl.addEventListener("dragover", function(event) {
      event.preventDefault();
    });

    itemEl.addEventListener("drop", function(event) {
      event.preventDefault();
      let target = event.dataTransfer.getData("text/plain")
      add_child(item.Name, target)
      displayList(elements, document.getElementById("Explorer_contaner"));
    });

    root.appendChild(itemEl);

    // 递归渲染展开状态下的子节点
    if (item.open && item.children.length > 0) {
      const childContainer = document.createElement("div");
      childContainer.style.marginLeft = "20px"; // 缩进子节点区分层级
      itemEl.appendChild(childContainer);
      displayList(item.children, childContainer);
    }
  }
}

3. 优化removeItem函数,简化逻辑

合并重复的遍历逻辑,减少冗余代码:

function removeItem(itemName) {
  let returnItem = null;

  function findAndRemove(arr) {
    for (let i = 0; i < arr.length; i++) {
      const item = arr[i];
      if (item.Name === itemName) {
        returnItem = arr.splice(i, 1)[0];
        return true; // 找到目标后终止递归
      }
      if (item.children && item.children.length) {
        if (findAndRemove(item.children)) {
          return true; // 子数组中找到目标,终止递归
        }
      }
    }
    return false;
  }

  findAndRemove(elements);
  return returnItem;
}

验证效果

修复完成后,每个新创建的Div元素都会拥有独立的children数组。调用add_child时,子节点只会被添加到目标父节点的children中,页面也会正确展示树形结构的层级关系和展开/折叠效果。

内容的提问来源于stack exchange,提问作者Westley Jacobsen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 12:57:28