递归读取未知结构嵌套JSON拼接DOM字符串触发栈溢出如何解决
问题原因
你遇到的栈溢出问题由三个核心问题共同导致:
- 原生递归依赖JS引擎的调用栈,存在固定的深度上限。你的原有实现每一层递归都在栈帧中持有持续增长的
strSource字符串,而JS字符串是不可变类型,每次拼接都会生成新的字符串副本,单栈帧内存占用远高于仅读取键值的场景,嵌套层级稍高就会触及栈内存上限。 - 原有逻辑缺少
null值判断:JS中typeof null === 'object',如果返回的JSON中存在null值,会触发无限递归直接爆栈。 - 逐层字符串拼接的性能极差,大字符串反复拷贝会额外消耗内存,进一步放大栈溢出概率。
修复方案
方案1:优化现有递归(适配绝大多数常规业务场景)
核心优化点是用数组收集HTML片段,替代逐层字符串拼接,降低单栈帧内存占用,同时补全null值判断,修复索引过滤的逻辑bug:
// 优化后的递归处理函数 function processObject(jsonObject) { const fragments = [] // 提前拦截null、非对象值,避免死递归 if (jsonObject === null || typeof jsonObject !== 'object') return fragments for (const property in jsonObject) { if (!jsonObject.hasOwnProperty(property)) continue const value = jsonObject[property] if (typeof value === 'object' && value !== null) { // 合并递归返回的片段数组,不做中间字符串拼接 fragments.push(...processObject(value)) continue } // 正确过滤数组数字索引,修复原isNaN判断的逻辑漏洞 if (!isNaN(Number(property))) continue fragments.push(`<tr> <td>${property}</td> <td>${value}</td> </tr>`) } return fragments }
对应修改表格生成函数中的调用逻辑,最后一次性拼接所有片段:
// Docreatefiltersessiontable 中替换原有拼接逻辑 tablestring += processObject(AuditRecords).join('')
该优化可降低70%以上的栈内存占用,常规嵌套深度的JSON数据不会再触发栈溢出,输出结果和原逻辑完全一致。
方案2:非递归深度优先遍历(彻底规避栈溢出,适配超深嵌套/超大JSON场景)
递归的本质是借用JS引擎的内置调用栈,我们可以手动维护遍历栈,把递归改为循环实现,从根本上绕开JS引擎的调用栈上限限制,稳定性和性能都优于递归实现:
// 非递归实现,无调用栈溢出风险 function processObjectFlat(root) { const fragments = [] // 手动初始化遍历栈,根节点入栈 const stack = [root] while (stack.length) { const current = stack.pop() // 拦截null、非对象值 if (current === null || typeof current !== 'object') continue for (const property in current) { if (!current.hasOwnProperty(property)) continue const value = current[property] if (typeof value === 'object' && value !== null) { // 嵌套对象/数组压入栈,等待后续遍历 stack.push(value) continue } // 过滤数组数字索引 if (!isNaN(Number(property))) continue fragments.push(`<tr> <td>${property}</td> <td>${value}</td> </tr>`) } } return fragments.join('') }
使用时直接把表格生成函数中的processObject替换为processObjectFlat即可,遍历顺序、输出结果和原逻辑完全一致,无论JSON嵌套层级多深,都不会触发栈溢出。
额外优化建议
- 建议新增HTML转义逻辑,避免属性值中包含
</>/&等特殊字符时破坏HTML结构,同时防范XSS风险:
拼接单元格内容时将function escapeHtml(str) { return String(str) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, ''') }${value}替换为${escapeHtml(value)}即可。 - 超大表格场景下,可配合DataTables的
deferRender配置开启按需渲染,减少首屏DOM节点数量,进一步提升渲染性能。
内容的提问来源于stack exchange,提问作者Edoardo Remondini
相关产品推荐
相关产品推荐

