当前位置:首页 > 问答 > 正文

掌握JavaScript随机数:构建高效算法的专业方法与实战分享

掌握JavaScript随机数:构建高效算法的专业方法与实战分享

说实话,JavaScript的Math.random()可能是最被低估的工具之一了,很多人觉得它就是个“随便生成个数字”的函数,但如果你真的深入挖掘,会发现它背后藏着不少坑和惊喜🤯。

为什么Math.random()不够“随机”?

Math.random()生成的是伪随机数——也就是说,它并不是真正的随机,而是基于算法模拟的,这意味着:

  • 它的分布是均匀的(理论上),但如果你需要加密级别的随机性,它不够安全🔒。
  • 它的种子(seed)通常是基于系统时间,所以如果你在同一毫秒内多次调用,可能会得到相似的结果😅。

我曾经在一个抽奖系统里踩过这个坑——用户疯狂点击“抽奖”按钮时,连续几次的结果几乎一样,场面一度非常尴尬……

更可控的随机:crypto.getRandomValues()

如果你需要更强的随机性,比如生成密钥或者高安全性的随机ID,可以用crypto.getRandomValues()

const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
console.log(array[0]); // 真·随机数

但它的缺点是——慢!而且如果你只是做个简单的随机颜色生成器,用它就有点杀鸡用牛刀了🐔🔪。

实战:如何生成特定范围的随机数?

最常见的需求:“给我一个1到10的随机整数。”很多人会这么写:

const randomNum = Math.floor(Math.random() * 10) + 1;

但这里有个小问题——Math.random()的范围是[0, 1),所以Math.random() * 10的范围是[0, 10)floor之后是0~9,再加1才是1~10

我曾经因为这个边界问题debug了半小时,最后发现是因为没考虑Math.random()可能(虽然概率极低)返回0,导致某些极端情况下的bug💥。

进阶:带权重的随机数

假设你要做一个游戏,怪物掉落的物品概率不同:

  • 普通装备:70%
  • 稀有装备:25%
  • 传说装备:5%

你可以这样实现:

function weightedRandom(weights) {
  const total = weights.reduce((a, b) => a + b, 0);
  const rand = Math.random() * total;
  let sum = 0;
  for (let i = 0; i < weights.length; i++) {
    sum += weights[i];
    if (rand < sum) return i;
  }
}
const loot = ["普通装备", "稀有装备", "传说装备"];
const weights = [70, 25, 5];
const result = loot[weightedRandom(weights)];
console.log(result); // 按概率返回

这个算法我在一个H5小游戏里用过,结果测试时发现传说装备掉率“似乎”比5%高……后来发现是因为我用Math.random()的分布不够均匀,在小样本下偏差更明显😅。

随机数的“可预测性”问题

如果你在做单元测试,可能需要“可控”的随机数,这时候可以自己实现一个伪随机数生成器(PRNG),比如经典的线性同余法

掌握JavaScript随机数:构建高效算法的专业方法与实战分享

class SeededRandom {
  constructor(seed = 42) {
    this.seed = seed;
  }
  next() {
    this.seed = (this.seed * 1664525 + 1013904223) % 4294967296;
    return this.seed / 4294967296;
  }
}
const rng = new SeededRandom(123);
console.log(rng.next()); // 每次运行结果相同

这个技巧在生成随机地图或者游戏关卡时特别有用,因为你可以通过相同的种子复现完全一样的随机序列🌍。

最后的小技巧:洗牌算法

如果你想随机打乱数组,千万别用array.sort(() => Math.random() - 0.5)!它的分布不均匀,会导致某些元素更容易出现在特定位置。

正确的做法是Fisher-Yates洗牌算法

function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}
const cards = ["A", "2", "3", "4", "5"];
console.log(shuffle(cards)); // 真正随机的打乱

我曾经在一个扑克游戏里用错了洗牌方法,结果被玩家发现某些牌组合出现的频率异常……惨痛的教训啊🃏💔。

JavaScript的随机数远不止Math.random()那么简单,不同的场景需要不同的策略,有时候你得在“真随机”和“性能”之间权衡,有时候又得在“不可预测性”和“可复现性”之间纠结。

最重要的是——永远记得测试你的随机分布!因为人类的直觉在概率面前,经常错得离谱🎲😆。