告别玄学!JavaScript的随机数终于能“听话”了!🎲

关注梦兽编程微信公众号,轻松摸鱼一下午

用了十几年Math.random(),你是不是也遇到过这样的尴尬:想要在游戏里生成一个每次加载都一样的地图?想在测试中复现那个只出现一次的随机bug?面对Math.random()这个完全看心情的家伙,你只能望洋兴叹,因为它每次运行都会给你一个全新的、不可预测的数字。就像一个完全不记事的"失忆"随机数生成器。

javascript 复制代码
// 每次运行都不一样,随缘得很
console.log(Math.random()); // 0.123456789
console.log(Math.random()); // 0.987654321

你无法"喂"给它一个特定的"种子",让它沿着你预设的轨迹生成数字。这在很多需要确定性行为的场景下,简直是噩梦。

别担心!救星来了!JavaScript即将迎来一个重量级新成员------Random API提案 (目前处于Stage 2阶段),它将彻底改变我们与随机数打交道的方式,带来一个革命性的特性:可复现的随机性(Reproducible Randomness)

Random API:给随机数装上"记忆"和"超能力"🚀

新的Random API的核心在于引入了"种子"(Seed)的概念。你可以给随机数生成器一个特定的种子,只要种子一样,它就能生成完全相同的随机数序列。这就像给随机数装上了"记忆",让它每次都能按照同一个剧本表演。

创建带有种子的随机数生成器:

javascript 复制代码
// 用一个特定的种子创建一个生成器,比如种子是 [42, 0, 0, 0]
const seededRandom = new Random.Seeded(new Uint8Array([42, 0, 0, 0]));

// 见证奇迹!这两行代码,无论你运行多少次,结果都是一样的!
console.log(seededRandom.random()); // 0.123456789 (每次都一样)
console.log(seededRandom.random()); // 0.987654321 (每次都一样)

请注意这里的"一样"是什么意思:如果你使用相同的种子,并且按照相同的顺序调用.random()方法,你将总是得到完全相同的随机数序列。 第一次调用总是返回同一个值,第二次调用总是返回同一个(但通常与第一次不同)值,以此类推。这并不是说每次调用.random()都会返回同一个数字,而是整个序列是可预测、可复现的。

实战演练:用确定性随机生成游戏世界!🏞

想象一下,你在开发一个沙盒游戏,每次进入游戏,地形都应该不同,但如果你保存游戏再加载,地形必须和保存时一模一样。使用Random API,这变得轻而易举:

javascript 复制代码
class TerrainGenerator {
  constructor(seed) {
    // 用种子创建随机生成器
    this.random = new Random.Seeded(seed);
    this.heightMap = [];
  }

  generateTerrain(width, height) {
    for (let y = 0; y < height; y++) {
      this.heightMap[y] = [];
      for (let x = 0; x < width; x++) {
        // 使用带有种子的随机数生成地形高度
        this.heightMap[y][x] = this.random.random() * 100;
      }
    }
    return this.heightMap;
  }

  // 保存当前生成器的状态,以便之后恢复
  saveState() {
    return this.random.getState();
  }

  // 从保存的状态恢复生成器
  restoreState(state) {
    this.random.setState(state);
  }
}

// 使用种子42生成第一块地形
const generator = new TerrainGenerator(new Uint8Array([42]));
const terrain1 = generator.generateTerrain(10, 10);

// 使用相同的种子42创建另一个生成器
const generator2 = new TerrainGenerator(new Uint8Array([42]));
const terrain2 = generator2.generateTerrain(10, 10);

// 见证奇迹!两块地形竟然完全相同!
console.log(JSON.stringify(terrain1) === JSON.stringify(terrain2)); // true

Random API的进阶超能力!🛠️

Random API的功能远不止于此,它还有一些非常实用的进阶特性:

1. 创建"子生成器":

有时候你需要多个独立的随机数序列,但又想它们之间有某种关联(比如都源自同一个主种子)。你可以从一个父生成器派生出子生成器:

javascript 复制代码
    // 创建一个父生成器
    const parent = new Random.Seeded(new Uint8Array([42]));

    // 创建两个子生成器,它们的种子来自父生成器
    const child1 = new Random.Seeded(parent.seed());
    const child2 = new Random.Seeded(parent.seed());

    // 这两个子生成器会产生不同的随机数序列,但它们的生成过程是确定的
    console.log(child1.random()); // 0.123456789
    console.log(child2.random()); // 0.987654321

2. 序列化和恢复状态:

你不仅可以保存和恢复整个生成器,还可以保存它当前的"状态"。这意味着你可以在生成到一半的时候暂停,保存状态,之后再从这个状态继续生成,保证后续序列完全一致。这对于需要中断和恢复的随机过程非常有用。

javascript 复制代码
    // 创建一个生成器
    const generator = new Random.Seeded(new Uint8Array([42]));

    // 生成一些数字
    console.log(generator.random()); // 0.123456789
    console.log(generator.random()); // 0.987654321

    // 保存当前状态
    const state = generator.getState();

    // 继续生成更多数字
    console.log(generator.random()); // 0.456789123

    // 恢复之前保存的状态
    generator.setState(state);

    // 再次生成,你会得到和恢复状态前一样的数字!
    console.log(generator.random()); // 0.456789123 (和上面一样)

更多实用场景:不止是游戏!💡

Random API的应用场景非常广泛:

1. 确定性测试:

在编写单元测试或集成测试时,你经常需要模拟随机数据。使用带有种子的随机数,你可以确保每次测试运行时都能得到相同的随机输入,从而更容易复现和调试问题。你可以临时替换掉Math.random()来实现确定性测试:

javascript 复制代码
    function testWithSeededRandom() {
      // 创建一个带有种子的随机生成器
      const random = new Random.Seeded(new Uint8Array([42]));

      // 临时替换掉 Math.random
      const originalRandom = Math.random;
      Math.random = random.random.bind(random);

      try {
        // 运行你的测试代码,里面的 Math.random 现在是确定的了
        runTests();
      } finally {
        // 恢复原来的 Math.random
        Math.random = originalRandom;
      }
    }

2. 程序化内容生成:

除了游戏地形,你还可以用它来生成角色属性、关卡布局、艺术图案等等,只要给定相同的种子,就能得到完全相同的结果。

javascript 复制代码
    class ProceduralContentGenerator {
      constructor(seed) {
        this.random = new Random.Seeded(seed);
      }

      generateCharacter() {
        const traits = ['brave', 'cunning', 'wise', 'strong', 'agile'];
        const name = this.generateName();
        // 使用 seededRandom 生成随机索引
        const trait = traits[Math.floor(this.random.random() * traits.length)];

        return { name, trait };
      }

      generateName() {
        const prefixes = ['A', 'E', 'I', 'O', 'U'];
        const suffixes = ['dor', 'lin', 'mar', 'tor', 'vin'];

        // 使用 seededRandom 生成随机前后缀
        const prefix = prefixes[Math.floor(this.random.random() * prefixes.length)];
        const suffix = suffixes[Math.floor(this.random.random() * suffixes.length)];

        return prefix + suffix;
      }
    }

    // 使用种子42创建生成器
    const generator = new ProceduralContentGenerator(new Uint8Array([42]));

    // 生成一个角色
    const character = generator.generateCharacter();
    console.log(character); // { name: 'Ador', trait: 'brave' }

    // 使用相同的种子42创建另一个生成器
    const generator2 = new ProceduralContentGenerator(new Uint8Array([42]));

    // 生成的角色竟然完全相同!
    const character2 = generator2.generateCharacter();
    console.log(character2); // { name: 'Ador', trait: 'brave' }

3. 安全随机数生成:

Random API也提供了生成密码学安全随机数的方法,这对于需要高度安全性的场景(如密钥生成)至关重要。

javascript 复制代码
    // 生成一个密码学安全的随机种子
    const secureSeed = Random.seed();

    // 使用安全种子创建生成器
    const secureRandom = new Random.Seeded(secureSeed);

    // 生成密码学安全的随机数
    console.log(secureRandom.random()); // 这是一个密码学安全的随机数

为什么这很重要?超越基础的意义!✨

新的Random API解决了几个长期存在的痛点:

它提供了可复现性 ,让你每次运行代码都能得到相同的随机数序列,这对于调试和复现bug至关重要。它保证了确定性 ,让你的代码在不同环境下表现一致。它支持状态管理 ,允许你保存和恢复随机数生成器的状态,实现更灵活的控制。当然,它也能生成密码学安全的随机数,满足高安全性的需求。

核心要点总结!🎯

新的Random API带来了可复现的随机性,让随机数不再完全失控。你可以用种子创建多个独立的随机生成器,并且能够保存和恢复它们的状态。这个API在程序化生成、测试等领域有着巨大的潜力。记住,它目前还在Stage 2阶段,但离我们越来越近了!

结语:JavaScript随机数的未来,是确定的!🚀

Random API的到来,对于需要精确控制随机行为的JavaScript开发者来说,无疑是一个游戏规则的改变者。无论你是游戏开发者、测试工程师,还是在进行任何需要可预测随机性的工作,这个API都将极大地提升你的开发效率和代码可靠性。

JavaScript随机数的未来已来,而且,它是确定的!

(注意:Random API目前处于TC39流程的Stage 2阶段,尚未在浏览器中普遍可用。你可以关注其在GitHub上的进展。)

相关推荐
萌萌哒草头将军7 分钟前
🚀🚀🚀尤雨溪:Vite 和 JavaScript 工具的未来
前端·vue.js·vuex
Fly-ping15 分钟前
【前端】cookie和web stroage(localStorage,sessionStorage)的使用方法及区别
前端
我家媳妇儿萌哒哒1 小时前
el-upload 点击上传按钮前先判断条件满足再弹选择文件框
前端·javascript·vue.js
加油,前进1 小时前
layui和vue父子级页面及操作
javascript·vue.js·layui
天天向上10241 小时前
el-tree按照用户勾选的顺序记录节点
前端·javascript·vue.js
sha虫剂1 小时前
如何用div手写一个富文本编辑器(contenteditable=“true“)
前端·vue.js·typescript
咔咔库奇1 小时前
深入探索 Vue 3 Fragments:从原理到实战的全方位指南
前端·javascript·vue.js
java_强哥1 小时前
uniapp实现聊天中的接发消息自动滚动、消息定位和回到底部
javascript·vue.js·uni-app
要加油哦~1 小时前
vue | vue 插件化机制,全局注册 和 局部注册
前端·javascript·vue.js