JS 生成随机数数组的几种常见写法

作者:函数茶馆

时间:2026-11-02

浏览:96

深入讲解在 JavaScript 中生成随机数数组的几种常见实现方案,包括基础 for 循环、Array.from 映射、fill 与 map 结合、不重复随机数数组生成以及各写法的性能与选型建议。

文章目录
在 JavaScript 开发中,生成指定长度且元素落在特定区间的随机数数组是一项高频基础任务。解决该问题的核心在于两个环节:一是构建能够按闭区间均匀输出整数的基础随机函数,二是选用适宜的数组初始化与迭代机制。本文将由浅入深介绍四种主流实现方法,并探讨不重复随机数组的构建策略与工程选型建议。
JS 生成随机数数组的几种常见写法
图 0:JavaScript 随机数数组常见技术方案概览

核心前提:区间随机整数生成公式

在讨论如何构建数组之前,必须明确基础随机数的获取方式。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(...)。
  • 调试简便:单步执行时可以清晰观测到每次迭代的索引变化与数据状态。
JS 生成随机数数组的几种常见写法
图 1:基础循环构建流程与内存分配步骤

写法二: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,从而能够正常执行映射回调。
JS 生成随机数数组的几种常见写法
图 2:Array.from 映射机制与稀疏数组处理区别

写法三: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);
}
JS 生成随机数数组的几种常见写法
图 3:不重复随机数数组的两种生成路径与执行特点

常见写法横向对比与技术选型

为了让不同场景下的方案选择更为清晰,下表从代码行数、执行性能、内存开销以及兼容性四个维度对上述方案进行综合整理:

实现方式 代码简洁度 执行性能 典型适用场景 注意事项
基础 for 循环 中等(需显式声明索引) 最高(无函数调用包装) 超大数据集、高性能要求、老旧浏览器 预先分配 new Array(n) 效果更佳
Array.from() 高(一行完成映射) 较高(原生底层迭代) 现代日常工程、业务代码、单元测试 mock 首选方案,直观易读且无稀疏数组风险
fill().map() 较高(纯链式调用) 良好(中间产生一次过渡填充) 函数式编程风格团队 必须先执行 .fill() 激活空槽
Set / 洗牌算法 视算法而定 线性受限 抽奖、乱序发牌、无重复索引抽样 当抽样率高时坚决选用洗牌算法防卡死
安全补充:业务随机 vs 密码学安全随机
请特别注意,本文介绍的 Math.random() 基于伪随机数生成器(PRNG),其随机序列具有确定性数学规律,切勿用于生成加密密钥、交易令牌(Token)或安全验证码。在对安全性有严格要求的场景中,应当改用 Web Cryptography API 提供的 crypto.getRandomValues(new Uint32Array(length))。
JS 生成随机数数组的几种常见写法
图 4:生成随机数组的技术选型与决策流程

总结

在 JavaScript 中生成随机数数组并没有唯一的标准答案:常规业务开发中,应优先选用语义明确、不易犯错的 Array.from({ length }, fn);在面对数万级及以上的大数据吞吐场景时,具备下标直写特性的传统 for 循环依然是兼顾速度与内存的最佳实践;而在涉及不重复抽样时,则需根据抽样密度在 Set 与 Fisher-Yates 洗牌算法之间做出权衡,以规避碰撞导致的性能退化。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章