Math.random() 的问题
const randomInt = (num, offset) => Math.floor(Math.random() * num) + offset;
Math.random() 生成的是伪随机数,不适合安全场景:
- 某些 JS 引擎实现可预测
- 不能用于:token、验证码、session ID、抽奖、游戏反作弊
用于 UI 动画、随机颜色、普通业务逻辑完全没问题,够快够简单。
安全随机整数
浏览器
export const randomInt = (min, max) => {
const range = max - min + 1;
const bytes = new Uint32Array(1);
crypto.getRandomValues(bytes);
return min + (bytes[0] % range);
};
有轻微的 modulo bias(取模偏差)——当 range 不能整除 0xFFFFFFFF 时,某些值出现概率略高。对大多数业务足够,抽奖/密码学场景用下面的拒绝采样版:
export const randomInt = (min, max) => {
const range = max - min + 1;
if (range <= 0 || range > 0xFFFFFFFF) throw new Error("invalid range");
const maxUint = 0xFFFFFFFF;
const limit = maxUint - (maxUint % range); // 去掉尾部偏差区间
const bytes = new Uint32Array(1);
let rand;
do {
crypto.getRandomValues(bytes);
rand = bytes[0];
} while (rand >= limit); // 拒绝偏差区间内的值
return min + (rand % range);
};
平均循环次数 < 2,性能影响可以忽略。
Node.js
import crypto from "crypto";
export const randomInt = (min, max) => {
return crypto.randomInt(min, max + 1); // 内部已处理 modulo bias
};
Node.js 的 crypto.randomInt 直接用就行,自动处理了偏差问题。
API 设计建议
原来的写法 randomInt(num, offset) 语义模糊——不清楚 num 是最大值还是范围长度:
// ❌ 不直观
randomInt(10, 5) // 5~14?还是 0~5 + 10 的偏移?
改成 (min, max) 更清晰:
// ✅ 直观
randomInt(5, 14) // [5, 14] 范围内均匀取整
场景选择
| 场景 | 推荐 |
|---|---|
| UI 动画、随机颜色 | Math.random() |
| 随机数组元素、占位数据 | Math.random() |
| 验证码、token | crypto.getRandomValues() |
| 抽奖、游戏随机 | crypto.getRandomValues() + 拒绝采样 |
| Node.js 服务端 | crypto.randomInt() |
性能上 Math.random() 比 crypto.getRandomValues() 快一个数量级,安全要求不高时不必引入加密随机数的开销。
