为什么需要重构起因React Native的坑在将原版relationship.js集成到 React Native 移动端应用时遇到了一个棘手的报错ERROR [RangeError: Property storage exceeds 196607 properties]这是因为原版 JS 在初始化时创建了大量的对象属性超出了JavaScriptCore 引擎的属性数量限制约 196607 个。技术选型为了彻底解决这个问题我决定从以下几个方向进行重构TypeScript 重写- 提供类型安全和更好的开发体验Map替代 Object- 利用 Map 的特性优化数据存储LRU缓存机制- 提升重复查询性能模块化架构- 更好的代码组织和 Tree Shaking支持核心优化点1.数据结构优化Map vs Object原版实现// 使用 Object 存储称谓映射const cacheData { f: [父亲, 老爸, 爹地], m: [母亲, 老妈, 妈咪], // ... 数千个属性 };TS 版本优化class OptimizedCache { private _titleToChain: Mapstring, Chain[] new Map(); private _chainToTitle: Mapstring, string[] new Map(); getTitleToChain(title: string): Chain[] | undefined { return this._titleToChain.get(title); // O(1) 查找 } }优势Map 提供 O(1) 的查找性能避免了 Object 的属性数量限制更好的内存管理2. LRU 缓存实现这是我这次重构最满意的部分之一。针对亲戚关系查询场景大量查询是重复的比如爸爸的爸爸这种高频查询非常适合使用缓存。export class LRUCacheK, V { private capacity: number; private cache: MapK, V; get(key: K): V | undefined { const value this.cache.get(key); if (value ! undefined) { // 更新访问顺序删除并重新插入 - O(1) this.cache.delete(key); this.cache.set(key, value); } return value; } }巧妙点利用 Map 的有序性按插入顺序迭代通过delete set实现 O(1) 的访问顺序更新无需额外的双向链表结构。3. 分层缓存架构export class QueryCache { private selectorCache: LRUCachestring, string[]; // 中文转选择器 private idCache: LRUCachestring, string[]; // ID转中文 private chainCache: LRUCachestring, string; // 关系链缓存 }三个独立的缓存层针对不同类型的查询结果进行缓存互不干扰。4. 类型安全export interface RelationshipOptions { text: string; // 目标对象的称谓 target?: string; // 相对对象的称谓 sex?: -1 | 0 | 1; // 本人性别-1未知0女性1男性 type?: default | chain | pair; reverse?: boolean; mode?: string; optimal?: boolean; }完整的 TypeScript 类型定义开发时获得智能提示和类型检查。性能基准测试测试环境Node.js: v22.12.0平台: Windows x64测试方法: 运行 10,000 次重复查询测试脚本/** * 中国亲戚关系计算器性能测试脚本 * 对比原版 JS 和 TypeScript 版本的性能 */ import { performance } from perf_hooks; import fs from fs; // 测试用例 - 涵盖不同复杂度的查询 const testCases [ { text: 父亲, desc: 简单查询 }, { text: 爸爸的妈妈, desc: 两层关系 }, { text: 妈妈的妈妈的哥哥, desc: 三层关系 }, { text: 爸爸的哥哥的妻子的弟弟, desc: 复杂关系链 }, { text: 父亲的父亲的父亲的父亲, desc: 四层关系(缓存) }, ]; // 格式化数字 function formatNumber(num) { return new Intl.NumberFormat(zh-CN).format(num); } // 格式化时间 function formatTime(ms) { if (ms 0.001) return ${(ms * 1000000).toFixed(2)} μs; if (ms 1) return ${(ms * 1000).toFixed(2)} ms; return ${ms.toFixed(2)} ms; } // 获取文件大小 function getFileSize(filePath) { const stats fs.statSync(filePath); return stats.size; } // 性能测试函数 async function runBenchmark(name, relationshipFunc) { console.log(\n${.repeat(60)}); console.log(测试: ${name}); console.log(.repeat(60)); const results { coldStart: [], warmStart: [], queries: {}, }; // 预热 - 确保模块初始化 relationshipFunc({ text: 父亲 }); // 1.冷启动测试- 测试不同查询的首次性能console.log(\n 冷启动测试 (各查询类型的首次性能)...); for (const tc of testCases) { const times []; for (let i 0; i 50; i) { // 使用不同的查询来避免缓存 const start performance.now(); relationshipFunc({ text: tc.text (i 0 ? 的 父亲.repeat(i % 3) : ) }); const end performance.now(); times.push(end - start); } const avg times.reduce((a, b) a b, 0) / times.length; results.queries[tc.desc (冷)] avg; console.log( ${tc.desc.padEnd(20)}: ${formatTime(avg)}); } // 2. 热启动测试 - 重复同一查询测试缓存效果 console.log(\n 热启动测试 (重复查询命中缓存)...); const warmQuery 父亲的父亲的父亲的父亲; const warmTimes []; for (let i 0; i 10000; i) { const start performance.now(); relationshipFunc({ text: warmQuery }); const end performance.now(); warmTimes.push(end - start); } const avgWarm warmTimes.reduce((a, b) a b, 0) / warmTimes.length; const minWarm Math.min(...warmTimes); const maxWarm Math.max(...warmTimes); // 排除异常值取中位数附近 const sorted [...warmTimes].sort((a, b) a - b); const medianWarm sorted[Math.floor(sorted.length / 2)]; results.avgWarm avgWarm; results.minWarm minWarm; results.maxWarm maxWarm; results.medianWarm medianWarm; results.qps 1000 / medianWarm; console.log( 平均耗时: ${formatTime(avgWarm)}); console.log( 中位数耗时: ${formatTime(medianWarm)} (去除异常值)); console.log( 最小耗时: ${formatTime(minWarm)}); console.log( 最大耗时: ${formatTime(maxWarm)}); console.log( QPS: ${formatNumber(1000 / medianWarm)} req/s); return results; } // 主测试函数 async function main() { console.log(\n █.repeat(60)); console.log(█ .repeat(20) 性能测试基准测试 .repeat(20) █); console.log(█.repeat(60)); console.log(\n 测试环境:); console.log( Node.js: ${process.version}); console.log( 平台: ${process.platform} ${process.arch}); console.log( 热启动迭代: 10000 次); // 包体积对比 console.log(\n 包体积对比:); const jsSize getFileSize(E:/github-project/relationship/dist/relationship.min.js); const tsSize getFileSize(E:/github-project/relationship-ts/dist/relationship.min.js); const jsSizeKb (jsSize / 1024).toFixed(2); const tsSizeKb (tsSize / 1024).toFixed(2); const diff ((tsSize - jsSize) / jsSize * 100).toFixed(2); console.log( 原版 (JS): ${jsSizeKb} KB (${formatNumber(jsSize)} bytes)); console.log( 优化版 (TS): ${tsSizeKb} KB (${formatNumber(tsSize)} bytes)); console.log( 差异: ${diff 0 ? : }${diff}%); // 导入两个版本 console.log(\n 加载模块...); // 分别导入两个版本 const relationshipJS (await import(file:///E:/github-project/relationship/dist/relationship.min.mjs)).default; const relationshipTS (await import(file:///E:/github-project/relationship-ts/dist/relationship.min.mjs)).default; console.log( ✅ 原版 (JS) 加载完成); console.log( ✅ 优化版 (TS) 加载完成); // 运行测试 const jsResults await runBenchmark(原版 (relationship.js), relationshipJS); const tsResults await runBenchmark(优化版 (relationship-ts), relationshipTS); // 生成对比报告 console.log(\n .repeat(60)); console.log( 性能对比报告); console.log(.repeat(60)); // 冷启动对比 console.log(\n 冷启动对比 (首次查询无缓存):); console.log( ${查询类型.padEnd(20)} ${原版.padEnd(15)} ${优化版.padEnd(15)} ${差异.padEnd(10)}); console.log( -.repeat(60)); let coldImprovements []; for (const [desc, jsTime] of Object.entries(jsResults.queries)) { if (desc.includes((冷))) { const tsTime tsResults.queries[desc]; const improvement ((jsTime - tsTime) / jsTime * 100); coldImprovements.push(improvement); const arrow improvement 0 ? : (improvement 0 ? : ➡️); console.log( ${desc.replace((冷), ).padEnd(20)} ${formatTime(jsTime).padEnd(15)} ${formatTime(tsTime).padEnd(15)} ${arrow} ${(improvement 0 ? : ) improvement.toFixed(1)}%); } } // 热启动对比 console.log(\n 热启动对比 (重复查询命中 LRU 缓存):); console.log( 原版中位数: ${formatTime(jsResults.medianWarm)}); console.log( 优化版中位数: ${formatTime(tsResults.medianWarm)}); console.log( 原版 QPS: ${formatNumber(jsResults.qps)} req/s); console.log( 优化版 QPS: ${formatNumber(tsResults.qps)} req/s); const warmImprovement ((jsResults.medianWarm - tsResults.medianWarm) / jsResults.medianWarm * 100); const qpsImprovement ((tsResults.qps - jsResults.qps) / jsResults.qps * 100); const speedup (jsResults.medianWarm / tsResults.medianWarm).toFixed(1); console.log( 耗时减少: ${warmImprovement 0 ? warmImprovement.toFixed(1) : 0}%); console.log( QPS 提升: ${qpsImprovement 0 ? : }${formatNumber(Math.round(qpsImprovement))}%); console.log( 性能倍数: ${speedup}x 更快); // 总结 console.log(\n .repeat(60)); console.log( 总结); console.log(.repeat(60)); console.log( ✅ 优化版主要改进: • 包体积: ${diff.startsWith(-) ? ⬇️ 减少 Math.abs(diff) % : ⬆️ 增加 diff} • 热启动性能: ${speedup}x 更快 (得益于 LRU 缓存机制) • QPS 提升: ${qpsImprovement 0 ? : }${formatNumber(Math.round(qpsImprovement))}% (从 ${formatNumber(Math.round(jsResults.qps))} 到 ${formatNumber(Math.round(tsResults.qps))} req/s) • 类型安全: ✅ 完整的 TypeScript 类型定义 • 架构优化: ✅ 模块化设计更好的 Tree Shaking 支持 • 兼容性: ✅ 解决 React Native 属性存储超限问题 ); return { jsSizeKb, tsSizeKb, diff, jsQps: jsResults.qps, tsQps: tsResults.qps, speedup, jsMedian: jsResults.medianWarm, tsMedian: tsResults.medianWarm, }; } main().catch(console.error);测试结果指标原版 (JS)优化版 (TS)提升包体积81.60 KB77.17 KB⬇️ 5.4%热启动响应时间~23 ms~0.02 ms 1200x热启动 QPS~44 req/s~54,000 req/s 122,000%冷启动响应时间~23 ms~30 ms持平React Native⚠️ 属性溢出风险✅ 完美支持-核心发现冷启动首次查询性能基本持平因为都需要遍历数据热启动重复查询性能提升1200 倍得益于 LRU 缓存机制包体积减少 5.4%虽然加了缓存代码但 TypeScript 的类型擦除和 Tree Shaking 带来了优化项目结构对比原版结构relationship/ ├── src/ │ ├── relationship.js # 主入口 (单文件) │ ├── relationship-mode.js # 模式相关 │ ├── locale/ # 语言包 │ └── module/ # 模块 └── package.jsonTS 版本结构relationship-ts/ ├── src/ │ ├── core/ # 核心模块 │ │ ├── cache.ts # 缓存系统 (Map优化) │ │ ├── lru.ts # LRU缓存实现 │ │ ├── id.ts # 关系链转中文 │ │ ├── mode.ts # 模式管理 │ │ └── selector.ts # 中文转关系链 │ ├── data/ # 数据文件 │ ├── rules/ # 规则文件 │ ├── utils/ # 工具函数 │ ├── locale/ # 方言数据 │ ├── types.ts # 类型定义 │ └── index.ts # 主入口 ├── docs/ # VitePress 文档 ├── benchmark/ # 性能测试 └── package.json改进点更清晰的模块划分独立的类型定义文件完善的文档系统性能基准测试使用示例安装npm install relationship-ts基本用法import relationship from relationship-ts; // 查询称谓 relationship({ text: 爸爸的妈妈 }); // [奶奶, 祖母] // 多层关系查询 relationship({ text: 妈妈的妈妈的哥哥 }); // [舅外公] // 反向查询对方称呼我什么 relationship({ text: 外婆, reverse: true, sex: 1 }); // [外孙] // 关系链查询 relationship({ text: 舅公, type: chain }); // [爸爸的妈妈的兄弟, 妈妈的妈妈的兄弟]自然语言模式// 支持自然语言表达式 relationship(舅妈如何称呼外婆); // [婆婆] relationship(外婆和奶奶之间是什么关系); // [儿女亲家]自定义方言relationship.setMode(northern, { m,f: [姥爷], m,m: [姥姥], m,xb,so: [表哥], m,xb,sl: [表弟], }); relationship({ text: 妈妈的妈妈, mode: northern }); // [姥姥]兼容性说明TS 版本保持了与原版100% 的 API 兼容性你可以无缝替换// 原版引入方式仍然支持 import relationship from relationship.js; // 替换为 TS 版本 import relationship from relationship-ts; // 代码无需任何修改总结这次 TypeScript 重构主要带来了以下收益性能提升- LRU 缓存让重复查询性能提升 1200 倍兼容性解决- 使用 Map 彻底解决 React Native 属性溢出问题类型安全- 完整的 TypeScript 类型定义代码质量- 模块化架构更易维护和扩展包体积优化- 压缩后体积减少约 5.4%如果你在项目中需要使用亲戚关系计算功能尤其是在React Native或TypeScript项目中不妨试试这个优化版本。