从零开始打造 RandBox:一个强大的 JavaScript 随机数据生成库(2/5)- 技术架构与核心实现
前言
在上一篇文章中,我们探讨了 RandBox 项目的起源和整体规划。今天,我将深入技术细节,分享 RandBox 核心架构的设计思路和关键实现。从随机数生成器的选择到 TypeScript 类型系统的设计,每一个技术决策都承载着对开发体验和性能的深度思考。
核心架构设计
分层架构模式
RandBox 采用经典的分层架构,确保代码的可维护性和扩展性:
┌─────────────────────────────────────────┐
│ API Layer (用户接口层) │
├─────────────────────────────────────────┤
│ Module Layer (模块层) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Person │ │Location │ │Finance │ ...│
│ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────┤
│ Core Layer (核心层) │
│ 随机数生成器 | 工具函数 | 类型定义 │
├─────────────────────────────────────────┤
│ Runtime Layer (运行时层) │
│ Node.js | Browser | 小程序 │
└─────────────────────────────────────────┘
核心模块详解
1. Core 模块 - 基础设施
Core 模块是整个 RandBox 的基石,提供基础的随机数生成能力和工具函数:
// src/core.ts 核心类设计
export class RandBox {
private mt: MersenneTwister;
private seed: string | number | undefined;
constructor(seed?: string | number) {
this.seed = seed;
this.mt = new MersenneTwister(this.getSeedValue(seed));
}
// 核心随机数生成方法
random(): number {
return this.mt.random();
}
// 工具方法:范围检查
testRange(test: boolean, message: string): void {
if (test) {
throw new RangeError(message);
}
}
// 工具方法:初始化选项
initOptions<T>(options: T | undefined, defaultOptions: T): T {
return { ...defaultOptions, ...options };
}
}
为什么选择 Mersenne Twister?
- 高质量随机性:周期长达 2^19937-1,几乎无重复
- 性能优秀:计算效率高,适合大量数据生成
- 可预测性:支持种子设置,便于测试和调试
- 广泛认可:被众多编程语言和库采用
2. 类型系统设计
TypeScript 类型系统是 RandBox 的核心优势之一,我们设计了完整的类型定义:
// src/types.ts 类型定义
export interface RandBoxOptions {
seed?: string | number;
}
// 基础数据类型的选项接口
export interface IntegerOptions {
min?: number;
max?: number;
exclude?: number[];
}
export interface StringOptions {
length?: number;
pool?: string;
alpha?: boolean;
numeric?: boolean;
symbols?: boolean;
casing?: 'lower' | 'upper' | 'mixed';
}
// 复杂类型的选项接口
export interface PersonOptions {
nationality?: 'zh' | 'en' | 'fr' | 'de';
gender?: 'male' | 'female' | 'mixed';
middle_initial?: boolean;
prefix?: boolean;
suffix?: boolean;
}
// 函数重载支持
export interface RandBoxInstance {
// integer 方法的多种调用方式
integer(): number;
integer(max: number): number;
integer(min: number, max: number): number;
integer(options: IntegerOptions): number;
// 其他方法...
name(options?: PersonOptions): string;
email(options?: EmailOptions): string;
}
类型设计原则:
- 渐进式复杂度:从简单调用到复杂配置的平滑过渡
- 智能推导:TypeScript 自动推导返回类型
- 可扩展性:接口设计便于后续功能扩展
3. 模块化设计实现
每个功能模块都独立开发,通过统一的模式进行组织:
// src/person.ts 示例模块
import { RandBox } from './core';
// 姓名生成函数
export function name(this: RandBox, options?: PersonOptions): string {
const opts = this.initOptions(options, {
nationality: 'zh',
gender: 'mixed',
middle_initial: false
});
const firstNames = DATA_SETS.firstNames[opts.nationality][opts.gender];
const lastNames = DATA_SETS.lastNames[opts.nationality];
const firstName = this.pickone(firstNames);
const lastName = this.pickone(lastNames);
return opts.nationality === 'zh'
? `${lastName}${firstName}`
: `${firstName} ${lastName}`;
}
// 电子邮件生成函数
export function email(this: RandBox, options?: EmailOptions): string {
const opts = this.initOptions(options, {
domain: null,
length: null
});
const username = opts.length
? this.string({ length: opts.length, casing: 'lower' })
: this.first().toLowerCase();
const domain = opts.domain || this.pickone(COMMON_DOMAINS);
return `${username}@${domain}`;
}
// 导出所有函数
export const personFunctions = { name, email, age, birthday, gender };
数据管理策略
1. 本地化数据组织
RandBox 内置了丰富的本地化数据,支持多种语言和地区:
// src/datasets.ts 数据集组织
export const DATA_SETS = {
firstNames: {
zh: {
male: ['伟', '强', '磊', '军', '洋', '勇', '杰', '华'],
female: ['秀英', '桂英', '秀兰', '玉兰', '桂兰', '秀珍', '丽'],
mixed: [] // 合并男女姓名
},
en: {
male: ['James', 'John', 'Robert', 'Michael', 'William'],
female: ['Mary', 'Patricia', 'Jennifer', 'Linda', 'Elizabeth']
}
},
lastNames: {
zh: ['王', '李', '张', '刘', '陈', '杨', '赵', '黄', '周', '吴'],
en: ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones']
},
// 地址数据
addresses: {
zh: {
provinces: ['北京市', '上海市', '广东省', '江苏省'],
cities: {
'北京市': ['朝阳区', '海淀区', '西城区', '东城区'],
'上海市': ['浦东新区', '黄浦区', '徐汇区', '长宁区']
}
}
},
// 更多数据集...
};
2. 智能缓存机制
为了提高性能,RandBox 实现了智能缓存:
class DataCache {
private cache = new Map<string, any>();
private maxSize = 1000;
get<T>(key: string, generator: () => T): T {
if (this.cache.has(key)) {
return this.cache.get(key);
}
const value = generator();
if (this.cache.size >= this.maxSize) {
// LRU 淘汰策略
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
return value;
}
}
算法核心实现
1. Mersenne Twister 实现
class MersenneTwister {
private mt: number[] = new Array(624);
private mti: number = 625;
constructor(seed?: number) {
this.init_genrand(seed || Date.now());
}
private init_genrand(s: number): void {
this.mt[0] = s >>> 0;
for (this.mti = 1; this.mti < 624; this.mti++) {
s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) +
(s & 0x0000ffff) * 1812433253) + this.mti;
this.mt[this.mti] >>>= 0;
}
}
genrand_int32(): number {
let y: number;
const mag01 = [0x0, 0x9908b0df];
if (this.mti >= 624) {
// 生成新的随机数序列
let kk: number;
for (kk = 0; kk < 227; kk++) {
y = (this.mt[kk] & 0x80000000) | (this.mt[kk + 1] & 0x7fffffff);
this.mt[kk] = this.mt[kk + 397] ^ (y >>> 1) ^ mag01[y & 0x1];
}
// ... 更多实现细节
this.mti = 0;
}
y = this.mt[this.mti++];
// Tempering
y ^= y >>> 11;
y ^= (y << 7) & 0x9d2c5680;
y ^= (y << 15) & 0xefc60000;
y ^= y >>> 18;
return y >>> 0;
}
random(): number {
return this.genrand_int32() * (1.0 / 4294967296.0);
}
}
2. 高级随机算法
正态分布实现(Box-Muller 变换)
export function normal(this: RandBox, mean = 0, deviation = 1): number {
// Box-Muller 变换生成正态分布
if (this.normal_cache !== null) {
const cached = this.normal_cache;
this.normal_cache = null;
return cached * deviation + mean;
}
const u1 = this.random();
const u2 = this.random();
const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
const z1 = Math.sqrt(-2 * Math.log(u1)) * Math.sin(2 * Math.PI * u2);
this.normal_cache = z1;
return z0 * deviation + mean;
}
加权随机选择实现
export function weighted<T>(
this: RandBox,
array: T[],
weights: number[]
): T {
if (array.length !== weights.length) {
throw new Error('Array and weights must have the same length');
}
// 计算累积权重
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
const cumulativeWeights: number[] = [];
let sum = 0;
for (let i = 0; i < weights.length; i++) {
sum += weights[i] / totalWeight;
cumulativeWeights[i] = sum;
}
// 随机选择
const random = this.random();
for (let i = 0; i < cumulativeWeights.length; i++) {
if (random <= cumulativeWeights[i]) {
return array[i];
}
}
return array[array.length - 1];
}
构建与优化
1. Rollup 配置优化
// rollup.config.js
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
import resolve from '@rollup/plugin-node-resolve';
export default {
input: 'src/index.ts',
output: [
// ESM 格式(支持 Tree Shaking)
{
file: 'dist/index.esm.mjs',
format: 'es',
sourcemap: true
},
// CommonJS 格式
{
file: 'dist/index.cjs',
format: 'cjs',
sourcemap: true
},
// UMD 格式(浏览器直接使用)
{
file: 'dist/index.umd.js',
format: 'umd',
name: 'RandBox',
sourcemap: true
}
],
plugins: [
resolve(),
typescript({
tsconfig: './tsconfig.json',
declaration: true,
declarationDir: 'dist'
}),
terser() // 代码压缩
],
// 分包配置,支持按需引入
external: id => !id.startsWith('.') && !id.startsWith('/'),
// 多入口配置
input: {
index: 'src/index.ts',
core: 'src/core.ts',
person: 'src/person.ts',
location: 'src/location.ts',
// ... 其他模块
}
};
2. Tree Shaking 优化
为了实现最佳的 Tree Shaking 效果,我们采用了特殊的导出策略:
// src/index.ts 主入口
export { RandBox as default } from './core';
export * from './types';
// 模块化导出
export * as basics from './basics';
export * as person from './person';
export * as location from './location';
// ... 其他模块
// src/person.ts 模块导出
export const name = function(this: RandBox, options?: PersonOptions): string {
// 实现
};
export const email = function(this: RandBox, options?: EmailOptions): string {
// 实现
};
// 批量导出(便于主类集成)
export const personFunctions = { name, email, age, birthday };
这样的设计允许用户以不同方式使用:
// 方式1:完整引入
import RandBox from 'randbox';
const randBox = new RandBox();
// 方式2:按模块引入
import * as person from 'randbox/dist/person';
console.log(person.name());
// 方式3:按需引入
import { name, email } from 'randbox/dist/person';
性能优化策略
1. 懒加载数据集
class LazyDataSet<T> {
private _data: T | null = null;
private loader: () => T;
constructor(loader: () => T) {
this.loader = loader;
}
get data(): T {
if (this._data === null) {
this._data = this.loader();
}
return this._data;
}
}
// 使用示例
const CHINESE_NAMES = new LazyDataSet(() =>
require('./datasets/chinese-names.json')
);
2. 内存池优化
class ObjectPool<T> {
private pool: T[] = [];
private createFn: () => T;
private resetFn: (obj: T) => void;
constructor(createFn: () => T, resetFn: (obj: T) => void) {
this.createFn = createFn;
this.resetFn = resetFn;
}
get(): T {
if (this.pool.length > 0) {
return this.pool.pop()!;
}
return this.createFn();
}
release(obj: T): void {
this.resetFn(obj);
this.pool.push(obj);
}
}
错误处理与调试
1. 友好的错误信息
export class RandBoxError extends Error {
constructor(message: string, public code: string) {
super(message);
this.name = 'RandBoxError';
}
}
export class ValidationError extends RandBoxError {
constructor(parameter: string, value: any, expected: string) {
super(
`Invalid parameter "${parameter}": expected ${expected}, got ${typeof value}`,
'VALIDATION_ERROR'
);
}
}
// 使用示例
export function integer(this: RandBox, min?: number, max?: number): number {
if (min !== undefined && typeof min !== 'number') {
throw new ValidationError('min', min, 'number');
}
if (max !== undefined && typeof max !== 'number') {
throw new ValidationError('max', max, 'number');
}
if (min !== undefined && max !== undefined && min > max) {
throw new RandBoxError(
`Minimum value (${min}) cannot be greater than maximum value (${max})`,
'INVALID_RANGE'
);
}
// 实现逻辑...
}
2. 调试支持
export class RandBox {
private debugMode = false;
private callHistory: Array<{ method: string; args: any[]; result: any }> = [];
enableDebug(): void {
this.debugMode = true;
}
getCallHistory(): ReadonlyArray<any> {
return this.callHistory;
}
private logCall(method: string, args: any[], result: any): void {
if (this.debugMode) {
this.callHistory.push({ method, args, result });
console.log(`RandBox.${method}(${JSON.stringify(args)}) -> ${result}`);
}
}
}
下期预告
在下一篇文章中,我将详细介绍如何实现 RandBox 的丰富功能模块,包括:
- 各个专业领域数据生成的实现细节
- 复杂算法的应用(如地理坐标生成、信用卡号验证等)
- 本地化数据的处理与优化
- 高级功能的实现技巧
敬请期待《从零开始打造 RandBox(3/5)- 功能模块深度实现》!
关于 RandBox
🏠 官网: https://randbox.top 📦 GitHub: https://github.com/027xiguapi/randbox 📚 文档: https://randbox.top/zh/docs
如果这个项目对你有帮助,欢迎给我们一个 ⭐ Star!
最后更新于: