在前端开发中,随机数生成是一个非常常见的需求,比如生成随机颜色、生成随机字符串等等。但是,由于众所周知的原因,JavaScript的随机数生成器并不可靠,因此我们需要使用一些特殊的方法来生成真正的随机数。其中一个方法就是使用Web Crypto API中的crypto.getRandomValues()方法。那究竟使用crypto.getRandomValues()和Math.random()这两个方法来生成随机数有什么区别呢?
抽奖系统的代码和运行效果,在本文章的第三节哈!心急可以先移步查看。
一、先谈crypto.getRandomValues()
crypto.getRandomValues()是Web Crypto API中提供的方法,用于生成真正的随机数。它使用操作系统提供的随机数生成器生成随机数,因此生成的随机数是完全不可预测的。这种方式生成的随机数在密码学应用中非常有用,因为密码学应用需要生成高质量的随机数来确保安全。
1. 使用方法
使用crypto.getRandomValues()生成随机数需要先创建一个类型化数组,然后调用该方法方法将随机数填充到这个数组中。随机数生成的范围取决于类型化数组的类型和长度。例如,使用8位无符号整数类型化数组可以生成0到255之间的随机数,而使用16位无符号整数类型化数组可以生成0到65535之间的随机数。
代码示例: (本文第三部分用该方法写了一个抽奖系统)
// 创建一个长度为1的32位无符号整数类型化数组
const randomArray = new Uint32Array(1);
// 将随机数填充到数组中
crypto.getRandomValues(randomArray);
// 获取数组中的随机数
const randomNumber = randomArray[0];
在使用crypto.getRandomValues()方法生成随机数时,不一定非要使用32位无符号整数类型化数组。根据需要,可以使用其他类型化数组来生成不同类型的随机数。
例如,如果需要生成一个0到255之间的随机整数,可以使用8位无符号整数类型化数组。代码示例:
// 创建一个长度为1的8位无符号整数类型化数组
const randomArray = new Uint8Array(1);
// 将随机数填充到数组中
crypto.getRandomValues(randomArray);
// 获取数组中的随机数
const randomNumber = randomArray[0];
值得注意的是,crypto.getRandomValues()方法只能在HTTPS协议下使用,否则会抛出异常。因为该方法使用的是操作系统提供的真随机数生成器,生成的随机数非常高质量,适用于密码学应用等需要高质量随机数的场景。然而在HTTP协议下,生成的随机数有可能会被中间人攻击者篡改,从而破坏密码学应用的安全性。因此,为了避免这种安全问题,crypto.getRandomValues()方法只能在HTTPS协议下使用。
2. 使用场景
crypto.getRandomValues()方法适用于需要高质量随机数的场景,例如密码学应用、加密密钥生成等。这是因为crypto.getRandomValues()方法生成的是真正的随机数,生成的随机数基于硬件随机性源(如硬件噪声)生成,因此非常难以预测。这使得它非常适合用于安全目的。
虽然JavaScript中有Math.random()方法可以生成随机数,但是它并不是真正的随机数生成器。它的生成方式是伪随机,也就是说,它实际上是基于某种算法生成的,而不是真正的随机数。这使得它容易被猜测和预测,因此不能用于安全目的。
相比之下,crypto.getRandomValues()生成的是真正的随机数,这些随机数基于硬件随机性源(如硬件噪声)生成,因此非常难以预测。
二、再说Math.random()
相比之下,Math.random()方法使用的是伪随机数生成器,因此生成的随机数是可预测的。这种生成方式生成的随机数在一些简单的应用场景下是足够用的,但在需要高质量随机数的场景下,它是不够用的。Math.random()适用于许多场景,例如模拟游戏、生成测试数据、快速生成不需要高度安全性的随机数等。
1. 使用方法
使用Math.random()方法生成随机数非常简单,只需要调用Math.random()方法即可。它会返回一个[0,1)之间的浮点数随机数。下面是一个使用Math.random()生成随机数的例子:
const randomNumber = Math.random();
2. 存在缺点
-
不是真正的随机
虽然
Math.random()函数可以生成看起来是随机的数字,但是它并不是真正的随机。它的算法是基于伪随机数生成器(PRNG)实现的,这意味着它是通过一个确定性的算法来生成数字的,而不是真正的随机过程。这就意味着,如果我们知道了这个算法,就可以预测下一个生成的数字。 -
不是安全的随机
由于
Math.random()函数不是真正的随机,因此它也不是安全的随机。也就是说,它生成的数字可能会被破解或预测,从而导致一些安全问题。例如,在密码重置过程中使用
Math.random()函数生成的随机码可能会被猜测到,从而导致账户被盗。 -
无法控制随机数的范围
虽然
Math.random()函数可以生成0到1之间的随机数,但是我们无法控制生成的随机数的范围。这就意味着,如果我们需要生成一个特定范围内的随机数,就需要进行额外的计算和处理。例如,如果我们需要生成一个1到100之间的随机数,就需要使用
Math.random()函数生成0到1之间的随机数,然后将其乘以100,再加上1,最后向下取整,才能得到一个1到100之间的随机数。
3. 增加Math.random()随机性
虽然Math.random()生成的随机数是可预测的,但是我们仍然可以通过一些技巧增加它的随机性。比如,我们可以使用当前时间戳来作为Math.random()的种子,这样就能得到更加随机的结果。但是,这种方式仍然不能与crypto.getRandomValues()相比,因为它生成的随机数仍然不是真正的随机数。
增加Math.random()随机性的代码:
// 使用当前时间戳作为种子
const randomNumber = Math.floor(Math.random() * new Date().getTime());
这样生成的随机数会受到当前时间的影响,因此更难以被预测。但是,重要场景仍然不建议使用。
三、用crypto.getRandomValues()开发抽奖系统
这里就不用Math.random() 来写抽奖系统了,毕竟是伪随机,实现也简单,大家自己搞得定。这里使用大家比较少用到的crypto.getRandomValues()来开发抽奖系统,确保生成的随机数是高质量的。
1. 运行效果图
可以在码上掘金上体验:
https://code.juejin.cn/pen/7220253949234774077
2. 代码实现
HTML和css代码可以在码上掘金查看,这里只展示js代码下面是实现的抽奖系统的js核心代码:
const prizeList = ['奖品1', '奖品2', '奖品3', '奖品4', '奖品5', '奖品6', '奖品7', '奖品8', '奖品9', '奖品10'];
const startBtn = document.querySelector('#start-btn');
const roulette = document.querySelector('.roulette');
const prize = document.querySelector('.prize');
const prizeAngle = 360 / prizeList.length;
let isSpinning = false;
//随机函数
function getRandomNumberInRange(min, max) {
const range = max - min + 1;
const randomArray = new Uint32Array(1);
let randomNumber = 0;
do {
crypto.getRandomValues(randomArray);
randomNumber = randomArray[0] % range;
} while (randomNumber >= range);
return min + randomNumber;
}
//摆放奖品
function setPricePos() {
let everyAngle = 360 / prizeList.length;
console.log(everyAngle)
let parentNode = document.getElementById('board');
let childNode = null;
prizeList.forEach((item, idx) => {
childNode = document.createElement('span');
childNode.textContent = item;
childNode.style.cssText = `transform:rotate(${everyAngle * idx}deg);`;
parentNode.appendChild(childNode);
})
}
setPricePos()
//开始抽奖
function startSpin() {
startBtn.disabled = true;
prize.textContent = '抽奖中';
if (isSpinning) {
return;
}
isSpinning = true;
const numberOfPrizes = prizeList.length;
const winningIndex = getRandomNumberInRange(0, numberOfPrizes - 1);
const winningPrize = prizeList[winningIndex];
let endAngle = prizeAngle * winningIndex + getRandomNumberInRange(0, prizeAngle - 1);
endAngle += 360 * 5;
const spinDuration = 5000; // 旋转持续时间
const animate = roulette.animate([
{ transform: 'rotate(0deg)' },
{ transform: `rotate(${endAngle}deg)` }
], {
duration: spinDuration,
easing: 'ease-in-out'
});
animate.addEventListener('finish', () => {
startBtn.disabled = false;
prize.textContent = winningPrize;
isSpinning = false;
});
}
startBtn.addEventListener('click', startSpin);
由于crypto.getRandomValues()生成的随机数是一个32位的无符号整数,因此在使用它生成指定范围内的随机数时,需要进行一些额外的处理,以避免生成的随机数偏离指定范围。上面的代码中,使用了一个do-while循环来确保生成的随机数在指定范围内。
四、写在最后
crypto.getRandomValues()和Math.random()这两个方法的区别在于生成随机数的方式不同。crypto.getRandomValues()使用操作系统提供的真随机数生成器,保证了生成的随机数的高质量,适用于密码学应用等需要高质量随机数的场景;
大家在实际开发中,需要根据具体情况选择合适的随机数生成方法。如果需要高质量的随机数,那么应该使用crypto.getRandomValues();如果只是在一些简单的应用场景下需要生成随机数,那么可以使用Math.random()。但是,无论使用哪种方法,都需要注意随机数的安全性,避免被破解而引发安全问题。
本文正在参加「金石计划」