揭秘mathjs架构:typed-function类型分派与依赖注入如何支撑任意数据类型扩展
【免费下载链接】mathjsAn extensive math library for JavaScript and Node.js项目地址: https://gitcode.com/gh_mirrors/ma/mathjs
mathjs 是一款功能全面的 JavaScript / Node.js 数学库(math library),能混算数字、大数(BigNumber)、复数、分数、单位与矩阵。它为什么还能让你轻松扩展自定义数据类型?答案就藏在架构里——typed-function 类型分派与依赖注入这两大核心机制。本文带你快速看懂这套设计,并学会用它扩展自己的类型。
一、先认识 mathjs:一个"可扩展"的数学引擎
mathjs 提供两大入口体验:
- 函数调用:
math.add、math.sqrt等数百个内置函数与常量; - 表达式解析:
evaluate('12.7 cm to inch')直接算出5 inch,还支持符号运算与自定义函数。
但真正让 mathjs 区别于普通数学库的,是它的架构目标:用统一方式混用任意数据类型,并且允许你低成本地加入新类型。这一目标靠两个"引擎"实现。
📚 官方架构说明见 README.md,扩展指南见 docs/core/extension.md。
二、第一引擎:typed-function 类型分派(Type Dispatch)
什么是"类型分派"
普通 JavaScript 函数只有一种实现,而typed function可以为同一函数名挂多个"签名",运行时根据参数类型自动选择对应实现:
// 同一个 max 函数,按参数类型分派到不同实现 const max = typed('max', { 'number, number': (a, b) => Math.max(a, b), 'BigNumber, BigNumber': (a, b) => (a.greaterThan(b) ? a : b) })这正是 mathjs 内部所有函数的构建方式——比如add同时拥有标量、稠密矩阵、稀疏矩阵等多套签名,源码见 src/function/arithmetic/add.js。
三个关键能力
| 能力 | 说明 | 举例 |
|---|---|---|
| 多签名分派 | 按参数类型自动路由 | math.add(2, 3)走数字签名,math.add(unit('5cm'), unit('2mm'))走单位签名 |
| 自动类型转换 | 签名不匹配时按"转换表"隐式转换参数 | math.add(3, fraction(1, 2))中number自动转为Fraction |
| 签名列合并 | 两个同名 typed 函数可无冲突地合并,即"给老函数加新类型" | 导入自定义add后,原有的add自动获得新类型支持 |
类型与转换表的注册集中在 src/core/function/typed.js:number、Complex、BigNumber、Fraction、Unit、Matrix等几十个类型按性能敏感顺序排列,随后通过typed.addConversions([...])声明了约 20 条隐式转换规则(如number → BigNumber、boolean → number),源码见 src/core/function/typed.js。
⚡ 注意转换是保守的:例如带超过 15 位有效数字的number不会隐式转BigNumber,会直接报错提示你显式使用bignumber(),避免悄悄丢失精度。
三、第二引擎:依赖注入(Dependency Injection)
工厂函数:函数不是直接导出的,而是"造"出来的
mathjs 中几乎每个功能模块都不直接导出函数,而是导出一个工厂函数(factory function)。以加法为例,src/function/arithmetic/add.js 开头声明:
const name = 'add' const dependencies = ['typed', 'matrix', 'addScalar', 'equalScalar', 'DenseMatrix', 'SparseMatrix', 'concat']这段"依赖清单"是关键:add不自己实现一切,它声明"我需要typed、addScalar、DenseMatrix这些依赖",由框架在创建时注入真实的实现。工厂机制的完整实现(含依赖校验、可选依赖?xxx、依赖拓扑排序)见 src/utils/factory.js。
依赖注入解决了什么问题
- 能力自动传导:一旦你给
add扩展了新类型支持,所有在底层调用add的函数(如sum、prod)也自动获得新类型支持,无需逐一手改——这就是"扩展一个函数,全家受益"。 - 轻量版可替换:如果只需要纯数字运算,可以用一个极简的
multiply实现注入到prod等函数中,绕开 BigNumber、矩阵等重型分支,得到体积与速度都更优的轻量实例。 - 实例化入口统一:核心函数
math.create(...)接收一批工厂函数,按依赖顺序创建出一个"函数集合"实例,见 src/core/create.js。所有导入都经由 src/core/function/import.js 完成,其中同名 typed 函数会自动合并签名而非报错覆盖。
四、动手扩展:给你的类型接入 mathjs
完整可运行示例见 examples/advanced/custom_datatype.js,思路只有三步:
- 注册新类型:用
factory('CustomValue', ['typed'], ...)定义类,并调用typed.addType({ name, test })让类型分派认识它; - 为新类型补签名:再造一个名为
add的 typed 函数,仅提供'CustomValue, CustomValue'这一个签名; - 导入实例:
math.import([createCustomValue, createAddCustomValue])。
导入后会发生两件"魔法":
math.add(new CustomValue(2), new CustomValue(3))返回CustomValue:5(签名合并生效);- 更妙的是
math.sum(...)等依赖add的函数自动支持CustomValue(依赖注入传导生效)。
🧩 官方文档中这一流程的详细说明(含工厂函数 meta 选项:lazy、recreateOnConfigChange、isTransformFunction等)见 docs/core/extension.md。
五、实用选型:完整版还是轻量数字版?
mathjs 的构建脚本会生成两类入口,对应不同的依赖注入组合:
| 入口 | 适用场景 | 说明 |
|---|---|---|
完整版(all) | 需要矩阵、单位、表达式解析等全部能力 | 工厂清单见 src/factoriesAny.js |
| 轻量数字版 | 只要纯number运算,追求体积与性能 | 纯函数实现不依赖 typed-function,见 src/entry/mainNumber.js 与 src/plain/number/ |
还可以只取依赖组做树摇:构建脚本会为每个函数生成"依赖全部工厂"的对象,例如math.create(addDependencies)只加载add及其依赖,不打包任何无关功能。这解释了为什么 mathjs 能同时服务浏览器小工具和科学计算服务。
六、小结
- typed-function 类型分派:一个函数名挂多套类型签名 + 自动类型转换 + 签名可合并,让"新类型"能无痛加入已有函数;
- 依赖注入 + 工厂函数:函数声明依赖、由
create/import统一注入,能力自动向下传导,也允许整体替换为轻量实现; - 两者配合,构成了 mathjs "任意数据类型扩展"的完整答案:扩展一个函数,整个函数生态随之受益。
想深入源码,建议按 src/utils/factory.js → src/core/create.js → src/core/function/typed.js → src/core/function/import.js 的顺序阅读,再配合 examples/advanced/ 下的定制示例练习。
【免费下载链接】mathjsAn extensive math library for JavaScript and Node.js项目地址: https://gitcode.com/gh_mirrors/ma/mathjs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考