当输入范围有限、结果稳定、运行时只需要完整工具库的一小部分能力时,可以在开发阶段提取必要的数据,浏览器只携带这份数据和一个小型解释算法。能否缩小产物要以实际构建结果为准,不能仅凭库的发布格式判断。
先确定需要保留的信息
查表不一定意味着枚举每个输入对应的完整输出。很多问题只需保存足以重建结果的结构信息。
以农历换算为例,一个年份可以用“闰哪个月”和“按实际时间顺序排列的各月天数”描述。给定整个支持区间的这些信息,再给一个公历与农历对应的起点,就能通过累计天数完成双向换算。这样只需每年保存一条记录,无需保存逐日对应表。
参考库可以提供每个农历月初一对应的公历日期。相邻月初一相减得到本月天数,最后一个月与下一年正月初一相减。闰月必须插在它重复的月份之后,保留独立的闰月标记。lunar-javascript 项目与示例提供公历、农历对象及转换能力;具体月长和闰月行为应按所用版本核验。
生成器可以作为低频维护工具运行,将结果保存为静态常量;这与每次构建都自动重新生成是两种不同的工作流,需明确选择。完整参考库仍可用于开发和校验,而使用查表实现的浏览器模块不再依赖它。
用少量位表示有限取值
如果每一段的长度只有两种可能,就能用一位区分。例如下面三个区间的长度只可能是 10 或 11:
const flags = 0b101;
const lengths = Array.from({ length: 3 }, (_, index) =>
10 + ((flags >>> index) & 1),
);
// [11, 10, 11]:第 0 段对应最低位。
农历每月 29 或 30 天,也可以采用同样的编码:低 13 位描述最多 13 个月的长度,高位记录闰月位置。关键是按时间顺序编码;插入闰月以后,后续月份的位下标随之增加,不能直接把月份编号当作位下标。
逻辑上需要多少位,与 JavaScript 数组在内存中占多少字节是不同指标。数值常量的文本体积、最终压缩传输体积和运行时堆内存也要分别测量。
用起点与累计偏移完成双向映射
对长度 [11, 10, 11] 建立累计边界 [0, 11, 21, 32],就能在“区间内的位置”和“全局位置”之间转换。
全局位置 = 当前区间起点 + 区间内序号 - 1
区间内序号 = 全局位置 - 当前区间起点 + 1
日期换算可以使用两层累计偏移:先定位年份,再定位年份中的月份。正向换算把起点、此前各年的天数、此前各月的天数和日偏移相加;反向换算先减去起点,再找到包含该偏移的年和月。
日历年月日可以映射到 UTC 日序号,避免使用本地午夜相减时受到夏令时影响。这里是在为日期编号,不是在把某个真实时刻改成另一时区。累计边界可首次使用时构建并缓存。
用差分与往返验证约束替换范围
验证至少区分下面几件事:
| 验证 | 能发现的问题 |
|---|---|
| 编码后再解码 | 位顺序、位宽、信息丢失 |
| 每个有效输入与参考实现比较 | 起点偏移、月长、闰月、年份定位错误 |
| 正向转换再反向转换 | 两个方向不自洽 |
| 范围与非法输入抽查 | 越界、非法月份或日期被错误接受 |
| 实际产物检查 | 重型依赖仍被其他运行时代码引入 |
往返自洽不能单独证明正确:两个方向可能共享同一种偏移错误。差分验证也有边界:如果表格由参考库生成,再与同一参考库比较,主要证明提取、编码和查表实现保留了该参考库的行为,不能独立证明参考库本身完全正确。
范围限定同样属于契约。例如农历年份与公历年份的边界不同,支持某个农历年必须包含该年最后一个月,即使其末日已经落到下一个公历年。
适用条件与维护成本
这类方案适合有限、稳定、能完整或充分枚举验证的输入域。结果实时变化、依赖用户身份或上下文、数据规模过大时,需要重新评估。
维护时应固定参考版本,保留可重现的生成方式,明确支持范围,并在参考库升级或编码规则变化后重新验证。范围之外应显式拒绝或使用另行定义的回退策略;有效日期全覆盖也不能代替所有非法输入的类型与边界检查。
2026-10-18 的本地验证包括:三个长度为 10/11 的区间共 8 种位编码,全部有效位置均通过编码解码与累计偏移往返断言;另以 lunar-javascript 1.7.7 对有限年份的查表实现执行逐日差分,确认有效日期、月长和闰月标记一致。前者是通用机制实验,后者只证明选定范围内与该参考版本一致。