当前位置: 首页 > news >正文

性能暴涨 1200 倍!我用 TypeScript 重构了这个经典项目

为什么需要重构?

起因:React Native
的"坑"

在将原版relationship.js集成到 React Native 移动端应用时,遇到了一个棘手的报错:

ERROR [RangeError: Property storage exceeds 196607 properties]

这是因为原版 JS 在初始化时创建了大量的对象属性,超出了JavaScriptCore 引擎的属性数量限制(约 196607 个)。

技术选型

为了彻底解决这个问题,我决定从以下几个方向进行重构:

  1. TypeScript 重写- 提供类型安全和更好的开发体验

  2. Map替代 Object- 利用 Map 的特性优化数据存储
  3. LRU缓存机制
    - 提升重复查询性能
  4. 模块化架构- 更好的代码组织和 Tree Shaking
    支持

核心优化点

1.
数据结构优化:Map vs Object

原版实现:

// 使用 Object 存储称谓映射const cacheData = { 'f': ['父亲', '老爸', '爹地'], 'm': ['母亲', '老妈', '妈咪'], // ... 数千个属性 };

TS 版本优化:

class OptimizedCache { private _titleToChain: Map<string, Chain[]> = new Map(); private _chainToTitle: Map<string, string[]> = new Map(); getTitleToChain(title: string): Chain[] | undefined { return this._titleToChain.get(title); // O(1) 查找 } }

优势:

  • Map 提供 O(1) 的查找性能
  • 避免了 Object 的属性数量限制
  • 更好的内存管理

2. LRU 缓存实现

这是我这次重构最满意的部分之一。针对亲戚关系查询场景,大量查询是重复的(比如"爸爸的爸爸"这种高频查询),非常适合使用缓存。

export class LRUCache<K, V> { private capacity: number; private cache: Map<K, 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: LRUCache<string, string[]>; // 中文转选择器 private idCache: LRUCache<string, string[]>; // ID转中文 private chainCache: LRUCache<string, 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.json

TS 版本结构

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,s&o': ['表哥'], 'm,xb,s&l': ['表弟'], }); relationship({ text: '妈妈的妈妈', mode: 'northern' }); // => ['姥姥']

兼容性说明

TS 版本保持了与原版100% 的 API 兼容性,你可以无缝替换:

// 原版引入方式仍然支持 import relationship from 'relationship.js'; // 替换为 TS 版本 import relationship from 'relationship-ts'; // 代码无需任何修改

总结

这次 TypeScript 重构主要带来了以下收益:

  1. 性能提升- LRU 缓存让重复查询性能提升 1200 倍
  2. 兼容性解决- 使用 Map 彻底解决 React Native 属性溢出问题
  3. 类型安全- 完整的 TypeScript 类型定义
  4. 代码质量- 模块化架构,更易维护和扩展
  5. 包体积优化- 压缩后体积减少约 5.4%

如果你在项目中需要使用亲戚关系计算功能,尤其是在React NativeTypeScript项目中,不妨试试这个优化版本。

http://www.jsqmd.com/news/303779/

相关文章:

  • 绝对干货! 网络安全面试题29问,(非常详细)零基础入门到精通,收藏这一篇就够了
  • Flink 2.0 从 flink-conf.yaml 到 config.yaml 的正确打开方式(含迁移与最佳实践)
  • [特殊字符] 在浏览器地址栏输入 URL 后,页面是怎么一步步显示出来的?
  • 动力电池点焊工艺解析
  • 【网络安全】红蓝对抗|备战大型攻防演练,这一篇就够了_红蓝对抗演练方案
  • 2026年重庆职业高中综合实力排行榜 择校指南与各类需求全景解析
  • 永辉超市卡合规回收的平台怎么选,四大误区揭秘
  • 2026一体化污水处理设备优质公司推荐
  • 2026英语雅思零基础培训辅导机构推荐榜单 核心解析 助力家长精准匹配适配零基础辅导机构
  • 孤能子视角:文心AI点评朝代分析系列
  • 2025年不容错过的烧菜火锅店,附近这6家实力上榜!社区火锅/特色美食/烧菜火锅/火锅/美食,烧菜火锅品牌排行
  • 孤能子视角:“心理学“
  • 黑客技术之黑客常见10大攻击技术,你知道几个?_黑客一般采用哪些网络攻击技术
  • 孤能子视角:“精神分析“
  • 2026最全网络安全工程师面试题(附答案),金九银十找工作必看!
  • 计算机毕业设计springboot基于Android的电动汽车电桩管理平台 Android端SpringBoot架构的新能源汽车智慧充电服务平台 基于SpringBoot与Android的电动汽车
  • 计算机毕业设计springboot大学校园心理咨询平台 基于 SpringBoot 的高校学生心理支持云平台 SpringBoot+Vue 校园心理健康服务互动系统
  • 播客人必看指南:2026年适合播客的麦克风品牌推荐
  • Kali Linux 无线网络攻防教学实验(从虚拟机安装到抓取WIFI握手包)
  • cesium 2.5D效果
  • 2026 英语雅思培训课程口碑推荐排行榜,综合课程数据与学员真实评价的避坑指南
  • 2026英语封闭雅思培训班辅导机构排行榜+核心解析 助力家长为孩子挑选适配的封闭备考优质机构
  • 2026年ELISA试剂盒优选指南,通蔚生物产品受关注,通蔚生物/小鼠ELISA,ELISA试剂盒直销厂家推荐
  • 盘点当前应用广泛的ELISA试剂盒品牌与特点,sod试剂盒/IL-6试剂盒,ELISA试剂盒实力厂家有哪些
  • 2026年NMN年货节哪个牌子好,十大NMN品牌实力榜,看完送礼不踩雷
  • 沃尔玛礼品卡1分钟合规回收操作流程与注意事项详解
  • 2026英语封闭雅思培训班辅导机构推荐榜单 核心解析 助力家长精准匹配适配封闭辅导机构
  • 深度剖析2025年国内外CRM排名,探寻行业佼佼者
  • 计算机Java毕设实战-基于微信小程序+物联网技术的宠物定位与监控系统设计与实现基于物联网技术的宠物定位与监控系统设计小程序【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • OpenAI将收集开发者反馈:新一代工具或将更贴近工程实际