使用structuredClone结合forEach处理大数组触发RangeError求助
问题分析
你的问题核心在于两个克隆方案的局限性:
structuredClone依赖递归实现深层克隆,面对大数组+深层嵌套+循环引用的对象时,递归调用栈会超出运行环境的栈容量上限,触发RangeError。JSON.parse(JSON.stringify())天生不支持循环引用(你的Model里previous和next互指形成了循环链路),因此直接使用会抛出类型错误。
改进方案
方案一:手动实现迭代式深度克隆(解决循环引用+避免栈溢出)
放弃递归克隆逻辑,改用栈/队列迭代的方式遍历对象,同时用Map记录已克隆的对象实例,既解决循环引用问题,又不会触发栈溢出。
具体实现代码:
function cloneModel(source: Model): Model { // 存储原对象与克隆对象的映射,处理循环引用 const clonedMap = new Map<object, object>(); // 用栈实现迭代遍历,替代递归 const stack: [source: any, target: any] = []; // 初始化克隆对象 const cloned = { ...source } as Model; clonedMap.set(source, cloned); stack.push([source, cloned]); while (stack.length > 0) { const [currentSource, currentTarget] = stack.pop()!; // 处理嵌套的children数组 if (currentSource.children) { currentTarget.children = []; for (const child of currentSource.children) { if (clonedMap.has(child)) { currentTarget.children.push(clonedMap.get(child) as Dto); } else { const clonedChild = { ...child }; clonedMap.set(child, clonedChild); currentTarget.children.push(clonedChild); stack.push([child, clonedChild]); } } } // 处理contentChunks数组(如果ContentChunk是简单结构直接浅拷贝,嵌套结构则参考children处理) if (currentSource.contentChunks) { currentTarget.contentChunks = [...currentSource.contentChunks]; } // 处理循环引用的previous/next if (currentSource.previous) { currentTarget.previous = clonedMap.get(currentSource.previous) as Model ?? undefined; } if (currentSource.next) { currentTarget.next = clonedMap.get(currentSource.next) as Model ?? undefined; } } return cloned; }
修改原代码,避免重复克隆:
const result: Model[] = []; array.forEach((element) => { let clonedElement: Model | undefined; // 先判断是否需要克隆,减少重复操作 if (condition1 || condition2) { clonedElement = cloneModel(element); } if (condition1 && clonedElement) { result.push(clonedElement); } if (condition2 && clonedElement) { result.push(clonedElement); } });
方案二:简化克隆逻辑(业务允许时使用)
如果你的业务场景不需要保留原对象previous和next的循环引用关系,可以临时移除这些属性,克隆完成后再重新构建链表:
function cloneWithoutCircularRefs(source: Model): Model { // 临时剥离循环引用属性 const { previous, next, ...tempObj } = source; // 克隆无循环引用的部分(大数组仍栈溢出则替换为上述迭代克隆) const cloned = structuredClone(tempObj) as Model; return cloned; } // 原代码改造 const result: Model[] = []; array.forEach((element) => { let clonedElement: Model | undefined; if (condition1 || condition2) { clonedElement = cloneWithoutCircularRefs(element); } if (condition1) result.push(clonedElement!); if (condition2) result.push(clonedElement!); }); // 重新构建链表(如果业务需要) result.forEach((item, index) => { item.previous = result[index - 1]; item.next = result[index + 1]; });
这个方案实现简单,但仅适用于不需要保留原链表指向的场景。
额外优化点
原代码中若condition1和condition2同时满足,会重复克隆同一个元素。改成先判断是否需要克隆、仅克隆一次再重复推入,能大幅节省大数组场景下的性能开销。
内容的提问来源于stack exchange,提问作者Juri
相关产品推荐
相关产品推荐

