JS 生成随机数数组的几种常见写法
深入讲解在 JavaScript 中生成随机数数组的几种常见实现方案,包括基础 for 循环、Array.from 映射、fill 与 map 结合、不重复随机数数组生成以及各写法的性能与选型建议。
核心前提:区间随机整数生成公式
在讨论如何构建数组之前,必须明确基础随机数的获取方式。JavaScript 提供的原生方法 Math.random() 返回一个伪随机浮点数,范围为半开区间 [0, 1)。若要生成闭区间 [min, max] 内的随机整数,通用的数学转换公式如下:
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
// 返回 [min, max] 之间的闭区间整数
return Math.floor(Math.random() * (max - min + 1)) + min;
}
掌握该辅助函数后,剩下的任务就是如何根据目标长度 length,批量产生并组织这些数字到数组中。
写法一:基础 for 循环(直观、高性能)
使用循环迭代是最符合直觉的传统写法。它不依赖 ES6+ 的特定 API,兼容所有运行环境。通过 new Array(length) 事先声明数组容量,再通过索引逐个赋值,可以避免数组动态扩容引发的内存重新分配。
function generateByLoop(length, min, max) {
const result = new Array(length);
for (let i = 0; i < length; i++) {
result[i] = Math.floor(Math.random() * (max - min + 1)) + min;
}
return result;
}
// 示例调用:生成 5 个处于 10 到 50 之间的随机数
const sample1 = generateByLoop(5, 10, 50);
console.log(sample1);
// 示例输出: [ 24, 48, 12, 35, 19 ]
技术要点与优势:
- 内存优化:使用固定下标
result[i] = ...赋值,性能略优于不断调用result.push(...)。 - 调试简便:单步执行时可以清晰观测到每次迭代的索引变化与数据状态。
写法二:Array.from()(现代标准、简洁推荐)
在 ES6 中引入的 Array.from() 是生成该类数组最地道且推荐的方案。它接受一个类数组对象(例如 { length: n })作为首个参数,并支持在第二个参数传入一个映射函数(mapFn),直接在转换过程中填充值。
function generateByArrayFrom(length, min, max) {
return Array.from({ length }, () =>
Math.floor(Math.random() * (max - min + 1)) + min
);
}
// 示例调用:生成 4 个处于 1 到 100 之间的随机数
const sample2 = generateByArrayFrom(4, 1, 100);
console.log(sample2);
// 示例输出: [ 73, 14, 92, 5 ]
初学者常常会尝试使用 new Array(n).map(...),却发现该写法完全不起作用。理解其背后的机制对于深入掌握 JavaScript 数组至关重要。
使用
new Array(5) 创建出来的数组仅有 length 属性,其内部是 5 个空槽(empty slots / holes),并非 undefined。原生的 Array.prototype.map 会主动跳过未初始化的空槽。而 Array.from() 则会按迭代器规范把这些空槽读取为 undefined,从而能够正常执行映射回调。
写法三:Array(n).fill().map()(链式调用写法)
如果你习惯于纯函数式的链式调用语法,可以通过 Array.prototype.fill() 先为稀疏数组填充占位元素(例如 null 或 0),将空槽转化为具备实际值的密集数组,之后便能平稳衔接 .map() 操作。
function generateByFillMap(length, min, max) {
return new Array(length)
.fill(null)
.map(() => Math.floor(Math.random() * (max - min + 1)) + min);
}
// 示例调用:生成 3 个 1 到 10 之间的随机数
const sample3 = generateByFillMap(3, 1, 10);
console.log(sample3);
// 示例输出: [ 7, 3, 9 ]
变体写法:扩展运算符
部分开发者偏好使用展开语法:[...new Array(length)].map(...)。展开运算符内部会调用数组迭代器,同样会将空槽展开为 undefined。不过需要留意的是,在数组长度极大(如数十万级别)时,展开语法可能引起调用栈或内存峰值开销,因此一般建议优先使用 Array.from 或 fill().map()。
写法四:进阶需求——生成“不重复”的随机数数组
在抽奖、卡牌抽取或打乱序列等实际业务中,经常要求生成的随机数各不相同(无重复元素)。如果直接生成,数字之间可能出现碰撞。根据生成规模与区间大小,通常有两种主流策略:
1. 基于 Set 集合循环采集(适合小量抽样、大数值区间)
当选取的数量 length 远小于区间范围 max - min + 1 时,随机数发生碰撞的概率较低。此时借助 Set 自动去重的特性,结合 while 循环不断填充,直到集合大小满足要求:
function generateUniqueRandomBySet(length, min, max) {
const range = max - min + 1;
if (length > range) {
throw new RangeError("请求长度超出了可用随机数区间范围!");
}
const set = new Set();
while (set.size < length) {
const num = Math.floor(Math.random() * range) + min;
set.add(num);
}
return Array.from(set);
}
// 示例调用:在 1 到 20 之间抽取 6 个不重复随机数
const sampleUnique = generateUniqueRandomBySet(6, 1, 20);
console.log(sampleUnique);
// 示例输出: [ 4, 19, 2, 11, 7, 15 ]
2. 基于 Fisher-Yates 洗牌算法截取(适合稠密抽样或密集区间)
若抽样数量接近区间大小(例如从 1~100 中随机抽取 90 个不重复数字),使用 Set 会因为后期碰撞概率剧增而导致 while 循环执行极慢甚至接近假死。此时最稳妥的做法是先生成候选池数组,随后运行标准的 Fisher-Yates 洗牌算法打乱顺序,再通过 slice 截取所需长度:
function generateUniqueRandomByShuffle(length, min, max) {
const range = max - min + 1;
if (length > range) {
throw new RangeError("请求长度超出了可用随机数区间范围!");
}
// 1. 初始化完整候选池 [min, ..., max]
const pool = Array.from({ length: range }, (_, i) => min + i);
// 2. Fisher-Yates 洗牌抽样前 length 个位置
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * (range - i)) + i;
// 交换当前位置与随机选中的位置
const temp = pool[i];
pool[i] = pool[randomIndex];
pool[randomIndex] = temp;
}
return pool.slice(0, length);
}
常见写法横向对比与技术选型
为了让不同场景下的方案选择更为清晰,下表从代码行数、执行性能、内存开销以及兼容性四个维度对上述方案进行综合整理:
| 实现方式 | 代码简洁度 | 执行性能 | 典型适用场景 | 注意事项 |
|---|---|---|---|---|
| 基础 for 循环 | 中等(需显式声明索引) | 最高(无函数调用包装) | 超大数据集、高性能要求、老旧浏览器 | 预先分配 new Array(n) 效果更佳 |
| Array.from() | 高(一行完成映射) | 较高(原生底层迭代) | 现代日常工程、业务代码、单元测试 mock | 首选方案,直观易读且无稀疏数组风险 |
| fill().map() | 较高(纯链式调用) | 良好(中间产生一次过渡填充) | 函数式编程风格团队 | 必须先执行 .fill() 激活空槽 |
| Set / 洗牌算法 | 视算法而定 | 线性受限 | 抽奖、乱序发牌、无重复索引抽样 | 当抽样率高时坚决选用洗牌算法防卡死 |
请特别注意,本文介绍的
Math.random() 基于伪随机数生成器(PRNG),其随机序列具有确定性数学规律,切勿用于生成加密密钥、交易令牌(Token)或安全验证码。在对安全性有严格要求的场景中,应当改用 Web Cryptography API 提供的 crypto.getRandomValues(new Uint32Array(length))。
总结
在 JavaScript 中生成随机数数组并没有唯一的标准答案:常规业务开发中,应优先选用语义明确、不易犯错的 Array.from({ length }, fn);在面对数万级及以上的大数据吞吐场景时,具备下标直写特性的传统 for 循环依然是兼顾速度与内存的最佳实践;而在涉及不重复抽样时,则需根据抽样密度在 Set 与 Fisher-Yates 洗牌算法之间做出权衡,以规避碰撞导致的性能退化。
JS大数值转字符串出现科学计数法怎么办
2026-11-05 17:09:21
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09







![JS创建二维数组:为什么Array(n).fill([])会导致数据联动](https://img.javascript.cn/tit/articles/13236/20261102/f618c991891ff37efb12df6af2ce0ccb0da8779a907fe1d2f7275d60c84fdd77.webp)
