JS树形结构添加子节点触发无限循环问题求助
问题分析与解决方案
核心问题:浅拷贝导致所有节点共享同一个children数组
你的代码中创建新元素时使用了Object.assign({}, divBase),这是浅拷贝操作。由于divBase里的children是数组类型,浅拷贝只会复制数组的引用地址,导致所有新建的Div元素的children属性都指向同一个数组。当你调用parent.children.push(child)时,所有节点的children数组都会同步更新,看起来就像子节点被添加到了所有节点中,进而可能引发递归遍历的无限循环。
其他待修复问题
displayList函数未递归渲染子节点,页面无法展示树形结构的层级关系;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
相关产品推荐
相关产品推荐

