原生JS抽取不重复数组元素触发Maximum call stack size exceeded的原因与规避方法
错误触发原因
- 调用栈溢出核心原因:你的实现采用了无尾优化的递归逻辑,JS引擎在执行普通递归时,每次调用
getObjects都会在调用栈中新增一层栈帧,当连续重试次数超过引擎调用栈上限(多数浏览器上限为几千到几万次)时,就会抛出栈溢出错误。 - 小数组高概率触发的原因:从4个元素的数组中抽取3个不重复元素,单次执行的成功概率仅为
(4*3*2)/(4*4*4) = 37.5%,单次失败概率高达62.5%,极端情况下会出现连续上百次重试的情况,进而触发栈溢出;而从100个元素的数组中抽取3个元素时,单次成功概率超过97%,几乎不会出现连续多次重试的情况,因此很难触发报错。 - 隐藏逻辑缺陷:数组索引默认从0开始,但你调用随机数函数时传入了起始值1,会永远取不到索引为0的第一个元素,不符合随机抽取的预期。
规避方案
方案1:递归改循环(最小改动量)
直接将递归重试替换为while循环,不会产生调用栈累积,从根源上避免栈溢出问题:
// 先修正随机数函数逻辑,统一生成[min, max]闭区间的整数 function getRandomNumber(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function getObjects() { let drawnItems; while(true) { const randomObject1 = array[getRandomNumber(0, array.length - 1)]; const randomObject2 = array[getRandomNumber(0, array.length - 1)]; const randomObject3 = array[getRandomNumber(0, array.length - 1)]; drawnItems = [randomObject1, randomObject2, randomObject3]; if(drawnItems.length === new Set(drawnItems).size) break; } return drawnItems; }
方案2:Fisher-Yates洗牌算法(更高效,适合全场景)
如果需要抽取的元素数量较多,重复随机尝试的方式效率极低,采用洗牌算法打乱数组后直接取前N个,不会产生重复问题,时间复杂度仅为O(n):
// 洗牌函数,不会修改原数组 function shuffleArray(arr) { const res = [...arr]; for (let i = res.length - 1; i > 0; i--) { const randomIdx = Math.floor(Math.random() * (i + 1)); [res[i], res[randomIdx]] = [res[randomIdx], res[i]]; } return res; } function getObjects() { // 打乱数组后取前3个元素,天然不重复 return shuffleArray(array).slice(0, 3); }
内容的提问来源于stack exchange,提问作者TravisScript
相关产品推荐
相关产品推荐

